mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into mobx6-migration
# Conflicts: # src/renderer/components/+user-management/user-management.tsx # src/renderer/components/layout/sidebar.tsx # yarn.lock
This commit is contained in:
commit
16d4fb1818
5
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
5
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Fixes #
|
||||||
|
|
||||||
|
**Description of changes:**
|
||||||
|
|
||||||
|
-
|
||||||
13
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## Changes since v
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
## 🧰 Maintenance
|
||||||
|
|
||||||
|
*
|
||||||
4
Makefile
4
Makefile
@ -41,6 +41,10 @@ lint:
|
|||||||
release-version:
|
release-version:
|
||||||
npm version $(CMD_ARGS) --git-tag-version false
|
npm version $(CMD_ARGS) --git-tag-version false
|
||||||
|
|
||||||
|
.PHONY: tag-release
|
||||||
|
tag-release:
|
||||||
|
scripts/tag-release.sh
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: binaries/client
|
test: binaries/client
|
||||||
yarn run jest $(or $(CMD_ARGS), "src")
|
yarn run jest $(or $(CMD_ARGS), "src")
|
||||||
|
|||||||
@ -2,10 +2,20 @@
|
|||||||
|
|
||||||
Lens releases are built by CICD automatically on git tags. The typical release process flow is the following:
|
Lens releases are built by CICD automatically on git tags. The typical release process flow is the following:
|
||||||
|
|
||||||
1. Create a release branch `release/v{version}` from `master` branch or from existing release branch (for example, `release/v3.5`) on patch releases.
|
1. From a clean and up to date `master` run `make release-version <version-type>` where `<version-type>` is one of the following:
|
||||||
2. Update changelog in `static/RELEASE_NOTES.md` and bump version in `package.json`.
|
- `major`
|
||||||
3. Create PR and put change log in description field.
|
- `minor`
|
||||||
4. After the PR is accepted, create a tag from release branch.
|
- `patch`
|
||||||
5. Push tag to GitHub.
|
- `premajor`
|
||||||
6. Publish automatically created GitHub release.
|
- `preminor`
|
||||||
7. Merge the release PR after the release is published and delete the release branch from GitHub.
|
- `prepatch`
|
||||||
|
- `prerelease [--preid=<prerelease-id>]`
|
||||||
|
- where `<prerelease-id>` is generally one of:
|
||||||
|
- `alpha`
|
||||||
|
- `beta`
|
||||||
|
- `rc`
|
||||||
|
1. Create PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release.
|
||||||
|
1. After the PR is accepted and passes CI. Go to the same branch and run `make tag-release`
|
||||||
|
1. Once CI passes again go to the releases tab on GitHub, create a new release from the tag that was created, make sure that the change log is the same as that of the PR.
|
||||||
|
1. Merge the release PR after the release is published. GitHub should delete the branch once it is merged.
|
||||||
|
1. If you have just released a new major or minor version then create a new `vMAJOR.MINOR` branch from that same tag and push it to master. This will be the target for future patch releases and shouldn't be deleted.
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
# Theme Color Reference
|
|
||||||
You can use theme-based CSS Variables to style an extension according to the active theme.
|
|
||||||
|
|
||||||
## Base Colors
|
|
||||||
- `--blue`: blue color.
|
|
||||||
- `--magenta`: magenta color.
|
|
||||||
- `--golden`: gold/yellow color.
|
|
||||||
- `--halfGray`: gray with some apacity applied.
|
|
||||||
- `--primary`: Lens brand (blue) color.
|
|
||||||
- `--colorSuccess`: successfull operations color.
|
|
||||||
- `--colorOk`: successfull operations (bright version) color.
|
|
||||||
- `--colorInfo`: informational, in-progress color.
|
|
||||||
- `--colorError`: critical error color.
|
|
||||||
- `--colorSoftError`: error color.
|
|
||||||
- `--colorWarning`: warning color.
|
|
||||||
- `--colorVague`: soft gray color for notices, hints etc.
|
|
||||||
- `--colorTerminated`: terminated, closed, stale color.
|
|
||||||
- `--boxShadow`: semi-transparent box-shadow color.
|
|
||||||
|
|
||||||
## Text Colors
|
|
||||||
- `--textColorPrimary`: foreground text color.
|
|
||||||
- `--textColorSecondary`: foreground text color for different paragraps, parts of text.
|
|
||||||
- `--textColorAccent`: foreground text color to highlight its parts.
|
|
||||||
|
|
||||||
## Border Colors
|
|
||||||
- `--borderColor`: border color.
|
|
||||||
- `--borderFaintColor`: fainted (lighter or darker, which depends on the theme) border color.
|
|
||||||
|
|
||||||
## Layout Colors
|
|
||||||
- `--mainBackground`: main background color for the app.
|
|
||||||
- `--contentColor`: background color for panels contains some data.
|
|
||||||
- `--layoutBackground`: background color for layout parts.
|
|
||||||
- `--layoutTabsBackground`: background color for general tabs.
|
|
||||||
- `--layoutTabsActiveColor`: foreground color for general tabs.
|
|
||||||
- `--layoutTabsLineColor`: background color for lines under general tabs.
|
|
||||||
|
|
||||||
## Sidebar Colors
|
|
||||||
- `--sidebarLogoBackground`: background color behind logo in sidebar.
|
|
||||||
- `--sidebarActiveColor`: foreground color for active menu items in sidebar.
|
|
||||||
- `--sidebarSubmenuActiveColor`: foreground color for active submenu items in sidebar.
|
|
||||||
- `--sidebarBackground`: background color for sidebar.
|
|
||||||
|
|
||||||
## Button Colors
|
|
||||||
- `--buttonPrimaryBackground`: button background color for primary actions.
|
|
||||||
- `--buttonDefaultBackground`: default button background color.
|
|
||||||
- `--buttonLightBackground`: light button background color.
|
|
||||||
- `--buttonAccentBackground`: accent button background color.
|
|
||||||
- `--buttonDisabledBackground`: disabled button background color.
|
|
||||||
|
|
||||||
## Table Colors
|
|
||||||
- `--tableBgcStripe`: background color for odd rows in table.
|
|
||||||
- `--tableBgcSelected`: background color for selected row in table.
|
|
||||||
- `--tableHeaderBackground`: background color for table header.
|
|
||||||
- `--tableHeaderBorderWidth`: border width under table header.
|
|
||||||
- `--tableHeaderBorderColor`: border color for line under table header.
|
|
||||||
- `--tableHeaderColor`: foreground color for table header.
|
|
||||||
- `--tableSelectedRowColor`: foreground color for selected row in table.
|
|
||||||
|
|
||||||
## Dock Colors
|
|
||||||
- `--dockHeadBackground`: background color for dock's header.
|
|
||||||
- `--dockInfoBackground`: background color for dock's info panel.
|
|
||||||
- `--dockInfoBorderColor`: border color for dock's info panel.
|
|
||||||
|
|
||||||
## Helm Chart Colors
|
|
||||||
- `--helmLogoBackground`: background color for chart logo.
|
|
||||||
- `--helmImgBackground`: background color for chart image.
|
|
||||||
- `--helmStableRepo`: background color for stable repo.
|
|
||||||
- `--helmIncubatorRepo`: background color for incubator repo.
|
|
||||||
- `--helmDescriptionHr`: Helm chart description separator line color.
|
|
||||||
- `--helmDescriptionBlockqouteColor`: Helm chart description blockquote color.
|
|
||||||
- `--helmDescriptionBlockqouteBorder`: Helm chart description blockquote border color.
|
|
||||||
- `--helmDescriptionBlockquoteBackground`: Helm chart description blockquote background color.
|
|
||||||
- `--helmDescriptionHeaders`: Helm chart description headers color.
|
|
||||||
- `--helmDescriptionH6`: Helm chart description header foreground color.
|
|
||||||
- `--helmDescriptionTdBorder`: Helm chart description table cell border color.
|
|
||||||
- `--helmDescriptionTrBackground`: Helm chart description table row background color.
|
|
||||||
- `--helmDescriptionCodeBackground`: Helm chart description code background color.
|
|
||||||
- `--helmDescriptionPreBackground`: Helm chart description pre background color.
|
|
||||||
- `--helmDescriptionPreColor`: Helm chart description pre foreground color.
|
|
||||||
|
|
||||||
## Terminal Colors
|
|
||||||
- `--terminalBackground`: Terminal background color.
|
|
||||||
- `--terminalForeground`: Terminal foreground color.
|
|
||||||
- `--terminalCursor`: Terminal cursor color.
|
|
||||||
- `--terminalCursorAccent`: Terminal cursor accent color.
|
|
||||||
- `--terminalSelection`: Terminal selection background color.
|
|
||||||
- `--terminalBlack`: Terminal black color.
|
|
||||||
- `--terminalRed`: Terminal red color.
|
|
||||||
- `--terminalGreen`: Terminal green color.
|
|
||||||
- `--terminalYellow`: Terminal yellow color.
|
|
||||||
- `--terminalBlue`: Terminal blue color.
|
|
||||||
- `--terminalMagenta`: Terminal magenta color.
|
|
||||||
- `--terminalCyan`: Terminal cyan color.
|
|
||||||
- `--terminalWhite`: Terminal white color.
|
|
||||||
- `--terminalBrightBlack`: Terminal bright black color.
|
|
||||||
- `--terminalBrightRed`: Terminal bright red color.
|
|
||||||
- `--terminalBrightGreen`: Terminal bright green color.
|
|
||||||
- `--terminalBrightYellow`: Terminal bright yellow color.
|
|
||||||
- `--terminalBrightBlue`: Terminal bright blue color.
|
|
||||||
- `--terminalBrightMagenta`: Terminal bright magenta color.
|
|
||||||
- `--terminalBrightCyan`: Terminal bright cyan color.
|
|
||||||
- `--terminalBrightWhite`: Terminal bright white color.
|
|
||||||
|
|
||||||
## Dialog Colors
|
|
||||||
- `--dialogHeaderBackground`: background color for dialog header.
|
|
||||||
- `--dialogFooterBackground`: background color for dialog footer.
|
|
||||||
|
|
||||||
## Detail Panel (Drawer) Colors
|
|
||||||
- `--drawerTitleText`: drawer title foreground color.
|
|
||||||
- `--drawerSubtitleBackground`: drawer subtitle foreground color.
|
|
||||||
- `--drawerItemNameColor`: foreground color for item name in drawer.
|
|
||||||
- `--drawerItemValueColor`: foreground color for item value in drawer.
|
|
||||||
|
|
||||||
## Misc Colors
|
|
||||||
- `--logsBackground`: background color for pod logs.
|
|
||||||
- `--clusterMenuBackground`: background color for cluster menu.
|
|
||||||
- `--clusterMenuBorderColor`: border color for cluster menu.
|
|
||||||
- `--clusterSettingsBackground`: background color for cluster settings.
|
|
||||||
- `--addClusterIconColor`: add cluster button background color.
|
|
||||||
- `--iconActiveColor`: active cluster icon foreground color.
|
|
||||||
- `--iconActiveBackground`: active cluster icon background color.
|
|
||||||
- `--filterAreaBackground`: page filter area (where selected namespaces are lister) background color.
|
|
||||||
- `--chartStripesColor`: bar chart zebra stripes background color.
|
|
||||||
- `--chartCapacityColor`: background color for capacity values in bar charts.
|
|
||||||
- `--pieChartDefaultColor`: default background color for pie chart values.
|
|
||||||
- `--selectOptionHoveredColor`: foregrond color for selected element in dropdown list.
|
|
||||||
- `--lineProgressBackground`: background color for progress line.
|
|
||||||
- `--radioActiveBackground`: background color for active radio buttons.
|
|
||||||
- `--menuActiveBackground`: background color for active menu items.
|
|
||||||
|
|
||||||
In most cases you would only need base, text and some of the layout colors.
|
|
||||||
@ -103,8 +103,6 @@ For example:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
A complete list of theme colors can be found in the [Color Reference](../color-reference).
|
|
||||||
|
|
||||||
### Theme Switching
|
### Theme Switching
|
||||||
|
|
||||||
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`.
|
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`.
|
||||||
|
|||||||
12
extensions/metrics-cluster-feature/package-lock.json
generated
12
extensions/metrics-cluster-feature/package-lock.json
generated
@ -2954,9 +2954,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4038,9 +4038,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
|
|||||||
12
extensions/node-menu/package-lock.json
generated
12
extensions/node-menu/package-lock.json
generated
@ -2934,9 +2934,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4026,9 +4026,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
|
|||||||
12
extensions/pod-menu/package-lock.json
generated
12
extensions/pod-menu/package-lock.json
generated
@ -3571,9 +3571,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4663,9 +4663,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
|
|||||||
@ -27,7 +27,6 @@ nav:
|
|||||||
- Extension Capabilities:
|
- Extension Capabilities:
|
||||||
- Common Capabilities: extensions/capabilities/common-capabilities.md
|
- Common Capabilities: extensions/capabilities/common-capabilities.md
|
||||||
- Styling: extensions/capabilities/styling.md
|
- Styling: extensions/capabilities/styling.md
|
||||||
- Color Reference: extensions/capabilities/color-reference.md
|
|
||||||
- Extension Guides:
|
- Extension Guides:
|
||||||
- Overview: extensions/guides/README.md
|
- Overview: extensions/guides/README.md
|
||||||
- Generator: extensions/guides/generator.md
|
- Generator: extensions/guides/generator.md
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
"version-checkout": "cat package.json | jq '.version' -r | xargs printf \"release/v%s\" | xargs git checkout -b",
|
"version-checkout": "cat package.json | jq '.version' -r | xargs printf \"release/v%s\" | xargs git checkout -b",
|
||||||
"version-commit": "cat package.json | jq '.version' -r | xargs printf \"release v%s\" | git commit --no-edit -s -F -",
|
"version-commit": "cat package.json | jq '.version' -r | xargs printf \"release v%s\" | git commit --no-edit -s -F -",
|
||||||
"version": "yarn run version-checkout && git add package.json && yarn run version-commit",
|
"version": "yarn run version-checkout && git add package.json && yarn run version-commit",
|
||||||
"postversion": "git push --set-upstream origin release/v$npm_package_version"
|
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bundledKubectlVersion": "1.18.15",
|
"bundledKubectlVersion": "1.18.15",
|
||||||
@ -199,7 +199,7 @@
|
|||||||
"filenamify": "^4.1.0",
|
"filenamify": "^4.1.0",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "^4.7.7",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^8.0.1",
|
"immer": "^8.0.1",
|
||||||
"js-yaml": "^3.14.0",
|
"js-yaml": "^3.14.0",
|
||||||
@ -234,7 +234,7 @@
|
|||||||
"tar": "^6.0.5",
|
"tar": "^6.0.5",
|
||||||
"tcp-port-used": "^1.0.1",
|
"tcp-port-used": "^1.0.1",
|
||||||
"tempy": "^0.5.0",
|
"tempy": "^0.5.0",
|
||||||
"url-parse": "^1.4.7",
|
"url-parse": "^1.5.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.2.0",
|
"win-ca": "^3.2.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
|
|||||||
10
scripts/tag-release.sh
Executable file
10
scripts/tag-release.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ${git branch --show-current} =~ ^release/v ]]
|
||||||
|
then
|
||||||
|
VERSION_STRING=$(cat package.json | jq '.version' -r | xargs printf "v%s")
|
||||||
|
git tag ${VERSION_STRING}
|
||||||
|
git push ${GIT_REMOTE:-origin} ${VERSION_STRING}
|
||||||
|
else
|
||||||
|
echo "You must be in a release branch"
|
||||||
|
fi
|
||||||
@ -4,42 +4,54 @@ export type KubeResource =
|
|||||||
"namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" |
|
"namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" |
|
||||||
"secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" |
|
"secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" |
|
||||||
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
|
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
|
||||||
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets";
|
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" |
|
||||||
|
"roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts";
|
||||||
|
|
||||||
export interface KubeApiResource {
|
export interface KubeApiResource extends KubeApiResourceData {
|
||||||
kind: string; // resource type (e.g. "Namespace")
|
|
||||||
apiName: KubeResource; // valid api resource name (e.g. "namespaces")
|
apiName: KubeResource; // valid api resource name (e.g. "namespaces")
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeApiResourceData {
|
||||||
|
kind: string; // resource type (e.g. "Namespace")
|
||||||
group?: string; // api-group
|
group?: string; // api-group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const apiResourceRecord: Record<KubeResource, KubeApiResourceData> = {
|
||||||
|
"clusterroles": { kind: "ClusterRole", group: "rbac.authorization.k8s.io" },
|
||||||
|
"clusterrolebindings": { kind: "ClusterRoleBinding", group: "rbac.authorization.k8s.io" },
|
||||||
|
"configmaps": { kind: "ConfigMap" },
|
||||||
|
"cronjobs": { kind: "CronJob", group: "batch" },
|
||||||
|
"customresourcedefinitions": { kind: "CustomResourceDefinition", group: "apiextensions.k8s.io" },
|
||||||
|
"daemonsets": { kind: "DaemonSet", group: "apps" },
|
||||||
|
"deployments": { kind: "Deployment", group: "apps" },
|
||||||
|
"endpoints": { kind: "Endpoint" },
|
||||||
|
"events": { kind: "Event" },
|
||||||
|
"horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler" },
|
||||||
|
"ingresses": { kind: "Ingress", group: "networking.k8s.io" },
|
||||||
|
"jobs": { kind: "Job", group: "batch" },
|
||||||
|
"namespaces": { kind: "Namespace" },
|
||||||
|
"limitranges": { kind: "LimitRange" },
|
||||||
|
"networkpolicies": { kind: "NetworkPolicy", group: "networking.k8s.io" },
|
||||||
|
"nodes": { kind: "Node" },
|
||||||
|
"persistentvolumes": { kind: "PersistentVolume" },
|
||||||
|
"persistentvolumeclaims": { kind: "PersistentVolumeClaim" },
|
||||||
|
"pods": { kind: "Pod" },
|
||||||
|
"poddisruptionbudgets": { kind: "PodDisruptionBudget", group: "policy" },
|
||||||
|
"podsecuritypolicies": { kind: "PodSecurityPolicy" },
|
||||||
|
"resourcequotas": { kind: "ResourceQuota" },
|
||||||
|
"replicasets": { kind: "ReplicaSet", group: "apps" },
|
||||||
|
"roles": { kind: "Role", group: "rbac.authorization.k8s.io" },
|
||||||
|
"rolebindings": { kind: "RoleBinding", group: "rbac.authorization.k8s.io" },
|
||||||
|
"secrets": { kind: "Secret" },
|
||||||
|
"serviceaccounts": { kind: "ServiceAccount", group: "core" },
|
||||||
|
"services": { kind: "Service" },
|
||||||
|
"statefulsets": { kind: "StatefulSet", group: "apps" },
|
||||||
|
"storageclasses": { kind: "StorageClass", group: "storage.k8s.io" },
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7)
|
// TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7)
|
||||||
export const apiResources: KubeApiResource[] = [
|
export const apiResources: KubeApiResource[] = Object.entries(apiResourceRecord)
|
||||||
{ kind: "ConfigMap", apiName: "configmaps" },
|
.map(([apiName, data]) => ({ apiName: apiName as KubeResource, ...data }));
|
||||||
{ kind: "CronJob", apiName: "cronjobs", group: "batch" },
|
|
||||||
{ kind: "CustomResourceDefinition", apiName: "customresourcedefinitions", group: "apiextensions.k8s.io" },
|
|
||||||
{ kind: "DaemonSet", apiName: "daemonsets", group: "apps" },
|
|
||||||
{ kind: "Deployment", apiName: "deployments", group: "apps" },
|
|
||||||
{ kind: "Endpoint", apiName: "endpoints" },
|
|
||||||
{ kind: "Event", apiName: "events" },
|
|
||||||
{ kind: "HorizontalPodAutoscaler", apiName: "horizontalpodautoscalers" },
|
|
||||||
{ kind: "Ingress", apiName: "ingresses", group: "networking.k8s.io" },
|
|
||||||
{ kind: "Job", apiName: "jobs", group: "batch" },
|
|
||||||
{ kind: "Namespace", apiName: "namespaces" },
|
|
||||||
{ kind: "LimitRange", apiName: "limitranges" },
|
|
||||||
{ kind: "NetworkPolicy", apiName: "networkpolicies", group: "networking.k8s.io" },
|
|
||||||
{ kind: "Node", apiName: "nodes" },
|
|
||||||
{ kind: "PersistentVolume", apiName: "persistentvolumes" },
|
|
||||||
{ kind: "PersistentVolumeClaim", apiName: "persistentvolumeclaims" },
|
|
||||||
{ kind: "Pod", apiName: "pods" },
|
|
||||||
{ kind: "PodDisruptionBudget", apiName: "poddisruptionbudgets", group: "policy" },
|
|
||||||
{ kind: "PodSecurityPolicy", apiName: "podsecuritypolicies" },
|
|
||||||
{ kind: "ResourceQuota", apiName: "resourcequotas" },
|
|
||||||
{ kind: "ReplicaSet", apiName: "replicasets", group: "apps" },
|
|
||||||
{ kind: "Secret", apiName: "secrets" },
|
|
||||||
{ kind: "Service", apiName: "services" },
|
|
||||||
{ kind: "StatefulSet", apiName: "statefulsets", group: "apps" },
|
|
||||||
{ kind: "StorageClass", apiName: "storageclasses", group: "storage.k8s.io" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function isAllowedResource(resources: KubeResource | KubeResource[]) {
|
export function isAllowedResource(resources: KubeResource | KubeResource[]) {
|
||||||
if (!Array.isArray(resources)) {
|
if (!Array.isArray(resources)) {
|
||||||
|
|||||||
@ -30,11 +30,11 @@ export const webpackDevServerPort = 9009;
|
|||||||
// Special runtime paths
|
// Special runtime paths
|
||||||
defineGlobal("__static", {
|
defineGlobal("__static", {
|
||||||
get() {
|
get() {
|
||||||
if (isDevelopment) {
|
const root = isDevelopment
|
||||||
return path.resolve(contextDir, "static");
|
? contextDir
|
||||||
}
|
: (process.resourcesPath ?? contextDir);
|
||||||
|
|
||||||
return path.resolve(process.resourcesPath, "static");
|
return path.resolve(root, "static");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface WelcomeMenuRegistration {
|
export interface WelcomeMenuRegistration {
|
||||||
title: string;
|
title: string | (() => string);
|
||||||
icon: string;
|
icon: string;
|
||||||
click: () => void | Promise<void>;
|
click: () => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +1,42 @@
|
|||||||
import { Router } from "../router";
|
import { Router } from "../router";
|
||||||
|
|
||||||
const staticRoot = __dirname;
|
|
||||||
|
|
||||||
class TestRouter extends Router {
|
|
||||||
protected resolveStaticRootPath() {
|
|
||||||
return staticRoot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Router", () => {
|
describe("Router", () => {
|
||||||
it("blocks path traversal attacks", async () => {
|
it("blocks path traversal attacks", async () => {
|
||||||
const router = new TestRouter();
|
const response: any = {
|
||||||
const res = {
|
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
end: jest.fn()
|
end: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
await router.handleStaticFile("../index.ts", res as any, {} as any, 0);
|
await (Router as any).handleStaticFile({
|
||||||
|
params: {
|
||||||
|
path: "../index.ts",
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
raw: {},
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toEqual(404);
|
expect(response.statusCode).toEqual(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("serves files under static root", async () => {
|
it("serves files under static root", async () => {
|
||||||
const router = new TestRouter();
|
const response: any = {
|
||||||
const res = {
|
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
write: jest.fn(),
|
write: jest.fn(),
|
||||||
setHeader: jest.fn(),
|
setHeader: jest.fn(),
|
||||||
end: jest.fn()
|
end: jest.fn()
|
||||||
};
|
};
|
||||||
const req = {
|
const req: any = {
|
||||||
url: ""
|
url: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
await router.handleStaticFile("router.test.ts", res as any, req as any, 0);
|
await (Router as any).handleStaticFile({
|
||||||
|
params: {
|
||||||
|
path: "router.test.ts",
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
raw: { req },
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toEqual(200);
|
expect(response.statusCode).toEqual(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttribu
|
|||||||
import { Kubectl } from "./kubectl";
|
import { Kubectl } from "./kubectl";
|
||||||
import { KubeconfigManager } from "./kubeconfig-manager";
|
import { KubeconfigManager } from "./kubeconfig-manager";
|
||||||
import { loadConfig, validateKubeConfig } from "../common/kube-helpers";
|
import { loadConfig, validateKubeConfig } from "../common/kube-helpers";
|
||||||
import { apiResources, KubeApiResource } from "../common/rbac";
|
import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../common/rbac";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { VersionDetector } from "./cluster-detectors/version-detector";
|
import { VersionDetector } from "./cluster-detectors/version-detector";
|
||||||
import { detectorRegistry } from "./cluster-detectors/detector-registry";
|
import { detectorRegistry } from "./cluster-detectors/detector-registry";
|
||||||
@ -676,7 +676,11 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAllowedResource(kind: string): boolean {
|
isAllowedResource(kind: string): boolean {
|
||||||
const apiResource = apiResources.find(resource => resource.kind === kind || resource.apiName === kind);
|
if ((kind as KubeResource) in apiResourceRecord) {
|
||||||
|
return this.allowedResources.includes(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResource = apiResources.find(resource => resource.kind === kind);
|
||||||
|
|
||||||
if (apiResource) {
|
if (apiResource) {
|
||||||
return this.allowedResources.includes(apiResource.apiName);
|
return this.allowedResources.includes(apiResource.apiName);
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import http from "http";
|
|
||||||
|
|
||||||
export abstract class LensApi {
|
|
||||||
protected respondJson(res: http.ServerResponse, content: {}, status = 200) {
|
|
||||||
this.respond(res, JSON.stringify(content), "application/json", status);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected respondText(res: http.ServerResponse, content: string, status = 200) {
|
|
||||||
this.respond(res, content, "text/plain", status);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
|
|
||||||
res.setHeader("Content-Type", contentType);
|
|
||||||
res.statusCode = status;
|
|
||||||
res.end(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +1,12 @@
|
|||||||
import { LensProtocolRouterMain } from "../router";
|
|
||||||
import { noop } from "../../../common/utils";
|
|
||||||
import { ExtensionsStore } from "../../../extensions/extensions-store";
|
|
||||||
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { LensMainExtension } from "../../../extensions/core-api";
|
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler";
|
import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler";
|
||||||
|
import { noop } from "../../../common/utils";
|
||||||
|
import { LensMainExtension } from "../../../extensions/core-api";
|
||||||
|
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||||
|
import { ExtensionsStore } from "../../../extensions/extensions-store";
|
||||||
|
import { LensProtocolRouterMain } from "../router";
|
||||||
|
|
||||||
jest.mock("../../../common/ipc");
|
jest.mock("../../../common/ipc");
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ describe("protocol router tests", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not throw when has valid host", async () => {
|
it.only("should not throw when has valid host", async () => {
|
||||||
const extId = uuid.v4();
|
const extId = uuid.v4();
|
||||||
const ext = new LensMainExtension({
|
const ext = new LensMainExtension({
|
||||||
id: extId,
|
id: extId,
|
||||||
@ -85,14 +86,13 @@ describe("protocol router tests", () => {
|
|||||||
expect(throwIfDefined(error)).not.toThrow();
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(throwIfDefined(error)).not.toThrow();
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(broadcastMessage).toHaveBeenNthCalledWith(1, ProtocolHandlerInternal, "lens://app");
|
expect(broadcastMessage).toHaveBeenNthCalledWith(1, ProtocolHandlerInternal, "lens://app/");
|
||||||
expect(broadcastMessage).toHaveBeenNthCalledWith(2, ProtocolHandlerExtension, "lens://extension/@mirantis/minikube");
|
expect(broadcastMessage).toHaveBeenNthCalledWith(2, ProtocolHandlerExtension, "lens://extension/@mirantis/minikube");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import path from "path";
|
|||||||
import { readFile } from "fs-extra";
|
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, versionRoute } from "./routes";
|
import { HelmApiRoute, KubeconfigRoute, MetricsRoute, PortForwardRoute, ResourceApplierApiRoute, VersionRoute } from "./routes";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
export interface RouterRequestOpts {
|
export interface RouterRequestOpts {
|
||||||
@ -38,18 +38,29 @@ export interface LensApiRequest<P = any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMimeType(filename: string) {
|
||||||
|
const mimeTypes: Record<string, string> = {
|
||||||
|
html: "text/html",
|
||||||
|
txt: "text/plain",
|
||||||
|
css: "text/css",
|
||||||
|
gif: "image/gif",
|
||||||
|
jpg: "image/jpeg",
|
||||||
|
png: "image/png",
|
||||||
|
svg: "image/svg+xml",
|
||||||
|
js: "application/javascript",
|
||||||
|
woff2: "font/woff2",
|
||||||
|
ttf: "font/ttf"
|
||||||
|
};
|
||||||
|
|
||||||
|
return mimeTypes[path.extname(filename).slice(1)] || "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
export class Router {
|
export class Router {
|
||||||
protected router: any;
|
protected router = new Call.Router();
|
||||||
protected staticRootPath: string;
|
protected static rootPath = path.resolve(__static);
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.router = new Call.Router();
|
|
||||||
this.addRoutes();
|
this.addRoutes();
|
||||||
this.staticRootPath = this.resolveStaticRootPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resolveStaticRootPath() {
|
|
||||||
return path.resolve(__static);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||||
@ -90,97 +101,80 @@ export class Router {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getMimeType(filename: string) {
|
protected static async handleStaticFile({ params, response, raw: { req } }: LensApiRequest): Promise<void> {
|
||||||
const mimeTypes: Record<string, string> = {
|
let filePath = params.path;
|
||||||
html: "text/html",
|
|
||||||
txt: "text/plain",
|
|
||||||
css: "text/css",
|
|
||||||
gif: "image/gif",
|
|
||||||
jpg: "image/jpeg",
|
|
||||||
png: "image/png",
|
|
||||||
svg: "image/svg+xml",
|
|
||||||
js: "application/javascript",
|
|
||||||
woff2: "font/woff2",
|
|
||||||
ttf: "font/ttf"
|
|
||||||
};
|
|
||||||
|
|
||||||
return mimeTypes[path.extname(filename).slice(1)] || "text/plain";
|
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
|
||||||
}
|
const asset = path.join(Router.rootPath, filePath);
|
||||||
|
const normalizedFilePath = path.resolve(asset);
|
||||||
|
|
||||||
async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage, retryCount = 0) {
|
if (!normalizedFilePath.startsWith(Router.rootPath)) {
|
||||||
const asset = path.join(this.staticRootPath, filePath);
|
response.statusCode = 404;
|
||||||
const normalizedFilePath = path.resolve(asset);
|
|
||||||
|
|
||||||
if (!normalizedFilePath.startsWith(this.staticRootPath)) {
|
return response.end();
|
||||||
res.statusCode = 404;
|
}
|
||||||
res.end();
|
|
||||||
|
|
||||||
return;
|
try {
|
||||||
|
const filename = path.basename(req.url);
|
||||||
|
// redirect requests to [appName].js, [appName].html /sockjs-node/ to webpack-dev-server (for hot-reload support)
|
||||||
|
const toWebpackDevServer = filename.includes(appName) || filename.includes("hot-update") || req.url.includes("sockjs-node");
|
||||||
|
|
||||||
|
if (isDevelopment && toWebpackDevServer) {
|
||||||
|
const redirectLocation = `http://localhost:${webpackDevServerPort}${req.url}`;
|
||||||
|
|
||||||
|
response.statusCode = 307;
|
||||||
|
response.setHeader("Location", redirectLocation);
|
||||||
|
|
||||||
|
return response.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await readFile(asset);
|
||||||
|
|
||||||
|
response.setHeader("Content-Type", getMimeType(asset));
|
||||||
|
response.write(data);
|
||||||
|
response.end();
|
||||||
|
} catch (err) {
|
||||||
|
if (retryCount > 5) {
|
||||||
|
logger.error("handleStaticFile:", err.toString());
|
||||||
|
response.statusCode = 404;
|
||||||
|
|
||||||
|
return response.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = `${publicPath}/${appName}.html`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const filename = path.basename(req.url);
|
|
||||||
// redirect requests to [appName].js, [appName].html /sockjs-node/ to webpack-dev-server (for hot-reload support)
|
|
||||||
const toWebpackDevServer = filename.includes(appName) || filename.includes("hot-update") || req.url.includes("sockjs-node");
|
|
||||||
|
|
||||||
if (isDevelopment && toWebpackDevServer) {
|
|
||||||
const redirectLocation = `http://localhost:${webpackDevServerPort}${req.url}`;
|
|
||||||
|
|
||||||
res.statusCode = 307;
|
|
||||||
res.setHeader("Location", redirectLocation);
|
|
||||||
res.end();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await readFile(asset);
|
|
||||||
|
|
||||||
res.setHeader("Content-Type", this.getMimeType(asset));
|
|
||||||
res.write(data);
|
|
||||||
res.end();
|
|
||||||
} catch (err) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected addRoutes() {
|
protected addRoutes() {
|
||||||
// Static assets
|
// Static assets
|
||||||
this.router.add(
|
this.router.add({ method: "get", path: "/{path*}" }, Router.handleStaticFile);
|
||||||
{ method: "get", path: "/{path*}" },
|
|
||||||
({ params, response, raw: { req } }: LensApiRequest) => {
|
|
||||||
this.handleStaticFile(params.path, response, req);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.router.add({ method: "get", path: "/version"}, versionRoute.getVersion.bind(versionRoute));
|
this.router.add({ method: "get", path: "/version" }, VersionRoute.getVersion);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}` }, kubeconfigRoute.routeServiceAccountRoute.bind(kubeconfigRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}` }, KubeconfigRoute.routeServiceAccountRoute);
|
||||||
|
|
||||||
// Metrics API
|
// Metrics API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, metricsRoute.routeMetrics.bind(metricsRoute));
|
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
||||||
|
|
||||||
// Port-forward API
|
// Port-forward API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, portForwardRoute.routePortForward.bind(portForwardRoute));
|
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, PortForwardRoute.routePortForward);
|
||||||
|
|
||||||
// Helm API
|
// Helm API
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, helmRoute.listCharts.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, helmRoute.getChart.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, HelmApiRoute.getChart);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, helmRoute.getChartValues.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, HelmApiRoute.getChartValues);
|
||||||
|
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, helmRoute.installChart.bind(helmRoute));
|
this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, HelmApiRoute.installChart);
|
||||||
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.updateRelease.bind(helmRoute));
|
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.updateRelease);
|
||||||
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, helmRoute.rollbackRelease.bind(helmRoute));
|
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, HelmApiRoute.rollbackRelease);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, helmRoute.listReleases.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, HelmApiRoute.listReleases);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.getRelease.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.getRelease);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, helmRoute.getReleaseValues.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, HelmApiRoute.getReleaseValues);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, helmRoute.getReleaseHistory.bind(helmRoute));
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, HelmApiRoute.getReleaseHistory);
|
||||||
this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.deleteRelease.bind(helmRoute));
|
this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.deleteRelease);
|
||||||
|
|
||||||
// Resource Applier API
|
// Resource Applier API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, resourceApplierRoute.applyResource.bind(resourceApplierRoute));
|
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.applyResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,143 +1,141 @@
|
|||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { helmService } from "../helm/helm-service";
|
import { helmService } from "../helm/helm-service";
|
||||||
import { LensApi } from "../lens-api";
|
import { respondJson, respondText } from "../utils/http-responses";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
|
||||||
class HelmApiRoute extends LensApi {
|
export class HelmApiRoute {
|
||||||
public async listCharts(request: LensApiRequest) {
|
static async listCharts(request: LensApiRequest) {
|
||||||
const { response } = request;
|
const { response } = request;
|
||||||
const charts = await helmService.listCharts();
|
const charts = await helmService.listCharts();
|
||||||
|
|
||||||
this.respondJson(response, charts);
|
respondJson(response, charts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChart(request: LensApiRequest) {
|
static async getChart(request: LensApiRequest) {
|
||||||
const { params, query, response } = request;
|
const { params, query, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const chart = await helmService.getChart(params.repo, params.chart, query.get("version"));
|
const chart = await helmService.getChart(params.repo, params.chart, query.get("version"));
|
||||||
|
|
||||||
this.respondJson(response, chart);
|
respondJson(response, chart);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChartValues(request: LensApiRequest) {
|
static async getChartValues(request: LensApiRequest) {
|
||||||
const { params, query, response } = request;
|
const { params, query, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const values = await helmService.getChartValues(params.repo, params.chart, query.get("version"));
|
const values = await helmService.getChartValues(params.repo, params.chart, query.get("version"));
|
||||||
|
|
||||||
this.respondJson(response, values);
|
respondJson(response, values);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async installChart(request: LensApiRequest) {
|
static async installChart(request: LensApiRequest) {
|
||||||
const { payload, cluster, response } = request;
|
const { payload, cluster, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.installChart(cluster, payload);
|
const result = await helmService.installChart(cluster, payload);
|
||||||
|
|
||||||
this.respondJson(response, result, 201);
|
respondJson(response, result, 201);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateRelease(request: LensApiRequest) {
|
static async updateRelease(request: LensApiRequest) {
|
||||||
const { cluster, params, payload, response } = request;
|
const { cluster, params, payload, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload );
|
const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload );
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async rollbackRelease(request: LensApiRequest) {
|
static async rollbackRelease(request: LensApiRequest) {
|
||||||
const { cluster, params, payload, response } = request;
|
const { cluster, params, payload, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
|
const result = await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listReleases(request: LensApiRequest) {
|
static async listReleases(request: LensApiRequest) {
|
||||||
const { cluster, params, response } = request;
|
const { cluster, params, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.listReleases(cluster, params.namespace);
|
const result = await helmService.listReleases(cluster, params.namespace);
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRelease(request: LensApiRequest) {
|
static async getRelease(request: LensApiRequest) {
|
||||||
const { cluster, params, response } = request;
|
const { cluster, params, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.getRelease(cluster, params.release, params.namespace);
|
const result = await helmService.getRelease(cluster, params.release, params.namespace);
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseValues(request: LensApiRequest) {
|
static async getReleaseValues(request: LensApiRequest) {
|
||||||
const { cluster, params, response, query } = request;
|
const { cluster, params, response, query } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.getReleaseValues(cluster, params.release, params.namespace, query.has("all"));
|
const result = await helmService.getReleaseValues(cluster, params.release, params.namespace, query.has("all"));
|
||||||
|
|
||||||
this.respondText(response, result);
|
respondText(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseHistory(request: LensApiRequest) {
|
static async getReleaseHistory(request: LensApiRequest) {
|
||||||
const { cluster, params, response } = request;
|
const { cluster, params, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.getReleaseHistory(cluster, params.release, params.namespace);
|
const result = await helmService.getReleaseHistory(cluster, params.release, params.namespace);
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteRelease(request: LensApiRequest) {
|
static async deleteRelease(request: LensApiRequest) {
|
||||||
const { cluster, params, response } = request;
|
const { cluster, params, response } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await helmService.deleteRelease(cluster, params.release, params.namespace);
|
const result = await helmService.deleteRelease(cluster, params.release, params.namespace);
|
||||||
|
|
||||||
this.respondJson(response, result);
|
respondJson(response, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const helmRoute = new HelmApiRoute();
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { LensApi } from "../lens-api";
|
import { respondJson } from "../utils/http-responses";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import { CoreV1Api, V1Secret } from "@kubernetes/client-node";
|
import { CoreV1Api, V1Secret } from "@kubernetes/client-node";
|
||||||
|
|
||||||
@ -40,9 +40,8 @@ function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class KubeconfigRoute extends LensApi {
|
export class KubeconfigRoute {
|
||||||
|
static async routeServiceAccountRoute(request: LensApiRequest) {
|
||||||
public async routeServiceAccountRoute(request: LensApiRequest) {
|
|
||||||
const { params, response, cluster} = request;
|
const { params, response, cluster} = request;
|
||||||
const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
|
const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
|
||||||
const secretList = await client.listNamespacedSecret(params.namespace);
|
const secretList = await client.listNamespacedSecret(params.namespace);
|
||||||
@ -53,8 +52,6 @@ class KubeconfigRoute extends LensApi {
|
|||||||
});
|
});
|
||||||
const data = generateKubeConfig(params.account, secret, cluster);
|
const data = generateKubeConfig(params.account, secret, cluster);
|
||||||
|
|
||||||
this.respondJson(response, data);
|
respondJson(response, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubeconfigRoute = new KubeconfigRoute();
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { LensApi } from "../lens-api";
|
import { respondJson } from "../utils/http-responses";
|
||||||
import { Cluster, ClusterMetadataKey } from "../cluster";
|
import { Cluster, ClusterMetadataKey } from "../cluster";
|
||||||
import { ClusterPrometheusMetadata } from "../../common/cluster-store";
|
import { ClusterPrometheusMetadata } from "../../common/cluster-store";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
@ -41,8 +41,8 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
|
|||||||
return Promise.all(queries.map(loadMetric));
|
return Promise.all(queries.map(loadMetric));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetricsRoute extends LensApi {
|
export class MetricsRoute {
|
||||||
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
||||||
const prometheusMetadata: ClusterPrometheusMetadata = {};
|
const prometheusMetadata: ClusterPrometheusMetadata = {};
|
||||||
|
|
||||||
@ -57,20 +57,19 @@ class MetricsRoute extends LensApi {
|
|||||||
|
|
||||||
if (!prometheusPath) {
|
if (!prometheusPath) {
|
||||||
prometheusMetadata.success = false;
|
prometheusMetadata.success = false;
|
||||||
this.respondJson(response, {});
|
|
||||||
|
|
||||||
return;
|
return respondJson(response, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// return data in same structure as query
|
// return data in same structure as query
|
||||||
if (typeof payload === "string") {
|
if (typeof payload === "string") {
|
||||||
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
|
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
|
||||||
|
|
||||||
this.respondJson(response, data);
|
respondJson(response, data);
|
||||||
} else if (Array.isArray(payload)) {
|
} else if (Array.isArray(payload)) {
|
||||||
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
|
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
|
||||||
|
|
||||||
this.respondJson(response, data);
|
respondJson(response, data);
|
||||||
} else {
|
} else {
|
||||||
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
|
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
|
||||||
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
|
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
|
||||||
@ -78,16 +77,14 @@ class MetricsRoute extends LensApi {
|
|||||||
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
||||||
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
||||||
|
|
||||||
this.respondJson(response, data);
|
respondJson(response, data);
|
||||||
}
|
}
|
||||||
prometheusMetadata.success = true;
|
prometheusMetadata.success = true;
|
||||||
} catch {
|
} catch {
|
||||||
prometheusMetadata.success = false;
|
prometheusMetadata.success = false;
|
||||||
this.respondJson(response, {});
|
respondJson(response, {});
|
||||||
} finally {
|
} finally {
|
||||||
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
|
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metricsRoute = new MetricsRoute();
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { LensApi } from "../lens-api";
|
|
||||||
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
|
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
|
||||||
import { Kubectl } from "../kubectl";
|
import { Kubectl } from "../kubectl";
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import * as tcpPortUsed from "tcp-port-used";
|
import * as tcpPortUsed from "tcp-port-used";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { getPortFrom } from "../utils/get-port";
|
import { getPortFrom } from "../utils/get-port";
|
||||||
|
import { respondJson } from "../utils/http-responses";
|
||||||
|
|
||||||
interface PortForwardArgs {
|
interface PortForwardArgs {
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
@ -95,9 +95,8 @@ class PortForward {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PortForwardRoute extends LensApi {
|
export class PortForwardRoute {
|
||||||
|
static async routePortForward(request: LensApiRequest) {
|
||||||
public async routePortForward(request: LensApiRequest) {
|
|
||||||
const { params, response, cluster} = request;
|
const { params, response, cluster} = request;
|
||||||
const { namespace, port, resourceType, resourceName } = params;
|
const { namespace, port, resourceType, resourceName } = params;
|
||||||
let portForward = PortForward.getPortforward({
|
let portForward = PortForward.getPortforward({
|
||||||
@ -117,18 +116,14 @@ class PortForwardRoute extends LensApi {
|
|||||||
const started = await portForward.start();
|
const started = await portForward.start();
|
||||||
|
|
||||||
if (!started) {
|
if (!started) {
|
||||||
this.respondJson(response, {
|
return respondJson(response, {
|
||||||
message: "Failed to open port-forward"
|
message: "Failed to open port-forward"
|
||||||
}, 400);
|
}, 400);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
portForward.open();
|
portForward.open();
|
||||||
|
|
||||||
this.respondJson(response, {});
|
respondJson(response, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const portForwardRoute = new PortForwardRoute();
|
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { LensApi } from "../lens-api";
|
import { respondJson, respondText } from "../utils/http-responses";
|
||||||
import { ResourceApplier } from "../resource-applier";
|
import { ResourceApplier } from "../resource-applier";
|
||||||
|
|
||||||
class ResourceApplierApiRoute extends LensApi {
|
export class ResourceApplierApiRoute {
|
||||||
public async applyResource(request: LensApiRequest) {
|
static async applyResource(request: LensApiRequest) {
|
||||||
const { response, cluster, payload } = request;
|
const { response, cluster, payload } = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resource = await new ResourceApplier(cluster).apply(payload);
|
const resource = await new ResourceApplier(cluster).apply(payload);
|
||||||
|
|
||||||
this.respondJson(response, [resource], 200);
|
respondJson(response, [resource], 200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const resourceApplierRoute = new ResourceApplierApiRoute();
|
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { LensApiRequest } from "../router";
|
import { LensApiRequest } from "../router";
|
||||||
import { LensApi } from "../lens-api";
|
import { respondJson } from "../utils/http-responses";
|
||||||
import { getAppVersion } from "../../common/utils";
|
import { getAppVersion } from "../../common/utils";
|
||||||
|
|
||||||
class VersionRoute extends LensApi {
|
export class VersionRoute {
|
||||||
public async getVersion(request: LensApiRequest) {
|
static async getVersion(request: LensApiRequest) {
|
||||||
const { response } = request;
|
const { response } = request;
|
||||||
|
|
||||||
this.respondJson(response, { version: getAppVersion()}, 200);
|
respondJson(response, { version: getAppVersion()}, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const versionRoute = new VersionRoute();
|
|
||||||
|
|||||||
15
src/main/utils/http-responses.ts
Normal file
15
src/main/utils/http-responses.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import http from "http";
|
||||||
|
|
||||||
|
export function respondJson(res: http.ServerResponse, content: any, status = 200) {
|
||||||
|
respond(res, JSON.stringify(content), "application/json", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function respondText(res: http.ServerResponse, content: string, status = 200) {
|
||||||
|
respond(res, content, "text/plain", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
|
||||||
|
res.setHeader("Content-Type", contentType);
|
||||||
|
res.statusCode = status;
|
||||||
|
res.end(content);
|
||||||
|
}
|
||||||
@ -107,19 +107,6 @@ describe("KubeObject", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
type TestCase = [string, any];
|
|
||||||
const tests: TestCase[] = [
|
|
||||||
["metadata.uid", { kind: "", apiVersion: "", metadata: { name: "", resourceVersion: "", selfLink: ""} }],
|
|
||||||
["metadata.name", { kind: "", apiVersion: "", metadata: { uid: "", resourceVersion: "", selfLink: "" } }],
|
|
||||||
["metadata.resourceVersion", { kind: "", apiVersion: "", metadata: { uid: "", name: "", selfLink: "" } }],
|
|
||||||
];
|
|
||||||
|
|
||||||
it.each(tests)("should reject with missing non-top level field: %s", (missingField, input) => {
|
|
||||||
expect(KubeObject.isPartialJsonApiData(input)).toBe(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
type TestCase = [string, any];
|
type TestCase = [string, any];
|
||||||
const tests: TestCase[] = [
|
const tests: TestCase[] = [
|
||||||
@ -192,7 +179,6 @@ describe("KubeObject", () => {
|
|||||||
["kind", { apiVersion: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
|
["kind", { apiVersion: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
|
||||||
["apiVersion", { kind: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
|
["apiVersion", { kind: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
|
||||||
["metadata", { kind: "", items: [], apiVersion: "" }],
|
["metadata", { kind: "", items: [], apiVersion: "" }],
|
||||||
["metadata.resourceVersion", { kind: "", items: [], apiVersion: "", metadata: { selfLink: "" } }],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
it.each(tests)("should reject with missing: %s", (missingField, input) => {
|
it.each(tests)("should reject with missing: %s", (missingField, input) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { KubeObject } from "../kube-object";
|
|||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
export class PodMetrics extends KubeObject {
|
export class PodMetrics extends KubeObject {
|
||||||
static kind = "Pod";
|
static kind = "PodMetrics";
|
||||||
static namespaced = true;
|
static namespaced = true;
|
||||||
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
|
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,7 @@ export class KubeObject implements ItemObject {
|
|||||||
static isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata {
|
static isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata {
|
||||||
return (
|
return (
|
||||||
isObject(object)
|
isObject(object)
|
||||||
&& hasTypedProperty(object, "resourceVersion", isString)
|
&& hasOptionalProperty(object, "resourceVersion", isString)
|
||||||
&& hasOptionalProperty(object, "selfLink", isString)
|
&& hasOptionalProperty(object, "selfLink", isString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -111,12 +111,28 @@ export class KubeObject implements ItemObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isPartialJsonApiMetadata(object: unknown): object is Partial<KubeJsonApiMetadata> {
|
||||||
|
return (
|
||||||
|
isObject(object)
|
||||||
|
&& hasOptionalProperty(object, "uid", isString)
|
||||||
|
&& hasOptionalProperty(object, "name", isString)
|
||||||
|
&& hasOptionalProperty(object, "resourceVersion", isString)
|
||||||
|
&& hasOptionalProperty(object, "selfLink", isString)
|
||||||
|
&& hasOptionalProperty(object, "namespace", isString)
|
||||||
|
&& hasOptionalProperty(object, "creationTimestamp", isString)
|
||||||
|
&& hasOptionalProperty(object, "continue", isString)
|
||||||
|
&& hasOptionalProperty(object, "finalizers", bindPredicate(isTypedArray, isString))
|
||||||
|
&& hasOptionalProperty(object, "labels", bindPredicate(isRecord, isString, isString))
|
||||||
|
&& hasOptionalProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static isPartialJsonApiData(object: unknown): object is Partial<KubeJsonApiData> {
|
static isPartialJsonApiData(object: unknown): object is Partial<KubeJsonApiData> {
|
||||||
return (
|
return (
|
||||||
isObject(object)
|
isObject(object)
|
||||||
&& hasOptionalProperty(object, "kind", isString)
|
&& hasOptionalProperty(object, "kind", isString)
|
||||||
&& hasOptionalProperty(object, "apiVersion", isString)
|
&& hasOptionalProperty(object, "apiVersion", isString)
|
||||||
&& hasOptionalProperty(object, "metadata", KubeObject.isKubeJsonApiMetadata)
|
&& hasOptionalProperty(object, "metadata", KubeObject.isPartialJsonApiMetadata)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -167,15 +167,13 @@ async function validatePackage(filePath: string): Promise<LensExtensionManifest>
|
|||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTempFilesAndValidate({ fileName, dataP }: InstallRequest, disposer: ExtendableDisposer): Promise<InstallRequestValidated | null> {
|
async function createTempFilesAndValidate({ fileName, dataP }: InstallRequest): Promise<InstallRequestValidated | null> {
|
||||||
// copy files to temp
|
// copy files to temp
|
||||||
await fse.ensureDir(getExtensionPackageTemp());
|
await fse.ensureDir(getExtensionPackageTemp());
|
||||||
|
|
||||||
// validate packages
|
// validate packages
|
||||||
const tempFile = getExtensionPackageTemp(fileName);
|
const tempFile = getExtensionPackageTemp(fileName);
|
||||||
|
|
||||||
disposer.push(() => fse.unlink(tempFile));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await dataP;
|
const data = await dataP;
|
||||||
|
|
||||||
@ -318,7 +316,7 @@ export async function attemptInstallByInfo({ name, version, requireConfirmation
|
|||||||
|
|
||||||
async function attemptInstall(request: InstallRequest, d?: ExtendableDisposer): Promise<void> {
|
async function attemptInstall(request: InstallRequest, d?: ExtendableDisposer): Promise<void> {
|
||||||
const dispose = disposer(ExtensionInstallationStateStore.startPreInstall(), d);
|
const dispose = disposer(ExtensionInstallationStateStore.startPreInstall(), d);
|
||||||
const validatedRequest = await createTempFilesAndValidate(request, dispose);
|
const validatedRequest = await createTempFilesAndValidate(request);
|
||||||
|
|
||||||
if (!validatedRequest) {
|
if (!validatedRequest) {
|
||||||
return dispose();
|
return dispose();
|
||||||
|
|||||||
@ -14,26 +14,34 @@ export class UserManagement extends React.Component {
|
|||||||
static get tabRoutes() {
|
static get tabRoutes() {
|
||||||
const tabRoutes: TabLayoutRoute[] = [];
|
const tabRoutes: TabLayoutRoute[] = [];
|
||||||
|
|
||||||
tabRoutes.push(
|
if (isAllowedResource("serviceaccounts")) {
|
||||||
{
|
tabRoutes.push({
|
||||||
title: "Service Accounts",
|
title: "Service Accounts",
|
||||||
component: ServiceAccounts,
|
component: ServiceAccounts,
|
||||||
url: serviceAccountsURL(),
|
url: serviceAccountsURL(),
|
||||||
routePath: serviceAccountsRoute.path.toString(),
|
routePath: serviceAccountsRoute.path.toString(),
|
||||||
},
|
});
|
||||||
{
|
}
|
||||||
|
|
||||||
|
if (isAllowedResource("rolebindings") || isAllowedResource("clusterrolebindings")) {
|
||||||
|
// TODO: seperate out these two pages
|
||||||
|
tabRoutes.push({
|
||||||
title: "Role Bindings",
|
title: "Role Bindings",
|
||||||
component: RoleBindings,
|
component: RoleBindings,
|
||||||
url: roleBindingsURL(),
|
url: roleBindingsURL(),
|
||||||
routePath: roleBindingsRoute.path.toString(),
|
routePath: roleBindingsRoute.path.toString(),
|
||||||
},
|
});
|
||||||
{
|
}
|
||||||
|
|
||||||
|
if (isAllowedResource("roles") || isAllowedResource("clusterroles")) {
|
||||||
|
// TODO: seperate out these two pages
|
||||||
|
tabRoutes.push({
|
||||||
title: "Roles",
|
title: "Roles",
|
||||||
component: Roles,
|
component: Roles,
|
||||||
url: rolesURL(),
|
url: rolesURL(),
|
||||||
routePath: rolesRoute.path.toString(),
|
routePath: rolesRoute.path.toString(),
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
if (isAllowedResource("podsecuritypolicies")) {
|
if (isAllowedResource("podsecuritypolicies")) {
|
||||||
tabRoutes.push({
|
tabRoutes.push({
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export class Welcome extends React.Component {
|
|||||||
<ul className="box">
|
<ul className="box">
|
||||||
{ welcomeMenuRegistry.getItems().map((item, index) => (
|
{ welcomeMenuRegistry.getItems().map((item, index) => (
|
||||||
<li key={index} className="flex grid-12" onClick={() => item.click()}>
|
<li key={index} className="flex grid-12" onClick={() => item.click()}>
|
||||||
<Icon material={item.icon} className="box col-1" /> <a className="box col-10">{item.title}</a> <Icon material="navigate_next" className="box col-1" />
|
<Icon material={item.icon} className="box col-1" /> <a className="box col-10">{typeof item.title === "string" ? item.title : item.title()}</a> <Icon material="navigate_next" className="box col-1" />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -255,6 +255,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
id="users"
|
id="users"
|
||||||
text="Access Control"
|
text="Access Control"
|
||||||
isActive={isActiveRoute(usersManagementRoute)}
|
isActive={isActiveRoute(usersManagementRoute)}
|
||||||
|
isHidden={UserManagement.tabRoutes.length === 0}
|
||||||
url={usersManagementURL()}
|
url={usersManagementURL()}
|
||||||
icon={<Icon material="security"/>}
|
icon={<Icon material="security"/>}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -106,7 +106,7 @@ html {
|
|||||||
&--is-disabled {
|
&--is-disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background: none !important;
|
background: none !important;
|
||||||
color: $contentColor;
|
color: inherit;
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +166,10 @@ html {
|
|||||||
&--is-focused {
|
&--is-focused {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--is-disabled:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,4 +26,9 @@ export const ResourceNames: Record<KubeResource, string> = {
|
|||||||
"podsecuritypolicies": "Pod Security Policies",
|
"podsecuritypolicies": "Pod Security Policies",
|
||||||
"poddisruptionbudgets": "Pod Disruption Budgets",
|
"poddisruptionbudgets": "Pod Disruption Budgets",
|
||||||
"limitranges": "Limit Ranges",
|
"limitranges": "Limit Ranges",
|
||||||
|
"roles": "Roles",
|
||||||
|
"rolebindings": "Role Bindings",
|
||||||
|
"clusterrolebindings": "Cluster Role Bindings",
|
||||||
|
"clusterroles": "Cluster Roles",
|
||||||
|
"serviceaccounts": "Service Accounts"
|
||||||
};
|
};
|
||||||
|
|||||||
22
yarn.lock
22
yarn.lock
@ -6587,10 +6587,10 @@ handle-thing@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
||||||
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
|
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
|
||||||
|
|
||||||
handlebars@^4.7.2, handlebars@^4.7.6:
|
handlebars@^4.7.2, handlebars@^4.7.6, handlebars@^4.7.7:
|
||||||
version "4.7.6"
|
version "4.7.7"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||||
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
|
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
neo-async "^2.6.0"
|
neo-async "^2.6.0"
|
||||||
@ -6767,9 +6767,9 @@ homedir-polyfill@^1.0.1:
|
|||||||
parse-passwd "^1.0.0"
|
parse-passwd "^1.0.0"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.8"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||||
|
|
||||||
hosted-git-info@^2.7.1, hosted-git-info@^2.8.9:
|
hosted-git-info@^2.7.1, hosted-git-info@^2.8.9:
|
||||||
version "2.8.9"
|
version "2.8.9"
|
||||||
@ -14117,10 +14117,10 @@ url-parse-lax@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prepend-http "^2.0.0"
|
prepend-http "^2.0.0"
|
||||||
|
|
||||||
url-parse@^1.4.3, url-parse@^1.4.7:
|
url-parse@^1.4.3, url-parse@^1.5.1:
|
||||||
version "1.4.7"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
|
||||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user