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

Merge branch 'master' into extensions/export-active-theme

This commit is contained in:
Alex Andreev 2020-11-16 09:34:11 +03:00
commit 96a9209637
63 changed files with 62075 additions and 1337 deletions

View File

@ -84,6 +84,8 @@ jobs:
displayName: Build bundled extensions displayName: Build bundled extensions
- script: make test - script: make test
displayName: Run tests displayName: Run tests
- script: make test-extensions
displayName: Run In-tree Extension tests
- script: make integration-mac - script: make integration-mac
displayName: Run integration tests displayName: Run integration tests
- script: make test-extensions - script: make test-extensions
@ -122,8 +124,6 @@ jobs:
displayName: Cache Yarn packages displayName: Cache Yarn packages
- script: make install-deps - script: make install-deps
displayName: Install dependencies displayName: Install dependencies
- script: make test-extensions
displayName: Run In-tree Extension tests
- script: make lint - script: make lint
displayName: Lint displayName: Lint
- script: make build-npm - script: make build-npm
@ -132,6 +132,8 @@ jobs:
displayName: Build bundled extensions displayName: Build bundled extensions
- script: make test - script: make test
displayName: Run tests displayName: Run tests
- script: make test-extensions
displayName: Run In-tree Extension tests
- bash: | - bash: |
sudo apt-get update sudo apt-get update
sudo apt-get install libgconf-2-4 conntrack -y sudo apt-get install libgconf-2-4 conntrack -y

2
.gitignore vendored
View File

