mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix/namespace-selector-gradients
This commit is contained in:
commit
550c678dc8
29
.eslintrc.js
29
.eslintrc.js
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
const packageJson = require("./package.json");
|
const packageJson = require("./package.json");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -27,9 +48,11 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
"unused-imports"
|
"header",
|
||||||
|
"unused-imports",
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
"indent": ["error", 2, {
|
"indent": ["error", 2, {
|
||||||
"SwitchCase": 1,
|
"SwitchCase": 1,
|
||||||
}],
|
}],
|
||||||
@ -72,6 +95,7 @@ module.exports = {
|
|||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
"header",
|
||||||
"unused-imports"
|
"unused-imports"
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@ -79,6 +103,7 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
"no-invalid-this": "off",
|
"no-invalid-this": "off",
|
||||||
"@typescript-eslint/no-invalid-this": ["error"],
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
@ -127,6 +152,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: [
|
plugins: [
|
||||||
|
"header",
|
||||||
"unused-imports"
|
"unused-imports"
|
||||||
],
|
],
|
||||||
extends: [
|
extends: [
|
||||||
@ -139,6 +165,7 @@ module.exports = {
|
|||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
"no-invalid-this": "off",
|
"no-invalid-this": "off",
|
||||||
"@typescript-eslint/no-invalid-this": ["error"],
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
|||||||
16
.github/workflows/license-header.yml
vendored
16
.github/workflows/license-header.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
css:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -23,16 +23,4 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" *.ts*
|
addlicense -check -l mit -c "OpenLens Authors" src/**/*.?css
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/common/**/*.ts*
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/common/**/*.?css
|
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/main/**/*.ts*
|
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/renderer/**/*.ts*
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/renderer/**/*.?css
|
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/extensions/**/*.ts*
|
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" extensions/**/*.ts*
|
|
||||||
|
|||||||
1
Makefile
1
Makefile
@ -125,6 +125,7 @@ docs:
|
|||||||
clean-extensions:
|
clean-extensions:
|
||||||
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
|
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
|
||||||
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
|
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
|
||||||
|
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm $(dir)/*.tgz || true)
|
||||||
|
|
||||||
.PHONY: clean-npm
|
.PHONY: clean-npm
|
||||||
clean-npm:
|
clean-npm:
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
module.exports = {
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
Trans: ({ children }: { children: React.ReactNode }) => children,
|
Trans: ({ children }: { children: React.ReactNode }) => children,
|
||||||
t: (message: string) => message
|
t: (message: string) => message
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
module.exports = {
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
require: jest.fn(),
|
require: jest.fn(),
|
||||||
match: jest.fn(),
|
match: jest.fn(),
|
||||||
app: {
|
app: {
|
||||||
|
|||||||
@ -1 +1,22 @@
|
|||||||
module.exports = {};
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {};
|
||||||
|
|||||||
@ -1 +1,21 @@
|
|||||||
module.exports = {};
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
export default {};
|
||||||
|
|||||||
@ -1,5 +1,23 @@
|
|||||||
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
|
/**
|
||||||
// Command: `yarn build:tray-icons`
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import jsdom from "jsdom";
|
import jsdom from "jsdom";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { helmCli } from "../src/main/helm/helm-cli";
|
import { helmCli } from "../src/main/helm/helm-cli";
|
||||||
|
|
||||||
helmCli.ensureBinary();
|
helmCli.ensureBinary();
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import packageInfo from "../package.json";
|
import packageInfo from "../package.json";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import request from "request";
|
import request from "request";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const { notarize } = require("electron-notarize");
|
const { notarize } = require("electron-notarize");
|
||||||
|
|
||||||
exports.default = async function notarizing(context) {
|
exports.default = async function notarizing(context) {
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import appInfo from "../package.json";
|
import appInfo from "../package.json";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import packageInfo from "../src/extensions/npm/extensions/package.json";
|
import packageInfo from "../src/extensions/npm/extensions/package.json";
|
||||||
|
|||||||
@ -1,12 +1 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block analytics %}
|
|
||||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-90E1JB4HW4"></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', 'G-90E1JB4HW4');
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -24,6 +24,7 @@ Each guide or code sample includes the following:
|
|||||||
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
||||||
| [Working with mobx](working-with-mobx.md) | |
|
| [Working with mobx](working-with-mobx.md) | |
|
||||||
| [Protocol Handlers](protocol-handlers.md) | |
|
| [Protocol Handlers](protocol-handlers.md) | |
|
||||||
|
| [Sending Data between main and renderer](ipc.md) | |
|
||||||
|
|
||||||
## Samples
|
## Samples
|
||||||
|
|
||||||
|
|||||||
131
docs/extensions/guides/ipc.md
Normal file
131
docs/extensions/guides/ipc.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# Inter Process Communication
|
||||||
|
|
||||||
|
A Lens Extension can utilize IPC to send information between its `LensRendererExtension` and its `LensMainExtension`.
|
||||||
|
This is useful when wanting to communicate directly within your extension.
|
||||||
|
For example, if a user logs into a service that your extension is a facade for and `main` needs to know some information so that you can start syncing items to the `Catalog`, this would be a good way to send that information along.
|
||||||
|
|
||||||
|
IPC channels are blocked off per extension.
|
||||||
|
Meaning that each extension can only communicate with itself.
|
||||||
|
|
||||||
|
## Types of IPC
|
||||||
|
|
||||||
|
There are two flavours of IPC that are provided:
|
||||||
|
|
||||||
|
- Event based
|
||||||
|
- Request based
|
||||||
|
|
||||||
|
### Event Based IPC
|
||||||
|
|
||||||
|
This is the same as an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter) but is not limited to just one Javascript process.
|
||||||
|
This is a good option when you need to report that something has happened but you don't need a response.
|
||||||
|
|
||||||
|
This is a fully two-way form of communication.
|
||||||
|
Both `LensMainExtension` and `LensRendererExtension` can do this sort of IPC.
|
||||||
|
|
||||||
|
### Request Based IPC
|
||||||
|
|
||||||
|
This is more like a Remote Procedure Call (RPC).
|
||||||
|
With this sort of IPC the caller waits for the result from the other side.
|
||||||
|
This is accomplished by returning a `Promise<T>` which needs to be `await`-ed.
|
||||||
|
|
||||||
|
This is a unidirectional form of communication.
|
||||||
|
Only `LensRendererExtension` can initiate this kind of request, and only `LensMainExtension` can and respond this this kind of request.
|
||||||
|
|
||||||
|
## Registering IPC Handlers and Listeners
|
||||||
|
|
||||||
|
The general terminology is as follows:
|
||||||
|
|
||||||
|
- A "handler" is the function that responds to a "Request Based IPC" event.
|
||||||
|
- A "listener" is the function that is called when a "Event Based IPC" event is emitted.
|
||||||
|
|
||||||
|
To register either a handler or a listener, you should do something like the following:
|
||||||
|
|
||||||
|
`main.ts`:
|
||||||
|
```typescript
|
||||||
|
import { LensMainExtension, Interface, Types, Store } from "@k8slens/extensions";
|
||||||
|
import { registerListeners, IpcMain } from "./helpers/main";
|
||||||
|
|
||||||
|
export class ExampleExtensionMain extends LensMainExtension {
|
||||||
|
onActivate() {
|
||||||
|
IpcMain.createInstance(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This file shows that you need to create an instance of the store to be able to use IPC.
|
||||||
|
Lens will automatically clean up that store and all the handlers on deactivation and uninstall.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`helpers/main.ts`:
|
||||||
|
```typescript
|
||||||
|
import { Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
export class IpcMain extends Store.MainIpcStore {
|
||||||
|
constructor(extension: LensMainExtension) {
|
||||||
|
super(extension);
|
||||||
|
|
||||||
|
this.listenIpc("initialize", onInitialize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInitialize(event: Types.IpcMainEvent, id: string) {
|
||||||
|
console.log(`starting to initialize: ${id}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In other files, it is not necessary to pass around any instances.
|
||||||
|
It should be able to just call `getInstance()` everywhere in your extension as needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`renderer.ts`:
|
||||||
|
```typescript
|
||||||
|
import { LensRendererExtension, Interface, Types } from "@k8slens/extensions";
|
||||||
|
import { IpcRenderer } from "./helpers/renderer";
|
||||||
|
|
||||||
|
export class ExampleExtensionRenderer extends LensRendererExtension {
|
||||||
|
onActivate() {
|
||||||
|
const ipc = IpcRenderer.createInstance(this);
|
||||||
|
|
||||||
|
setTimeout(() => ipc.broadcastIpc("initialize", "an-id"), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also needed to create an instance to broadcast messages too.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`helpers/renderer.ts`:
|
||||||
|
```typescript
|
||||||
|
import { Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
export class IpcMain extends Store.RendererIpcStore {}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is necessary to create child classes of these `abstract class`'s in your extension before you can use them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
As this example shows: the channel names *must* be the same.
|
||||||
|
It should also be noted that "listeners" and "handlers" are specific to either `LensRendererExtension` and `LensMainExtension`.
|
||||||
|
There is no behind the scenes transfer of these functions.
|
||||||
|
|
||||||
|
If you want to register a "handler" you would call `Store.MainIpcStore.handleIpc(...)` instead.
|
||||||
|
The cleanup of these handlers is handled by Lens itself.
|
||||||
|
|
||||||
|
`Store.RendererIpcStore.broadcastIpc(...)` and `Store.MainIpcStore.broadcastIpc(...)` sends an event to all renderer frames and to main.
|
||||||
|
Because of this, no matter where you broadcast from, all listeners in `main` and `renderer` will be notified.
|
||||||
|
|
||||||
|
### Allowed Values
|
||||||
|
|
||||||
|
This IPC mechanism utilizes the [Structured Clone Algorithm](developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) for serialization.
|
||||||
|
This means that more types than what are JSON serializable can be used, but not all the information will be passed through.
|
||||||
|
|
||||||
|
## Using IPC
|
||||||
|
|
||||||
|
Calling IPC is very simple.
|
||||||
|
If you are meaning to do an event based call, merely call `broadcastIpc(<channel>, ...<args>)` from within your extension.
|
||||||
|
|
||||||
|
If you are meaning to do a request based call from `renderer`, you should do `const res = await Store.RendererIpcStore.invokeIpc(<channel>, ...<args>));` instead.
|
||||||
@ -10,7 +10,6 @@ The custom Lens UI elements that you can add include:
|
|||||||
* [Cluster page menus](#clusterpagemenus)
|
* [Cluster page menus](#clusterpagemenus)
|
||||||
* [Global pages](#globalpages)
|
* [Global pages](#globalpages)
|
||||||
* [Global page menus](#globalpagemenus)
|
* [Global page menus](#globalpagemenus)
|
||||||
* [Cluster features](#clusterfeatures)
|
|
||||||
* [App preferences](#apppreferences)
|
* [App preferences](#apppreferences)
|
||||||
* [Status bar items](#statusbaritems)
|
* [Status bar items](#statusbaritems)
|
||||||
* [KubeObject menu items](#kubeobjectmenuitems)
|
* [KubeObject menu items](#kubeobjectmenuitems)
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
* [App](modules/_core_api_app_.md)
|
* [App](modules/_core_api_app_.md)
|
||||||
* [ClusterFeature](modules/_core_api_cluster_feature_.md)
|
|
||||||
* [EventBus](modules/_core_api_event_bus_.md)
|
* [EventBus](modules/_core_api_event_bus_.md)
|
||||||
* [Store](modules/_core_api_stores_.md)
|
* [Store](modules/_core_api_stores_.md)
|
||||||
* [Util](modules/_core_api_utils_.md)
|
* [Util](modules/_core_api_utils_.md)
|
||||||
|
|||||||
@ -5,13 +5,38 @@
|
|||||||
|
|
||||||
:root > * {
|
:root > * {
|
||||||
/* Footer */
|
/* Footer */
|
||||||
--md-footer-bg-color: #3d90ce;
|
--md-footer-bg-color: #e8e8e8;
|
||||||
|
--md-footer-bg-color--dark: #cccdcf;
|
||||||
|
--md-footer-fg-color: #3d90ce;
|
||||||
|
--md-footer-fg-color--light: #fff;
|
||||||
|
|
||||||
|
/* Background */
|
||||||
|
--md-default-bg-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-color-scheme="slate"] {
|
||||||
|
/* Footer */
|
||||||
|
--md-footer-bg-color: #2e3136;
|
||||||
|
--md-footer-bg-color--dark: #262b2f;
|
||||||
|
--md-footer-fg-color: #3d90ce;
|
||||||
|
--md-footer-fg-color--light: #fff;
|
||||||
|
|
||||||
|
/* Background */
|
||||||
|
--md-default-bg-color: #1e2124;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-version__list {
|
.md-version__list {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-header-nav__title {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-header__title {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
ul.video-list {
|
ul.video-list {
|
||||||
counter-reset: section;
|
counter-reset: section;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|||||||
1
extensions/.gitignore
vendored
Normal file
1
extensions/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*/*.tgz
|
||||||
@ -1661,12 +1661,6 @@
|
|||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"js-tokens": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"json-parse-better-errors": {
|
"json-parse-better-errors": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||||
@ -1721,15 +1715,6 @@
|
|||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -1896,12 +1881,6 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mobx": {
|
|
||||||
"version": "5.15.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
|
|
||||||
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@ -2213,17 +2192,6 @@
|
|||||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"prop-types": {
|
|
||||||
"version": "15.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
|
||||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
@ -2322,23 +2290,6 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
|
||||||
"version": "16.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
|
||||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
|
||||||
"version": "16.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.7",
|
"version": "2.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||||
|
|||||||
@ -8,17 +8,18 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack && npm pack",
|
||||||
"dev": "npm run build --watch",
|
"dev": "webpack --watch",
|
||||||
"test": "echo NO TESTS"
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2"
|
||||||
"mobx": "^5.15.5",
|
|
||||||
"react": "^16.13.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -22,7 +42,8 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
"mobx": "var global.Mobx"
|
"mobx": "var global.Mobx",
|
||||||
|
"mobx-react": "var global.MobxReact"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
1729
extensions/metrics-cluster-feature/package-lock.json
generated
1729
extensions/metrics-cluster-feature/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,18 +8,21 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack && npm pack",
|
||||||
"dev": "npm run build --watch",
|
"dev": "webpack --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
"test": "jest --passWithNoTests --env=jsdom src $@",
|
||||||
|
"clean": "rm -rf dist/ && rm *.tgz"
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"mobx": "^5.15.5",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"webpack": "^4.44.2"
|
"webpack": "^4.44.2"
|
||||||
}
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,58 +19,24 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LensRendererExtension, Interface, Component, Catalog} from "@k8slens/extensions";
|
import React from "react";
|
||||||
import { MetricsFeature } from "./src/metrics-feature";
|
import { LensRendererExtension, Catalog } from "@k8slens/extensions";
|
||||||
|
import { MetricsSettings } from "./src/metrics-settings";
|
||||||
|
|
||||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||||
onActivate() {
|
entitySettings = [
|
||||||
const category = Catalog.catalogCategories.getForGroupKind<Catalog.KubernetesClusterCategory>("entity.k8slens.dev", "KubernetesCluster");
|
{
|
||||||
|
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||||
if (!category) {
|
kind: "KubernetesCluster",
|
||||||
return;
|
title: "Lens Metrics",
|
||||||
}
|
priority: 5,
|
||||||
|
components: {
|
||||||
category.on("contextMenuOpen", this.clusterContextMenuOpen.bind(this));
|
View: ({ entity = null }: { entity: Catalog.KubernetesCluster}) => {
|
||||||
}
|
return (
|
||||||
|
<MetricsSettings cluster={entity} />
|
||||||
async clusterContextMenuOpen(cluster: Catalog.KubernetesCluster, ctx: Interface.CatalogEntityContextMenuContext) {
|
);
|
||||||
if (!cluster.status.active) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricsFeature = new MetricsFeature();
|
|
||||||
|
|
||||||
await metricsFeature.updateStatus(cluster);
|
|
||||||
|
|
||||||
if (metricsFeature.status.installed) {
|
|
||||||
if (metricsFeature.status.canUpgrade) {
|
|
||||||
ctx.menuItems.unshift({
|
|
||||||
icon: "refresh",
|
|
||||||
title: "Upgrade Lens Metrics stack",
|
|
||||||
onClick: async () => {
|
|
||||||
metricsFeature.upgrade(cluster);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ctx.menuItems.unshift({
|
|
||||||
icon: "toggle_off",
|
|
||||||
title: "Uninstall Lens Metrics stack",
|
|
||||||
onClick: async () => {
|
|
||||||
await metricsFeature.uninstall(cluster);
|
|
||||||
|
|
||||||
Component.Notifications.info(`Lens Metrics has been removed from ${cluster.metadata.name}`, { timeout: 10_000 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ctx.menuItems.unshift({
|
|
||||||
icon: "toggle_on",
|
|
||||||
title: "Install Lens Metrics stack",
|
|
||||||
onClick: async () => {
|
|
||||||
metricsFeature.install(cluster);
|
|
||||||
|
|
||||||
Component.Notifications.info(`Lens Metrics is now installed to ${cluster.metadata.name}`, { timeout: 10_000 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,5 @@ apiVersion: v1
|
|||||||
kind: Namespace
|
kind: Namespace
|
||||||
metadata:
|
metadata:
|
||||||
name: lens-metrics
|
name: lens-metrics
|
||||||
|
annotations:
|
||||||
|
extensionVersion: "{{ version }}"
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
{{#if prometheus.enabled}}
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
@ -14,3 +15,4 @@ spec:
|
|||||||
protocol: TCP
|
protocol: TCP
|
||||||
port: 80
|
port: 80
|
||||||
targetPort: 9090
|
targetPort: 9090
|
||||||
|
{{/if}}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
{{#if prometheus.enabled}}
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
@ -46,14 +47,14 @@ spec:
|
|||||||
serviceAccountName: prometheus
|
serviceAccountName: prometheus
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: chown
|
- name: chown
|
||||||
image: docker.io/alpine:3.9
|
image: docker.io/alpine:3.12
|
||||||
command: ["chown", "-R", "65534:65534", "/var/lib/prometheus"]
|
command: ["chown", "-R", "65534:65534", "/var/lib/prometheus"]
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: data
|
- name: data
|
||||||
mountPath: /var/lib/prometheus
|
mountPath: /var/lib/prometheus
|
||||||
containers:
|
containers:
|
||||||
- name: prometheus
|
- name: prometheus
|
||||||
image: quay.io/prometheus/prometheus:v2.19.3
|
image: quay.io/prometheus/prometheus:v2.26.0
|
||||||
args:
|
args:
|
||||||
- --web.listen-address=0.0.0.0:9090
|
- --web.listen-address=0.0.0.0:9090
|
||||||
- --config.file=/etc/prometheus/prometheus.yaml
|
- --config.file=/etc/prometheus/prometheus.yaml
|
||||||
@ -114,3 +115,4 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
storage: {{persistence.size}}
|
storage: {{persistence.size}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@ -41,7 +41,7 @@ spec:
|
|||||||
hostPID: true
|
hostPID: true
|
||||||
containers:
|
containers:
|
||||||
- name: node-exporter
|
- name: node-exporter
|
||||||
image: quay.io/prometheus/node-exporter:v1.0.1
|
image: quay.io/prometheus/node-exporter:v1.1.2
|
||||||
args:
|
args:
|
||||||
- --path.procfs=/host/proc
|
- --path.procfs=/host/proc
|
||||||
- --path.sysfs=/host/sys
|
- --path.sysfs=/host/sys
|
||||||
|
|||||||
@ -39,7 +39,7 @@ spec:
|
|||||||
serviceAccountName: kube-state-metrics
|
serviceAccountName: kube-state-metrics
|
||||||
containers:
|
containers:
|
||||||
- name: kube-state-metrics
|
- name: kube-state-metrics
|
||||||
image: quay.io/coreos/kube-state-metrics:v1.9.7
|
image: quay.io/coreos/kube-state-metrics:v1.9.8
|
||||||
ports:
|
ports:
|
||||||
- name: metrics
|
- name: metrics
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
@ -52,7 +52,7 @@ spec:
|
|||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
memory: 150Mi
|
memory: 32Mi
|
||||||
limits:
|
limits:
|
||||||
cpu: 200m
|
cpu: 200m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
|
|||||||
@ -19,12 +19,15 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ClusterFeature, Catalog, K8sApi } from "@k8slens/extensions";
|
import { Catalog, K8sApi } from "@k8slens/extensions";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
export interface MetricsConfiguration {
|
export interface MetricsConfiguration {
|
||||||
// Placeholder for Metrics config structure
|
// Placeholder for Metrics config structure
|
||||||
|
prometheus: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
persistence: {
|
persistence: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
storageClass: string;
|
storageClass: string;
|
||||||
@ -43,78 +46,72 @@ export interface MetricsConfiguration {
|
|||||||
alertManagers: string[];
|
alertManagers: string[];
|
||||||
replicas: number;
|
replicas: number;
|
||||||
storageClass: string;
|
storageClass: string;
|
||||||
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetricsFeature extends ClusterFeature.Feature {
|
export interface MetricsStatus {
|
||||||
name = "metrics";
|
installed: boolean;
|
||||||
latestVersion = "v2.19.3-lens1";
|
canUpgrade: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
templateContext: MetricsConfiguration = {
|
export class MetricsFeature {
|
||||||
persistence: {
|
name = "lens-metrics";
|
||||||
enabled: false,
|
latestVersion = "v2.26.0-lens1";
|
||||||
storageClass: null,
|
|
||||||
size: "20G",
|
|
||||||
},
|
|
||||||
nodeExporter: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
retention: {
|
|
||||||
time: "2d",
|
|
||||||
size: "5GB",
|
|
||||||
},
|
|
||||||
kubeStateMetrics: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
alertManagers: null,
|
|
||||||
replicas: 1,
|
|
||||||
storageClass: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
async install(cluster: Catalog.KubernetesCluster): Promise<void> {
|
protected stack: K8sApi.ResourceStack;
|
||||||
|
|
||||||
|
constructor(protected cluster: Catalog.KubernetesCluster) {
|
||||||
|
this.stack = new K8sApi.ResourceStack(cluster, this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
get resourceFolder() {
|
||||||
|
return path.join(__dirname, "../resources/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(config: MetricsConfiguration): Promise<string> {
|
||||||
// Check if there are storageclasses
|
// Check if there are storageclasses
|
||||||
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
const storageClassApi = K8sApi.forCluster(this.cluster, K8sApi.StorageClass);
|
||||||
const scs = await storageClassApi.list();
|
const scs = await storageClassApi.list();
|
||||||
|
|
||||||
this.templateContext.persistence.enabled = scs.some(sc => (
|
config.persistence.enabled = scs.some(sc => (
|
||||||
sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" ||
|
sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" ||
|
||||||
sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
|
sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
|
||||||
));
|
));
|
||||||
|
|
||||||
super.applyResources(cluster, path.join(__dirname, "../resources/"));
|
config.version = this.latestVersion;
|
||||||
|
|
||||||
|
return this.stack.kubectlApplyFolder(this.resourceFolder, config, ["--prune"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgrade(cluster: Catalog.KubernetesCluster): Promise<void> {
|
async upgrade(config: MetricsConfiguration): Promise<string> {
|
||||||
return this.install(cluster);
|
return this.install(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus(cluster: Catalog.KubernetesCluster): Promise<ClusterFeature.FeatureStatus> {
|
async getStatus(): Promise<MetricsStatus> {
|
||||||
|
const status: MetricsStatus = { installed: false, canUpgrade: false};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
const namespaceApi = K8sApi.forCluster(this.cluster, K8sApi.Namespace);
|
||||||
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
const namespace = await namespaceApi.get({name: "lens-metrics"});
|
||||||
|
|
||||||
if (prometheus?.kind) {
|
if (namespace?.kind) {
|
||||||
this.status.installed = true;
|
const currentVersion = namespace.metadata.annotations?.extensionVersion || "0.0.0";
|
||||||
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
|
||||||
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
|
status.installed = true;
|
||||||
|
status.canUpgrade = semver.lt(currentVersion, this.latestVersion, true);
|
||||||
} else {
|
} else {
|
||||||
this.status.installed = false;
|
status.installed = false;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (e?.error?.code === 404) {
|
if (e?.error?.code === 404) {
|
||||||
this.status.installed = false;
|
status.installed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall(cluster: Catalog.KubernetesCluster): Promise<void> {
|
async uninstall(config: MetricsConfiguration): Promise<string> {
|
||||||
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
return this.stack.kubectlDeleteFolder(this.resourceFolder, config);
|
||||||
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
|
||||||
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
|
||||||
|
|
||||||
await namespaceApi.delete({name: "lens-metrics"});
|
|
||||||
await clusterRoleBindingApi.delete({name: "lens-prometheus"});
|
|
||||||
await clusterRoleApi.delete({name: "lens-prometheus"});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
279
extensions/metrics-cluster-feature/src/metrics-settings.tsx
Normal file
279
extensions/metrics-cluster-feature/src/metrics-settings.tsx
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import { Component, Catalog, K8sApi } from "@k8slens/extensions";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
import { MetricsFeature, MetricsConfiguration } from "./metrics-feature";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
cluster: Catalog.KubernetesCluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class MetricsSettings extends React.Component<Props> {
|
||||||
|
@observable featureStates = {
|
||||||
|
prometheus: false,
|
||||||
|
kubeStateMetrics: false,
|
||||||
|
nodeExporter: false
|
||||||
|
};
|
||||||
|
@observable canUpgrade = false;
|
||||||
|
@observable upgrading = false;
|
||||||
|
@observable changed = false;
|
||||||
|
@observable inProgress = false;
|
||||||
|
|
||||||
|
config: MetricsConfiguration = {
|
||||||
|
prometheus: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
persistence: {
|
||||||
|
enabled: false,
|
||||||
|
storageClass: null,
|
||||||
|
size: "20G",
|
||||||
|
},
|
||||||
|
nodeExporter: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
retention: {
|
||||||
|
time: "2d",
|
||||||
|
size: "5GB",
|
||||||
|
},
|
||||||
|
kubeStateMetrics: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
alertManagers: null,
|
||||||
|
replicas: 1,
|
||||||
|
storageClass: null,
|
||||||
|
};
|
||||||
|
feature: MetricsFeature;
|
||||||
|
|
||||||
|
@computed get isTogglable() {
|
||||||
|
if (this.inProgress) return false;
|
||||||
|
if (!this.props.cluster.status.active) return false;
|
||||||
|
if (this.canUpgrade) return false;
|
||||||
|
if (!this.isActiveMetricsProvider) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get metricsProvider() {
|
||||||
|
return this.props.cluster.spec?.metrics?.prometheus?.type || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isActiveMetricsProvider() {
|
||||||
|
return (!this.metricsProvider || this.metricsProvider === "lens");
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.feature = new MetricsFeature(this.props.cluster);
|
||||||
|
|
||||||
|
await this.updateFeatureStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateFeatureStates() {
|
||||||
|
const status = await this.feature.getStatus();
|
||||||
|
|
||||||
|
this.canUpgrade = status.canUpgrade;
|
||||||
|
|
||||||
|
if (this.canUpgrade) {
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statefulSet = K8sApi.forCluster(this.props.cluster, K8sApi.StatefulSet);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
||||||
|
this.featureStates.prometheus = true;
|
||||||
|
} catch(e) {
|
||||||
|
if (e?.error?.code === 404) {
|
||||||
|
this.featureStates.prometheus = false;
|
||||||
|
} else {
|
||||||
|
this.featureStates.prometheus = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deployment = K8sApi.forCluster(this.props.cluster, K8sApi.Deployment);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deployment.get({name: "kube-state-metrics", namespace: "lens-metrics"});
|
||||||
|
this.featureStates.kubeStateMetrics = true;
|
||||||
|
} catch(e) {
|
||||||
|
if (e?.error?.code === 404) {
|
||||||
|
this.featureStates.kubeStateMetrics = false;
|
||||||
|
} else {
|
||||||
|
this.featureStates.kubeStateMetrics = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const daemonSet = K8sApi.forCluster(this.props.cluster, K8sApi.DaemonSet);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await daemonSet.get({name: "node-exporter", namespace: "lens-metrics"});
|
||||||
|
this.featureStates.nodeExporter = true;
|
||||||
|
} catch(e) {
|
||||||
|
if (e?.error?.code === 404) {
|
||||||
|
this.featureStates.nodeExporter = false;
|
||||||
|
} else {
|
||||||
|
this.featureStates.nodeExporter = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
this.config.prometheus.enabled = !!this.featureStates.prometheus;
|
||||||
|
this.config.kubeStateMetrics.enabled = !!this.featureStates.kubeStateMetrics;
|
||||||
|
this.config.nodeExporter.enabled = !!this.featureStates.nodeExporter;
|
||||||
|
|
||||||
|
this.inProgress = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.config.prometheus.enabled && !this.config.kubeStateMetrics.enabled && !this.config.nodeExporter.enabled) {
|
||||||
|
await this.feature.uninstall(this.config);
|
||||||
|
} else {
|
||||||
|
await this.feature.install(this.config);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.inProgress = false;
|
||||||
|
this.changed = false;
|
||||||
|
|
||||||
|
await this.updateFeatureStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async togglePrometheus(enabled: boolean) {
|
||||||
|
this.featureStates.prometheus = enabled;
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleKubeStateMetrics(enabled: boolean) {
|
||||||
|
this.featureStates.kubeStateMetrics = enabled;
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleNodeExporter(enabled: boolean) {
|
||||||
|
this.featureStates.nodeExporter = enabled;
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get buttonLabel() {
|
||||||
|
const allDisabled = !this.featureStates.kubeStateMetrics && !this.featureStates.nodeExporter && !this.featureStates.prometheus;
|
||||||
|
|
||||||
|
if (this.inProgress && this.canUpgrade) return "Upgrading ...";
|
||||||
|
if (this.inProgress && allDisabled) return "Uninstalling ...";
|
||||||
|
if (this.inProgress) return "Applying ...";
|
||||||
|
if (this.canUpgrade) return "Upgrade";
|
||||||
|
|
||||||
|
if (this.changed && allDisabled) {
|
||||||
|
return "Uninstall";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Apply";
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ !this.props.cluster.status.active && (
|
||||||
|
<section>
|
||||||
|
<p style={ {color: "var(--colorError)"} }>
|
||||||
|
Lens Metrics settings requires established connection to the cluster.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
{ !this.isActiveMetricsProvider && (
|
||||||
|
<section>
|
||||||
|
<p style={ {color: "var(--colorError)"} }>
|
||||||
|
Other metrics provider is currently active. See "Metrics" tab for details.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<section>
|
||||||
|
<Component.SubTitle title="Prometheus" />
|
||||||
|
<Component.FormSwitch
|
||||||
|
control={
|
||||||
|
<Component.Switcher
|
||||||
|
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
||||||
|
checked={!!this.featureStates.prometheus && this.props.cluster.status.active}
|
||||||
|
onChange={v => this.togglePrometheus(v.target.checked)}
|
||||||
|
name="prometheus"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Enable bundled Prometheus metrics stack"
|
||||||
|
/>
|
||||||
|
<small className="hint">
|
||||||
|
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Component.SubTitle title="Kube State Metrics" />
|
||||||
|
<Component.FormSwitch
|
||||||
|
control={
|
||||||
|
<Component.Switcher
|
||||||
|
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
||||||
|
checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.active}
|
||||||
|
onChange={v => this.toggleKubeStateMetrics(v.target.checked)}
|
||||||
|
name="node-exporter"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Enable bundled kube-state-metrics stack"
|
||||||
|
/>
|
||||||
|
<small className="hint">
|
||||||
|
Enable Kubernetes API object metrics for your cluster.
|
||||||
|
Enable this only if you don't have existing kube-state-metrics stack installed.
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Component.SubTitle title="Node Exporter" />
|
||||||
|
<Component.FormSwitch
|
||||||
|
control={
|
||||||
|
<Component.Switcher
|
||||||
|
disabled={this.featureStates.nodeExporter === undefined || !this.isTogglable}
|
||||||
|
checked={!!this.featureStates.nodeExporter && this.props.cluster.status.active}
|
||||||
|
onChange={v => this.toggleNodeExporter(v.target.checked)}
|
||||||
|
name="node-exporter"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Enable bundled node-exporter stack"
|
||||||
|
/>
|
||||||
|
<small className="hint">
|
||||||
|
Enable node level metrics for your cluster.
|
||||||
|
Enable this only if you don't have existing node-exporter stack installed.
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Component.Button
|
||||||
|
label={this.buttonLabel}
|
||||||
|
waiting={this.inProgress}
|
||||||
|
onClick={() => this.save()}
|
||||||
|
primary
|
||||||
|
disabled={!this.changed} />
|
||||||
|
|
||||||
|
{this.canUpgrade && (<small className="hint">
|
||||||
|
An update is available for enabled metrics components.
|
||||||
|
</small>)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,8 +16,8 @@
|
|||||||
"jsx": "react"
|
"jsx": "react"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./*.ts",
|
"./**/*.ts",
|
||||||
"./*.tsx"
|
"./**/*.tsx"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -22,7 +42,8 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
"mobx": "var global.Mobx"
|
"mobx": "var global.Mobx",
|
||||||
|
"mobx-react": "var global.MobxReact"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
45
extensions/node-menu/package-lock.json
generated
45
extensions/node-menu/package-lock.json
generated
@ -4037,15 +4037,6 @@
|
|||||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
|
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -4247,12 +4238,6 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mobx": {
|
|
||||||
"version": "5.15.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
|
|
||||||
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@ -4793,25 +4778,6 @@
|
|||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
|
||||||
"version": "15.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
|
||||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.8.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react-is": {
|
|
||||||
"version": "16.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
@ -4922,17 +4888,6 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
|
||||||
"version": "16.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
|
||||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "17.0.1",
|
"version": "17.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
||||||
|
|||||||
@ -8,16 +8,17 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack && npm pack",
|
||||||
"dev": "npm run build --watch",
|
"dev": "webpack --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"mobx": "^5.15.5",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"webpack": "^4.44.2"
|
"webpack": "^4.44.2"
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -22,7 +42,8 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
"mobx": "var global.Mobx"
|
"mobx": "var global.Mobx",
|
||||||
|
"mobx-react": "var global.MobxReact"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
45
extensions/pod-menu/package-lock.json
generated
45
extensions/pod-menu/package-lock.json
generated
@ -4674,15 +4674,6 @@
|
|||||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
|
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -4884,12 +4875,6 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mobx": {
|
|
||||||
"version": "5.15.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
|
|
||||||
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@ -5430,25 +5415,6 @@
|
|||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
|
||||||
"version": "15.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
|
||||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.8.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react-is": {
|
|
||||||
"version": "16.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
@ -5559,17 +5525,6 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
|
||||||
"version": "16.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
|
||||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "17.0.1",
|
"version": "17.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
||||||
|
|||||||
@ -8,16 +8,17 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack && npm pack",
|
||||||
"dev": "npm run build --watch",
|
"dev": "webpack --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"mobx": "^5.15.5",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"webpack": "^4.44.2"
|
"webpack": "^4.44.2"
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -22,7 +42,8 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
"mobx": "var global.Mobx"
|
"mobx": "var global.Mobx",
|
||||||
|
"mobx-react": "var global.MobxReact"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
@ -1,5 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* @jest-environment node
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -8,7 +25,7 @@
|
|||||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
||||||
cluster and vice versa.
|
cluster and vice versa.
|
||||||
*/
|
*/
|
||||||
import { Application } from "spectron";
|
import type { Application } from "spectron";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { listHelmRepositories } from "../helpers/utils";
|
import { listHelmRepositories } from "../helpers/utils";
|
||||||
import { fail } from "assert";
|
import { fail } from "assert";
|
||||||
|
|||||||
@ -1,10 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
|
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
|
||||||
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
|
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
|
||||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
||||||
cluster and vice versa.
|
cluster and vice versa.
|
||||||
*/
|
*/
|
||||||
import { Application } from "spectron";
|
import type { Application } from "spectron";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { minikubeReady, waitForMinikubeDashboard } from "../helpers/minikube";
|
import { minikubeReady, waitForMinikubeDashboard } from "../helpers/minikube";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|||||||
@ -1,4 +1,25 @@
|
|||||||
import { Application } from "spectron";
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Application } from "spectron";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
|
||||||
jest.setTimeout(60000);
|
jest.setTimeout(60000);
|
||||||
|
|||||||
@ -1,5 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
import { Application } from "spectron";
|
import type { Application } from "spectron";
|
||||||
|
|
||||||
export function minikubeReady(testNamespace: string): boolean {
|
export function minikubeReady(testNamespace: string): boolean {
|
||||||
// determine if minikube is running
|
// determine if minikube is running
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { Application } from "spectron";
|
import { Application } from "spectron";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|||||||
@ -19,5 +19,4 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { ClusterFeature as Feature } from "../cluster-feature";
|
|
||||||
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature";
|
|
||||||
12
mkdocs.yml
12
mkdocs.yml
@ -7,6 +7,9 @@ repo_name: GitHub
|
|||||||
repo_url: https://github.com/lensapp/lens
|
repo_url: https://github.com/lensapp/lens
|
||||||
copyright: Copyright © 2021 <a href="https://mirantis.com/">Mirantis Inc.</a> - All rights reserved.
|
copyright: Copyright © 2021 <a href="https://mirantis.com/">Mirantis Inc.</a> - All rights reserved.
|
||||||
edit_uri: ""
|
edit_uri: ""
|
||||||
|
google_analytics:
|
||||||
|
- UA-159377374-2
|
||||||
|
- auto
|
||||||
nav:
|
nav:
|
||||||
- Overview: README.md
|
- Overview: README.md
|
||||||
- Getting Started: getting-started/README.md
|
- Getting Started: getting-started/README.md
|
||||||
@ -35,6 +38,7 @@ nav:
|
|||||||
- Stores: extensions/guides/stores.md
|
- Stores: extensions/guides/stores.md
|
||||||
- Working with MobX: extensions/guides/working-with-mobx.md
|
- Working with MobX: extensions/guides/working-with-mobx.md
|
||||||
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
||||||
|
- IPC: extensions/guides/ipc.md
|
||||||
- Testing and Publishing:
|
- Testing and Publishing:
|
||||||
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
||||||
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
||||||
@ -55,14 +59,14 @@ theme:
|
|||||||
favicon: img/favicon.ico
|
favicon: img/favicon.ico
|
||||||
logo: img/lens-logo-icon.svg
|
logo: img/lens-logo-icon.svg
|
||||||
palette:
|
palette:
|
||||||
- scheme: default
|
|
||||||
toggle:
|
|
||||||
icon: material/toggle-switch
|
|
||||||
name: Switch to light mode
|
|
||||||
- scheme: slate
|
- scheme: slate
|
||||||
toggle:
|
toggle:
|
||||||
icon: material/toggle-switch-off-outline
|
icon: material/toggle-switch-off-outline
|
||||||
name: Switch to dark mode
|
name: Switch to dark mode
|
||||||
|
- scheme: default
|
||||||
|
toggle:
|
||||||
|
icon: material/toggle-switch
|
||||||
|
name: Switch to light mode
|
||||||
features:
|
features:
|
||||||
- toc.autohide
|
- toc.autohide
|
||||||
- search.suggest
|
- search.suggest
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "open-lens",
|
"name": "open-lens",
|
||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"version": "5.0.0-beta.4",
|
"version": "5.0.0-beta.5",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -98,8 +98,8 @@
|
|||||||
"from": "extensions/",
|
"from": "extensions/",
|
||||||
"to": "./extensions/",
|
"to": "./extensions/",
|
||||||
"filter": [
|
"filter": [
|
||||||
"**/*.js*",
|
"**/*.tgz",
|
||||||
"**/*.yml*",
|
"**/package.json",
|
||||||
"!**/node_modules"
|
"!**/node_modules"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -291,7 +291,6 @@
|
|||||||
"@types/tar": "^4.0.4",
|
"@types/tar": "^4.0.4",
|
||||||
"@types/tcp-port-used": "^1.0.0",
|
"@types/tcp-port-used": "^1.0.0",
|
||||||
"@types/tempy": "^0.3.0",
|
"@types/tempy": "^0.3.0",
|
||||||
"@types/universal-analytics": "^0.4.4",
|
|
||||||
"@types/url-parse": "^1.4.3",
|
"@types/url-parse": "^1.4.3",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/webdriverio": "^4.13.0",
|
"@types/webdriverio": "^4.13.0",
|
||||||
@ -315,6 +314,7 @@
|
|||||||
"electron-builder": "^22.10.5",
|
"electron-builder": "^22.10.5",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"eslint": "^7.7.0",
|
"eslint": "^7.7.0",
|
||||||
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-react": "^7.21.5",
|
"eslint-plugin-react": "^7.21.5",
|
||||||
"eslint-plugin-unused-imports": "^1.0.1",
|
"eslint-plugin-unused-imports": "^1.0.1",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
|
|||||||
@ -20,10 +20,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { CatalogEntityItem } from "../../renderer/components/+catalog/catalog-entity.store";
|
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { HotbarStore } from "../hotbar-store";
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
|
||||||
|
jest.mock("../../renderer/api/catalog-entity-registry", () => ({
|
||||||
|
catalogEntityRegistry: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
|
||||||
|
name: "mycluster",
|
||||||
|
source: "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5",
|
||||||
|
name: "my_shiny_cluster",
|
||||||
|
source: "remote"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const testCluster = {
|
const testCluster = {
|
||||||
uid: "test",
|
uid: "test",
|
||||||
name: "test",
|
name: "test",
|
||||||
@ -87,6 +107,17 @@ const awsCluster = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jest.mock("electron", () => {
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
getVersion: () => "99.99.99",
|
||||||
|
getPath: () => "tmp",
|
||||||
|
getLocale: () => "en",
|
||||||
|
setLoginItemSettings: (): void => void 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
@ -127,10 +158,9 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("adds items", () => {
|
it("adds items", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const entity = new CatalogEntityItem(testCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(entity);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
expect(items.length).toEqual(1);
|
expect(items.length).toEqual(1);
|
||||||
@ -138,10 +168,9 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("removes items", () => {
|
it("removes items", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const entity = new CatalogEntityItem(testCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(entity);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.removeFromHotbar("test");
|
hotbarStore.removeFromHotbar("test");
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
@ -150,10 +179,9 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("does nothing if removing with invalid uid", () => {
|
it("does nothing if removing with invalid uid", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const entity = new CatalogEntityItem(testCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(entity);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.removeFromHotbar("invalid uid");
|
hotbarStore.removeFromHotbar("invalid uid");
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
@ -162,14 +190,11 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("moves item to empty cell", () => {
|
it("moves item to empty cell", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
const minikube = new CatalogEntityItem(minikubeCluster);
|
|
||||||
const aws = new CatalogEntityItem(awsCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikube);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(aws);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeNull();
|
expect(hotbarStore.getActive().items[5]).toBeNull();
|
||||||
|
|
||||||
@ -181,14 +206,11 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("moves items down", () => {
|
it("moves items down", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
const minikube = new CatalogEntityItem(minikubeCluster);
|
|
||||||
const aws = new CatalogEntityItem(awsCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikube);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(aws);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
// aws -> test
|
// aws -> test
|
||||||
hotbarStore.restackItems(2, 0);
|
hotbarStore.restackItems(2, 0);
|
||||||
@ -200,14 +222,11 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("moves items up", () => {
|
it("moves items up", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
const minikube = new CatalogEntityItem(minikubeCluster);
|
|
||||||
const aws = new CatalogEntityItem(awsCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikube);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(aws);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
// test -> aws
|
// test -> aws
|
||||||
hotbarStore.restackItems(0, 2);
|
hotbarStore.restackItems(0, 2);
|
||||||
@ -219,10 +238,9 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
it("does nothing when item moved to same cell", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.restackItems(0, 0);
|
hotbarStore.restackItems(0, 0);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
|
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
|
||||||
@ -230,15 +248,12 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
it("new items takes first empty cell", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
const minikube = new CatalogEntityItem(minikubeCluster);
|
|
||||||
const aws = new CatalogEntityItem(awsCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(aws);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
hotbarStore.restackItems(0, 3);
|
hotbarStore.restackItems(0, 3);
|
||||||
hotbarStore.addToHotbar(minikube);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
|
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
|
||||||
});
|
});
|
||||||
@ -250,10 +265,9 @@ describe("HotbarStore", () => {
|
|||||||
console.error = jest.fn();
|
console.error = jest.fn();
|
||||||
|
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const test = new CatalogEntityItem(testCluster);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(test);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
||||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
||||||
@ -264,4 +278,111 @@ describe("HotbarStore", () => {
|
|||||||
console.error = err;
|
console.error = err;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("pre beta-5 migrations", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
HotbarStore.resetInstance();
|
||||||
|
const mockOpts = {
|
||||||
|
"tmp": {
|
||||||
|
"lens-hotbar-store.json": JSON.stringify({
|
||||||
|
__internal__: {
|
||||||
|
migrations: {
|
||||||
|
version: "5.0.0-beta.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hotbars": [
|
||||||
|
{
|
||||||
|
"id": "3caac17f-aec2-4723-9694-ad204465d935",
|
||||||
|
"name": "myhotbar",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "1dfa26e2ebab15780a3547e9c7fa785c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "55b42c3c7ba3b04193416cda405269a5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "176fd331968660832f62283219d7eb6e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "61c4fb45528840ebad1badc25da41d14",
|
||||||
|
"name": "user1-context",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "27d6f99fe9e7548a6e306760bfe19969",
|
||||||
|
"name": "foo2",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "c0b20040646849bb4dcf773e43a0bf27",
|
||||||
|
"name": "multinode-demo",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
return HotbarStore.createInstance().load();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to retrieve a hotbar", () => {
|
||||||
|
const hotbar = HotbarStore.getInstance().getById("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
|
||||||
|
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears cells without entity", () => {
|
||||||
|
const items = HotbarStore.getInstance().hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[2]).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds extra data to cells with according entity", () => {
|
||||||
|
const items = HotbarStore.getInstance().hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[0]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "mycluster",
|
||||||
|
source: "local",
|
||||||
|
uid: "1dfa26e2ebab15780a3547e9c7fa785c"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(items[1]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "my_shiny_cluster",
|
||||||
|
source: "remote",
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,6 +23,12 @@ import { SearchStore } from "../search-store";
|
|||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
jest.mock("electron", () => ({
|
||||||
|
app: {
|
||||||
|
getPath: () => "/foo",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
let searchStore: SearchStore = null;
|
let searchStore: SearchStore = null;
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import Config from "conf";
|
import Config from "conf";
|
||||||
import { Options as ConfOptions } from "conf/dist/source/types";
|
import type { Options as ConfOptions } from "conf/dist/source/types";
|
||||||
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
|
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
|
||||||
import { IReactionOptions, observable, reaction, runInAction, when } from "mobx";
|
import { IReactionOptions, observable, reaction, runInAction, when } from "mobx";
|
||||||
import Singleton from "./utils/singleton";
|
import Singleton from "./utils/singleton";
|
||||||
|
|||||||
@ -21,15 +21,31 @@
|
|||||||
|
|
||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||||
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { requestMain } from "../ipc";
|
import { requestMain } from "../ipc";
|
||||||
import { productName } from "../vars";
|
import { productName } from "../vars";
|
||||||
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
|
||||||
|
export type KubernetesClusterPrometheusMetrics = {
|
||||||
|
address?: {
|
||||||
|
namespace: string;
|
||||||
|
service: string;
|
||||||
|
port: number;
|
||||||
|
prefix: string;
|
||||||
|
};
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type KubernetesClusterSpec = {
|
export type KubernetesClusterSpec = {
|
||||||
kubeconfigPath: string;
|
kubeconfigPath: string;
|
||||||
kubeconfigContext: string;
|
kubeconfigContext: string;
|
||||||
|
metrics?: {
|
||||||
|
source: string;
|
||||||
|
prometheus?: KubernetesClusterPrometheusMetrics;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
||||||
@ -40,6 +56,38 @@ export class KubernetesCluster extends CatalogEntity<CatalogEntityMetadata, Kube
|
|||||||
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
public readonly kind = "KubernetesCluster";
|
public readonly kind = "KubernetesCluster";
|
||||||
|
|
||||||
|
async connect(): Promise<void> {
|
||||||
|
if (app) {
|
||||||
|
const cluster = ClusterStore.getInstance().getById(this.metadata.uid);
|
||||||
|
|
||||||
|
if (!cluster) return;
|
||||||
|
|
||||||
|
await cluster.activate();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestMain(clusterActivateHandler, this.metadata.uid, false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect(): Promise<void> {
|
||||||
|
if (app) {
|
||||||
|
const cluster = ClusterStore.getInstance().getById(this.metadata.uid);
|
||||||
|
|
||||||
|
if (!cluster) return;
|
||||||
|
|
||||||
|
cluster.disconnect();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestMain(clusterDisconnectHandler, this.metadata.uid, false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
async onRun(context: CatalogEntityActionContext) {
|
async onRun(context: CatalogEntityActionContext) {
|
||||||
context.navigate(`/cluster/${this.metadata.uid}`);
|
context.navigate(`/cluster/${this.metadata.uid}`);
|
||||||
}
|
}
|
||||||
@ -55,7 +103,6 @@ export class KubernetesCluster extends CatalogEntity<CatalogEntityMetadata, Kube
|
|||||||
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
||||||
context.menuItems = [
|
context.menuItems = [
|
||||||
{
|
{
|
||||||
icon: "settings",
|
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
onlyVisibleForSource: "local",
|
onlyVisibleForSource: "local",
|
||||||
onClick: async () => context.navigate(`/entity/${this.metadata.uid}/settings`)
|
onClick: async () => context.navigate(`/entity/${this.metadata.uid}/settings`)
|
||||||
@ -64,7 +111,6 @@ export class KubernetesCluster extends CatalogEntity<CatalogEntityMetadata, Kube
|
|||||||
|
|
||||||
if (this.metadata.labels["file"]?.startsWith(ClusterStore.storedKubeConfigFolder)) {
|
if (this.metadata.labels["file"]?.startsWith(ClusterStore.storedKubeConfigFolder)) {
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
icon: "delete",
|
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
onlyVisibleForSource: "local",
|
onlyVisibleForSource: "local",
|
||||||
onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid),
|
onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid),
|
||||||
@ -75,14 +121,20 @@ export class KubernetesCluster extends CatalogEntity<CatalogEntityMetadata, Kube
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.status.phase == "connected") {
|
if (this.status.phase == "connected") {
|
||||||
context.menuItems.unshift({
|
context.menuItems.push({
|
||||||
icon: "link_off",
|
|
||||||
title: "Disconnect",
|
title: "Disconnect",
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
ClusterStore.getInstance().deactivate(this.metadata.uid);
|
ClusterStore.getInstance().deactivate(this.metadata.uid);
|
||||||
requestMain(clusterDisconnectHandler, this.metadata.uid);
|
requestMain(clusterDisconnectHandler, this.metadata.uid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
context.menuItems.push({
|
||||||
|
title: "Connect",
|
||||||
|
onClick: async () => {
|
||||||
|
context.navigate(`/cluster/${this.metadata.uid}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const category = catalogCategoryRegistry.getCategoryForEntity<KubernetesClusterCategory>(this);
|
const category = catalogCategoryRegistry.getCategoryForEntity<KubernetesClusterCategory>(this);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export type WebLinkSpec = {
|
|||||||
|
|
||||||
export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
|
export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
|
||||||
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
public readonly kind = "KubernetesCluster";
|
public readonly kind = "WebLink";
|
||||||
|
|
||||||
async onRun() {
|
async onRun() {
|
||||||
window.open(this.spec.url, "_blank");
|
window.open(this.spec.url, "_blank");
|
||||||
|
|||||||
@ -19,26 +19,38 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, computed, observable, toJS } from "mobx";
|
import { action, computed, observable } from "mobx";
|
||||||
|
import { Disposer, ExtendedMap } from "../utils";
|
||||||
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
|
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
|
||||||
|
|
||||||
export class CatalogCategoryRegistry {
|
export class CatalogCategoryRegistry {
|
||||||
@observable protected categories: CatalogCategory[] = [];
|
protected categories = observable.set<CatalogCategory>();
|
||||||
|
|
||||||
@action add(category: CatalogCategory) {
|
@action add(category: CatalogCategory): Disposer {
|
||||||
this.categories.push(category);
|
this.categories.add(category);
|
||||||
|
|
||||||
|
return () => this.categories.delete(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action remove(category: CatalogCategory) {
|
@computed private get groupKindLookup(): Map<string, Map<string, CatalogCategory>> {
|
||||||
this.categories = this.categories.filter((cat) => cat.apiVersion !== category.apiVersion && cat.kind !== category.kind);
|
// ExtendedMap has the convenience methods `getOrInsert` and `strictSet`
|
||||||
|
const res = new ExtendedMap<string, ExtendedMap<string, CatalogCategory>>();
|
||||||
|
|
||||||
|
for (const category of this.categories) {
|
||||||
|
res
|
||||||
|
.getOrInsert(category.spec.group, ExtendedMap.new)
|
||||||
|
.strictSet(category.spec.names.kind, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get items() {
|
@computed get items() {
|
||||||
return toJS(this.categories);
|
return Array.from(this.categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
getForGroupKind<T extends CatalogCategory>(group: string, kind: string) {
|
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
|
||||||
return this.categories.find((c) => c.spec.group === group && c.spec.names.kind === kind) as T;
|
return this.groupKindLookup.get(group)?.get(kind) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
|
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
|
||||||
@ -60,17 +72,11 @@ export class CatalogCategoryRegistry {
|
|||||||
return new specVersion.entityClass(data);
|
return new specVersion.entityClass(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData) {
|
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
|
||||||
const splitApiVersion = data.apiVersion.split("/");
|
const splitApiVersion = data.apiVersion.split("/");
|
||||||
const group = splitApiVersion[0];
|
const group = splitApiVersion[0];
|
||||||
|
|
||||||
const category = this.categories.find((category) => {
|
return this.getForGroupKind(group, data.kind);
|
||||||
return category.spec.group === group && category.spec.names.kind === data.kind;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!category) return null;
|
|
||||||
|
|
||||||
return category as T;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, computed, observable, IComputedValue, IObservableArray } from "mobx";
|
import { action, computed, observable, IComputedValue, IObservableArray } from "mobx";
|
||||||
import { CatalogEntity } from "./catalog-entity";
|
import type { CatalogEntity } from "./catalog-entity";
|
||||||
import { iter } from "../utils";
|
import { iter } from "../utils";
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
|
|||||||
@ -83,7 +83,6 @@ export interface CatalogEntityActionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogEntityContextMenu {
|
export interface CatalogEntityContextMenu {
|
||||||
icon: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
onlyVisibleForSource?: string; // show only if empty or if matches with entity source
|
onlyVisibleForSource?: string; // show only if empty or if matches with entity source
|
||||||
onClick: () => void | Promise<void>;
|
onClick: () => void | Promise<void>;
|
||||||
@ -92,6 +91,10 @@ export interface CatalogEntityContextMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CatalogEntityAddMenu extends CatalogEntityContextMenu {
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CatalogEntitySettingsMenu {
|
export interface CatalogEntitySettingsMenu {
|
||||||
group?: string;
|
group?: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -111,7 +114,7 @@ export interface CatalogEntitySettingsContext {
|
|||||||
|
|
||||||
export interface CatalogEntityAddMenuContext {
|
export interface CatalogEntityAddMenuContext {
|
||||||
navigate: (url: string) => void;
|
navigate: (url: string) => void;
|
||||||
menuItems: CatalogEntityContextMenu[];
|
menuItems: CatalogEntityAddMenu[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CatalogEntitySpec = Record<string, any>;
|
export type CatalogEntitySpec = Record<string, any>;
|
||||||
|
|||||||
@ -31,14 +31,13 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
|||||||
export const clusterRefreshHandler = "cluster:refresh";
|
export const clusterRefreshHandler = "cluster:refresh";
|
||||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||||
|
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||||
|
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
return ClusterStore.getInstance()
|
||||||
|
.getById(clusterId)
|
||||||
if (cluster) {
|
?.activate(force);
|
||||||
return cluster.activate(force);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
|
handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
|
||||||
@ -46,15 +45,14 @@ if (ipcMain) {
|
|||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
|
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
|
||||||
|
cluster.pushState();
|
||||||
return cluster.pushState();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
return ClusterStore.getInstance()
|
||||||
|
.getById(clusterId)
|
||||||
if (cluster) return cluster.refresh({ refreshMetadata: true });
|
?.refresh({ refreshMetadata: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
||||||
@ -67,14 +65,39 @@ if (ipcMain) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
handleRequest(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||||
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
|
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const applier = new ResourceApplier(cluster);
|
const applier = new ResourceApplier(cluster);
|
||||||
|
|
||||||
applier.kubectlApplyAll(resources);
|
try {
|
||||||
|
const stdout = await applier.kubectlApplyAll(resources, extraArgs);
|
||||||
|
|
||||||
|
return { stdout };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { stderr: error };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handleRequest(clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||||
|
appEventBus.emit({name: "cluster", action: "kubectl-delete-all"});
|
||||||
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
|
if (cluster) {
|
||||||
|
const applier = new ResourceApplier(cluster);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stdout = await applier.kubectlDeleteAll(resources, extraArgs);
|
||||||
|
|
||||||
|
return { stdout };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { stderr: error };
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw `${clusterId} is not a valid cluster id`;
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,9 +30,9 @@ import logger from "../main/logger";
|
|||||||
import { appEventBus } from "./event-bus";
|
import { appEventBus } from "./event-bus";
|
||||||
import { dumpConfigYaml } from "./kube-helpers";
|
import { dumpConfigYaml } from "./kube-helpers";
|
||||||
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||||
import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
|
import type { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
|
||||||
import { disposer, noop } from "./utils";
|
import { disposer, noop } from "./utils";
|
||||||
|
|
||||||
export interface ClusterIconUpload {
|
export interface ClusterIconUpload {
|
||||||
|
|||||||
@ -53,12 +53,11 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
|
|
||||||
emit(...data: D) {
|
emit(...data: D) {
|
||||||
[...this.listeners].every(([callback, options]) => {
|
[...this.listeners].every(([callback, options]) => {
|
||||||
if (options.once) this.removeListener(callback);
|
if (options.once) {
|
||||||
const result = callback(...data);
|
this.removeListener(callback);
|
||||||
|
}
|
||||||
|
|
||||||
if (result === false) return; // break cycle
|
return callback(...data) !== false;
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,12 +23,14 @@ import { action, comparer, observable, toJS } from "mobx";
|
|||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import migrations from "../migrations/hotbar-store";
|
import migrations from "../migrations/hotbar-store";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { CatalogEntityItem } from "../renderer/components/+catalog/catalog-entity.store";
|
|
||||||
import isNull from "lodash/isNull";
|
import isNull from "lodash/isNull";
|
||||||
|
import { CatalogEntity } from "./catalog";
|
||||||
|
|
||||||
export interface HotbarItem {
|
export interface HotbarItem {
|
||||||
entity: {
|
entity: {
|
||||||
uid: string;
|
uid: string;
|
||||||
|
name?: string;
|
||||||
|
source?: string;
|
||||||
};
|
};
|
||||||
params?: {
|
params?: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@ -79,8 +81,12 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hotbarIndex(id: string) {
|
||||||
|
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
get activeHotbarIndex() {
|
get activeHotbarIndex() {
|
||||||
return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId);
|
return this.hotbarIndex(this.activeHotbarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
get initialItems() {
|
get initialItems() {
|
||||||
@ -144,11 +150,16 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(item: CatalogEntityItem, cellIndex = -1) {
|
@action
|
||||||
|
addToHotbar(item: CatalogEntity, cellIndex = -1) {
|
||||||
const hotbar = this.getActive();
|
const hotbar = this.getActive();
|
||||||
const newItem = { entity: { uid: item.id }};
|
const newItem = { entity: {
|
||||||
|
uid: item.metadata.uid,
|
||||||
|
name: item.metadata.name,
|
||||||
|
source: item.metadata.source
|
||||||
|
}};
|
||||||
|
|
||||||
if (hotbar.items.find(i => i?.entity.uid === item.id)) {
|
if (hotbar.items.find(i => i?.entity.uid === item.metadata.uid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +178,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
removeFromHotbar(uid: string) {
|
removeFromHotbar(uid: string) {
|
||||||
const hotbar = this.getActive();
|
const hotbar = this.getActive();
|
||||||
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
||||||
|
|||||||
@ -42,35 +42,33 @@ function getSubFrames(): ClusterFrameInfo[] {
|
|||||||
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
|
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function broadcastMessage(channel: string, ...args: any[]) {
|
export function broadcastMessage(channel: string, ...args: any[]) {
|
||||||
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
||||||
|
|
||||||
if (!views) return;
|
if (!views) return;
|
||||||
|
|
||||||
if (ipcRenderer) {
|
ipcRenderer?.send(channel, ...args);
|
||||||
ipcRenderer.send(channel, ...args);
|
ipcMain?.emit(channel, ...args);
|
||||||
} else if (ipcMain) {
|
|
||||||
ipcMain.emit(channel, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const view of views) {
|
const subFramesP = ipcRenderer
|
||||||
const type = view.getType();
|
? requestMain(subFramesChannel)
|
||||||
|
: Promise.resolve(getSubFrames());
|
||||||
|
|
||||||
logger.silly(`[IPC]: broadcasting "${channel}" to ${type}=${view.id}`, { args });
|
subFramesP
|
||||||
view.send(channel, ...args);
|
.then(subFrames => {
|
||||||
|
for (const view of views) {
|
||||||
|
try {
|
||||||
|
logger.silly(`[IPC]: broadcasting "${channel}" to ${view.getType()}=${view.id}`, { args });
|
||||||
|
view.send(channel, ...args);
|
||||||
|
|
||||||
try {
|
for (const frameInfo of subFrames) {
|
||||||
const subFrames: ClusterFrameInfo[] = ipcRenderer
|
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
|
||||||
? await requestMain(subFramesChannel)
|
}
|
||||||
: getSubFrames();
|
} catch (error) {
|
||||||
|
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
|
||||||
for (const frameInfo of subFrames) {
|
}
|
||||||
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||||
|
|||||||
@ -19,10 +19,12 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from "events";
|
import type { EventEmitter } from "events";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
|
import type { Disposer } from "../utils";
|
||||||
|
|
||||||
export type HandlerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
export type ListenerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
||||||
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
||||||
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
||||||
|
|
||||||
@ -34,22 +36,22 @@ export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
|||||||
* @param verifier The function to be called to verify that the args are the correct type
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
*/
|
*/
|
||||||
export function onceCorrect<
|
export function onceCorrect<
|
||||||
EM extends EventEmitter,
|
IPC extends EventEmitter,
|
||||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any
|
||||||
>({
|
>({
|
||||||
source,
|
source,
|
||||||
channel,
|
channel,
|
||||||
listener,
|
listener,
|
||||||
verifier,
|
verifier,
|
||||||
}: {
|
}: {
|
||||||
source: EM,
|
source: IPC,
|
||||||
channel: string | symbol,
|
channel: string,
|
||||||
listener: L,
|
listener: Listener,
|
||||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||||
}): void {
|
}): void {
|
||||||
function handler(event: HandlerEvent<EM>, ...args: unknown[]): void {
|
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]): void {
|
||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
source.removeListener(channel, handler); // remove immediately
|
source.removeListener(channel, wrappedListener); // remove immediately
|
||||||
|
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
||||||
@ -58,7 +60,7 @@ export function onceCorrect<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.on(channel, handler);
|
source.on(channel, wrappedListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,25 +70,53 @@ export function onceCorrect<
|
|||||||
* @param verifier The function to be called to verify that the args are the correct type
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
*/
|
*/
|
||||||
export function onCorrect<
|
export function onCorrect<
|
||||||
EM extends EventEmitter,
|
IPC extends EventEmitter,
|
||||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any
|
||||||
>({
|
>({
|
||||||
source,
|
source,
|
||||||
channel,
|
channel,
|
||||||
listener,
|
listener,
|
||||||
verifier,
|
verifier,
|
||||||
}: {
|
}: {
|
||||||
source: EM,
|
source: IPC,
|
||||||
channel: string | symbol,
|
channel: string,
|
||||||
listener: L,
|
listener: Listener,
|
||||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||||
}): void {
|
}): Disposer {
|
||||||
source.on(channel, (event, ...args: unknown[]) => {
|
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
|
||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
||||||
} else {
|
} else {
|
||||||
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
source.on(channel, wrappedListener);
|
||||||
|
|
||||||
|
return () => source.off(channel, wrappedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleCorrect<
|
||||||
|
Handler extends (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
|
||||||
|
>({
|
||||||
|
channel,
|
||||||
|
handler,
|
||||||
|
verifier,
|
||||||
|
}: {
|
||||||
|
channel: string,
|
||||||
|
handler: Handler,
|
||||||
|
verifier: ListVerifier<Rest<Parameters<Handler>>>,
|
||||||
|
}): Disposer {
|
||||||
|
function wrappedHandler(event: Electron.IpcMainInvokeEvent, ...args: unknown[]): ReturnType<Handler> {
|
||||||
|
if (verifier(args)) {
|
||||||
|
return handler(event, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(`Invalid args for invoke on channel: ${channel}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(channel, wrappedHandler);
|
||||||
|
|
||||||
|
return () => ipcMain.removeHandler(channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UpdateInfo } from "electron-updater";
|
import type { UpdateInfo } from "electron-updater";
|
||||||
|
|
||||||
export const UpdateAvailableChannel = "update-available";
|
export const UpdateAvailableChannel = "update-available";
|
||||||
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
|
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
|
||||||
|
|||||||
152
src/common/k8s/resource-stack.ts
Normal file
152
src/common/k8s/resource-stack.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import fse from "fs-extra";
|
||||||
|
import path from "path";
|
||||||
|
import hb from "handlebars";
|
||||||
|
import { ResourceApplier } from "../../main/resource-applier";
|
||||||
|
import type { KubernetesCluster } from "../catalog-entities";
|
||||||
|
import logger from "../../main/logger";
|
||||||
|
import { app } from "electron";
|
||||||
|
import { requestMain } from "../ipc";
|
||||||
|
import { clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../cluster-ipc";
|
||||||
|
import { ClusterStore } from "../cluster-store";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
import { productName } from "../vars";
|
||||||
|
|
||||||
|
export class ResourceStack {
|
||||||
|
constructor(protected cluster: KubernetesCluster, protected name: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param folderPath folder path that is searched for files defining kubernetes resources.
|
||||||
|
* @param templateContext sets the template parameters that are to be applied to any templated kubernetes resources that are to be applied.
|
||||||
|
*/
|
||||||
|
async kubectlApplyFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise<string> {
|
||||||
|
const resources = await this.renderTemplates(folderPath, templateContext);
|
||||||
|
|
||||||
|
return this.applyResources(resources, extraArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param folderPath folder path that is searched for files defining kubernetes resources.
|
||||||
|
* @param templateContext sets the template parameters that are to be applied to any templated kubernetes resources that are to be applied.
|
||||||
|
*/
|
||||||
|
async kubectlDeleteFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise<string> {
|
||||||
|
const resources = await this.renderTemplates(folderPath, templateContext);
|
||||||
|
|
||||||
|
return this.deleteResources(resources, extraArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async applyResources(resources: string[], extraArgs?: string[]): Promise<string> {
|
||||||
|
const clusterModel = ClusterStore.getInstance().getById(this.cluster.metadata.uid);
|
||||||
|
|
||||||
|
if (!clusterModel) {
|
||||||
|
throw new Error(`cluster not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kubectlArgs = extraArgs || [];
|
||||||
|
|
||||||
|
kubectlArgs = this.appendKubectlArgs(kubectlArgs);
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
return await new ResourceApplier(clusterModel).kubectlApplyAll(resources, kubectlArgs);
|
||||||
|
} else {
|
||||||
|
const response = await requestMain(clusterKubectlApplyAllHandler, this.cluster.metadata.uid, resources, kubectlArgs);
|
||||||
|
|
||||||
|
if (response.stderr) {
|
||||||
|
throw new Error(response.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async deleteResources(resources: string[], extraArgs?: string[]): Promise<string> {
|
||||||
|
const clusterModel = ClusterStore.getInstance().getById(this.cluster.metadata.uid);
|
||||||
|
|
||||||
|
if (!clusterModel) {
|
||||||
|
throw new Error(`cluster not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kubectlArgs = extraArgs || [];
|
||||||
|
|
||||||
|
kubectlArgs = this.appendKubectlArgs(kubectlArgs);
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
return await new ResourceApplier(clusterModel).kubectlDeleteAll(resources, kubectlArgs);
|
||||||
|
} else {
|
||||||
|
const response = await requestMain(clusterKubectlDeleteAllHandler, this.cluster.metadata.uid, resources, kubectlArgs);
|
||||||
|
|
||||||
|
if (response.stderr) {
|
||||||
|
throw new Error(response.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected appendKubectlArgs(kubectlArgs: string[]) {
|
||||||
|
if (!kubectlArgs.includes("-l") && !kubectlArgs.includes("--label")) {
|
||||||
|
return kubectlArgs.concat(["-l", `app.kubernetes.io/name=${this.name}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubectlArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async renderTemplates(folderPath: string, templateContext: any): Promise<string[]> {
|
||||||
|
const resources: string[] = [];
|
||||||
|
|
||||||
|
logger.info(`[RESOURCE-STACK]: render templates from ${folderPath}`);
|
||||||
|
const files = await fse.readdir(folderPath);
|
||||||
|
|
||||||
|
for(const filename of files) {
|
||||||
|
const file = path.join(folderPath, filename);
|
||||||
|
const raw = await fse.readFile(file);
|
||||||
|
let resourceData: string;
|
||||||
|
|
||||||
|
if (filename.endsWith(".hb")) {
|
||||||
|
const template = hb.compile(raw.toString());
|
||||||
|
|
||||||
|
resourceData = template(templateContext);
|
||||||
|
} else {
|
||||||
|
resourceData = raw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resourceData.trim()) continue;
|
||||||
|
|
||||||
|
const resourceArray = yaml.safeLoadAll(resourceData.toString());
|
||||||
|
|
||||||
|
resourceArray.forEach((resource) => {
|
||||||
|
if (resource?.metadata) {
|
||||||
|
resource.metadata.labels ||= {};
|
||||||
|
resource.metadata.labels["app.kubernetes.io/name"] = this.name;
|
||||||
|
resource.metadata.labels["app.kubernetes.io/managed-by"] = productName;
|
||||||
|
resource.metadata.labels["app.kubernetes.io/created-by"] = "resource-stack";
|
||||||
|
}
|
||||||
|
|
||||||
|
resources.push(yaml.safeDump(resource));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -230,7 +230,7 @@ export function getNodeWarningConditions(node: V1Node) {
|
|||||||
*
|
*
|
||||||
* Note: This function returns an error instead of throwing it, returning `undefined` if the validation passes
|
* Note: This function returns an error instead of throwing it, returning `undefined` if the validation passes
|
||||||
*/
|
*/
|
||||||
export function validateKubeConfig(config: KubeConfig, contextName: string, validationOpts: KubeConfigValidationOpts = {}): Error | undefined {
|
export function validateKubeConfig(config: KubeConfig, contextName: string, validationOpts: KubeConfigValidationOpts = {}): Error | void {
|
||||||
try {
|
try {
|
||||||
// we only receive a single context, cluster & user object here so lets validate them as this
|
// we only receive a single context, cluster & user object here so lets validate them as this
|
||||||
// will be called when we add a new cluster to Lens
|
// will be called when we add a new cluster to Lens
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Url from "url-parse";
|
import type Url from "url-parse";
|
||||||
|
|
||||||
export enum RoutingErrorType {
|
export enum RoutingErrorType {
|
||||||
INVALID_PROTOCOL = "invalid-protocol",
|
INVALID_PROTOCOL = "invalid-protocol",
|
||||||
@ -52,6 +52,8 @@ export class RoutingError extends Error {
|
|||||||
return "no extension ID";
|
return "no extension ID";
|
||||||
case RoutingErrorType.MISSING_EXTENSION:
|
case RoutingErrorType.MISSING_EXTENSION:
|
||||||
return "extension not found";
|
return "extension not found";
|
||||||
|
default:
|
||||||
|
return `unknown error: ${this.type}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,12 +24,12 @@ import { countBy } from "lodash";
|
|||||||
import { Singleton } from "../utils";
|
import { Singleton } from "../utils";
|
||||||
import { pathToRegexp } from "path-to-regexp";
|
import { pathToRegexp } from "path-to-regexp";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import Url from "url-parse";
|
import type Url from "url-parse";
|
||||||
import { RoutingError, RoutingErrorType } from "./error";
|
import { RoutingError, RoutingErrorType } from "./error";
|
||||||
import { ExtensionsStore } from "../../extensions/extensions-store";
|
import { ExtensionsStore } from "../../extensions/extensions-store";
|
||||||
import { ExtensionLoader } from "../../extensions/extension-loader";
|
import { ExtensionLoader } from "../../extensions/extension-loader";
|
||||||
import { LensExtension } from "../../extensions/lens-extension";
|
import type { LensExtension } from "../../extensions/lens-extension";
|
||||||
import { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler-registry";
|
import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler-registry";
|
||||||
|
|
||||||
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
||||||
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbortController } from "abort-controller";
|
import type { AbortController } from "abort-controller";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that will be resolved after at least `timeout` ms have
|
* Return a promise that will be resolved after at least `timeout` ms have
|
||||||
|
|||||||
@ -22,19 +22,17 @@
|
|||||||
import { action, IEnhancer, IObservableMapInitialValues, ObservableMap } from "mobx";
|
import { action, IEnhancer, IObservableMapInitialValues, ObservableMap } from "mobx";
|
||||||
|
|
||||||
export class ExtendedMap<K, V> extends Map<K, V> {
|
export class ExtendedMap<K, V> extends Map<K, V> {
|
||||||
constructor(protected getDefault: () => V, entries?: readonly (readonly [K, V])[] | null) {
|
static new<K, V>(entries?: readonly (readonly [K, V])[] | null): ExtendedMap<K, V> {
|
||||||
super(entries);
|
return new ExtendedMap<K, V>(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrInsert(key: K, val: V): V {
|
/**
|
||||||
if (this.has(key)) {
|
* Get the value behind `key`. If it was not pressent, first insert the value returned by `getVal`
|
||||||
return this.get(key);
|
* @param key The key to insert into the map with
|
||||||
}
|
* @param getVal A function that returns a new instance of `V`.
|
||||||
|
* @returns The value in the map
|
||||||
return this.set(key, val).get(key);
|
*/
|
||||||
}
|
getOrInsert(key: K, getVal: () => V): V {
|
||||||
|
|
||||||
getOrInsertWith(key: K, getVal: () => V): V {
|
|
||||||
if (this.has(key)) {
|
if (this.has(key)) {
|
||||||
return this.get(key);
|
return this.get(key);
|
||||||
}
|
}
|
||||||
@ -42,12 +40,29 @@ export class ExtendedMap<K, V> extends Map<K, V> {
|
|||||||
return this.set(key, getVal()).get(key);
|
return this.set(key, getVal()).get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrDefault(key: K): V {
|
/**
|
||||||
|
* Set the value associated with `key` iff there was not a previous value
|
||||||
|
* @throws if `key` already in map
|
||||||
|
* @returns `this` so that `strictSet` can be chained
|
||||||
|
*/
|
||||||
|
strictSet(key: K, val: V): this {
|
||||||
if (this.has(key)) {
|
if (this.has(key)) {
|
||||||
return this.get(key);
|
throw new TypeError("Duplicate key in map");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.set(key, this.getDefault()).get(key);
|
return this.set(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value associated with `key`
|
||||||
|
* @throws if `key` did not a value associated with it
|
||||||
|
*/
|
||||||
|
strictGet(key: K): V {
|
||||||
|
if (!this.has(key)) {
|
||||||
|
throw new TypeError("key not in map");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.get(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,8 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type Falsey = false | 0 | "" | null | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new type safe empty Iterable
|
* Create a new type safe empty Iterable
|
||||||
* @returns An `Iterable` that yields 0 items
|
* @returns An `Iterable` that yields 0 items
|
||||||
@ -57,6 +59,36 @@ export function* map<T, U>(src: Iterable<T>, fn: (from: T) => U): Iterable<U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The single layer flattening of an iterator, discarding `Falsey` values.
|
||||||
|
* @param src A type that can be iterated over
|
||||||
|
* @param fn The function that returns either an iterable over items that should be filtered out or a `Falsey` value indicating that it should be ignored
|
||||||
|
*/
|
||||||
|
export function* filterFlatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U | Falsey> | Falsey): Iterable<U> {
|
||||||
|
for (const from of src) {
|
||||||
|
if (!from) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapping = fn(from);
|
||||||
|
|
||||||
|
if (!mapping) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const mapped of mapping) {
|
||||||
|
if (mapped) {
|
||||||
|
yield mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new iterator that yields the items that each call to `fn` would produce
|
||||||
|
* @param src A type that can be iterated over
|
||||||
|
* @param fn A function that returns an iterator
|
||||||
|
*/
|
||||||
export function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>): Iterable<U> {
|
export function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>): Iterable<U> {
|
||||||
for (const from of src) {
|
for (const from of src) {
|
||||||
yield* fn(from);
|
yield* fn(from);
|
||||||
@ -83,7 +115,7 @@ export function* filter<T>(src: Iterable<T>, fn: (from: T) => any): Iterable<T>
|
|||||||
* @param src A type that can be iterated over
|
* @param src A type that can be iterated over
|
||||||
* @param fn The function that is called for each value
|
* @param fn The function that is called for each value
|
||||||
*/
|
*/
|
||||||
export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U): Iterable<U> {
|
export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U | Falsey): Iterable<U> {
|
||||||
for (const from of src) {
|
for (const from of src) {
|
||||||
const res = fn(from);
|
const res = fn(from);
|
||||||
|
|
||||||
@ -99,7 +131,7 @@ export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U): Iterable
|
|||||||
* @param src A type that can be iterated over
|
* @param src A type that can be iterated over
|
||||||
* @param fn The function that is called for each value
|
* @param fn The function that is called for each value
|
||||||
*/
|
*/
|
||||||
export function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U): Iterable<U> {
|
export function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U | null | undefined): Iterable<U> {
|
||||||
for (const from of src) {
|
for (const from of src) {
|
||||||
const res = fn(from);
|
const res = fn(from);
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { app, remote } from "electron";
|
import { app, remote } from "electron";
|
||||||
import { ensureDirSync, writeFileSync } from "fs-extra";
|
import { ensureDirSync, writeFileSync } from "fs-extra";
|
||||||
import { WriteFileOptions } from "fs";
|
import type { WriteFileOptions } from "fs";
|
||||||
|
|
||||||
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
|
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
|
||||||
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);
|
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);
|
||||||
|
|||||||
@ -77,6 +77,8 @@ jest.mock(
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}),
|
}),
|
||||||
on: jest.fn(
|
on: jest.fn(
|
||||||
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||||
|
|||||||
@ -1,156 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2021 OpenLens Authors
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to
|
|
||||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import hb from "handlebars";
|
|
||||||
import { observable } from "mobx";
|
|
||||||
import { ResourceApplier } from "../main/resource-applier";
|
|
||||||
import { KubernetesCluster } from "./core-api/catalog";
|
|
||||||
import logger from "../main/logger";
|
|
||||||
import { app } from "electron";
|
|
||||||
import { requestMain } from "../common/ipc";
|
|
||||||
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
|
||||||
import { ClusterStore } from "../common/cluster-store";
|
|
||||||
|
|
||||||
export interface ClusterFeatureStatus {
|
|
||||||
/** feature's current version, as set by the implementation */
|
|
||||||
currentVersion: string;
|
|
||||||
/** feature's latest version, as set by the implementation */
|
|
||||||
latestVersion: string;
|
|
||||||
/** whether the feature is installed or not, as set by the implementation */
|
|
||||||
installed: boolean;
|
|
||||||
/** whether the feature can be upgraded or not, as set by the implementation */
|
|
||||||
canUpgrade: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class ClusterFeature {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this field sets the template parameters that are to be applied to any templated kubernetes resources that are to be installed for the feature.
|
|
||||||
* See the renderTemplates() method for more details
|
|
||||||
*/
|
|
||||||
templateContext: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this field holds the current feature status, is accessed directly by Lens
|
|
||||||
*/
|
|
||||||
@observable status: ClusterFeatureStatus = {
|
|
||||||
currentVersion: null,
|
|
||||||
installed: false,
|
|
||||||
latestVersion: null,
|
|
||||||
canUpgrade: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be installed. The implementation
|
|
||||||
* of this method should install kubernetes resources using the applyResources() method, or by directly accessing the kubernetes api (K8sApi)
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be installed on
|
|
||||||
*/
|
|
||||||
abstract install(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
|
|
||||||
* of this method should upgrade the kubernetes resources already installed, if relevant to the feature
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be upgraded on
|
|
||||||
*/
|
|
||||||
abstract upgrade(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
|
|
||||||
* of this method should uninstall kubernetes resources using the kubernetes api (K8sApi)
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be uninstalled from
|
|
||||||
*/
|
|
||||||
abstract uninstall(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
|
|
||||||
* of this method should provide the current status information. The currentVersion and latestVersion fields may be displayed by Lens in describing the feature.
|
|
||||||
* The installed field should be set to true if the feature has been installed, otherwise false. Also, Lens relies on the canUpgrade field to determine if the feature
|
|
||||||
* can be upgraded so the implementation should set the canUpgrade field according to specific rules for the feature, if relevant.
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature may be installed on
|
|
||||||
*
|
|
||||||
* @return a promise, resolved with the updated ClusterFeatureStatus
|
|
||||||
*/
|
|
||||||
abstract updateStatus(cluster: KubernetesCluster): Promise<ClusterFeatureStatus>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this is a helper method that conveniently applies kubernetes resources to the cluster.
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the resources are to be applied to
|
|
||||||
* @param resourceSpec as a string type this is a folder path that is searched for files specifying kubernetes resources. The files are read and if any of the resource
|
|
||||||
* files are templated, the template parameters are filled using the templateContext field (See renderTemplate() method). Finally the resources are applied to the
|
|
||||||
* cluster. As a string[] type resourceSpec is treated as an array of fully formed (not templated) kubernetes resources that are applied to the cluster
|
|
||||||
*/
|
|
||||||
protected async applyResources(cluster: KubernetesCluster, resourceSpec: string | string[]) {
|
|
||||||
let resources: string[];
|
|
||||||
|
|
||||||
const clusterModel = ClusterStore.getInstance().getById(cluster.metadata.uid);
|
|
||||||
|
|
||||||
if (!clusterModel) {
|
|
||||||
throw new Error(`cluster not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( typeof resourceSpec === "string" ) {
|
|
||||||
resources = this.renderTemplates(resourceSpec);
|
|
||||||
} else {
|
|
||||||
resources = resourceSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app) {
|
|
||||||
await new ResourceApplier(clusterModel).kubectlApplyAll(resources);
|
|
||||||
} else {
|
|
||||||
await requestMain(clusterKubectlApplyAllHandler, cluster.metadata.uid, resources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this is a helper method that conveniently reads kubernetes resource files into a string array. It also fills templated resource files with the template parameter values
|
|
||||||
* specified by the templateContext field. Templated files must end with the extension '.hb' and the template syntax must be compatible with handlebars.js
|
|
||||||
*
|
|
||||||
* @param folderPath this is a folder path that is searched for files defining kubernetes resources.
|
|
||||||
*
|
|
||||||
* @return an array of strings, each string being the contents of a resource file found in the folder path. This can be passed directly to applyResources()
|
|
||||||
*/
|
|
||||||
protected renderTemplates(folderPath: string): string[] {
|
|
||||||
const resources: string[] = [];
|
|
||||||
|
|
||||||
logger.info(`[FEATURE]: render templates from ${folderPath}`);
|
|
||||||
fs.readdirSync(folderPath).forEach(filename => {
|
|
||||||
const file = path.join(folderPath, filename);
|
|
||||||
const raw = fs.readFileSync(file);
|
|
||||||
|
|
||||||
if (filename.endsWith(".hb")) {
|
|
||||||
const template = hb.compile(raw.toString());
|
|
||||||
|
|
||||||
resources.push(template(this.templateContext));
|
|
||||||
} else {
|
|
||||||
resources.push(raw.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -28,16 +28,16 @@ import * as App from "./app";
|
|||||||
import * as EventBus from "./event-bus";
|
import * as EventBus from "./event-bus";
|
||||||
import * as Store from "./stores";
|
import * as Store from "./stores";
|
||||||
import * as Util from "./utils";
|
import * as Util from "./utils";
|
||||||
import * as ClusterFeature from "./cluster-feature";
|
|
||||||
import * as Interface from "../interfaces";
|
import * as Interface from "../interfaces";
|
||||||
import * as Catalog from "./catalog";
|
import * as Catalog from "./catalog";
|
||||||
|
import * as Types from "./types";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
App,
|
App,
|
||||||
EventBus,
|
EventBus,
|
||||||
Catalog,
|
Catalog,
|
||||||
ClusterFeature,
|
|
||||||
Interface,
|
Interface,
|
||||||
Store,
|
Store,
|
||||||
|
Types,
|
||||||
Util,
|
Util,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,3 +20,5 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export { ExtensionStore } from "../extension-store";
|
export { ExtensionStore } from "../extension-store";
|
||||||
|
export { MainIpcStore } from "../main-ipc-store";
|
||||||
|
export { RendererIpcStore } from "../renderer-ipc-store";
|
||||||
|
|||||||
@ -19,15 +19,6 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.MetricsSelect {
|
export type IpcMainInvokeEvent = Electron.IpcMainInvokeEvent;
|
||||||
$spacing: $padding;
|
export type IpcRendererEvent = Electron.IpcRendererEvent;
|
||||||
--flex-gap: #{$spacing};
|
export type IpcMainEvent = Electron.IpcMainEvent;
|
||||||
|
|
||||||
.Badge {
|
|
||||||
margin-top: $spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Button {
|
|
||||||
margin-top: $spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -342,10 +342,13 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
const manifest = await fse.readJson(manifestPath);
|
const manifest = await fse.readJson(manifestPath);
|
||||||
const installedManifestPath = this.getInstalledManifestPath(manifest.name);
|
const installedManifestPath = this.getInstalledManifestPath(manifest.name);
|
||||||
const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath);
|
const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath);
|
||||||
|
const extensionDir = path.dirname(manifestPath);
|
||||||
|
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||||
|
const absolutePath = (await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: installedManifestPath,
|
id: installedManifestPath,
|
||||||
absolutePath: path.dirname(manifestPath),
|
absolutePath,
|
||||||
manifestPath: installedManifestPath,
|
manifestPath: installedManifestPath,
|
||||||
manifest,
|
manifest,
|
||||||
isBundled,
|
isBundled,
|
||||||
|
|||||||
@ -325,6 +325,8 @@ export class ExtensionLoader extends Singleton {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: can't load extension main at ${extAbsolutePath}: ${error}`, { extension, error });
|
logger.error(`${logModule}: can't load extension main at ${extAbsolutePath}: ${error}`, { extension, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtension(extId: LensExtensionId): InstalledExtension {
|
getExtension(extId: LensExtensionId): InstalledExtension {
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { BaseStore } from "../common/base-store";
|
import { BaseStore } from "../common/base-store";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { LensExtension } from "./lens-extension";
|
import type { LensExtension } from "./lens-extension";
|
||||||
|
|
||||||
export abstract class ExtensionStore<T> extends BaseStore<T> {
|
export abstract class ExtensionStore<T> extends BaseStore<T> {
|
||||||
protected extension: LensExtension;
|
protected extension: LensExtension;
|
||||||
|
|||||||
39
src/extensions/ipc-store.ts
Normal file
39
src/extensions/ipc-store.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { Singleton } from "../common/utils";
|
||||||
|
import type { LensExtension } from "./lens-extension";
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
import { broadcastMessage } from "../common/ipc";
|
||||||
|
|
||||||
|
export const IpcPrefix = Symbol();
|
||||||
|
|
||||||
|
export abstract class IpcStore extends Singleton {
|
||||||
|
readonly [IpcPrefix]: string;
|
||||||
|
|
||||||
|
constructor(protected extension: LensExtension) {
|
||||||
|
super();
|
||||||
|
this[IpcPrefix] = createHash("sha256").update(extension.id).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastIpc(channel: string, ...args: any[]): void {
|
||||||
|
broadcastMessage(`extensions@${this[IpcPrefix]}:${channel}`, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,7 +23,8 @@ import type { InstalledExtension } from "./extension-discovery";
|
|||||||
import { action, observable, reaction } from "mobx";
|
import { action, observable, reaction } from "mobx";
|
||||||
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { ProtocolHandlerRegistration } from "./registries/protocol-handler-registry";
|
import type { ProtocolHandlerRegistration } from "./registries";
|
||||||
|
import { disposer } from "../common/utils";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||||
@ -37,6 +38,8 @@ export interface LensExtensionManifest {
|
|||||||
lens?: object; // fixme: add more required fields for validation
|
lens?: object; // fixme: add more required fields for validation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Disposers = Symbol();
|
||||||
|
|
||||||
export class LensExtension {
|
export class LensExtension {
|
||||||
readonly id: LensExtensionId;
|
readonly id: LensExtensionId;
|
||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: LensExtensionManifest;
|
||||||
@ -46,6 +49,7 @@ export class LensExtension {
|
|||||||
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
||||||
|
|
||||||
@observable private isEnabled = false;
|
@observable private isEnabled = false;
|
||||||
|
[Disposers] = disposer();
|
||||||
|
|
||||||
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -62,6 +66,10 @@ export class LensExtension {
|
|||||||
return this.manifest.version;
|
return this.manifest.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.manifest.description;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getExtensionFileFolder returns the path to an already created folder. This
|
* getExtensionFileFolder returns the path to an already created folder. This
|
||||||
* folder is for the sole use of this extension.
|
* folder is for the sole use of this extension.
|
||||||
@ -73,15 +81,11 @@ export class LensExtension {
|
|||||||
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get description() {
|
|
||||||
return this.manifest.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async enable() {
|
async enable() {
|
||||||
if (this.isEnabled) return;
|
if (this.isEnabled) return;
|
||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
this.onActivate();
|
this.onActivate?.();
|
||||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +93,8 @@ export class LensExtension {
|
|||||||
async disable() {
|
async disable() {
|
||||||
if (!this.isEnabled) return;
|
if (!this.isEnabled) return;
|
||||||
this.isEnabled = false;
|
this.isEnabled = false;
|
||||||
this.onDeactivate();
|
this.onDeactivate?.();
|
||||||
|
this[Disposers]();
|
||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +130,12 @@ export class LensExtension {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onActivate() {
|
protected onActivate(): void {
|
||||||
// mock
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDeactivate() {
|
protected onDeactivate(): void {
|
||||||
// mock
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,12 +19,12 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { MenuRegistration } from "./registries/menu-registry";
|
|
||||||
import { LensExtension } from "./lens-extension";
|
import { LensExtension } from "./lens-extension";
|
||||||
import { WindowManager } from "../main/window-manager";
|
import { WindowManager } from "../main/window-manager";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry";
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
import { CatalogEntity, catalogEntityRegistry } from "../common/catalog";
|
import { CatalogEntity, catalogEntityRegistry } from "../common/catalog";
|
||||||
import { IObservableArray } from "mobx";
|
import type { IObservableArray } from "mobx";
|
||||||
|
import type { MenuRegistration } from "./registries";
|
||||||
|
|
||||||
export class LensMainExtension extends LensExtension {
|
export class LensMainExtension extends LensExtension {
|
||||||
appMenus: MenuRegistration[] = [];
|
appMenus: MenuRegistration[] = [];
|
||||||
|
|||||||
@ -26,8 +26,8 @@ import type {
|
|||||||
import type { Cluster } from "../main/cluster";
|
import type { Cluster } from "../main/cluster";
|
||||||
import { LensExtension } from "./lens-extension";
|
import { LensExtension } from "./lens-extension";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry";
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
import { CommandRegistration } from "./registries/command-registry";
|
import type { CommandRegistration } from "./registries/command-registry";
|
||||||
import { EntitySettingRegistration } from "./registries/entity-setting-registry";
|
import type { EntitySettingRegistration } from "./registries/entity-setting-registry";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
globalPages: PageRegistration[] = [];
|
globalPages: PageRegistration[] = [];
|
||||||
|
|||||||
45
src/extensions/main-ipc-store.ts
Normal file
45
src/extensions/main-ipc-store.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { IpcPrefix, IpcStore } from "./ipc-store";
|
||||||
|
import { Disposers } from "./lens-extension";
|
||||||
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
|
|
||||||
|
export abstract class MainIpcStore extends IpcStore {
|
||||||
|
constructor(extension: LensMainExtension) {
|
||||||
|
super(extension);
|
||||||
|
extension[Disposers].push(() => MainIpcStore.resetInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIpc(channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcMain.handle(prefixedChannel, handler);
|
||||||
|
this.extension[Disposers].push(() => ipcMain.removeHandler(prefixedChannel));
|
||||||
|
}
|
||||||
|
|
||||||
|
listenIpc(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcMain.addListener(prefixedChannel, listener);
|
||||||
|
this.extension[Disposers].push(() => ipcMain.removeListener(prefixedChannel, listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { CatalogEntity } from "../../common/catalog";
|
import type { CatalogEntity } from "../../common/catalog";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface EntitySettingViewProps {
|
export interface EntitySettingViewProps {
|
||||||
@ -32,13 +32,14 @@ export interface EntitySettingComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EntitySettingRegistration {
|
export interface EntitySettingRegistration {
|
||||||
title: string;
|
|
||||||
kind: string;
|
|
||||||
apiVersions: string[];
|
apiVersions: string[];
|
||||||
source?: string;
|
kind: string;
|
||||||
|
title: string;
|
||||||
components: EntitySettingComponents;
|
components: EntitySettingComponents;
|
||||||
|
source?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
priority?: number;
|
priority?: number;
|
||||||
|
group?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisteredEntitySetting extends EntitySettingRegistration {
|
export interface RegisteredEntitySetting extends EntitySettingRegistration {
|
||||||
|
|||||||
@ -32,3 +32,4 @@ export * from "./kube-object-status-registry";
|
|||||||
export * from "./command-registry";
|
export * from "./command-registry";
|
||||||
export * from "./entity-setting-registry";
|
export * from "./entity-setting-registry";
|
||||||
export * from "./welcome-menu-registry";
|
export * from "./welcome-menu-registry";
|
||||||
|
export * from "./protocol-handler-registry";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface KubeObjectDetailComponents {
|
export interface KubeObjectDetailComponents {
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface KubeObjectMenuComponents {
|
export interface KubeObjectMenuComponents {
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
|
import type { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface KubeObjectStatusRegistration {
|
export interface KubeObjectStatusRegistration {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
|
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
|
||||||
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
|
import type { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
|
||||||
import { createPageParam } from "../../renderer/navigation/helpers";
|
import { createPageParam } from "../../renderer/navigation/helpers";
|
||||||
|
|
||||||
export interface PageRegistration {
|
export interface PageRegistration {
|
||||||
@ -119,9 +119,9 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
|
|||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> {
|
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> | undefined {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
Object.entries(params).forEach(([name, value]) => {
|
Object.entries(params).forEach(([name, value]) => {
|
||||||
const paramInit: PageParamInit = typeof value === "object"
|
const paramInit: PageParamInit = typeof value === "object"
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
// Extensions API -> Status bar customizations
|
// Extensions API -> Status bar customizations
|
||||||
|
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
interface StatusBarComponents {
|
interface StatusBarComponents {
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export * from "../../renderer/components/checkbox";
|
|||||||
export * from "../../renderer/components/radio";
|
export * from "../../renderer/components/radio";
|
||||||
export * from "../../renderer/components/select";
|
export * from "../../renderer/components/select";
|
||||||
export * from "../../renderer/components/slider";
|
export * from "../../renderer/components/slider";
|
||||||
|
export * from "../../renderer/components/switch";
|
||||||
export * from "../../renderer/components/input/input";
|
export * from "../../renderer/components/input/input";
|
||||||
|
|
||||||
// command-overlay
|
// command-overlay
|
||||||
|
|||||||
@ -20,11 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export { isAllowedResource } from "../../common/rbac";
|
export { isAllowedResource } from "../../common/rbac";
|
||||||
|
export { ResourceStack } from "../../common/k8s/resource-stack";
|
||||||
export { apiManager } from "../../renderer/api/api-manager";
|
export { apiManager } from "../../renderer/api/api-manager";
|
||||||
export { KubeObjectStore } from "../../renderer/kube-object.store";
|
export { KubeObjectStore } from "../../renderer/kube-object.store";
|
||||||
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
|
export { KubeApi, forCluster } from "../../renderer/api/kube-api";
|
||||||
export { KubeObject } from "../../renderer/api/kube-object";
|
export { KubeObject } from "../../renderer/api/kube-object";
|
||||||
export { Pod, podsApi, PodsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
export { Pod, podsApi, PodsApi } from "../../renderer/api/endpoints";
|
||||||
export { Node, nodesApi, NodesApi } from "../../renderer/api/endpoints";
|
export { Node, nodesApi, NodesApi } from "../../renderer/api/endpoints";
|
||||||
export { Deployment, deploymentApi, DeploymentApi } from "../../renderer/api/endpoints";
|
export { Deployment, deploymentApi, DeploymentApi } from "../../renderer/api/endpoints";
|
||||||
export { DaemonSet, daemonSetApi } from "../../renderer/api/endpoints";
|
export { DaemonSet, daemonSetApi } from "../../renderer/api/endpoints";
|
||||||
@ -32,7 +33,7 @@ export { StatefulSet, statefulSetApi } from "../../renderer/api/endpoints";
|
|||||||
export { Job, jobApi } from "../../renderer/api/endpoints";
|
export { Job, jobApi } from "../../renderer/api/endpoints";
|
||||||
export { CronJob, cronJobApi } from "../../renderer/api/endpoints";
|
export { CronJob, cronJobApi } from "../../renderer/api/endpoints";
|
||||||
export { ConfigMap, configMapApi } from "../../renderer/api/endpoints";
|
export { ConfigMap, configMapApi } from "../../renderer/api/endpoints";
|
||||||
export { Secret, secretsApi, ISecretRef } from "../../renderer/api/endpoints";
|
export { Secret, secretsApi } from "../../renderer/api/endpoints";
|
||||||
export { ReplicaSet, replicaSetApi } from "../../renderer/api/endpoints";
|
export { ReplicaSet, replicaSetApi } from "../../renderer/api/endpoints";
|
||||||
export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints";
|
export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints";
|
||||||
export { LimitRange, limitRangeApi } from "../../renderer/api/endpoints";
|
export { LimitRange, limitRangeApi } from "../../renderer/api/endpoints";
|
||||||
@ -53,7 +54,13 @@ export { RoleBinding, roleBindingApi } from "../../renderer/api/endpoints";
|
|||||||
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
|
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
|
||||||
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
|
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
|
||||||
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";
|
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";
|
||||||
export { KubeObjectStatus, KubeObjectStatusLevel } from "./kube-object-status";
|
export { KubeObjectStatusLevel } from "./kube-object-status";
|
||||||
|
|
||||||
|
// types
|
||||||
|
export type { IKubeApiCluster } from "../../renderer/api/kube-api";
|
||||||
|
export type { IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
||||||
|
export type { ISecretRef } from "../../renderer/api/endpoints";
|
||||||
|
export type { KubeObjectStatus } from "./kube-object-status";
|
||||||
|
|
||||||
// stores
|
// stores
|
||||||
export type { EventStore } from "../../renderer/components/+events/event.store";
|
export type { EventStore } from "../../renderer/components/+events/event.store";
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { navigation } from "../../renderer/navigation";
|
|||||||
export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
|
export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
|
||||||
export { navigate, isActiveRoute } from "../../renderer/navigation/helpers";
|
export { navigate, isActiveRoute } from "../../renderer/navigation/helpers";
|
||||||
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
|
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
|
||||||
export { IURLParams } from "../../common/utils/buildUrl";
|
export type { IURLParams } from "../../common/utils/buildUrl";
|
||||||
|
|
||||||
// exporting to extensions-api version of helper without `isSystem` flag
|
// exporting to extensions-api version of helper without `isSystem` flag
|
||||||
export function createPageParam<V = string>(init: PageParamInit<V>) {
|
export function createPageParam<V = string>(init: PageParamInit<V>) {
|
||||||
|
|||||||
44
src/extensions/renderer-ipc-store.ts
Normal file
44
src/extensions/renderer-ipc-store.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
import { IpcPrefix, IpcStore } from "./ipc-store";
|
||||||
|
import { Disposers } from "./lens-extension";
|
||||||
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
|
|
||||||
|
export abstract class RendererIpcStore extends IpcStore {
|
||||||
|
constructor(extension: LensRendererExtension) {
|
||||||
|
super(extension);
|
||||||
|
extension[Disposers].push(() => RendererIpcStore.resetInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
listenIpc(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcRenderer.addListener(prefixedChannel, listener);
|
||||||
|
this.extension[Disposers].push(() => ipcRenderer.removeListener(prefixedChannel, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeIpc(channel: string, ...args: any[]): Promise<any> {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
return ipcRenderer.invoke(prefixedChannel, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import fetchMock from "jest-fetch-mock";
|
import fetchMock from "jest-fetch-mock";
|
||||||
// rewire global.fetch to call 'fetchMock'
|
// rewire global.fetch to call 'fetchMock'
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|||||||
@ -44,6 +44,12 @@ jest.mock("winston", () => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock("electron", () => ({
|
||||||
|
app: {
|
||||||
|
getPath: () => "/foo",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("../../common/ipc");
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("child_process");
|
jest.mock("child_process");
|
||||||
jest.mock("tcp-port-used");
|
jest.mock("tcp-port-used");
|
||||||
@ -56,7 +62,7 @@ import { ChildProcess, spawn } from "child_process";
|
|||||||
import { bundledKubectlPath, Kubectl } from "../kubectl";
|
import { bundledKubectlPath, Kubectl } from "../kubectl";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { waitUntilUsed } from "tcp-port-used";
|
import { waitUntilUsed } from "tcp-port-used";
|
||||||
import { Readable } from "stream";
|
import type { Readable } from "stream";
|
||||||
import { UserStore } from "../../common/user-store";
|
import { UserStore } from "../../common/user-store";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
|
|||||||
@ -28,6 +28,12 @@ const logger = {
|
|||||||
crit: jest.fn(),
|
crit: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jest.mock("electron", () => ({
|
||||||
|
app: {
|
||||||
|
getPath: () => `/tmp`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("winston", () => ({
|
jest.mock("winston", () => ({
|
||||||
format: {
|
format: {
|
||||||
colorize: jest.fn(),
|
colorize: jest.fn(),
|
||||||
@ -47,7 +53,7 @@ jest.mock("winston", () => ({
|
|||||||
import { KubeconfigManager } from "../kubeconfig-manager";
|
import { KubeconfigManager } from "../kubeconfig-manager";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import { ContextHandler } from "../context-handler";
|
import type { ContextHandler } from "../context-handler";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { loadYaml } from "@kubernetes/client-node";
|
import { loadYaml } from "@kubernetes/client-node";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
@ -91,7 +97,9 @@ describe("kubeconfig manager tests", () => {
|
|||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
});
|
});
|
||||||
contextHandler = jest.fn() as any;
|
contextHandler = {
|
||||||
|
ensureServer: () => Promise.resolve(),
|
||||||
|
} as any;
|
||||||
jest.spyOn(KubeconfigManager.prototype, "resolveProxyUrl", "get").mockReturnValue("http://127.0.0.1:9191/foo");
|
jest.spyOn(KubeconfigManager.prototype, "resolveProxyUrl", "get").mockReturnValue("http://127.0.0.1:9191/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -103,7 +111,7 @@ describe("kubeconfig manager tests", () => {
|
|||||||
const kubeConfManager = new KubeconfigManager(cluster, contextHandler);
|
const kubeConfManager = new KubeconfigManager(cluster, contextHandler);
|
||||||
|
|
||||||
expect(logger.error).not.toBeCalled();
|
expect(logger.error).not.toBeCalled();
|
||||||
expect(await kubeConfManager.getPath()).toBe(`tmp${path.sep}kubeconfig-foo`);
|
expect(await kubeConfManager.getPath()).toBe(`${path.sep}tmp${path.sep}kubeconfig-foo`);
|
||||||
// this causes an intermittent "ENXIO: no such device or address, read" error
|
// this causes an intermittent "ENXIO: no such device or address, read" error
|
||||||
// const file = await fse.readFile(await kubeConfManager.getPath());
|
// const file = await fse.readFile(await kubeConfManager.getPath());
|
||||||
const file = fse.readFileSync(await kubeConfManager.getPath());
|
const file = fse.readFileSync(await kubeConfManager.getPath());
|
||||||
|
|||||||
@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
import { reaction, toJS } from "mobx";
|
import { reaction, toJS } from "mobx";
|
||||||
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "../common/ipc";
|
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "../common/ipc";
|
||||||
import { CatalogEntityRegistry} from "../common/catalog";
|
import type { CatalogEntityRegistry} from "../common/catalog";
|
||||||
import "../common/catalog-entities/kubernetes-cluster";
|
import "../common/catalog-entities/kubernetes-cluster";
|
||||||
import { Disposer } from "../common/utils";
|
import type { Disposer } from "../common/utils";
|
||||||
|
|
||||||
export class CatalogPusher {
|
export class CatalogPusher {
|
||||||
static init(catalog: CatalogEntityRegistry) {
|
static init(catalog: CatalogEntityRegistry) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user