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

2
.gitignore vendored
View File

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

View File

@ -15,7 +15,8 @@ download-bins:
yarn download-bins
install-deps:
yarn install --frozen-lockfile
yarn install --frozen-lockfile --verbose
yarn check --verify-tree --integrity
compile-dev:
yarn compile:main --cache
@ -56,10 +57,10 @@ else
endif
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:
$(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
yarn npm:fix-package-version

View File

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

View File

@ -100,6 +100,8 @@ import { ExamplePage } from "./src/example-page"
export default class ExampleRendererExtension extends LensRendererExtension {
globalPages = [
{
id: "example",
routePath: "/example",
components: {
Page: ExamplePage,
}
@ -109,6 +111,7 @@ export default class ExampleRendererExtension extends LensRendererExtension {
globalPageMenus = [
{
title: "Example page", // used in icon's tooltip
target: { pageId: "example" }
components: {
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.
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):
@ -137,7 +137,7 @@ Currently, there is no prescribed way of detecting changes to the theme in JavaS
## 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.

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 {
onActivate() {
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.getMeta());
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.name, this.id);
}
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": {
"build": "webpack --config webpack.config.js",
"dev": "npm run build --watch",
"test": "echo NO TESTS"
"test": "jest --passWithNoTests --env=jsdom src $@"
},
"dependencies": {
"react-open-doodles": "^1.0.5"
},
"devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2"

View File

@ -5,11 +5,21 @@ import React from "react"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
path: "/extension-example",
id: "example",
routePath: "/extension-example",
title: "Example Extension",
components: {
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": {
"build": "webpack -p",
"dev": "webpack --watch",
"test": "echo NO TESTS"
"test": "jest --passWithNoTests --env=jsdom src $@"
},
"dependencies": {},
"devDependencies": {
"@types/webpack": "^4.41.17",
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"@types/webpack": "^4.41.17",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"react": "^16.13.1",
"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": {
"build": "webpack --config webpack.config.js",
"dev": "npm run build --watch",
"test": "echo NO TESTS"
"test": "jest --passWithNoTests --env=jsdom src $@"
},
"dependencies": {
"semver": "^7.3.2"
},
"devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"react": "^16.13.1",
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2",
"mobx": "^5.15.5",
"react": "^16.13.1"
"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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -10,15 +10,16 @@
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "npm run build --watch",
"test": "echo NO TESTS"
"test": "jest --passWithNoTests --env=jsdom src $@"
},
"dependencies": {},
"devDependencies": {
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2",
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"mobx": "^5.15.5",
"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",
label: "Support",
click: () => {
this.navigate();
this.navigate("/support");
}
}
]

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,6 +5,8 @@ import { SupportPage } from "./src/support";
export default class SupportPageRendererExtension extends LensRendererExtension {
globalPages: Interface.PageRegistration[] = [
{
id: "support",
routePath: "/support",
components: {
Page: SupportPage,
}
@ -14,7 +16,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
statusBarItems: Interface.StatusBarRegistration[] = [
{
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/>
</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": {
"build": "webpack -p",
"dev": "webpack --watch",
"test": "echo NO TESTS"
"test": "jest --passWithNoTests --env=jsdom src $@"
},
"dependencies": {},
"devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"@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",
"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",
"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",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "4.0.0-beta.1",
"version": "4.0.0-beta.2",
"main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.",
"license": "MIT",
@ -11,35 +11,35 @@
"email": "info@k8slens.dev"
},
"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-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "yarn compile:main --watch",
"dev:renderer": "yarn webpack-dev-server --config webpack.renderer.ts",
"dev:extension-types": "yarn compile:extension-types --watch",
"dev:main": "yarn run compile:main --watch",
"dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
"dev:extension-types": "yarn run compile:extension-types --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts",
"compile:i18n": "lingui compile",
"compile:extension-types": "rollup --config src/extensions/rollup.config.js",
"npm:fix-package-version": "ts-node build/set_npm_version.ts",
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens",
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens",
"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",
"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",
"build:win": "yarn run compile && electron-builder --win --dir -c.productName=Lens",
"test": "jest --env=jsdom src $@",
"integration": "jest --coverage integration $@",
"dist": "yarn compile && electron-builder --publish onTag",
"dist:win": "yarn compile && electron-builder --publish onTag --x64 --ia32",
"dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null",
"dist": "yarn run compile && electron-builder --publish onTag",
"dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32",
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
"postinstall": "patch-package",
"i18n:extract": "lingui extract",
"i18n:extract": "yarn run lingui extract",
"download-bins": "concurrently yarn:download:*",
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
"download:helm": "yarn run ts-node build/download_helm.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",
"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": {
"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() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { key: ext })
registries.menuRegistry.add(ext.appMenus)
]);
}
loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.globalPageRegistry.add(ext.globalPages, { key: ext }),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }),
registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }),
registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }),
registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }),
registries.globalPageRegistry.add(ext.globalPages, ext),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
registries.appPreferenceRegistry.add(ext.appPreferences),
registries.clusterFeatureRegistry.add(ext.clusterFeatures),
registries.statusBarRegistry.add(ext.statusBarItems),
]);
}
loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext })
registries.clusterPageRegistry.add(ext.clusterPages, ext),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
])
}

