mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into react-table-with-resizable-columns
This commit is contained in:
commit
7d2dfaf179
6
.adr.json
Normal file
6
.adr.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"path": "docs/architecture/decisions/",
|
||||||
|
"prefix": "",
|
||||||
|
"digits": 4
|
||||||
|
}
|
||||||
4
.github/workflows/check-docs.yml
vendored
4
.github/workflows/check-docs.yml
vendored
@ -23,8 +23,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn typedocs-extensions-api
|
yarn typedocs-extensions-api
|
||||||
|
|
||||||
- name: Verify that the markdown is valid
|
- name: Verify that the markdown is valid
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/electronegativity.yml
vendored
2
.github/workflows/electronegativity.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
- uses: doyensec/electronegativity-action@v1.1
|
- uses: doyensec/electronegativity-action@v1.1
|
||||||
with:
|
with:
|
||||||
input: src/
|
input: src/
|
||||||
electron-version: "15.5.7"
|
electron-version: "19.0.4"
|
||||||
severity: medium
|
severity: medium
|
||||||
|
|
||||||
- name: Upload sarif
|
- name: Upload sarif
|
||||||
|
|||||||
8
.github/workflows/mkdocs-manual.yml
vendored
8
.github/workflows/mkdocs-manual.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: '${{ github.event.inputs.version }}'
|
ref: "${{ github.event.inputs.version }}"
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@ -43,8 +43,8 @@ jobs:
|
|||||||
- name: Checkout master branch from lens
|
- name: Checkout master branch from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: 'master'
|
path: "master"
|
||||||
ref: 'master'
|
ref: "master"
|
||||||
|
|
||||||
- name: Bring in latest mkdocs.yml from master
|
- name: Bring in latest mkdocs.yml from master
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
4
.github/workflows/publish-release-npm.yml
vendored
4
.github/workflows/publish-release-npm.yml
vendored
@ -23,11 +23,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate NPM package
|
- name: Generate NPM package
|
||||||
run: |
|
run: |
|
||||||
make build-npm
|
make build-npm
|
||||||
|
|
||||||
- name: publish new release
|
- name: publish new release
|
||||||
if: contains(github.ref, 'refs/tags/v')
|
if: contains(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
make publish-npm
|
make publish-npm
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
4
.yarnrc
4
.yarnrc
@ -1,3 +1,3 @@
|
|||||||
disturl "https://atom.io/download/electron"
|
disturl "https://electronjs.org/headers"
|
||||||
target "15.5.0"
|
target "19.0.4"
|
||||||
runtime "electron"
|
runtime "electron"
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { promisify } from "util";
|
|||||||
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||||
import type { SingleBar } from "cli-progress";
|
import type { SingleBar } from "cli-progress";
|
||||||
import { MultiBar } from "cli-progress";
|
import { MultiBar } from "cli-progress";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import { extract } from "tar-stream";
|
import { extract } from "tar-stream";
|
||||||
import gunzip from "gunzip-maybe";
|
import gunzip from "gunzip-maybe";
|
||||||
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
||||||
|
|||||||
@ -18,7 +18,7 @@ exports.default = async function notarizing(context) {
|
|||||||
const appName = context.packager.appInfo.productFilename;
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
|
||||||
return await notarize({
|
return await notarize({
|
||||||
appBundleId: "io.kontena.lens-app",
|
appBundleId: process.env.APPBUNDLEID || "io.kontena.lens-app",
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: process.env.APPLEID,
|
appleId: process.env.APPLEID,
|
||||||
appleIdPassword: process.env.APPLEIDPASS,
|
appleIdPassword: process.env.APPLEIDPASS,
|
||||||
|
|||||||
2
docs/architecture/decisions/README.md
Normal file
2
docs/architecture/decisions/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ Some of the most-important fields include:
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/lensapp/lens-extension-samples",
|
"homepage": "https://github.com/lensapp/lens-extension-samples",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.12",
|
"node": "^16.14.2",
|
||||||
"lens": "5.4"
|
"lens": "5.4"
|
||||||
},
|
},
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
@ -72,7 +72,7 @@ Some of the most-important fields include:
|
|||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"@types/react": "^17.0.44",
|
"@types/react": "^17.0.44",
|
||||||
"@types/node": "^14.18.12",
|
"@types/node": "^16.14.2",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
}
|
}
|
||||||
|
|||||||
44
package.json
44
package.json
@ -12,6 +12,10 @@
|
|||||||
"email": "info@k8slens.dev"
|
"email": "info@k8slens.dev"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"adr:create": "echo \"What is the title?\"; read title; adr new \"$title\"",
|
||||||
|
"adr:change-status": "echo \"Decision number?:\"; read decision; adr status $decision",
|
||||||
|
"adr:update-readme": "adr update",
|
||||||
|
"adr:list": "adr list",
|
||||||
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
|
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
|
||||||
"dev-build": "concurrently yarn:compile:*",
|
"dev-build": "concurrently yarn:compile:*",
|
||||||
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
||||||
@ -210,16 +214,15 @@
|
|||||||
"@kubernetes/client-node": "^0.17.0",
|
"@kubernetes/client-node": "^0.17.0",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
"@ogre-tools/fp": "9.0.1",
|
"@ogre-tools/fp": "9.0.1",
|
||||||
"@ogre-tools/injectable": "9.0.1",
|
"@ogre-tools/injectable": "9.0.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.1",
|
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.2",
|
||||||
"@ogre-tools/injectable-extension-for-mobx": "9.0.1",
|
"@ogre-tools/injectable-extension-for-mobx": "9.0.2",
|
||||||
"@ogre-tools/injectable-react": "9.0.1",
|
"@ogre-tools/injectable-react": "9.0.2",
|
||||||
"@sentry/electron": "^3.0.7",
|
"@sentry/electron": "^3.0.7",
|
||||||
"@sentry/integrations": "^6.19.3",
|
"@sentry/integrations": "^6.19.3",
|
||||||
"@side/jest-runtime": "^1.0.1",
|
"@side/jest-runtime": "^1.0.1",
|
||||||
"@tanstack/react-table": "^8.5.5",
|
"@tanstack/react-table": "^8.5.5",
|
||||||
"@types/circular-dependency-plugin": "5.0.5",
|
"@types/circular-dependency-plugin": "5.0.5",
|
||||||
"abort-controller": "^3.0.0",
|
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
"await-lock": "^2.2.2",
|
"await-lock": "^2.2.2",
|
||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
@ -249,7 +252,7 @@
|
|||||||
"mobx-observable-history": "^2.0.3",
|
"mobx-observable-history": "^2.0.3",
|
||||||
"mobx-react": "^7.5.2",
|
"mobx-react": "^7.5.2",
|
||||||
"mobx-utils": "^6.0.4",
|
"mobx-utils": "^6.0.4",
|
||||||
"mock-fs": "^5.1.2",
|
"mock-fs": "^5.1.4",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.34",
|
"moment-timezone": "^0.5.34",
|
||||||
"monaco-editor": "^0.29.1",
|
"monaco-editor": "^0.29.1",
|
||||||
@ -284,7 +287,8 @@
|
|||||||
"winston": "^3.8.1",
|
"winston": "^3.8.1",
|
||||||
"winston-console-format": "^1.0.8",
|
"winston-console-format": "^1.0.8",
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
"ws": "^8.8.0"
|
"ws": "^8.8.1",
|
||||||
|
"xterm-link-provider": "^1.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "1.6.4",
|
"@async-fn/jest": "1.6.4",
|
||||||
@ -293,7 +297,7 @@
|
|||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
"@sentry/types": "^6.19.7",
|
"@sentry/types": "^6.19.7",
|
||||||
"@swc/core": "^1.2.218",
|
"@swc/core": "^1.2.223",
|
||||||
"@swc/jest": "^0.2.22",
|
"@swc/jest": "^0.2.22",
|
||||||
"@testing-library/dom": "^7.31.2",
|
"@testing-library/dom": "^7.31.2",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
@ -315,7 +319,7 @@
|
|||||||
"@types/hapi__subtext": "^7.0.0",
|
"@types/hapi__subtext": "^7.0.0",
|
||||||
"@types/html-webpack-plugin": "^3.2.6",
|
"@types/html-webpack-plugin": "^3.2.6",
|
||||||
"@types/http-proxy": "^1.17.9",
|
"@types/http-proxy": "^1.17.9",
|
||||||
"@types/jest": "^28.1.3",
|
"@types/jest": "^28.1.6",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/lodash": "^4.14.181",
|
"@types/lodash": "^4.14.181",
|
||||||
@ -323,7 +327,7 @@
|
|||||||
"@types/md5-file": "^4.0.2",
|
"@types/md5-file": "^4.0.2",
|
||||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^16.11.45",
|
"@types/node": "^16.11.47",
|
||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/npm": "^2.0.32",
|
"@types/npm": "^2.0.32",
|
||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
@ -352,8 +356,9 @@
|
|||||||
"@types/webpack-dev-server": "^4.7.2",
|
"@types/webpack-dev-server": "^4.7.2",
|
||||||
"@types/webpack-env": "^1.17.0",
|
"@types/webpack-env": "^1.17.0",
|
||||||
"@types/webpack-node-externals": "^2.5.3",
|
"@types/webpack-node-externals": "^2.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||||
"@typescript-eslint/parser": "^5.31.0",
|
"@typescript-eslint/parser": "^5.31.0",
|
||||||
|
"adr": "^1.4.1",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
@ -364,12 +369,12 @@
|
|||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.1",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.3.10",
|
"dompurify": "^2.3.10",
|
||||||
"electron": "^15.5.7",
|
"electron": "^19.0.4",
|
||||||
"electron-builder": "^23.1.0",
|
"electron-builder": "^23.3.3",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.14.49",
|
"esbuild": "^0.14.53",
|
||||||
"esbuild-loader": "^2.19.0",
|
"esbuild-loader": "^2.19.0",
|
||||||
"eslint": "^8.20.0",
|
"eslint": "^8.21.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
@ -382,7 +387,7 @@
|
|||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"include-media": "^1.4.9",
|
"include-media": "^1.4.9",
|
||||||
"jest": "^28.1.2",
|
"jest": "^28.1.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
"jest-environment-jsdom": "^28.1.3",
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
@ -393,7 +398,7 @@
|
|||||||
"node-gyp": "^8.3.0",
|
"node-gyp": "^8.3.0",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"playwright": "^1.24.1",
|
"playwright": "^1.24.2",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
@ -402,9 +407,10 @@
|
|||||||
"react-refresh-typescript": "^2.0.7",
|
"react-refresh-typescript": "^2.0.7",
|
||||||
"react-router-dom": "^5.3.3",
|
"react-router-dom": "^5.3.3",
|
||||||
"react-select": "^5.4.0",
|
"react-select": "^5.4.0",
|
||||||
"react-select-event": "^5.5.0",
|
"react-select-event": "^5.5.1",
|
||||||
|
"react-table": "^7.8.0",
|
||||||
"react-window": "^1.8.7",
|
"react-window": "^1.8.7",
|
||||||
"sass": "^1.53.0",
|
"sass": "^1.54.2",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sharp": "^0.30.7",
|
"sharp": "^0.30.7",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
|
|||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import type { Logger } from "../../common/logger";
|
import type { Logger } from "../../common/logger";
|
||||||
|
import getRandomIdInjectable from "../../common/utils/get-random-id.injectable";
|
||||||
|
|
||||||
describe("clicking tray menu item originating from extension", () => {
|
describe("clicking tray menu item originating from extension", () => {
|
||||||
let applicationBuilder: ApplicationBuilder;
|
let applicationBuilder: ApplicationBuilder;
|
||||||
@ -20,6 +21,7 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
logErrorMock = jest.fn();
|
logErrorMock = jest.fn();
|
||||||
|
|
||||||
mainDi.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
mainDi.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
||||||
|
mainDi.override(getRandomIdInjectable, () => () => "some-random-id");
|
||||||
});
|
});
|
||||||
|
|
||||||
await applicationBuilder.render();
|
await applicationBuilder.render();
|
||||||
@ -42,7 +44,7 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
|
|
||||||
it("when item is clicked, triggers the click handler", () => {
|
it("when item is clicked, triggers the click handler", () => {
|
||||||
applicationBuilder.tray.click(
|
applicationBuilder.tray.click(
|
||||||
"some-label-tray-menu-item-for-extension-some-extension-id",
|
"some-random-id-tray-menu-item-for-extension-some-extension-id",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(clickMock).toHaveBeenCalled();
|
expect(clickMock).toHaveBeenCalled();
|
||||||
@ -55,13 +57,13 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
applicationBuilder.tray.click(
|
applicationBuilder.tray.click(
|
||||||
"some-label-tray-menu-item-for-extension-some-extension-id",
|
"some-random-id-tray-menu-item-for-extension-some-extension-id",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("logs the error", () => {
|
it("logs the error", () => {
|
||||||
expect(logErrorMock).toHaveBeenCalledWith(
|
expect(logErrorMock).toHaveBeenCalledWith(
|
||||||
'[TRAY]: Clicking of tray item "some-label" from extension "some-extension-id" failed.',
|
'[TRAY]: Clicking of tray item "some-random-id" from extension "some-extension-id" failed.',
|
||||||
expect.any(Error),
|
expect.any(Error),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -72,13 +74,13 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
clickMock.mockImplementation(() => Promise.reject("some-rejection"));
|
clickMock.mockImplementation(() => Promise.reject("some-rejection"));
|
||||||
|
|
||||||
applicationBuilder.tray.click(
|
applicationBuilder.tray.click(
|
||||||
"some-label-tray-menu-item-for-extension-some-extension-id",
|
"some-random-id-tray-menu-item-for-extension-some-extension-id",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("logs the error", () => {
|
it("logs the error", () => {
|
||||||
expect(logErrorMock).toHaveBeenCalledWith(
|
expect(logErrorMock).toHaveBeenCalledWith(
|
||||||
'[TRAY]: Clicking of tray item "some-label" from extension "some-extension-id" failed.',
|
'[TRAY]: Clicking of tray item "some-random-id" from extension "some-extension-id" failed.',
|
||||||
"some-rejection",
|
"some-rejection",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -92,7 +94,7 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
it("does not have the tray menu item from extension", () => {
|
it("does not have the tray menu item from extension", () => {
|
||||||
expect(
|
expect(
|
||||||
applicationBuilder.tray.get(
|
applicationBuilder.tray.get(
|
||||||
"some-label-tray-menu-item-for-extension-some-extension-id",
|
"some-random-id-tray-menu-item-for-extension-some-extension-id",
|
||||||
),
|
),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
@ -103,7 +105,7 @@ describe("clicking tray menu item originating from extension", () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
applicationBuilder.tray.get(
|
applicationBuilder.tray.get(
|
||||||
"some-label-tray-menu-item-for-extension-some-extension-id",
|
"some-random-id-tray-menu-item-for-extension-some-extension-id",
|
||||||
),
|
),
|
||||||
).not.toBeNull();
|
).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,6 +13,7 @@ describe("preferences: extension adding tray items", () => {
|
|||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
let someObservableForVisibility: IObservableValue<boolean>;
|
let someObservableForVisibility: IObservableValue<boolean>;
|
||||||
let someObservableForEnabled: IObservableValue<boolean>;
|
let someObservableForEnabled: IObservableValue<boolean>;
|
||||||
|
let someObservableLabel: IObservableValue<string>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
builder = getApplicationBuilder();
|
builder = getApplicationBuilder();
|
||||||
@ -25,6 +26,7 @@ describe("preferences: extension adding tray items", () => {
|
|||||||
|
|
||||||
someObservableForVisibility = observable.box(false);
|
someObservableForVisibility = observable.box(false);
|
||||||
someObservableForEnabled = observable.box(false);
|
someObservableForEnabled = observable.box(false);
|
||||||
|
someObservableLabel = observable.box("Some label");
|
||||||
|
|
||||||
const testExtension = getExtensionFake({
|
const testExtension = getExtensionFake({
|
||||||
id: "some-extension-id",
|
id: "some-extension-id",
|
||||||
@ -33,38 +35,51 @@ describe("preferences: extension adding tray items", () => {
|
|||||||
mainOptions: {
|
mainOptions: {
|
||||||
trayMenus: [
|
trayMenus: [
|
||||||
{
|
{
|
||||||
|
id: "some-controlled-visibility",
|
||||||
label: "some-controlled-visibility",
|
label: "some-controlled-visibility",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
visible: computed(() => someObservableForVisibility.get()),
|
visible: computed(() => someObservableForVisibility.get()),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "some-uncontrolled-visibility",
|
||||||
label: "some-uncontrolled-visibility",
|
label: "some-uncontrolled-visibility",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "some-controlled-enabled",
|
||||||
label: "some-controlled-enabled",
|
label: "some-controlled-enabled",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
enabled: computed(() => someObservableForEnabled.get()),
|
enabled: computed(() => someObservableForEnabled.get()),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "some-uncontrolled-enabled",
|
||||||
label: "some-uncontrolled-enabled",
|
label: "some-uncontrolled-enabled",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "some-statically-enabled",
|
||||||
label: "some-statically-enabled",
|
label: "some-statically-enabled",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "some-statically-disabled",
|
||||||
label: "some-statically-disabled",
|
label: "some-statically-disabled",
|
||||||
click: () => {},
|
click: () => {},
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-item-with-controlled-label",
|
||||||
|
label: computed(() => someObservableLabel.get()),
|
||||||
|
click: () => {},
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -72,6 +87,37 @@ describe("preferences: extension adding tray items", () => {
|
|||||||
builder.extensions.enable(testExtension);
|
builder.extensions.enable(testExtension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given controlled label", () => {
|
||||||
|
it("has the label", () => {
|
||||||
|
const item = builder.tray.get(
|
||||||
|
"some-item-with-controlled-label-tray-menu-item-for-extension-some-extension",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(item?.label).toBe("Some label");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when label changes, updates the label", () => {
|
||||||
|
runInAction(() => {
|
||||||
|
someObservableLabel.set("Some new label");
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = builder.tray.get(
|
||||||
|
"some-item-with-controlled-label-tray-menu-item-for-extension-some-extension",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(item?.label).toBe("Some new label");
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given item is statically disabled, item is disabled", () => {
|
||||||
|
const item = builder.tray.get(
|
||||||
|
"some-statically-disabled-tray-menu-item-for-extension-some-extension",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(item?.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("shows item which doesn't control the visibility", () => {
|
it("shows item which doesn't control the visibility", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get(
|
builder.tray.get(
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import { forRemoteCluster, KubeApi } from "../kube-api";
|
import { forRemoteCluster, KubeApi } from "../kube-api";
|
||||||
import { KubeJsonApi } from "../kube-json-api";
|
import { KubeJsonApi } from "../kube-json-api";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import { delay } from "../../utils/delay";
|
import { delay } from "../../utils/delay";
|
||||||
import { PassThrough } from "stream";
|
import { PassThrough } from "stream";
|
||||||
import { ApiManager } from "../api-manager";
|
import { ApiManager } from "../api-manager";
|
||||||
|
|||||||
@ -58,11 +58,11 @@ export interface ResourceMetricSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseHorizontalPodAutoscalerMetricSpec {
|
export interface BaseHorizontalPodAutoscalerMetricSpec {
|
||||||
resource: ResourceMetricSource;
|
|
||||||
object: ObjectMetricSource;
|
|
||||||
external: ExternalMetricSource;
|
|
||||||
pods: PodsMetricSource;
|
|
||||||
containerResource: ContainerResourceMetricSource;
|
containerResource: ContainerResourceMetricSource;
|
||||||
|
external: ExternalMetricSource;
|
||||||
|
object: ObjectMetricSource;
|
||||||
|
pods: PodsMetricSource;
|
||||||
|
resource: ResourceMetricSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HorizontalPodAutoscalerMetricSpec =
|
export type HorizontalPodAutoscalerMetricSpec =
|
||||||
@ -72,6 +72,55 @@ export type HorizontalPodAutoscalerMetricSpec =
|
|||||||
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
|
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
|
||||||
| OptionVarient<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">;
|
| OptionVarient<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">;
|
||||||
|
|
||||||
|
export interface ContainerResourceMetricStatus {
|
||||||
|
container: string;
|
||||||
|
currentAverageUtilization?: number;
|
||||||
|
currentAverageValue: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalMetricStatus {
|
||||||
|
currentAverageValue?: string;
|
||||||
|
currentValue: string;
|
||||||
|
metricName: string;
|
||||||
|
metricSelector?: LabelSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectMetricStatus {
|
||||||
|
averageValue?: string;
|
||||||
|
currentValue?: string;
|
||||||
|
metricName: string;
|
||||||
|
selector?: LabelSelector;
|
||||||
|
target: CrossVersionObjectReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PodsMetricStatus {
|
||||||
|
currentAverageValue: string;
|
||||||
|
metricName: string;
|
||||||
|
selector?: LabelSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceMetricStatus {
|
||||||
|
currentAverageUtilization?: number;
|
||||||
|
currentAverageValue: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseHorizontalPodAutoscalerMetricStatus {
|
||||||
|
containerResource: ContainerResourceMetricStatus;
|
||||||
|
external: ExternalMetricStatus;
|
||||||
|
object: ObjectMetricStatus;
|
||||||
|
pods: PodsMetricStatus;
|
||||||
|
resource: ResourceMetricStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HorizontalPodAutoscalerMetricStatus =
|
||||||
|
| OptionVarient<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricStatus, "resource">
|
||||||
|
| OptionVarient<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricStatus, "external">
|
||||||
|
| OptionVarient<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricStatus, "object">
|
||||||
|
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricStatus, "pods">
|
||||||
|
| OptionVarient<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricStatus, "containerResource">;
|
||||||
|
|
||||||
export interface CrossVersionObjectReference {
|
export interface CrossVersionObjectReference {
|
||||||
kind: string;
|
kind: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -89,7 +138,7 @@ export interface HorizontalPodAutoscalerStatus {
|
|||||||
conditions?: BaseKubeObjectCondition[];
|
conditions?: BaseKubeObjectCondition[];
|
||||||
currentReplicas: number;
|
currentReplicas: number;
|
||||||
desiredReplicas: number;
|
desiredReplicas: number;
|
||||||
currentMetrics: HorizontalPodAutoscalerMetricSpec[];
|
currentMetrics?: HorizontalPodAutoscalerMetricStatus[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetricCurrentTarget {
|
interface MetricCurrentTarget {
|
||||||
@ -142,114 +191,11 @@ export class HorizontalPodAutoscaler extends KubeObject<
|
|||||||
return this.status?.currentMetrics ?? [];
|
return this.status?.currentMetrics ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getMetricName(metric: HorizontalPodAutoscalerMetricSpec): string {
|
|
||||||
switch (metric.type) {
|
|
||||||
case HpaMetricType.Resource:
|
|
||||||
return metric.resource.name;
|
|
||||||
case HpaMetricType.Pods:
|
|
||||||
return metric.pods.metricName;
|
|
||||||
case HpaMetricType.Object:
|
|
||||||
return metric.object.metricName;
|
|
||||||
case HpaMetricType.External:
|
|
||||||
return metric.external.metricName;
|
|
||||||
case HpaMetricType.ContainerResource:
|
|
||||||
return metric.containerResource.name;
|
|
||||||
default:
|
|
||||||
return `<unknown metric type: ${(metric as HorizontalPodAutoscalerMetricSpec).type}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getResourceMetricValue(currentMetric: ResourceMetricSource | undefined, targetMetric: ResourceMetricSource): MetricCurrentTarget {
|
|
||||||
return {
|
|
||||||
current: (
|
|
||||||
currentMetric?.targetAverageUtilization
|
|
||||||
? `${currentMetric.targetAverageUtilization}%`
|
|
||||||
: currentMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
target: (
|
|
||||||
targetMetric?.targetAverageUtilization
|
|
||||||
? `${targetMetric.targetAverageUtilization}%`
|
|
||||||
: targetMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getPodsMetricValue(currentMetric: PodsMetricSource | undefined, targetMetric: PodsMetricSource): MetricCurrentTarget {
|
|
||||||
return {
|
|
||||||
current: currentMetric?.targetAverageValue,
|
|
||||||
target: targetMetric?.targetAverageValue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getObjectMetricValue(currentMetric: ObjectMetricSource | undefined, targetMetric: ObjectMetricSource): MetricCurrentTarget {
|
|
||||||
return {
|
|
||||||
current: (
|
|
||||||
currentMetric?.targetValue
|
|
||||||
?? currentMetric?.averageValue
|
|
||||||
),
|
|
||||||
target: (
|
|
||||||
targetMetric?.targetValue
|
|
||||||
?? targetMetric?.averageValue
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getExternalMetricValue(currentMetric: ExternalMetricSource | undefined, targetMetric: ExternalMetricSource): MetricCurrentTarget {
|
|
||||||
return {
|
|
||||||
current: (
|
|
||||||
currentMetric?.targetValue
|
|
||||||
?? currentMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
target: (
|
|
||||||
targetMetric?.targetValue
|
|
||||||
?? targetMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getContainerResourceMetricValue(currentMetric: ContainerResourceMetricSource | undefined, targetMetric: ContainerResourceMetricSource): MetricCurrentTarget {
|
|
||||||
return {
|
|
||||||
current: (
|
|
||||||
currentMetric?.targetAverageUtilization
|
|
||||||
? `${currentMetric.targetAverageUtilization}%`
|
|
||||||
: currentMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
target: (
|
|
||||||
targetMetric?.targetAverageUtilization
|
|
||||||
? `${targetMetric.targetAverageUtilization}%`
|
|
||||||
: targetMetric?.targetAverageValue
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getMetricCurrentTarget(metric: HorizontalPodAutoscalerMetricSpec): MetricCurrentTarget {
|
|
||||||
const currentMetric = this.getMetrics()
|
|
||||||
.find(m => (
|
|
||||||
m.type === metric.type
|
|
||||||
&& this.getMetricName(m) === this.getMetricName(metric)
|
|
||||||
));
|
|
||||||
|
|
||||||
switch (metric.type) {
|
|
||||||
case HpaMetricType.Resource:
|
|
||||||
return this.getResourceMetricValue(currentMetric?.resource, metric.resource);
|
|
||||||
case HpaMetricType.Pods:
|
|
||||||
return this.getPodsMetricValue(currentMetric?.pods, metric.pods);
|
|
||||||
case HpaMetricType.Object:
|
|
||||||
return this.getObjectMetricValue(currentMetric?.object, metric.object);
|
|
||||||
case HpaMetricType.External:
|
|
||||||
return this.getExternalMetricValue(currentMetric?.external, metric.external);
|
|
||||||
case HpaMetricType.ContainerResource:
|
|
||||||
return this.getContainerResourceMetricValue(currentMetric?.containerResource, metric.containerResource);
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetricValues(metric: HorizontalPodAutoscalerMetricSpec): string {
|
getMetricValues(metric: HorizontalPodAutoscalerMetricSpec): string {
|
||||||
const {
|
const {
|
||||||
current = "unknown",
|
current = "unknown",
|
||||||
target = "unknown",
|
target = "unknown",
|
||||||
} = this.getMetricCurrentTarget(metric);
|
} = getMetricCurrentTarget(metric, this.getCurrentMetrics());
|
||||||
|
|
||||||
return `${current} / ${target}`;
|
return `${current} / ${target}`;
|
||||||
}
|
}
|
||||||
@ -263,3 +209,105 @@ export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMetricName(metric: HorizontalPodAutoscalerMetricSpec | HorizontalPodAutoscalerMetricStatus): string | undefined {
|
||||||
|
switch (metric.type) {
|
||||||
|
case HpaMetricType.Resource:
|
||||||
|
return metric.resource.name;
|
||||||
|
case HpaMetricType.Pods:
|
||||||
|
return metric.pods.metricName;
|
||||||
|
case HpaMetricType.Object:
|
||||||
|
return metric.object.metricName;
|
||||||
|
case HpaMetricType.External:
|
||||||
|
return metric.external.metricName;
|
||||||
|
case HpaMetricType.ContainerResource:
|
||||||
|
return metric.containerResource.name;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResourceMetricValue(currentMetric: ResourceMetricStatus | undefined, targetMetric: ResourceMetricSource): MetricCurrentTarget {
|
||||||
|
return {
|
||||||
|
current: (
|
||||||
|
typeof currentMetric?.currentAverageUtilization === "number"
|
||||||
|
? `${currentMetric.currentAverageUtilization}%`
|
||||||
|
: currentMetric?.currentAverageValue
|
||||||
|
),
|
||||||
|
target: (
|
||||||
|
typeof targetMetric?.targetAverageUtilization === "number"
|
||||||
|
? `${targetMetric.targetAverageUtilization}%`
|
||||||
|
: targetMetric?.targetAverageValue
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPodsMetricValue(currentMetric: PodsMetricStatus | undefined, targetMetric: PodsMetricSource): MetricCurrentTarget {
|
||||||
|
return {
|
||||||
|
current: currentMetric?.currentAverageValue,
|
||||||
|
target: targetMetric?.targetAverageValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectMetricValue(currentMetric: ObjectMetricStatus | undefined, targetMetric: ObjectMetricSource): MetricCurrentTarget {
|
||||||
|
return {
|
||||||
|
current: (
|
||||||
|
currentMetric?.currentValue
|
||||||
|
?? currentMetric?.averageValue
|
||||||
|
),
|
||||||
|
target: (
|
||||||
|
targetMetric?.targetValue
|
||||||
|
?? targetMetric?.averageValue
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExternalMetricValue(currentMetric: ExternalMetricStatus | undefined, targetMetric: ExternalMetricSource): MetricCurrentTarget {
|
||||||
|
return {
|
||||||
|
current: (
|
||||||
|
currentMetric?.currentValue
|
||||||
|
?? currentMetric?.currentAverageValue
|
||||||
|
),
|
||||||
|
target: (
|
||||||
|
targetMetric?.targetValue
|
||||||
|
?? targetMetric?.targetAverageValue
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContainerResourceMetricValue(currentMetric: ContainerResourceMetricStatus | undefined, targetMetric: ContainerResourceMetricSource): MetricCurrentTarget {
|
||||||
|
return {
|
||||||
|
current: (
|
||||||
|
typeof currentMetric?.currentAverageUtilization === "number"
|
||||||
|
? `${currentMetric.currentAverageUtilization}%`
|
||||||
|
: currentMetric?.currentAverageValue
|
||||||
|
),
|
||||||
|
target: (
|
||||||
|
typeof targetMetric?.targetAverageUtilization === "number"
|
||||||
|
? `${targetMetric.targetAverageUtilization}%`
|
||||||
|
: targetMetric?.targetAverageValue
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMetricCurrentTarget(spec: HorizontalPodAutoscalerMetricSpec, status: HorizontalPodAutoscalerMetricStatus[]): MetricCurrentTarget {
|
||||||
|
const currentMetric = status.find(m => (
|
||||||
|
m.type === spec.type
|
||||||
|
&& getMetricName(m) === getMetricName(spec)
|
||||||
|
));
|
||||||
|
|
||||||
|
switch (spec.type) {
|
||||||
|
case HpaMetricType.Resource:
|
||||||
|
return getResourceMetricValue(currentMetric?.resource, spec.resource);
|
||||||
|
case HpaMetricType.Pods:
|
||||||
|
return getPodsMetricValue(currentMetric?.pods, spec.pods);
|
||||||
|
case HpaMetricType.Object:
|
||||||
|
return getObjectMetricValue(currentMetric?.object, spec.object);
|
||||||
|
case HpaMetricType.External:
|
||||||
|
return getExternalMetricValue(currentMetric?.external, spec.external);
|
||||||
|
case HpaMetricType.ContainerResource:
|
||||||
|
return getContainerResourceMetricValue(currentMetric?.containerResource, spec.containerResource);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { KubeJsonApi } from "./kube-json-api";
|
|||||||
import type { Disposer } from "../utils";
|
import type { Disposer } from "../utils";
|
||||||
import { isDefined, noop, WrappedAbortController } from "../utils";
|
import { isDefined, noop, WrappedAbortController } from "../utils";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
import type AbortController from "abort-controller";
|
|
||||||
import type { AgentOptions } from "https";
|
import type { AgentOptions } from "https";
|
||||||
import { Agent } from "https";
|
import { Agent } from "https";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
@ -29,6 +28,9 @@ import logger from "../logger";
|
|||||||
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
import autoRegistrationEmitterInjectable from "./api-manager/auto-registration-emitter.injectable";
|
import autoRegistrationEmitterInjectable from "./api-manager/auto-registration-emitter.injectable";
|
||||||
|
|
||||||
|
// TODO: upgrade node-fetch once we are starting to use ES modules
|
||||||
|
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options used for creating a `KubeApi`
|
* The options used for creating a `KubeApi`
|
||||||
*/
|
*/
|
||||||
@ -717,7 +719,7 @@ export class KubeApi<
|
|||||||
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
|
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
|
||||||
const watchUrl = this.getWatchUrl(namespace);
|
const watchUrl = this.getWatchUrl(namespace);
|
||||||
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
||||||
signal: abortController.signal,
|
signal: abortController.signal as LegacyAbortSignal,
|
||||||
timeout: 600_000,
|
timeout: 600_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -15,13 +15,15 @@ import { ItemStore } from "../item.store";
|
|||||||
import type { KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "./kube-api";
|
import type { KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "./kube-api";
|
||||||
import { parseKubeApi } from "./kube-api-parse";
|
import { parseKubeApi } from "./kube-api-parse";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
import { entries } from "../utils/objects";
|
import { entries } from "../utils/objects";
|
||||||
|
|
||||||
|
// TODO: upgrade node-fetch once we are starting to use ES modules
|
||||||
|
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
|
||||||
|
|
||||||
export type OnLoadFailure = (error: unknown) => void;
|
export type OnLoadFailure = (error: unknown) => void;
|
||||||
|
|
||||||
export interface KubeObjectStoreLoadingParams {
|
export interface KubeObjectStoreLoadingParams {
|
||||||
@ -477,7 +479,8 @@ export abstract class KubeObjectStore<
|
|||||||
callback,
|
callback,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { signal } = abortController;
|
// TODO: upgrade node-fetch once we are starting to use ES modules
|
||||||
|
const signal = abortController.signal as LegacyAbortSignal;
|
||||||
|
|
||||||
const callback: KubeApiWatchCallback<D> = (data, error) => {
|
const callback: KubeApiWatchCallback<D> = (data, error) => {
|
||||||
if (!this.isLoaded || error?.type === "aborted") return;
|
if (!this.isLoaded || error?.type === "aborted") return;
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export interface GlobalOverride {
|
||||||
|
injectable: Injectable<any, any, any>;
|
||||||
|
overridingInstantiate: any;
|
||||||
|
}
|
||||||
|
|
||||||
export const getGlobalOverride = <T extends Injectable<any, any, any>>(
|
export const getGlobalOverride = <T extends Injectable<any, any, any>>(
|
||||||
injectable: T,
|
injectable: T,
|
||||||
overridingInstantiate: T["instantiate"],
|
overridingInstantiate: T["instantiate"],
|
||||||
|
|||||||
32
src/common/utils/__tests__/union-env-path.test.ts
Normal file
32
src/common/utils/__tests__/union-env-path.test.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import { unionPATHs } from "../union-env-path";
|
||||||
|
|
||||||
|
describe("unionPATHs", () => {
|
||||||
|
it("return the same path if given only one with no double delimiters", () => {
|
||||||
|
expect(unionPATHs(`/bin/bar${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("return equivalent path if given only one with no double delimiters", () => {
|
||||||
|
expect(unionPATHs(`/bin/bar${path.delimiter}${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove duplicate entries, appending non duplicates in order received", () => {
|
||||||
|
expect(unionPATHs(
|
||||||
|
`/bin/bar${path.delimiter}/usr/bin`,
|
||||||
|
`/bin/bar${path.delimiter}/usr/lens/bat`,
|
||||||
|
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove duplicate entries, appending non duplicates in order received, 3", () => {
|
||||||
|
expect(unionPATHs(
|
||||||
|
`/bin/bar${path.delimiter}/usr/bin`,
|
||||||
|
`/bin/bar${path.delimiter}/usr/lens/bat`,
|
||||||
|
`/usr/local/lens${path.delimiter}/usr/bin`,
|
||||||
|
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat${path.delimiter}/usr/local/lens`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -2,10 +2,13 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import AbortController from "abort-controller";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is like an `AbortController` but will also abort if the parent aborts,
|
||||||
|
* but won't make the parent abort if this aborts (single direction)
|
||||||
|
*/
|
||||||
export class WrappedAbortController extends AbortController {
|
export class WrappedAbortController extends AbortController {
|
||||||
constructor(parent?: AbortController) {
|
constructor(parent?: AbortController | undefined) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
parent?.signal.addEventListener("abort", () => {
|
parent?.signal.addEventListener("abort", () => {
|
||||||
|
|||||||
@ -3,18 +3,38 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { TypedRegEx } from "typed-regex";
|
||||||
|
|
||||||
// Helper to convert CPU K8S units to numbers
|
// Helper to convert CPU K8S units to numbers
|
||||||
|
|
||||||
const thousand = 1000;
|
const unitConverters = new Map([
|
||||||
const million = thousand * thousand;
|
["n", 1000 ** -3],
|
||||||
const shortBillion = thousand * million;
|
["u", 1000 ** -2],
|
||||||
|
["m", 1000 ** -1], // milli
|
||||||
|
["", 1000 ** 0], // no units
|
||||||
|
["k", 1000 ** 1],
|
||||||
|
["M", 1000 ** 2],
|
||||||
|
["G", 1000 ** 3],
|
||||||
|
["P", 1000 ** 4],
|
||||||
|
["T", 1000 ** 5],
|
||||||
|
["E", 1000 ** 6],
|
||||||
|
]);
|
||||||
|
|
||||||
export function cpuUnitsToNumber(cpu: string) {
|
const cpuUnitsRegex = TypedRegEx("^(?<digits>[+-]?[0-9.]+(e[-+]?[0-9]+)?)(?<unit>[EinumkKMGTP]*)$");
|
||||||
const cpuNum = parseInt(cpu);
|
|
||||||
|
|
||||||
if (cpu.includes("m")) return cpuNum / thousand;
|
export function cpuUnitsToNumber(value: string) {
|
||||||
if (cpu.includes("u")) return cpuNum / million;
|
const match = cpuUnitsRegex.captures(value);
|
||||||
if (cpu.includes("n")) return cpuNum / shortBillion;
|
|
||||||
|
|
||||||
return parseFloat(cpu);
|
if (!match) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { digits = "", unit } = match;
|
||||||
|
const conversion = unitConverters.get(unit);
|
||||||
|
|
||||||
|
if (conversion === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat(digits) * conversion;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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
|
||||||
* passed. If `failFast` is provided then the promise is also resolved if it has
|
* passed. If `failFast` is provided then the promise is also resolved if it has
|
||||||
|
|||||||
@ -22,7 +22,6 @@ export * from "./hash-set";
|
|||||||
export * from "./n-fircate";
|
export * from "./n-fircate";
|
||||||
export * from "./noop";
|
export * from "./noop";
|
||||||
export * from "./observable-crate/impl";
|
export * from "./observable-crate/impl";
|
||||||
export * from "./openBrowser";
|
|
||||||
export * from "./paths";
|
export * from "./paths";
|
||||||
export * from "./promise-exec";
|
export * from "./promise-exec";
|
||||||
export * from "./readonly";
|
export * from "./readonly";
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||||
|
import openLinkInBrowserInjectable from "./open-link-in-browser.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(openLinkInBrowserInjectable, () => async () => {});
|
||||||
28
src/common/utils/open-link-in-browser.injectable.ts
Normal file
28
src/common/utils/open-link-in-browser.injectable.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { shell } from "electron";
|
||||||
|
|
||||||
|
const allowedProtocols = new Set(["http:", "https:"]);
|
||||||
|
|
||||||
|
export type OpenLinkInBrowser = (url: string) => Promise<void>;
|
||||||
|
|
||||||
|
const openLinkInBrowserInjectable = getInjectable({
|
||||||
|
id: "open-link-in-browser",
|
||||||
|
instantiate: (): OpenLinkInBrowser => (
|
||||||
|
async (url) => {
|
||||||
|
const { protocol } = new URL(url);
|
||||||
|
|
||||||
|
if (!allowedProtocols.has(protocol)) {
|
||||||
|
throw new TypeError("not an http(s) URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
await shell.openExternal(url);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openLinkInBrowserInjectable;
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { shell } from "electron";
|
|
||||||
|
|
||||||
const allowedProtocols = new Set(["http:", "https:"]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a link using the program configured as the default browser
|
|
||||||
* on the target platform. Will reject URLs with a scheme other than
|
|
||||||
* http or https to prevent programs other than the default browser
|
|
||||||
* running.
|
|
||||||
*
|
|
||||||
* @param url The URL to open
|
|
||||||
*/
|
|
||||||
export function openBrowser(url: string): Promise<void> {
|
|
||||||
if (allowedProtocols.has(new URL(url).protocol)) {
|
|
||||||
return shell.openExternal(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(new TypeError("not an http(s) URL"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use openBrowser
|
|
||||||
*/
|
|
||||||
export const openExternal = openBrowser;
|
|
||||||
@ -3,8 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AbortSignal } from "abort-controller";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new promise that will be rejected when the signal rejects.
|
* Creates a new promise that will be rejected when the signal rejects.
|
||||||
*
|
*
|
||||||
|
|||||||
20
src/common/utils/union-env-path.ts
Normal file
20
src/common/utils/union-env-path.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import * as iter from "./iter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join all entires with a PATH env var delimated string together
|
||||||
|
* @param PATHs Any number of PATH env variables
|
||||||
|
*
|
||||||
|
* NOTE: This function does not attempt to handle any sort of escape sequences since after testing
|
||||||
|
* it was found that `zsh` (at least on `macOS`) does not when trying to find programs
|
||||||
|
*/
|
||||||
|
export function unionPATHs(...PATHs: string[]): string {
|
||||||
|
const entries = new Set(iter.filterFlatMap(PATHs, PATH => PATH.split(path.delimiter)));
|
||||||
|
|
||||||
|
return iter.join(entries.values(), path.delimiter);
|
||||||
|
}
|
||||||
@ -3,6 +3,15 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { Singleton, openExternal, openBrowser, getAppVersion } from "../../common/utils";
|
import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable";
|
||||||
|
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||||
|
|
||||||
|
export { Singleton, getAppVersion } from "../../common/utils";
|
||||||
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
|
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
|
||||||
export { cssNames } from "../../renderer/utils/cssNames";
|
export { cssNames } from "../../renderer/utils/cssNames";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link openBrowser} instead
|
||||||
|
*/
|
||||||
|
export const openExternal = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable);
|
||||||
|
export const openBrowser = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable);
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import configurePackages from "./common/configure-packages";
|
|||||||
import { configure } from "mobx";
|
import { configure } from "mobx";
|
||||||
import { setImmediate } from "timers";
|
import { setImmediate } from "timers";
|
||||||
import { TextEncoder, TextDecoder as TextDecoderNode } from "util";
|
import { TextEncoder, TextDecoder as TextDecoderNode } from "util";
|
||||||
|
import glob from "glob";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
// setup default configuration for external npm-packages
|
// setup default configuration for external npm-packages
|
||||||
configurePackages();
|
configurePackages();
|
||||||
@ -45,3 +47,13 @@ global.ResizeObserver = class {
|
|||||||
|
|
||||||
jest.mock("./renderer/components/monaco-editor/monaco-editor");
|
jest.mock("./renderer/components/monaco-editor/monaco-editor");
|
||||||
jest.mock("./renderer/components/tooltip/withTooltip");
|
jest.mock("./renderer/components/tooltip/withTooltip");
|
||||||
|
|
||||||
|
const getInjectables = (environment: "renderer" | "main", filePathGlob: string) =>
|
||||||
|
glob.sync(`./{common,extensions,${environment}}/**/${filePathGlob}`, {
|
||||||
|
cwd: __dirname,
|
||||||
|
}).map(x => path.resolve(__dirname, x));
|
||||||
|
|
||||||
|
(global as any).rendererInjectablePaths = getInjectables("renderer", "*.injectable.{ts,tsx}");
|
||||||
|
(global as any).rendererGlobalOverridePaths = getInjectables("renderer", "*.global-override-for-injectable.{ts,tsx}");
|
||||||
|
(global as any).mainInjectablePaths = getInjectables("main", "*.injectable.{ts,tsx}");
|
||||||
|
(global as any).mainGlobalOverridePaths = getInjectables("main", "*.global-override-for-injectable.{ts,tsx}");
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
||||||
import { afterRootFrameIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/after-root-frame-is-ready-injection-token";
|
|
||||||
import updatingIsEnabledInjectable from "../updating-is-enabled.injectable";
|
import updatingIsEnabledInjectable from "../updating-is-enabled.injectable";
|
||||||
|
import { afterApplicationIsLoadedInjectionToken } from "../../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
|
||||||
|
|
||||||
const startCheckingForUpdatesInjectable = getInjectable({
|
const startCheckingForUpdatesInjectable = getInjectable({
|
||||||
id: "start-checking-for-updates",
|
id: "start-checking-for-updates",
|
||||||
@ -23,7 +23,7 @@ const startCheckingForUpdatesInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
injectionToken: afterRootFrameIsReadyInjectionToken,
|
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default startCheckingForUpdatesInjectable;
|
export default startCheckingForUpdatesInjectable;
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-front-end-injection-token";
|
|
||||||
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
||||||
|
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||||
|
|
||||||
const stopCheckingForUpdatesInjectable = getInjectable({
|
const stopCheckingForUpdatesInjectable = getInjectable({
|
||||||
id: "stop-checking-for-updates",
|
id: "stop-checking-for-updates",
|
||||||
@ -21,7 +21,7 @@ const stopCheckingForUpdatesInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
injectionToken: beforeQuitOfFrontEndInjectionToken,
|
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stopCheckingForUpdatesInjectable;
|
export default stopCheckingForUpdatesInjectable;
|
||||||
|
|||||||
@ -11,9 +11,8 @@ import { watch } from "chokidar";
|
|||||||
import type { Stats } from "fs";
|
import type { Stats } from "fs";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import type stream from "stream";
|
|
||||||
import type { Disposer } from "../../../common/utils";
|
import type { Disposer } from "../../../common/utils";
|
||||||
import { bytesToUnits, getOrInsertWith, iter, noop } from "../../../common/utils";
|
import { disposer, bytesToUnits, getOrInsertWith, iter, noop } from "../../../common/utils";
|
||||||
import logger from "../../logger";
|
import logger from "../../logger";
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
|
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
|
||||||
@ -265,47 +264,35 @@ const diffChangedConfigFor = (dependencies: ComputeDiffDependencies) => ({ fileP
|
|||||||
return noop;
|
return noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with an AbortController with fs.readFile when we upgrade to Node 16 (after it comes out)
|
const controller = new AbortController();
|
||||||
const fileReader = fs.createReadStream(filePath, {
|
const fileContentsP = fs.promises.readFile(filePath, {
|
||||||
mode: fs.constants.O_RDONLY,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
const readStream: stream.Readable = fileReader;
|
const cleanup = disposer(
|
||||||
const decoder = new TextDecoder("utf-8", { fatal: true });
|
() => controller.abort(),
|
||||||
let fileString = "";
|
);
|
||||||
let closed = false;
|
|
||||||
|
|
||||||
const cleanup = () => {
|
fileContentsP
|
||||||
closed = true;
|
.then((fileData) => {
|
||||||
fileReader.close(); // This may not close the stream.
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||||
// Artificially marking end-of-stream, as if the underlying resource had
|
|
||||||
// indicated end-of-file by itself, allows the stream to close.
|
|
||||||
// This does not cancel pending read operations, and if there is such an
|
|
||||||
// operation, the process may still not be able to exit successfully
|
|
||||||
// until it finishes.
|
|
||||||
fileReader.push(null);
|
|
||||||
fileReader.read(0);
|
|
||||||
readStream.removeAllListeners();
|
|
||||||
};
|
|
||||||
|
|
||||||
readStream
|
|
||||||
.on("data", (chunk: Buffer) => {
|
|
||||||
try {
|
try {
|
||||||
fileString += decoder.decode(chunk, { stream: true });
|
const fileString = decoder.decode(fileData);
|
||||||
|
|
||||||
|
computeDiff(dependencies)(fileString, source, filePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(`${logPrefix} skipping ${filePath}: ${error}`);
|
logger.warn(`${logPrefix} skipping ${filePath}: ${error}`);
|
||||||
source.clear();
|
source.clear();
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("close", () => cleanup())
|
.catch(error => {
|
||||||
.on("error", error => {
|
if (controller.signal.aborted) {
|
||||||
cleanup();
|
return;
|
||||||
logger.warn(`${logPrefix} failed to read file: ${error}`, { filePath });
|
|
||||||
})
|
|
||||||
.on("end", () => {
|
|
||||||
if (!closed) {
|
|
||||||
computeDiff(dependencies)(fileString, source, filePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.warn(`${logPrefix} failed to read file: ${error}`, { filePath });
|
||||||
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
return cleanup;
|
return cleanup;
|
||||||
|
|||||||
@ -96,8 +96,6 @@ export class DistributionDetector extends BaseClusterDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getKubernetesVersion() {
|
public async getKubernetesVersion() {
|
||||||
if (this.cluster.version) return this.cluster.version;
|
|
||||||
|
|
||||||
const response = await this.k8sRequest("/version");
|
const response = await this.k8sRequest("/version");
|
||||||
|
|
||||||
return response.gitVersion;
|
return response.gitVersion;
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import glob from "glob";
|
import { kebabCase, noop, chunk } from "lodash/fp";
|
||||||
import { kebabCase, memoize, noop, chunk } from "lodash/fp";
|
|
||||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||||
import { createContainer } from "@ogre-tools/injectable";
|
import { createContainer } from "@ogre-tools/injectable";
|
||||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
@ -101,6 +100,7 @@ import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
|||||||
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
||||||
import type { HotbarStore } from "../common/hotbars/store";
|
import type { HotbarStore } from "../common/hotbars/store";
|
||||||
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
|
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
|
||||||
|
import type { GlobalOverride } from "../common/test-utils/get-global-override";
|
||||||
|
|
||||||
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
||||||
const {
|
const {
|
||||||
@ -113,9 +113,9 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
|
|
||||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||||
|
|
||||||
const filePaths = getInjectableFilePaths();
|
const injectables: Injectable<any, any, any>[] = (global as any).mainInjectablePaths.map(
|
||||||
|
(filePath: string) => require(filePath).default,
|
||||||
const injectables = filePaths.map(filePath => require(filePath).default);
|
);
|
||||||
|
|
||||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||||
di.register(...chunkInjectables);
|
di.register(...chunkInjectables);
|
||||||
@ -124,10 +124,8 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
di.preventSideEffects();
|
di.preventSideEffects();
|
||||||
|
|
||||||
if (doGeneralOverrides) {
|
if (doGeneralOverrides) {
|
||||||
const globalOverrideFilePaths = getGlobalOverridePaths();
|
const globalOverrides: GlobalOverride[] = (global as any).mainGlobalOverridePaths.map(
|
||||||
|
(filePath: string) => require(filePath).default,
|
||||||
const globalOverrides = globalOverrideFilePaths.map(
|
|
||||||
(filePath) => require(filePath).default,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
globalOverrides.forEach(globalOverride => {
|
globalOverrides.forEach(globalOverride => {
|
||||||
@ -215,20 +213,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
return di;
|
return di;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInjectableFilePaths = memoize(() => [
|
|
||||||
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getGlobalOverridePaths = memoize(() =>
|
|
||||||
glob.sync(
|
|
||||||
"../{common,extensions,main}/**/*.global-override-for-injectable.{ts,tsx}",
|
|
||||||
|
|
||||||
{ cwd: __dirname },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: Reorganize code in Runnables to get rid of requirement for override
|
// TODO: Reorganize code in Runnables to get rid of requirement for override
|
||||||
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
||||||
[
|
[
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { docsUrl, productName, supportUrl } from "../../common/vars";
|
import { docsUrl, productName, supportUrl } from "../../common/vars";
|
||||||
import { broadcastMessage } from "../../common/ipc";
|
import { broadcastMessage } from "../../common/ipc";
|
||||||
import { openBrowser } from "../../common/utils";
|
|
||||||
import type { MenuItemConstructorOptions } from "electron";
|
import type { MenuItemConstructorOptions } from "electron";
|
||||||
import { webContents } from "electron";
|
import { webContents } from "electron";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
@ -25,6 +24,7 @@ import applicationWindowInjectable from "../start-main-application/lens-window/a
|
|||||||
import reloadWindowInjectable from "../start-main-application/lens-window/reload-window.injectable";
|
import reloadWindowInjectable from "../start-main-application/lens-window/reload-window.injectable";
|
||||||
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
|
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
|
||||||
import processCheckingForUpdatesInjectable from "../application-update/check-for-updates/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "../application-update/check-for-updates/process-checking-for-updates.injectable";
|
||||||
|
import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable";
|
||||||
|
|
||||||
function ignoreIf(check: boolean, menuItems: MenuItemOpts[]) {
|
function ignoreIf(check: boolean, menuItems: MenuItemOpts[]) {
|
||||||
return check ? [] : menuItems;
|
return check ? [] : menuItems;
|
||||||
@ -54,6 +54,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
|||||||
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
|
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
|
||||||
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||||
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
|
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
|
||||||
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
|
||||||
logger.info(`[MENU]: autoUpdateEnabled=${updatingIsEnabled}`);
|
logger.info(`[MENU]: autoUpdateEnabled=${updatingIsEnabled}`);
|
||||||
|
|
||||||
@ -260,7 +261,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
|||||||
label: "Documentation",
|
label: "Documentation",
|
||||||
id: "documentation",
|
id: "documentation",
|
||||||
click: async () => {
|
click: async () => {
|
||||||
openBrowser(docsUrl).catch((error) => {
|
openLinkInBrowser(docsUrl).catch((error) => {
|
||||||
logger.error("[MENU]: failed to open browser", { error });
|
logger.error("[MENU]: failed to open browser", { error });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -269,7 +270,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
|||||||
label: "Support",
|
label: "Support",
|
||||||
id: "support",
|
id: "support",
|
||||||
click: async () => {
|
click: async () => {
|
||||||
openBrowser(supportUrl).catch((error) => {
|
openLinkInBrowser(supportUrl).catch((error) => {
|
||||||
logger.error("[MENU]: failed to open browser", { error });
|
logger.error("[MENU]: failed to open browser", { error });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import os from "os";
|
|||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { isSnap } from "../common/vars";
|
import { isSnap } from "../common/vars";
|
||||||
|
import { unionPATHs } from "../common/utils/union-env-path";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shellSync loads what would have been the environment if this application was
|
* shellSync loads what would have been the environment if this application was
|
||||||
@ -25,7 +26,8 @@ export async function shellSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isSnap) {
|
if (!isSnap) {
|
||||||
process.env.PATH = env.PATH;
|
// Prefer the synced PATH over the initial one
|
||||||
|
process.env.PATH = unionPATHs(env.PATH ?? "", process.env.PATH ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The spread operator allows joining of objects. The precedence is last to first.
|
// The spread operator allows joining of objects. The precedence is last to first.
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
import applicationWindowStateInjectable from "./application-window-state.injectable";
|
import applicationWindowStateInjectable from "./application-window-state.injectable";
|
||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
import { openBrowser } from "../../../../common/utils";
|
|
||||||
import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable";
|
import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable";
|
||||||
import type { ElectronWindow } from "./create-lens-window.injectable";
|
import type { ElectronWindow } from "./create-lens-window.injectable";
|
||||||
import type { RequireExactlyOne } from "type-fest";
|
import type { RequireExactlyOne } from "type-fest";
|
||||||
|
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
|
||||||
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
|
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
instantiate: (di): CreateElectronWindow => {
|
instantiate: (di): CreateElectronWindow => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const sendToChannelInLensWindow = di.inject(sendToChannelInElectronBrowserWindowInjectable);
|
const sendToChannelInLensWindow = di.inject(sendToChannelInElectronBrowserWindowInjectable);
|
||||||
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
|
||||||
return (configuration) => {
|
return (configuration) => {
|
||||||
const applicationWindowState = di.inject(
|
const applicationWindowState = di.inject(
|
||||||
@ -76,9 +77,7 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
nodeIntegrationInSubFrames: true,
|
nodeIntegrationInSubFrames: true,
|
||||||
webviewTag: true,
|
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
nativeWindowOpen: false,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,20 +87,16 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
.on("focus", () => {
|
.on("focus", () => {
|
||||||
configuration.onFocus?.();
|
configuration.onFocus?.();
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("blur", () => {
|
.on("blur", () => {
|
||||||
configuration.onBlur?.();
|
configuration.onBlur?.();
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("closed", () => {
|
.on("closed", () => {
|
||||||
configuration.onClose();
|
configuration.onClose();
|
||||||
applicationWindowState.unmanage();
|
applicationWindowState.unmanage();
|
||||||
})
|
})
|
||||||
|
|
||||||
.webContents.on("dom-ready", () => {
|
.webContents.on("dom-ready", () => {
|
||||||
configuration.onDomReady?.();
|
configuration.onDomReady?.();
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("did-fail-load", (_event, code, desc) => {
|
.on("did-fail-load", (_event, code, desc) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
`[CREATE-ELECTRON-WINDOW]: Failed to load window "${configuration.id}"`,
|
`[CREATE-ELECTRON-WINDOW]: Failed to load window "${configuration.id}"`,
|
||||||
@ -111,54 +106,13 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("did-finish-load", () => {
|
.on("did-finish-load", () => {
|
||||||
logger.info(
|
logger.info(
|
||||||
`[CREATE-ELECTRON-WINDOW]: Window "${configuration.id}" loaded`,
|
`[CREATE-ELECTRON-WINDOW]: Window "${configuration.id}" loaded`,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("will-attach-webview", (event, webPreferences, params) => {
|
|
||||||
logger.debug(
|
|
||||||
`[CREATE-ELECTRON-WINDOW]: Attaching webview to window "${configuration.id}"`,
|
|
||||||
);
|
|
||||||
// Following is security recommendations because we allow webview tag (webviewTag: true)
|
|
||||||
// suggested by https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation
|
|
||||||
// and https://www.electronjs.org/docs/tutorial/security#10-do-not-use-allowpopups
|
|
||||||
|
|
||||||
if (webPreferences.preload) {
|
|
||||||
logger.warn(
|
|
||||||
"[CREATE-ELECTRON-WINDOW]: Strip away preload scripts of webview",
|
|
||||||
);
|
|
||||||
delete webPreferences.preload;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error some electron version uses webPreferences.preloadURL/webPreferences.preload
|
|
||||||
if (webPreferences.preloadURL) {
|
|
||||||
logger.warn(
|
|
||||||
"[CREATE-ELECTRON-WINDOW]: Strip away preload scripts of webview",
|
|
||||||
);
|
|
||||||
delete webPreferences.preload;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.allowpopups) {
|
|
||||||
logger.warn(
|
|
||||||
"[CREATE-ELECTRON-WINDOW]: We do not allow allowpopups props, stop webview from renderer",
|
|
||||||
);
|
|
||||||
|
|
||||||
// event.preventDefault() will destroy the guest page.
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always disable Node.js integration for all webviews
|
|
||||||
webPreferences.nodeIntegration = false;
|
|
||||||
webPreferences.nativeWindowOpen = false;
|
|
||||||
})
|
|
||||||
|
|
||||||
.setWindowOpenHandler((details) => {
|
.setWindowOpenHandler((details) => {
|
||||||
openBrowser(details.url).catch((error) => {
|
openLinkInBrowser(details.url).catch((error) => {
|
||||||
logger.error("[CREATE-ELECTRON-WINDOW]: failed to open browser", {
|
logger.error("[CREATE-ELECTRON-WINDOW]: failed to open browser", {
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { afterRootFrameIsReadyInjectionToken } from "../../runnable-tokens/after-root-frame-is-ready-injection-token";
|
import { afterApplicationIsLoadedInjectionToken } from "../../runnable-tokens/after-application-is-loaded-injection-token";
|
||||||
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
import ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
|
import ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
|
||||||
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
||||||
@ -27,7 +27,7 @@ const startKubeConfigSyncInjectable = getInjectable({
|
|||||||
|
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
|
|
||||||
injectionToken: afterRootFrameIsReadyInjectionToken,
|
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default startKubeConfigSyncInjectable;
|
export default startKubeConfigSyncInjectable;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { beforeQuitOfFrontEndInjectionToken } from "../../runnable-tokens/before-quit-of-front-end-injection-token";
|
import { beforeQuitOfBackEndInjectionToken } from "../../runnable-tokens/before-quit-of-back-end-injection-token";
|
||||||
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
||||||
|
|
||||||
const stopKubeConfigSyncInjectable = getInjectable({
|
const stopKubeConfigSyncInjectable = getInjectable({
|
||||||
@ -19,7 +19,7 @@ const stopKubeConfigSyncInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
injectionToken: beforeQuitOfFrontEndInjectionToken,
|
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stopKubeConfigSyncInjectable;
|
export default stopKubeConfigSyncInjectable;
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
import { kebabCase } from "lodash/fp";
|
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
@ -16,7 +15,7 @@ import { withErrorSuppression } from "../../../common/utils/with-error-suppressi
|
|||||||
import type { WithErrorLoggingFor } from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
import type { WithErrorLoggingFor } from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||||
import withErrorLoggingInjectable from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
import withErrorLoggingInjectable from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||||
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
|
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
|
||||||
import { isBoolean } from "../../../common/utils";
|
import { isBoolean, isString } from "../../../common/utils";
|
||||||
|
|
||||||
const trayMenuItemRegistratorInjectable = getInjectable({
|
const trayMenuItemRegistratorInjectable = getInjectable({
|
||||||
id: "tray-menu-item-registrator",
|
id: "tray-menu-item-registrator",
|
||||||
@ -38,7 +37,7 @@ export default trayMenuItemRegistratorInjectable;
|
|||||||
|
|
||||||
const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor: WithErrorLoggingFor, getRandomId: () => string) => {
|
const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor: WithErrorLoggingFor, getRandomId: () => string) => {
|
||||||
const _toItemInjectables = (parentId: string | null) => (registration: TrayMenuRegistration): Injectable<TrayMenuItem, TrayMenuItem, void>[] => {
|
const _toItemInjectables = (parentId: string | null) => (registration: TrayMenuRegistration): Injectable<TrayMenuItem, TrayMenuItem, void>[] => {
|
||||||
const trayItemId = registration.id || kebabCase(registration.label || getRandomId());
|
const trayItemId = registration.id || getRandomId();
|
||||||
const id = `${trayItemId}-tray-menu-item-for-extension-${extension.sanitizedExtensionId}`;
|
const id = `${trayItemId}-tray-menu-item-for-extension-${extension.sanitizedExtensionId}`;
|
||||||
|
|
||||||
const parentInjectable = getInjectable({
|
const parentInjectable = getInjectable({
|
||||||
@ -51,7 +50,18 @@ const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor:
|
|||||||
|
|
||||||
separator: registration.type === "separator",
|
separator: registration.type === "separator",
|
||||||
|
|
||||||
label: computed(() => registration.label || ""),
|
label: computed(() => {
|
||||||
|
if (!registration.label) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isString(registration.label)) {
|
||||||
|
return registration.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return registration.label.get();
|
||||||
|
}),
|
||||||
|
|
||||||
tooltip: registration.toolTip,
|
tooltip: registration.toolTip,
|
||||||
|
|
||||||
click: () => {
|
click: () => {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
|
|
||||||
export interface TrayMenuRegistration {
|
export interface TrayMenuRegistration {
|
||||||
label?: string;
|
label?: string | IComputedValue<string>;
|
||||||
click?: (menuItem: TrayMenuRegistration) => void;
|
click?: (menuItem: TrayMenuRegistration) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
type?: "normal" | "separator" | "submenu";
|
type?: "normal" | "separator" | "submenu";
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import shellEnvironment from "shell-env";
|
import shellEnvironment from "shell-env";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
|
||||||
export type EnvironmentVariables = Record<string, string>;
|
export type EnvironmentVariables = Partial<Record<string, string>>;
|
||||||
|
|
||||||
let shellSyncFailed = false;
|
let shellSyncFailed = false;
|
||||||
|
|
||||||
|
|||||||
@ -63,27 +63,13 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
|
|||||||
case HpaMetricType.Resource: {
|
case HpaMetricType.Resource: {
|
||||||
const metricSpec = metric.resource ?? metric.containerResource;
|
const metricSpec = metric.resource ?? metric.containerResource;
|
||||||
const addition = metricSpec.targetAverageUtilization
|
const addition = metricSpec.targetAverageUtilization
|
||||||
? "(as a percentage of request)"
|
? " (as a percentage of request)"
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return (
|
return `Resource ${metricSpec.name} on Pods${addition}`;
|
||||||
<>
|
|
||||||
Resource
|
|
||||||
{metricSpec.name}
|
|
||||||
{" "}
|
|
||||||
on Pods
|
|
||||||
{addition}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case HpaMetricType.Pods:
|
case HpaMetricType.Pods:
|
||||||
return (
|
return `${metric.pods.metricName} on Pods`;
|
||||||
<>
|
|
||||||
{metric.pods.metricName}
|
|
||||||
{" "}
|
|
||||||
on Pods
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case HpaMetricType.Object: {
|
case HpaMetricType.Object: {
|
||||||
return (
|
return (
|
||||||
@ -95,15 +81,7 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
case HpaMetricType.External:
|
case HpaMetricType.External:
|
||||||
return (
|
return `${metric.external.metricName} on ${JSON.stringify(metric.external.metricSelector)}`;
|
||||||
<>
|
|
||||||
{metric.external.metricName}
|
|
||||||
{" "}
|
|
||||||
on
|
|
||||||
{" "}
|
|
||||||
{JSON.stringify(metric.external.metricSelector)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import logger from "../../../common/logger";
|
|||||||
export interface ResourceQuotaDetailsProps extends KubeObjectDetailsProps<ResourceQuota> {
|
export interface ResourceQuotaDetailsProps extends KubeObjectDetailsProps<ResourceQuota> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformUnit(name: string, value: string): number {
|
function transformUnit(name: string, value: string): number | undefined {
|
||||||
if (name.includes("memory") || name.includes("storage")) {
|
if (name.includes("memory") || name.includes("storage")) {
|
||||||
return unitsToBytes(value);
|
return unitsToBytes(value);
|
||||||
}
|
}
|
||||||
@ -36,23 +36,38 @@ function renderQuotas(quota: ResourceQuota): JSX.Element[] {
|
|||||||
|
|
||||||
return object.entries(hard)
|
return object.entries(hard)
|
||||||
.filter(hasDefinedTupleValue)
|
.filter(hasDefinedTupleValue)
|
||||||
.map(([name, value]) => {
|
.map(([name, rawMax]) => {
|
||||||
const current = transformUnit(name, value);
|
const rawCurrent = used[name] ?? "0";
|
||||||
const max = transformUnit(name, value);
|
const current = transformUnit(name, rawCurrent);
|
||||||
const usage = max === 0 ? 100 : Math.ceil(current / max * 100); // special case 0 max as always 100% usage
|
const max = transformUnit(name, rawMax);
|
||||||
|
|
||||||
|
if (current === undefined || max === undefined) {
|
||||||
|
return (
|
||||||
|
<div key={name} className={cssNames("param", kebabCase(name))}>
|
||||||
|
<span className="title">{name}</span>
|
||||||
|
<span className="value">
|
||||||
|
{`${rawCurrent} / ${rawMax}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = max === 0
|
||||||
|
? 100 // special case 0 max as always 100% usage
|
||||||
|
: current / max * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={name} className={cssNames("param", kebabCase(name))}>
|
<div key={name} className={cssNames("param", kebabCase(name))}>
|
||||||
<span className="title">{name}</span>
|
<span className="title">{name}</span>
|
||||||
<span className="value">
|
<span className="value">
|
||||||
{`${used[name]} / ${value}`}
|
{`${rawCurrent} / ${rawMax}`}
|
||||||
</span>
|
</span>
|
||||||
<LineProgress
|
<LineProgress
|
||||||
max={max}
|
max={max}
|
||||||
value={current}
|
value={current}
|
||||||
tooltip={(
|
tooltip={(
|
||||||
<p>
|
<p>
|
||||||
{`Set: ${value}. Usage: ${usage}%`}
|
{`Set: ${rawMax}. Usage: ${+usage.toFixed(2)}%`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { autoBind, cssNames } from "../../utils";
|
import { autoBind, cssNames } from "../../utils";
|
||||||
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
|
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
|
||||||
import { openPortForward } from "../../port-forward";
|
|
||||||
import type { MenuActionsProps } from "../menu/menu-actions";
|
import type { MenuActionsProps } from "../menu/menu-actions";
|
||||||
import { MenuActions } from "../menu/menu-actions";
|
import { MenuActions } from "../menu/menu-actions";
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
@ -15,6 +14,8 @@ import { Notifications } from "../notifications";
|
|||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import portForwardDialogModelInjectable from "../../port-forward/port-forward-dialog-model/port-forward-dialog-model.injectable";
|
import portForwardDialogModelInjectable from "../../port-forward/port-forward-dialog-model/port-forward-dialog-model.injectable";
|
||||||
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
||||||
|
import type { OpenPortForward } from "../../port-forward/open-port-forward.injectable";
|
||||||
|
import openPortForwardInjectable from "../../port-forward/open-port-forward.injectable";
|
||||||
|
|
||||||
export interface PortForwardMenuProps extends MenuActionsProps {
|
export interface PortForwardMenuProps extends MenuActionsProps {
|
||||||
portForward: PortForwardItem;
|
portForward: PortForwardItem;
|
||||||
@ -24,6 +25,7 @@ export interface PortForwardMenuProps extends MenuActionsProps {
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
portForwardStore: PortForwardStore;
|
portForwardStore: PortForwardStore;
|
||||||
openPortForwardDialog: (item: PortForwardItem) => void;
|
openPortForwardDialog: (item: PortForwardItem) => void;
|
||||||
|
openPortForward: OpenPortForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NonInjectedPortForwardMenu<Props extends PortForwardMenuProps & Dependencies> extends React.Component<Props> {
|
class NonInjectedPortForwardMenu<Props extends PortForwardMenuProps & Dependencies> extends React.Component<Props> {
|
||||||
@ -94,7 +96,7 @@ class NonInjectedPortForwardMenu<Props extends PortForwardMenuProps & Dependenci
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ portForward.status === "Active" && (
|
{ portForward.status === "Active" && (
|
||||||
<MenuItem onClick={() => openPortForward(portForward)}>
|
<MenuItem onClick={() => this.props.openPortForward(portForward)}>
|
||||||
<Icon
|
<Icon
|
||||||
material="open_in_browser"
|
material="open_in_browser"
|
||||||
interactive={toolbar}
|
interactive={toolbar}
|
||||||
@ -139,6 +141,7 @@ export const PortForwardMenu = withInjectables<Dependencies, PortForwardMenuProp
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
portForwardStore: di.inject(portForwardStoreInjectable),
|
portForwardStore: di.inject(portForwardStoreInjectable),
|
||||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||||
|
openPortForward: di.inject(openPortForwardInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { cssNames } from "../../utils";
|
|||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||||
import { openPortForward, predictProtocol } from "../../port-forward";
|
import { predictProtocol } from "../../port-forward";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
||||||
@ -21,6 +21,8 @@ import portForwardDialogModelInjectable from "../../port-forward/port-forward-di
|
|||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable";
|
import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable";
|
||||||
import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable";
|
import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable";
|
||||||
|
import type { OpenPortForward } from "../../port-forward/open-port-forward.injectable";
|
||||||
|
import openPortForwardInjectable from "../../port-forward/open-port-forward.injectable";
|
||||||
|
|
||||||
export interface ServicePortComponentProps {
|
export interface ServicePortComponentProps {
|
||||||
service: Service;
|
service: Service;
|
||||||
@ -32,6 +34,7 @@ interface Dependencies {
|
|||||||
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
||||||
aboutPortForwarding: () => void;
|
aboutPortForwarding: () => void;
|
||||||
notifyErrorPortForwarding: (message: string) => void;
|
notifyErrorPortForwarding: (message: string) => void;
|
||||||
|
openPortForward: OpenPortForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -88,7 +91,7 @@ class NonInjectedServicePortComponent extends React.Component<ServicePortCompone
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { service, port } = this.props;
|
const { service, port, openPortForward } = this.props;
|
||||||
let portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "service",
|
kind: "service",
|
||||||
name: service.getName(),
|
name: service.getName(),
|
||||||
@ -180,7 +183,7 @@ class NonInjectedServicePortComponent extends React.Component<ServicePortCompone
|
|||||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||||
{port.toString()}
|
{port.toString()}
|
||||||
</span>
|
</span>
|
||||||
<Button primary onClick={portForwardAction}>
|
<Button primary onClick={portForwardAction}>
|
||||||
{" "}
|
{" "}
|
||||||
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
||||||
{" "}
|
{" "}
|
||||||
@ -202,6 +205,7 @@ export const ServicePortComponent = withInjectables<Dependencies, ServicePortCom
|
|||||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||||
|
openPortForward: di.inject(openPortForwardInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { workloadInjectionToken } from "../workload-injection-token";
|
import { workloadInjectionToken } from "../workload-injection-token";
|
||||||
import { ResourceNames } from "../../../../utils/rbac";
|
import { ResourceNames } from "../../../../utils/rbac";
|
||||||
import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable";
|
import navigateToCronJobsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable";
|
||||||
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
||||||
import cronJobsStoreInjectable from "../../../+workloads-cronjobs/store.injectable";
|
import cronJobsStoreInjectable from "../../../+workloads-cronjobs/store.injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
@ -14,7 +14,7 @@ const cronJobsWorkloadInjectable = getInjectable({
|
|||||||
id: "cron-jobs-workload",
|
id: "cron-jobs-workload",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const navigate = di.inject(navigateToPodsInjectable);
|
const navigate = di.inject(navigateToCronJobsInjectable);
|
||||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
const store = di.inject(cronJobsStoreInjectable);
|
const store = di.inject(cronJobsStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -5,16 +5,16 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { workloadInjectionToken } from "../workload-injection-token";
|
import { workloadInjectionToken } from "../workload-injection-token";
|
||||||
import { ResourceNames } from "../../../../utils/rbac";
|
import { ResourceNames } from "../../../../utils/rbac";
|
||||||
import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable";
|
|
||||||
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
||||||
import replicasetsStoreInjectable from "../../../+workloads-replicasets/store.injectable";
|
import replicasetsStoreInjectable from "../../../+workloads-replicasets/store.injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
|
import navigateToReplicasetsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable";
|
||||||
|
|
||||||
const replicasetsWorkloadInjectable = getInjectable({
|
const replicasetsWorkloadInjectable = getInjectable({
|
||||||
id: "replicasets-workload",
|
id: "replicasets-workload",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const navigate = di.inject(navigateToPodsInjectable);
|
const navigate = di.inject(navigateToReplicasetsInjectable);
|
||||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
const store = di.inject(replicasetsStoreInjectable);
|
const store = di.inject(replicasetsStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -5,16 +5,16 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { workloadInjectionToken } from "../workload-injection-token";
|
import { workloadInjectionToken } from "../workload-injection-token";
|
||||||
import { ResourceNames } from "../../../../utils/rbac";
|
import { ResourceNames } from "../../../../utils/rbac";
|
||||||
import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable";
|
|
||||||
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
import namespaceStoreInjectable from "../../../+namespaces/store.injectable";
|
||||||
import statefulsetsStoreInjectable from "../../../+workloads-statefulsets/store.injectable";
|
import statefulsetsStoreInjectable from "../../../+workloads-statefulsets/store.injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
|
import navigateToStatefulsetsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable";
|
||||||
|
|
||||||
const statefulsetsWorkloadInjectable = getInjectable({
|
const statefulsetsWorkloadInjectable = getInjectable({
|
||||||
id: "statefulsets-workload",
|
id: "statefulsets-workload",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const navigate = di.inject(navigateToPodsInjectable);
|
const navigate = di.inject(navigateToStatefulsetsInjectable);
|
||||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
const store = di.inject(statefulsetsStoreInjectable);
|
const store = di.inject(statefulsetsStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { cssNames } from "../../utils";
|
|||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||||
import { openPortForward, predictProtocol } from "../../port-forward";
|
import { predictProtocol } from "../../port-forward";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
|
||||||
@ -21,6 +21,8 @@ import portForwardDialogModelInjectable from "../../port-forward/port-forward-di
|
|||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable";
|
import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable";
|
||||||
import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable";
|
import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable";
|
||||||
|
import type { OpenPortForward } from "../../port-forward/open-port-forward.injectable";
|
||||||
|
import openPortForwardInjectable from "../../port-forward/open-port-forward.injectable";
|
||||||
|
|
||||||
export interface PodContainerPortProps {
|
export interface PodContainerPortProps {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -32,6 +34,7 @@ interface Dependencies {
|
|||||||
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
||||||
aboutPortForwarding: () => void;
|
aboutPortForwarding: () => void;
|
||||||
notifyErrorPortForwarding: (message: string) => void;
|
notifyErrorPortForwarding: (message: string) => void;
|
||||||
|
openPortForward: OpenPortForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -86,7 +89,7 @@ class NonInjectedPodContainerPort extends React.Component<PodContainerPortProps
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port, openPortForward } = this.props;
|
||||||
let portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "pod",
|
kind: "pod",
|
||||||
name: pod.getName(),
|
name: pod.getName(),
|
||||||
@ -178,7 +181,7 @@ class NonInjectedPodContainerPort extends React.Component<PodContainerPortProps
|
|||||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
<Button primary onClick={portForwardAction}>
|
<Button primary onClick={portForwardAction}>
|
||||||
{" "}
|
{" "}
|
||||||
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
||||||
{" "}
|
{" "}
|
||||||
@ -200,6 +203,7 @@ export const PodContainerPort = withInjectables<Dependencies, PodContainerPortPr
|
|||||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||||
|
openPortForward: di.inject(openPortForwardInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export class PodStore extends KubeObjectStore<Pod, PodApi> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cpu: total.cpu + cpuUnitsToNumber(cpu),
|
cpu: total.cpu + (cpuUnitsToNumber(cpu) ?? 0),
|
||||||
memory: total.memory + unitsToBytes(memory),
|
memory: total.memory + unitsToBytes(memory),
|
||||||
};
|
};
|
||||||
}, empty);
|
}, empty);
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge:not(.isExpanded) {
|
.badge:not(.scrollable):not(.isExpanded) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export class ClusterFrameHandler {
|
|||||||
(view: LensView) => {
|
(view: LensView) => {
|
||||||
logger.info(`[LENS-VIEW]: cluster id=${clusterId} should now be visible`);
|
logger.info(`[LENS-VIEW]: cluster id=${clusterId} should now be visible`);
|
||||||
view.frame.classList.remove("hidden");
|
view.frame.classList.remove("hidden");
|
||||||
|
view.frame.focus();
|
||||||
ipcRenderer.send(clusterVisibilityHandler, clusterId);
|
ipcRenderer.send(clusterVisibilityHandler, clusterId);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable"
|
|||||||
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
|
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
|
||||||
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
|
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
|
||||||
import themeStoreInjectable from "../../../themes/store.injectable";
|
import themeStoreInjectable from "../../../themes/store.injectable";
|
||||||
|
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
|
||||||
|
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
|
||||||
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
|
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
|
||||||
|
|
||||||
@ -22,6 +24,8 @@ const createTerminalInjectable = getInjectable({
|
|||||||
terminalConfig: di.inject(terminalConfigInjectable),
|
terminalConfig: di.inject(terminalConfigInjectable),
|
||||||
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
|
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
|
||||||
themeStore: di.inject(themeStoreInjectable),
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
|
isMac: di.inject(isMacInjectable),
|
||||||
|
openLinkInBrowser: di.inject(openLinkInBrowserInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (tabId, api) => new Terminal(dependencies, { tabId, api });
|
return (tabId, api) => new Terminal(dependencies, { tabId, api });
|
||||||
|
|||||||
@ -12,19 +12,22 @@ import type { TabId } from "../dock/store";
|
|||||||
import type { TerminalApi } from "../../../api/terminal-api";
|
import type { TerminalApi } from "../../../api/terminal-api";
|
||||||
import type { ThemeStore } from "../../../themes/store";
|
import type { ThemeStore } from "../../../themes/store";
|
||||||
import { disposer } from "../../../utils";
|
import { disposer } from "../../../utils";
|
||||||
import { isMac } from "../../../../common/vars";
|
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import { clipboard } from "electron";
|
import { clipboard } from "electron";
|
||||||
import logger from "../../../../common/logger";
|
import logger from "../../../../common/logger";
|
||||||
import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
|
import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { TerminalChannels } from "../../../../common/terminal/channels";
|
import { TerminalChannels } from "../../../../common/terminal/channels";
|
||||||
|
import { LinkProvider } from "xterm-link-provider";
|
||||||
|
import type { OpenLinkInBrowser } from "../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
|
||||||
export interface TerminalDependencies {
|
export interface TerminalDependencies {
|
||||||
readonly spawningPool: HTMLElement;
|
readonly spawningPool: HTMLElement;
|
||||||
readonly terminalConfig: IComputedValue<TerminalConfig>;
|
readonly terminalConfig: IComputedValue<TerminalConfig>;
|
||||||
readonly terminalCopyOnSelect: IComputedValue<boolean>;
|
readonly terminalCopyOnSelect: IComputedValue<boolean>;
|
||||||
readonly themeStore: ThemeStore;
|
readonly themeStore: ThemeStore;
|
||||||
|
readonly isMac: boolean;
|
||||||
|
openLinkInBrowser: OpenLinkInBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerminalArguments {
|
export interface TerminalArguments {
|
||||||
@ -93,7 +96,6 @@ export class Terminal {
|
|||||||
this.xterm.loadAddon(this.fitAddon);
|
this.xterm.loadAddon(this.fitAddon);
|
||||||
|
|
||||||
this.xterm.open(this.dependencies.spawningPool);
|
this.xterm.open(this.dependencies.spawningPool);
|
||||||
this.xterm.registerLinkMatcher(/https?:\/\/[^\s]+/i, this.onClickLink);
|
|
||||||
this.xterm.attachCustomKeyEventHandler(this.keyHandler);
|
this.xterm.attachCustomKeyEventHandler(this.keyHandler);
|
||||||
this.xterm.onSelectionChange(this.onSelectionChange);
|
this.xterm.onSelectionChange(this.onSelectionChange);
|
||||||
|
|
||||||
@ -108,7 +110,16 @@ export class Terminal {
|
|||||||
this.api.on("data", this.onApiData);
|
this.api.on("data", this.onApiData);
|
||||||
window.addEventListener("resize", this.onResize);
|
window.addEventListener("resize", this.onResize);
|
||||||
|
|
||||||
|
const linkProvider = new LinkProvider(
|
||||||
|
this.xterm,
|
||||||
|
/https?:\/\/[^\s]+/i,
|
||||||
|
(event, link) => this.dependencies.openLinkInBrowser(link),
|
||||||
|
undefined,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
this.disposer.push(
|
this.disposer.push(
|
||||||
|
this.xterm.registerLinkProvider(linkProvider),
|
||||||
reaction(() => this.theme, colors => this.xterm.setOption("theme", colors), {
|
reaction(() => this.theme, colors => this.xterm.setOption("theme", colors), {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}),
|
}),
|
||||||
@ -169,10 +180,6 @@ export class Terminal {
|
|||||||
this.viewport.scrollTop = this.scrollPos; // restore last scroll position
|
this.viewport.scrollTop = this.scrollPos; // restore last scroll position
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickLink = (evt: MouseEvent, link: string) => {
|
|
||||||
window.open(link, "_blank");
|
|
||||||
};
|
|
||||||
|
|
||||||
onContextMenu = () => {
|
onContextMenu = () => {
|
||||||
if (
|
if (
|
||||||
// don't paste if user hasn't turned on the feature
|
// don't paste if user hasn't turned on the feature
|
||||||
@ -229,7 +236,7 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Ctrl+K: clear the entire buffer, making the prompt line the new first line on mac os
|
//Ctrl+K: clear the entire buffer, making the prompt line the new first line on mac os
|
||||||
if (isMac && metaKey) {
|
if (this.dependencies.isMac && metaKey) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case "KeyK":
|
case "KeyK":
|
||||||
this.onClear();
|
this.onClear();
|
||||||
|
|||||||
@ -54,11 +54,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 0;
|
height: 3px;
|
||||||
height: $unit * 0.5;
|
transition: opacity 150ms;
|
||||||
transition: width 150ms;
|
|
||||||
background: currentColor;
|
background: currentColor;
|
||||||
color: var(--halfGray)
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -76,6 +75,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: auto;
|
right: auto;
|
||||||
color: var(--line-color-active);
|
color: var(--line-color-active);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import glob from "glob";
|
import { noop, chunk } from "lodash/fp";
|
||||||
import { memoize, noop, chunk } from "lodash/fp";
|
|
||||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||||
import {
|
import {
|
||||||
createContainer,
|
createContainer,
|
||||||
@ -73,6 +72,7 @@ import forceUpdateModalRootFrameComponentInjectable from "./application-update/f
|
|||||||
import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injectable";
|
import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injectable";
|
||||||
import getEntitySettingCommandsInjectable from "./components/command-palette/registered-commands/get-entity-setting-commands.injectable";
|
import getEntitySettingCommandsInjectable from "./components/command-palette/registered-commands/get-entity-setting-commands.injectable";
|
||||||
import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable";
|
import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable";
|
||||||
|
import type { GlobalOverride } from "../common/test-utils/get-global-override";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||||
const {
|
const {
|
||||||
@ -85,9 +85,9 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
|
|
||||||
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
|
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
|
||||||
|
|
||||||
const filePaths = getInjectableFilePaths();
|
const injectables: Injectable<any, any, any>[] = (global as any).rendererInjectablePaths.map(
|
||||||
|
(filePath: string) => require(filePath).default,
|
||||||
const injectables = filePaths.map(filePath => require(filePath).default);
|
);
|
||||||
|
|
||||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||||
di.register(...chunkInjectables);
|
di.register(...chunkInjectables);
|
||||||
@ -96,10 +96,8 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
di.preventSideEffects();
|
di.preventSideEffects();
|
||||||
|
|
||||||
if (doGeneralOverrides) {
|
if (doGeneralOverrides) {
|
||||||
const globalOverrideFilePaths = getGlobalOverridePaths();
|
const globalOverrides: GlobalOverride[] = (global as any).rendererGlobalOverridePaths.map(
|
||||||
|
(filePath: string) => require(filePath).default,
|
||||||
const globalOverrides = globalOverrideFilePaths.map(
|
|
||||||
(filePath) => require(filePath).default,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
globalOverrides.forEach(globalOverride => {
|
globalOverrides.forEach(globalOverride => {
|
||||||
@ -232,20 +230,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
return di;
|
return di;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInjectableFilePaths = memoize(() => [
|
|
||||||
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getGlobalOverridePaths = memoize(() =>
|
|
||||||
glob.sync(
|
|
||||||
"../{common,extensions,renderer}/**/*.global-override-for-injectable.{ts,tsx}",
|
|
||||||
|
|
||||||
{ cwd: __dirname },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
||||||
injectables.forEach(injectable => {
|
injectables.forEach(injectable => {
|
||||||
di.override(injectable, () => () => {
|
di.override(injectable, () => () => {
|
||||||
|
|||||||
@ -5,11 +5,14 @@
|
|||||||
import { comparer, reaction } from "mobx";
|
import { comparer, reaction } from "mobx";
|
||||||
import type { Disposer } from "../../common/utils";
|
import type { Disposer } from "../../common/utils";
|
||||||
import { disposer, getOrInsert, noop, WrappedAbortController } from "../../common/utils";
|
import { disposer, getOrInsert, noop, WrappedAbortController } from "../../common/utils";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context";
|
import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context";
|
||||||
import logger from "../../common/logger";
|
import logger from "../../common/logger";
|
||||||
import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store";
|
import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store";
|
||||||
|
import type { RequestInit } from "node-fetch";
|
||||||
|
|
||||||
|
// TODO: upgrade node-fetch once we are starting to use ES modules
|
||||||
|
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
|
||||||
|
|
||||||
// Kubernetes watch-api client
|
// Kubernetes watch-api client
|
||||||
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
||||||
@ -103,7 +106,7 @@ export class KubeWatchApi {
|
|||||||
|
|
||||||
const loadThenSubscribe = async (namespaces: string[] | undefined) => {
|
const loadThenSubscribe = async (namespaces: string[] | undefined) => {
|
||||||
try {
|
try {
|
||||||
await store.loadAll({ namespaces, reqInit: { signal: childController.signal }, onLoadFailure });
|
await store.loadAll({ namespaces, reqInit: { signal: childController.signal as LegacyAbortSignal }, onLoadFailure });
|
||||||
unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController }));
|
unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof DOMException)) {
|
if (!(error instanceof DOMException)) {
|
||||||
|
|||||||
38
src/renderer/port-forward/open-port-forward.injectable.ts
Normal file
38
src/renderer/port-forward/open-port-forward.injectable.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable";
|
||||||
|
import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable";
|
||||||
|
import type { ForwardedPort } from "./port-forward-item";
|
||||||
|
import { portForwardAddress } from "./port-forward-utils";
|
||||||
|
|
||||||
|
export type OpenPortForward = (portForward: ForwardedPort) => void;
|
||||||
|
|
||||||
|
const openPortForwardInjectable = getInjectable({
|
||||||
|
id: "open-port-forward",
|
||||||
|
instantiate: (di): OpenPortForward => {
|
||||||
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return (portForward) => {
|
||||||
|
const browseTo = portForwardAddress(portForward);
|
||||||
|
|
||||||
|
openLinkInBrowser(browseTo)
|
||||||
|
.catch(error => {
|
||||||
|
logger.error(`failed to open in browser: ${error}`, {
|
||||||
|
port: portForward.port,
|
||||||
|
kind: portForward.kind,
|
||||||
|
namespace: portForward.namespace,
|
||||||
|
name: portForward.name,
|
||||||
|
});
|
||||||
|
showErrorNotification(`Failed to open ${browseTo} in browser`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openPortForwardInjectable;
|
||||||
@ -14,7 +14,6 @@ import { Wizard, WizardStep } from "../components/wizard";
|
|||||||
import { Input } from "../components/input";
|
import { Input } from "../components/input";
|
||||||
import { cssNames } from "../utils";
|
import { cssNames } from "../utils";
|
||||||
import type { PortForwardStore } from "./port-forward-store/port-forward-store";
|
import type { PortForwardStore } from "./port-forward-store/port-forward-store";
|
||||||
import { openPortForward } from "./port-forward-utils";
|
|
||||||
import { Checkbox } from "../components/checkbox";
|
import { Checkbox } from "../components/checkbox";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import type { PortForwardDialogData, PortForwardDialogModel } from "./port-forward-dialog-model/port-forward-dialog-model";
|
import type { PortForwardDialogData, PortForwardDialogModel } from "./port-forward-dialog-model/port-forward-dialog-model";
|
||||||
@ -23,6 +22,8 @@ import logger from "../../common/logger";
|
|||||||
import portForwardStoreInjectable from "./port-forward-store/port-forward-store.injectable";
|
import portForwardStoreInjectable from "./port-forward-store/port-forward-store.injectable";
|
||||||
import aboutPortForwardingInjectable from "./about-port-forwarding.injectable";
|
import aboutPortForwardingInjectable from "./about-port-forwarding.injectable";
|
||||||
import notifyErrorPortForwardingInjectable from "./notify-error-port-forwarding.injectable";
|
import notifyErrorPortForwardingInjectable from "./notify-error-port-forwarding.injectable";
|
||||||
|
import type { OpenPortForward } from "./open-port-forward.injectable";
|
||||||
|
import openPortForwardInjectable from "./open-port-forward.injectable";
|
||||||
|
|
||||||
export interface PortForwardDialogProps extends Partial<DialogProps> {}
|
export interface PortForwardDialogProps extends Partial<DialogProps> {}
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ interface Dependencies {
|
|||||||
model: PortForwardDialogModel;
|
model: PortForwardDialogModel;
|
||||||
aboutPortForwarding: () => void;
|
aboutPortForwarding: () => void;
|
||||||
notifyErrorPortForwarding: (message: string) => void;
|
notifyErrorPortForwarding: (message: string) => void;
|
||||||
|
openPortForward: OpenPortForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -89,7 +91,7 @@ class NonInjectedPortForwardDialog extends Component<PortForwardDialogProps & De
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (portForward.status === "Active" && data.openInBrowser) {
|
if (portForward.status === "Active" && data.openInBrowser) {
|
||||||
openPortForward(portForward);
|
this.props.openPortForward(portForward);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[PORT-FORWARD-DIALOG]: ${error}`, portForward);
|
logger.error(`[PORT-FORWARD-DIALOG]: ${error}`, portForward);
|
||||||
@ -177,6 +179,7 @@ export const PortForwardDialog = withInjectables<Dependencies, PortForwardDialog
|
|||||||
model: di.inject(portForwardDialogModelInjectable),
|
model: di.inject(portForwardDialogModelInjectable),
|
||||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||||
|
openPortForward: di.inject(openPortForwardInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,34 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { openBrowser } from "../utils";
|
|
||||||
import { Notifications } from "../components/notifications";
|
|
||||||
import type { ForwardedPort } from "./port-forward-item";
|
import type { ForwardedPort } from "./port-forward-item";
|
||||||
import logger from "../../common/logger";
|
|
||||||
|
|
||||||
export function portForwardAddress(portForward: ForwardedPort) {
|
export function portForwardAddress(portForward: ForwardedPort) {
|
||||||
return `${portForward.protocol ?? "http"}://localhost:${portForward.forwardPort}`;
|
return `${portForward.protocol ?? "http"}://localhost:${portForward.forwardPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openPortForward(portForward: ForwardedPort) {
|
|
||||||
const browseTo = portForwardAddress(portForward);
|
|
||||||
|
|
||||||
openBrowser(browseTo)
|
|
||||||
.catch(error => {
|
|
||||||
logger.error(`failed to open in browser: ${error}`, {
|
|
||||||
port: portForward.port,
|
|
||||||
kind: portForward.kind,
|
|
||||||
namespace: portForward.namespace,
|
|
||||||
name: portForward.name,
|
|
||||||
});
|
|
||||||
Notifications.error(`Failed to open ${browseTo} in browser`);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function predictProtocol(name: string | undefined) {
|
export function predictProtocol(name: string | undefined) {
|
||||||
return name === "https" ? "https" : "http";
|
return name === "https" ? "https" : "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user