@ -15,4 +15,4 @@ src/extensions/*/*.d.ts
types/extension-api.d.ts types/extension-api.d.ts
types/extension-renderer-api.d.ts types/extension-renderer-api.d.ts
extensions/*/dist extensions/*/dist
docs/extensions/api docs/extensions/api

View File

@ -15,7 +15,8 @@ download-bins:
yarn download-bins yarn download-bins
install-deps: install-deps:
yarn install --frozen-lockfile yarn install --frozen-lockfile --verbose
yarn check --verify-tree --integrity
compile-dev: compile-dev:
yarn compile:main --cache yarn compile:main --cache
@ -56,10 +57,10 @@ else
endif endif
build-extensions: build-extensions:
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) build;) $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install && npm run build || exit $?);)
test-extensions: test-extensions:
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) test;) $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install --dev && npm run test || exit $?);)
build-npm: build-extension-types build-npm: build-extension-types
yarn npm:fix-package-version yarn npm:fix-package-version

View File

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

View File

@ -100,6 +100,8 @@ import { ExamplePage } from "./src/example-page"
export default class ExampleRendererExtension extends LensRendererExtension { export default class ExampleRendererExtension extends LensRendererExtension {
globalPages = [ globalPages = [
{ {
id: "example",
routePath: "/example",
components: { components: {
Page: ExamplePage, Page: ExamplePage,
} }
@ -109,6 +111,7 @@ export default class ExampleRendererExtension extends LensRendererExtension {
globalPageMenus = [ globalPageMenus = [
{ {
title: "Example page", // used in icon's tooltip title: "Example page", // used in icon's tooltip
target: { pageId: "example" }
components: { components: {
Icon: () => <Component.Icon material="arrow"/>, Icon: () => <Component.Icon material="arrow"/>,
} }

View File

@ -53,7 +53,7 @@ When Lens is loaded, it transforms the selected theme `json` file into a list of
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 process is repeated, and new CSS Variables appear instead of 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 build-in Lens components such as buttons, dropdowns, checkboxes etc. 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.
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):
@ -137,7 +137,7 @@ Currently, there is no prescribed way of detecting changes to the theme in JavaS
## Injected styles ## Injected styles
Every extention 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 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.
Extension may overwrite these if needed. They have low CSS specificity, so overriding them should be fairly easy. Extension may overwrite these if needed. They have low CSS specificity, so overriding them should be fairly easy.

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

View File

@ -2,10 +2,10 @@ import { LensMainExtension } from "@k8slens/extensions";
export default class ExampleExtensionMain extends LensMainExtension { export default class ExampleExtensionMain extends LensMainExtension {
onActivate() { onActivate() {
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.getMeta()); console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.name, this.id);
} }
onDeactivate() { onDeactivate() {
console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.getMeta()); console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.name, this.id);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -11,13 +11,14 @@
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"dev": "npm run build --watch", "dev": "npm run build --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": { "dependencies": {
"react-open-doodles": "^1.0.5" "react-open-doodles": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",
"typescript": "^4.0.3", "typescript": "^4.0.3",
"webpack": "^4.44.2" "webpack": "^4.44.2"

View File

@ -5,11 +5,21 @@ import React from "react"
export default class ExampleExtension extends LensRendererExtension { export default class ExampleExtension extends LensRendererExtension {
clusterPages = [ clusterPages = [
{ {
path: "/extension-example", id: "example",
routePath: "/extension-example",
title: "Example Extension", title: "Example Extension",
components: { components: {
Page: () => <ExamplePage extension={this}/>, Page: () => <ExamplePage extension={this}/>,
MenuIcon: ExampleIcon, }
}
]
clusterPageMenus = [
{
target: { pageId: "example", params: {} },
title: "Example Extension",
components: {
Icon: ExampleIcon,
} }
} }
] ]

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,13 @@
"scripts": { "scripts": {
"build": "webpack -p", "build": "webpack -p",
"dev": "webpack --watch", "dev": "webpack --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@types/webpack": "^4.41.17",
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"@types/webpack": "^4.41.17",
"jest": "^26.6.3",
"mobx": "^5.15.5", "mobx": "^5.15.5",
"react": "^16.13.1", "react": "^16.13.1",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,18 @@
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"dev": "npm run build --watch", "dev": "npm run build --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": { "dependencies": {
"semver": "^7.3.2" "semver": "^7.3.2"
}, },
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"react": "^16.13.1",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",
"typescript": "^4.0.3", "typescript": "^4.0.3",
"webpack": "^4.44.2", "webpack": "^4.44.2"
"mobx": "^5.15.5",
"react": "^16.13.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

File diff suppressed because it is too large Load Diff

View File

@ -10,15 +10,16 @@
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"dev": "npm run build --watch", "dev": "npm run build --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"react": "^16.13.1",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",
"typescript": "^4.0.3", "typescript": "^4.0.3",
"webpack": "^4.44.2", "webpack": "^4.44.2"
"mobx": "^5.15.5",
"react": "^16.13.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

File diff suppressed because it is too large Load Diff

View File

@ -10,15 +10,16 @@
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"dev": "npm run build --watch", "dev": "npm run build --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"ts-loader": "^8.0.4", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"typescript": "^4.0.3", "jest": "^26.6.3",
"webpack": "^4.44.2",
"mobx": "^5.15.5", "mobx": "^5.15.5",
"react": "^16.13.1", "react": "^16.13.1",
"@k8slens/extensions": "file:../../src/extensions/npm/extensions" "ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "webpack -p", "build": "webpack -p",
"dev": "webpack --watch", "dev": "webpack --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
@ -16,6 +16,7 @@
"@types/react-router": "^5.1.8", "@types/react-router": "^5.1.8",
"@types/webpack": "^4.41.17", "@types/webpack": "^4.41.17",
"css-loader": "^5.0.0", "css-loader": "^5.0.0",
"jest": "^26.6.3",
"mobx": "^5.15.5", "mobx": "^5.15.5",
"react": "^16.13.1", "react": "^16.13.1",
"sass-loader": "^10.0.4", "sass-loader": "^10.0.4",

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
install-deps:
yarn install
build: install-deps
yarn run build
test:
yarn run test

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,20 @@
"scripts": { "scripts": {
"build": "webpack -p", "build": "webpack -p",
"dev": "webpack --watch", "dev": "webpack --watch",
"test": "echo NO TESTS" "test": "jest --passWithNoTests --env=jsdom src $@"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"@types/analytics-node": "^3.1.3", "@types/analytics-node": "^3.1.3",
"analytics-node": "^3.4.0-beta.3",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"node-machine-id": "^1.1.12",
"react": "^16.13.1",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",
"typescript": "^4.0.3", "typescript": "^4.0.3",
"webpack": "^4.44.2",
"mobx": "^5.15.5",
"react": "^16.13.1",
"node-machine-id": "^1.1.12",
"universal-analytics": "^0.4.23", "universal-analytics": "^0.4.23",
"analytics-node": "^3.4.0-beta.3" "webpack": "^4.44.2"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "kontena-lens", "name": "kontena-lens",
"productName": "Lens", "productName": "Lens",
"description": "Lens - The Kubernetes IDE", "description": "Lens - The Kubernetes IDE",
"version": "4.0.0-beta.1", "version": "4.0.0-beta.2",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.", "copyright": "© 2020, Mirantis, Inc.",
"license": "MIT", "license": "MIT",
@ -11,35 +11,35 @@
"email": "info@k8slens.dev" "email": "info@k8slens.dev"
}, },
"scripts": { "scripts": {
"dev": "concurrently -k \"yarn dev-run -C\" yarn:dev:*", "dev": "concurrently -k \"yarn run dev-run -C\" yarn:dev:*",
"dev-build": "concurrently yarn:compile:*", "dev-build": "concurrently yarn:compile:*",
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"", "dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "yarn compile:main --watch", "dev:main": "yarn run compile:main --watch",
"dev:renderer": "yarn webpack-dev-server --config webpack.renderer.ts", "dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
"dev:extension-types": "yarn compile:extension-types --watch", "dev:extension-types": "yarn run compile:extension-types --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "webpack --config webpack.main.ts", "compile:main": "yarn run webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts", "compile:renderer": "yarn run webpack --config webpack.renderer.ts",
"compile:i18n": "lingui compile", "compile:i18n": "yarn run lingui compile",
"compile:extension-types": "rollup --config src/extensions/rollup.config.js", "compile:extension-types": "yarn run rollup --config src/extensions/rollup.config.js",
"npm:fix-package-version": "ts-node build/set_npm_version.ts", "npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens", "build:linux": "yarn run compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens", "build:mac": "yarn run compile && electron-builder --mac --dir -c.productName=Lens",
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens", "build:win": "yarn run compile && electron-builder --win --dir -c.productName=Lens",
"test": "jest --env=jsdom src $@", "test": "jest --env=jsdom src $@",
"integration": "jest --coverage integration $@", "integration": "jest --coverage integration $@",
"dist": "yarn compile && electron-builder --publish onTag", "dist": "yarn run compile && electron-builder --publish onTag",
"dist:win": "yarn compile && electron-builder --publish onTag --x64 --ia32", "dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32",
"dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null", "dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
"postinstall": "patch-package", "postinstall": "patch-package",
"i18n:extract": "lingui extract", "i18n:extract": "yarn run lingui extract",
"download-bins": "concurrently yarn:download:*", "download-bins": "concurrently yarn:download:*",
"download:kubectl": "yarn run ts-node build/download_kubectl.ts", "download:kubectl": "yarn run ts-node build/download_kubectl.ts",
"download:helm": "yarn run ts-node build/download_helm.ts", "download:helm": "yarn run ts-node build/download_helm.ts",
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts", "build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
"lint": "eslint $@ --ext js,ts,tsx --max-warnings=0 src/", "lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 src/",
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest", "mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
"typedocs-extensions-api": "yarn typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts" "typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts"
}, },
"config": { "config": {
"bundledKubectlVersion": "1.17.11", "bundledKubectlVersion": "1.17.11",

View File

@ -0,0 +1,23 @@
import { LensExtension } from "../lens-extension"
let ext: LensExtension = null
describe("lens extension", () => {
beforeEach(async () => {
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1"
},
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})
describe("name", () => {
it("returns name", () => {
expect(ext.name).toBe("foo-bar")
})
})
})

View File

@ -57,29 +57,29 @@ export class ExtensionLoader {
loadOnMain() { loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main') logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((ext: LensMainExtension) => [ this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { key: ext }) registries.menuRegistry.add(ext.appMenus)
]); ]);
} }
loadOnClusterManagerRenderer() { loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((ext: LensRendererExtension) => [ this.autoInitExtensions((ext: LensRendererExtension) => [
registries.globalPageRegistry.add(ext.globalPages, { key: ext }), registries.globalPageRegistry.add(ext.globalPages, ext),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }), registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }), registries.appPreferenceRegistry.add(ext.appPreferences),
registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }), registries.clusterFeatureRegistry.add(ext.clusterFeatures),
registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }), registries.statusBarRegistry.add(ext.statusBarItems),
]); ]);
} }
loadOnClusterRenderer() { loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((ext: LensRendererExtension) => [ this.autoInitExtensions((ext: LensRendererExtension) => [
registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }), registries.clusterPageRegistry.add(ext.clusterPages, ext),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }), registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }), registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }), registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext }) registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
]) ])
} }

View File

@ -1,6 +1,5 @@
import type { InstalledExtension } from "./extension-manager"; import type { InstalledExtension } from "./extension-manager";
import { action, observable, reaction } from "mobx"; import { action, observable, reaction } from "mobx";
import { compile } from "path-to-regexp"
import logger from "../main/logger"; import logger from "../main/logger";
export type LensExtensionId = string; // path to manifest (package.json) export type LensExtensionId = string; // path to manifest (package.json)
@ -15,7 +14,6 @@ export interface LensExtensionManifest {
} }
export class LensExtension { export class LensExtension {
readonly routePrefix = "/extension/:name"
readonly manifest: LensExtensionManifest; readonly manifest: LensExtensionManifest;
readonly manifestPath: string; readonly manifestPath: string;
readonly isBundled: boolean; readonly isBundled: boolean;
@ -44,14 +42,6 @@ export class LensExtension {
return this.manifest.description return this.manifest.description
} }
getPageUrl(baseUrl = "") {
return compile(this.routePrefix)({ name: this.name }) + baseUrl;
}
getPageRoute(baseRoute = "") {
return this.routePrefix + baseRoute;
}
@action @action
async enable() { async enable() {
if (this.isEnabled) return; if (this.isEnabled) return;

View File

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

View File

@ -1,6 +1,7 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries" import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
import { observable } from "mobx"; import { observable } from "mobx";
import { LensExtension } from "./lens-extension" import { LensExtension } from "./lens-extension"
import { getPageUrl } from "./registries/page-registry"
export class LensRendererExtension extends LensExtension { export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = [] @observable.shallow globalPages: PageRegistration[] = []
@ -16,6 +17,6 @@ export class LensRendererExtension extends LensExtension {
async navigate(location?: string) { async navigate(location?: string) {
const { navigate } = await import("../renderer/navigation"); const { navigate } = await import("../renderer/navigation");
navigate(this.getPageUrl(location)); navigate(getPageUrl(this, location));
} }
} }

View File

@ -0,0 +1,82 @@
import { getPageUrl, globalPageRegistry } from "../page-registry"
import { LensExtension } from "../../lens-extension"
import React from "react";
let ext: LensExtension = null
describe("getPageUrl", () => {
beforeEach(async () => {
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1"
},
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})
it("returns a page url for extension", () => {
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
})
it("allows to pass base url as parameter", () => {
expect(getPageUrl(ext, "/test")).toBe("/extension/foo-bar/test")
})
it("removes @", () => {
ext.manifest.name = "@foo/bar"
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
})
it("adds / prefix", () => {
expect(getPageUrl(ext, "test")).toBe("/extension/foo-bar/test")
})
})
describe("globalPageRegistry", () => {
beforeEach(async () => {
ext = new LensExtension({
manifest: {
name: "@acme/foo-bar",
version: "0.1.1"
},
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
globalPageRegistry.add([
{
id: "test-page",
components: {
Page: () => React.createElement('Text')
}
},
{
id: "another-page",
components: {
Page: () => React.createElement('Text')
}
},
], ext)
})
describe("getByPageMenuTarget", () => {
it("returns matching page", () => {
const page = globalPageRegistry.getByPageMenuTarget({
pageId: "test-page",
extensionId: ext.name
})
expect(page.id).toEqual("test-page")
})
it("returns null if target not found", () => {
const page = globalPageRegistry.getByPageMenuTarget({
pageId: "wrong-page",
extensionId: ext.name
})
expect(page).toBeNull()
})
})
})

View File

@ -1,12 +1,12 @@
import type React from "react" import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface AppPreferenceComponents { export interface AppPreferenceComponents {
Hint: React.ComponentType<any>; Hint: React.ComponentType<any>;
Input: React.ComponentType<any>; Input: React.ComponentType<any>;
} }
export interface AppPreferenceRegistration extends BaseRegistryItem { export interface AppPreferenceRegistration {
title: string; title: string;
components: AppPreferenceComponents; components: AppPreferenceComponents;
} }

View File

@ -1,65 +1,24 @@
// Base class for extensions-api registries // Base class for extensions-api registries
import { action, observable } from "mobx"; import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
import { getRandId } from "../../common/utils";
export type BaseRegistryKey = LensExtension | null; export class BaseRegistry<T = any> {
export type BaseRegistryItemId = string | symbol; private items = observable<T>([], { deep: false });
export interface BaseRegistryItem { getItems(): T[] {
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided return this.items.toJS();
}
export interface BaseRegistryAddMeta {
key?: BaseRegistryKey;
merge?: boolean
}
export class BaseRegistry<T extends BaseRegistryItem = any> {
private items = observable.map<BaseRegistryKey, T[]>([], { deep: false });
getItems(): (T & { extension?: LensExtension | null })[] {
return Array.from(this.items).map(([ext, items]) => {
return items.map(item => ({
...item,
extension: ext,
}))
}).flat()
}
getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T {
const byId = (item: BaseRegistryItem) => item.id === itemId;
if (key) {
return this.items.get(key)?.find(byId)
}
return this.getItems().find(byId);
} }
@action @action
add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) { add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => { const normalizedItems = (Array.isArray(items) ? items : [items])
item.id = item.id || getRandId(); this.items.push(...normalizedItems);
return item; return () => this.remove(...normalizedItems);
});
if (merge && this.items.has(key)) {
const newItems = new Set(this.items.get(key));
normalizedItems.forEach(item => newItems.add(item))
this.items.set(key, [...newItems]);
} else {
this.items.set(key, normalizedItems);
}
return () => this.remove(normalizedItems, key)
} }
@action @action
remove(items: T[], key: BaseRegistryKey = null) { remove(...items: T[]) {
const storedItems = this.items.get(key); items.forEach(item => {
if (!storedItems) return; this.items.remove(item); // works because of {deep: false};
const newItems = storedItems.filter(item => !items.includes(item)); // works because of {deep: false}; })
if (newItems.length > 0) {
this.items.set(key, newItems)
} else {
this.items.delete(key);
}
} }
} }

View File

@ -1,12 +1,12 @@
import type React from "react" import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { ClusterFeature } from "../cluster-feature"; import { ClusterFeature } from "../cluster-feature";
export interface ClusterFeatureComponents { export interface ClusterFeatureComponents {
Description: React.ComponentType<any>; Description: React.ComponentType<any>;
} }
export interface ClusterFeatureRegistration extends BaseRegistryItem { export interface ClusterFeatureRegistration {
title: string; title: string;
components: ClusterFeatureComponents components: ClusterFeatureComponents
feature: ClusterFeature feature: ClusterFeature

View File

@ -1,11 +1,11 @@
import React from "react" import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectDetailComponents { export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>; Details: React.ComponentType<any>;
} }
export interface KubeObjectDetailRegistration extends BaseRegistryItem { export interface KubeObjectDetailRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
components: KubeObjectDetailComponents; components: KubeObjectDetailComponents;

View File

@ -1,11 +1,11 @@
import React from "react" import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectMenuComponents { export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<any>; MenuItem: React.ComponentType<any>;
} }
export interface KubeObjectMenuRegistration extends BaseRegistryItem { export interface KubeObjectMenuRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
components: KubeObjectMenuComponents; components: KubeObjectMenuComponents;

View File

@ -1,7 +1,7 @@
import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api"; import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectStatusRegistration extends BaseRegistryItem { export interface KubeObjectStatusRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus; resolve: (object: KubeObject) => KubeObjectStatus;

View File

@ -1,20 +1,21 @@
// Extensions-api -> Register page menu items // Extensions-api -> Register page menu items
import type React from "react"; import type React from "react";
import { action } from "mobx";
import type { IconProps } from "../../renderer/components/icon"; import type { IconProps } from "../../renderer/components/icon";
import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
export interface PageMenuRegistration extends BaseRegistryItem { export interface PageMenuTarget {
id: BaseRegistryItemId; // required id from page-registry item to match with pageId: string;
url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name" extensionId?: string;
title: React.ReactNode; params?: object;
components: PageMenuComponents;
subMenus?: PageSubMenuRegistration[];
} }
export interface PageSubMenuRegistration { export interface PageMenuRegistration {
url: string; target?: PageMenuTarget;
title: React.ReactNode; title: React.ReactNode;
components: PageMenuComponents;
} }
export interface PageMenuComponents { export interface PageMenuComponents {
@ -22,13 +23,18 @@ export interface PageMenuComponents {
} }
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> { export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
getItems() {
return super.getItems().map(item => { @action
item.url = item.extension.getPageUrl(item.url) add(items: T[], ext?: LensExtension) {
return item const normalizedItems = items.map((menu) => {
}); if (menu.target && !menu.target.extensionId) {
menu.target.extensionId = ext.name
}
return menu
})
return super.add(normalizedItems);
} }
} }
export const globalPageMenuRegistry = new PageMenuRegistry<Omit<PageMenuRegistration, "subMenus">>(); export const globalPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
export const clusterPageMenuRegistry = new PageMenuRegistry(); export const clusterPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();

View File

@ -1,33 +1,59 @@
// Extensions-api -> Custom page registration // Extensions-api -> Custom page registration
import React from "react"; import React from "react";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; 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";
export interface PageRegistration extends BaseRegistryItem { 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" 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 exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
components: PageComponents; components: PageComponents;
subPages?: SubPageRegistration[];
}
export interface SubPageRegistration {
routePath: string; // required for sub-pages
exact?: boolean;
components: PageComponents;
} }
export interface PageComponents { export interface PageComponents {
Page: React.ComponentType<any>; Page: React.ComponentType<any>;
} }
const routePrefix = "/extension/:name"
export function sanitizeExtensioName(name: string) {
return name.replace("@", "").replace("/", "-")
}
export function getPageUrl(ext: LensExtension, baseUrl = "") {
if (baseUrl !== "" && !baseUrl.startsWith("/")) {
baseUrl = "/" + baseUrl
}
const validUrlName = sanitizeExtensioName(ext.name);
return compile(routePrefix)({ name: validUrlName }) + baseUrl;
}
export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> { export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> {
getItems() {
return super.getItems().map(item => { @action
item.routePath = item.extension.getPageRoute(item.routePath) add(items: T[], ext?: LensExtension) {
return item const normalizedItems = items.map((page) => {
}); if (!page.routePath) {
page.routePath = `/${page.id}`
}
page.routePath = getPageUrl(ext, page.routePath)
return page
})
return super.add(normalizedItems);
}
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
} }
} }
export const globalPageRegistry = new PageRegistry<Omit<PageRegistration, "subPages">>(); export const globalPageRegistry = new PageRegistry<PageRegistration>();
export const clusterPageRegistry = new PageRegistry(); export const clusterPageRegistry = new PageRegistry<PageRegistration>();

View File

@ -1,9 +1,9 @@
// Extensions API -> Status bar customizations // Extensions API -> Status bar customizations
import React from "react"; import React from "react";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface StatusBarRegistration extends BaseRegistryItem { export interface StatusBarRegistration {
item?: React.ReactNode; item?: React.ReactNode;
} }

View File

@ -6,6 +6,7 @@ import { readFile } from "fs-extra"
import { Cluster } from "./cluster" import { Cluster } from "./cluster"
import { apiPrefix, appName, publicPath, isDevelopment, webpackDevServerPort } from "../common/vars"; import { apiPrefix, appName, publicPath, isDevelopment, webpackDevServerPort } from "../common/vars";
import { helmRoute, kubeconfigRoute, metricsRoute, portForwardRoute, resourceApplierRoute, watchRoute } from "./routes"; import { helmRoute, kubeconfigRoute, metricsRoute, portForwardRoute, resourceApplierRoute, watchRoute } from "./routes";
import logger from "./logger"
export interface RouterRequestOpts { export interface RouterRequestOpts {
req: http.IncomingMessage; req: http.IncomingMessage;
@ -94,7 +95,7 @@ export class Router {
return mimeTypes[path.extname(filename).slice(1)] || "text/plain" return mimeTypes[path.extname(filename).slice(1)] || "text/plain"
} }
async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage) { async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage, retryCount = 0) {
const asset = path.join(__static, filePath); const asset = path.join(__static, filePath);
try { try {
const filename = path.basename(req.url); const filename = path.basename(req.url);
@ -112,7 +113,13 @@ export class Router {
res.write(data); res.write(data);
res.end(); res.end();
} catch (err) { } catch (err) {
this.handleStaticFile(`${publicPath}/${appName}.html`, res, req); if (retryCount > 5) {
logger.error("handleStaticFile:", err.toString())
res.statusCode = 404
res.end()
return
}
this.handleStaticFile(`${publicPath}/${appName}.html`, res, req, Math.max(retryCount, 0) + 1);
} }
} }
@ -120,7 +127,7 @@ export class Router {
// Static assets // Static assets
this.router.add( this.router.add(
{ method: 'get', path: '/{path*}' }, { method: 'get', path: '/{path*}' },
({ params, response, path, raw: { req }}: LensApiRequest) => { ({ params, response, path, raw: { req } }: LensApiRequest) => {
this.handleStaticFile(params.path, response, req); this.handleStaticFile(params.path, response, req);
}); });

View File

@ -1,4 +1,5 @@
.LandingPage { .LandingPage {
width: 100%;
height: 100%; height: 100%;
text-align: center; text-align: center;
z-index: 0; z-index: 0;

View File

@ -117,7 +117,7 @@ export class Workspaces extends React.Component {
const isEditing = this.editingWorkspaces.has(workspaceId); const isEditing = this.editingWorkspaces.has(workspaceId);
const editingWorkspace = this.editingWorkspaces.get(workspaceId); const editingWorkspace = this.editingWorkspaces.get(workspaceId);
const managed = !!ownerRef const managed = !!ownerRef
const className = cssNames("workspace flex gaps", { const className = cssNames("workspace flex gaps align-center", {
active: isActive, active: isActive,
editing: isEditing, editing: isEditing,
default: isDefault, default: isDefault,

View File

@ -74,27 +74,8 @@ export class App extends React.Component {
} }
renderExtensionRoutes() { renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => { return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => {
const Component = () => { const Component = () => {
if (subPages) {
const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => {
const menuItem = clusterPageMenuRegistry.getById(pageId);
if (!menuItem) return;
return {
routePath, exact,
component: Page,
url: menuItem.url,
title: menuItem.title,
}
}).filter(Boolean);
if (tabs.length > 0) {
return (
<Page>
<TabLayout tabs={tabs}/>
</Page>
)
}
}
return <Page/> return <Page/>
}; };
return <Route key={routePath} path={routePath} exact={exact} component={Component}/> return <Route key={routePath} path={routePath} exact={exact} component={Component}/>

View File

@ -5,7 +5,6 @@ import { remote } from "electron"
import type { Cluster } from "../../../main/cluster"; import type { Cluster } from "../../../main/cluster";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd"; import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { matchPath } from "react-router";
import { _i18n } from "../../i18n"; import { _i18n } from "../../i18n";
import { t, Trans } from "@lingui/macro"; import { t, Trans } from "@lingui/macro";
import { userStore } from "../../../common/user-store"; import { userStore } from "../../../common/user-store";
@ -15,7 +14,7 @@ import { ClusterIcon } from "../cluster-icon";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { autobind, cssNames, IClassName } from "../../utils"; import { autobind, cssNames, IClassName } from "../../utils";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { navigate, navigation } from "../../navigation"; import { isActiveRoute, navigate } from "../../navigation";
import { addClusterURL } from "../+add-cluster"; import { addClusterURL } from "../+add-cluster";
import { clusterSettingsURL } from "../+cluster-settings"; import { clusterSettingsURL } from "../+cluster-settings";
import { landingURL } from "../+landing-page"; import { landingURL } from "../+landing-page";
@ -24,6 +23,7 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route"; import { clusterViewURL } from "./cluster-view.route";
import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries"; import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
import { compile } from "path-to-regexp";
interface Props { interface Props {
className?: IClassName; className?: IClassName;
@ -149,17 +149,16 @@ export class ClustersMenu extends React.Component<Props> {
)} )}
</div> </div>
<div className="extensions"> <div className="extensions">
{globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { {globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getById(menuItemId); const registeredPage = globalPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return; if (!registeredPage) return;
const { routePath, exact } = registeredPage; const { routePath, exact } = registeredPage;
const isActive = !!matchPath(navigation.location.pathname, { path: routePath, exact });
return ( return (
<Icon <Icon
key={routePath} key={routePath}
tooltip={title} tooltip={title}
active={isActive} active={isActiveRoute({ path: routePath, exact })}
onClick={() => navigate(url)} onClick={() => navigate(compile(routePath)(target.params))}
/> />
) )
})} })}

View File

@ -30,6 +30,7 @@ import { isActiveRoute } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac" import { isAllowedResource } from "../../../common/rbac"
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { clusterPageMenuRegistry, clusterPageRegistry } from "../../../extensions/registries"; import { clusterPageMenuRegistry, clusterPageRegistry } from "../../../extensions/registries";
import { compile } from "path-to-regexp";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false }); const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = { type SidebarContextValue = {
@ -191,10 +192,11 @@ export class Sidebar extends React.Component<Props> {
> >
{this.renderCustomResources()} {this.renderCustomResources()}
</SidebarNavItem> </SidebarNavItem>
{clusterPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { {clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = clusterPageRegistry.getById(menuItemId); const registeredPage = clusterPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return; if (!registeredPage) return;
const { routePath, exact } = registeredPage; const { routePath, exact } = registeredPage;
const url = compile(routePath)(target.params)
return ( return (
<SidebarNavItem <SidebarNavItem
key={url} key={url}

View File

@ -3,6 +3,7 @@
position: relative; position: relative;
padding: $padding * 2; padding: $padding * 2;
width: 100%;
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-columns: 1fr 40%; grid-template-columns: 1fr 40%;

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! 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.1 (current version) ## 4.0.0-beta.2 (current version)
- Extension API - Extension API
- Improved pod logs - Improved pod logs

View File

@ -188,7 +188,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
isDevelopment && new webpack.HotModuleReplacementPlugin(), isDevelopment && new webpack.HotModuleReplacementPlugin(),
isDevelopment && new ReactRefreshWebpackPlugin(), isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean), ].filter(Boolean),
} }
} }