View File

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

View File

@ -2,13 +2,14 @@ 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"
export class LensMainExtension extends LensExtension {
@observable.shallow appMenus: MenuRegistration[] = []
async navigate(location?: string, frameId?: number) {
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);
}
}

View File

@ -1,6 +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"
export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
@ -16,6 +17,6 @@ export class LensRendererExtension extends LensExtension {
async navigate(location?: string) {
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 { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { BaseRegistry } from "./base-registry";
export interface AppPreferenceComponents {
Hint: React.ComponentType<any>;
Input: React.ComponentType<any>;
}
export interface AppPreferenceRegistration extends BaseRegistryItem {
export interface AppPreferenceRegistration {
title: string;
components: AppPreferenceComponents;
}

View File

@ -1,65 +1,24 @@
// Base class for extensions-api registries
import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
import { getRandId } from "../../common/utils";
export type BaseRegistryKey = LensExtension | null;
export type BaseRegistryItemId = string | symbol;
export class BaseRegistry<T = any> {
private items = observable<T>([], { deep: false });
export interface BaseRegistryItem {
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided
}
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);
getItems(): T[] {
return this.items.toJS();
}
@action
add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) {
const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => {
item.id = item.id || getRandId();
return item;
});
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)
add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items])
this.items.push(...normalizedItems);
return () => this.remove(...normalizedItems);
}
@action
remove(items: T[], key: BaseRegistryKey = null) {
const storedItems = this.items.get(key);
if (!storedItems) return;
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);
}
remove(...items: T[]) {
items.forEach(item => {
this.items.remove(item); // works because of {deep: false};
})
}
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
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;
apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus;

View File

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

View File

@ -1,33 +1,59 @@
// Extensions-api -> Custom page registration
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"
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
components: PageComponents;
subPages?: SubPageRegistration[];
}
export interface SubPageRegistration {
routePath: string; // required for sub-pages
exact?: boolean;
components: PageComponents;
}
export interface PageComponents {
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> {
getItems() {
return super.getItems().map(item => {
item.routePath = item.extension.getPageRoute(item.routePath)
return item
});
@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);
}
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 clusterPageRegistry = new PageRegistry();
export const globalPageRegistry = new PageRegistry<PageRegistration>();
export const clusterPageRegistry = new PageRegistry<PageRegistration>();

View File

@ -1,9 +1,9 @@
// Extensions API -> Status bar customizations
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;
}

View File

@ -6,6 +6,7 @@ import { readFile } from "fs-extra"
import { Cluster } from "./cluster"
import { apiPrefix, appName, publicPath, isDevelopment, webpackDevServerPort } from "../common/vars";
import { helmRoute, kubeconfigRoute, metricsRoute, portForwardRoute, resourceApplierRoute, watchRoute } from "./routes";
import logger from "./logger"
export interface RouterRequestOpts {
req: http.IncomingMessage;
@ -94,7 +95,7 @@ export class Router {
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);
try {
const filename = path.basename(req.url);
@ -112,7 +113,13 @@ export class Router {
res.write(data);
res.end();
} 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
this.router.add(
{ method: 'get', path: '/{path*}' },
({ params, response, path, raw: { req }}: LensApiRequest) => {
({ params, response, path, raw: { req } }: LensApiRequest) => {
this.handleStaticFile(params.path, response, req);
});

View File

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

View File

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

View File

@ -74,27 +74,8 @@ export class App extends React.Component {
}
renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => {
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => {
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 <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 { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { observer } from "mobx-react";
import { matchPath } from "react-router";
import { _i18n } from "../../i18n";
import { t, Trans } from "@lingui/macro";
import { userStore } from "../../../common/user-store";
@ -15,7 +14,7 @@ import { ClusterIcon } from "../cluster-icon";
import { Icon } from "../icon";
import { autobind, cssNames, IClassName } from "../../utils";
import { Badge } from "../badge";
import { navigate, navigation } from "../../navigation";
import { isActiveRoute, navigate } from "../../navigation";
import { addClusterURL } from "../+add-cluster";
import { clusterSettingsURL } from "../+cluster-settings";
import { landingURL } from "../+landing-page";
@ -24,6 +23,7 @@ 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";
interface Props {
className?: IClassName;
@ -149,17 +149,16 @@ export class ClustersMenu extends React.Component<Props> {
)}
</div>
<div className="extensions">
{globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getById(menuItemId);
{globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return;
const { routePath, exact } = registeredPage;
const isActive = !!matchPath(navigation.location.pathname, { path: routePath, exact });
return (
<Icon
key={routePath}
tooltip={title}
active={isActive}
onClick={() => navigate(url)}
active={isActiveRoute({ path: routePath, exact })}
onClick={() => navigate(compile(routePath)(target.params))}
/>
)
})}

View File

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

View File

@ -3,6 +3,7 @@
position: relative;
padding: $padding * 2;
width: 100%;
height: 100%;
display: grid;
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!
## 4.0.0-beta.1 (current version)
## 4.0.0-beta.2 (current version)
- Extension API
- Improved pod logs

View File

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