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
|
||||
run: |
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
|
||||
- name: Verify that the markdown is valid
|
||||
run: |
|
||||
|
||||
2
.github/workflows/electronegativity.yml
vendored
2
.github/workflows/electronegativity.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
- uses: doyensec/electronegativity-action@v1.1
|
||||
with:
|
||||
input: src/
|
||||
electron-version: "15.5.7"
|
||||
electron-version: "19.0.4"
|
||||
severity: medium
|
||||
|
||||
- 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
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: '${{ github.event.inputs.version }}'
|
||||
ref: "${{ github.event.inputs.version }}"
|
||||
|
||||
- name: Using Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
@ -43,8 +43,8 @@ jobs:
|
||||
- name: Checkout master branch from lens
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'master'
|
||||
ref: 'master'
|
||||
path: "master"
|
||||
ref: "master"
|
||||
|
||||
- name: Bring in latest mkdocs.yml from master
|
||||
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
|
||||
run: |
|
||||
make build-npm
|
||||
make build-npm
|
||||
|
||||
- name: publish new release
|
||||
if: contains(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
make publish-npm
|
||||
make publish-npm
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
4
.yarnrc
4
.yarnrc
@ -1,3 +1,3 @@
|
||||
disturl "https://atom.io/download/electron"
|
||||
target "15.5.0"
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "19.0.4"
|
||||
runtime "electron"
|
||||
|
||||
@ -13,7 +13,6 @@ import { promisify } from "util";
|
||||
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||
import type { SingleBar } from "cli-progress";
|
||||
import { MultiBar } from "cli-progress";
|
||||
import AbortController from "abort-controller";
|
||||
import { extract } from "tar-stream";
|
||||
import gunzip from "gunzip-maybe";
|
||||
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
||||
|
||||
@ -18,7 +18,7 @@ exports.default = async function notarizing(context) {
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
appBundleId: "io.kontena.lens-app",
|
||||
appBundleId: process.env.APPBUNDLEID || "io.kontena.lens-app",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
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",
|
||||
"homepage": "https://github.com/lensapp/lens-extension-samples",
|
||||
"engines": {
|
||||
"node": "^14.18.12",
|
||||
"node": "^16.14.2",
|
||||
"lens": "5.4"
|
||||
},
|
||||
"main": "dist/main.js",
|
||||
@ -72,7 +72,7 @@ Some of the most-important fields include:
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.5.5",
|
||||
"@types/react": "^17.0.44",
|
||||
"@types/node": "^14.18.12",
|
||||
"@types/node": "^16.14.2",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
|
||||
44
package.json
44
package.json
@ -12,6 +12,10 @@
|
||||
"email": "info@k8slens.dev"
|
||||
},
|
||||
"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-build": "concurrently yarn:compile:*",
|
||||
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
||||
@ -210,16 +214,15 @@
|
||||
"@kubernetes/client-node": "^0.17.0",
|
||||
"@material-ui/styles": "^4.11.5",
|
||||
"@ogre-tools/fp": "9.0.1",
|
||||
"@ogre-tools/injectable": "9.0.1",
|
||||
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.1",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "9.0.1",
|
||||
"@ogre-tools/injectable-react": "9.0.1",
|
||||
"@ogre-tools/injectable": "9.0.2",
|
||||
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.2",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "9.0.2",
|
||||
"@ogre-tools/injectable-react": "9.0.2",
|
||||
"@sentry/electron": "^3.0.7",
|
||||
"@sentry/integrations": "^6.19.3",
|
||||
"@side/jest-runtime": "^1.0.1",
|
||||
"@tanstack/react-table": "^8.5.5",
|
||||
"@types/circular-dependency-plugin": "5.0.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"auto-bind": "^4.0.0",
|
||||
"await-lock": "^2.2.2",
|
||||
"byline": "^5.0.0",
|
||||
@ -249,7 +252,7 @@
|
||||
"mobx-observable-history": "^2.0.3",
|
||||
"mobx-react": "^7.5.2",
|
||||
"mobx-utils": "^6.0.4",
|
||||
"mock-fs": "^5.1.2",
|
||||
"mock-fs": "^5.1.4",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"monaco-editor": "^0.29.1",
|
||||
@ -284,7 +287,8 @@
|
||||
"winston": "^3.8.1",
|
||||
"winston-console-format": "^1.0.8",
|
||||
"winston-transport-browserconsole": "^1.0.5",
|
||||
"ws": "^8.8.0"
|
||||
"ws": "^8.8.1",
|
||||
"xterm-link-provider": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@async-fn/jest": "1.6.4",
|
||||
@ -293,7 +297,7 @@
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||
"@sentry/types": "^6.19.7",
|
||||
"@swc/core": "^1.2.218",
|
||||
"@swc/core": "^1.2.223",
|
||||
"@swc/jest": "^0.2.22",
|
||||
"@testing-library/dom": "^7.31.2",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@ -315,7 +319,7 @@
|
||||
"@types/hapi__subtext": "^7.0.0",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/http-proxy": "^1.17.9",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/lodash": "^4.14.181",
|
||||
@ -323,7 +327,7 @@
|
||||
"@types/md5-file": "^4.0.2",
|
||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^16.11.45",
|
||||
"@types/node": "^16.11.47",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/npm": "^2.0.32",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
@ -352,8 +356,9 @@
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@types/webpack-env": "^1.17.0",
|
||||
"@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",
|
||||
"adr": "^1.4.1",
|
||||
"ansi_up": "^5.1.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
@ -364,12 +369,12 @@
|
||||
"css-loader": "^6.7.1",
|
||||
"deepdash": "^5.3.9",
|
||||
"dompurify": "^2.3.10",
|
||||
"electron": "^15.5.7",
|
||||
"electron-builder": "^23.1.0",
|
||||
"electron": "^19.0.4",
|
||||
"electron-builder": "^23.3.3",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild": "^0.14.49",
|
||||
"esbuild": "^0.14.53",
|
||||
"esbuild-loader": "^2.19.0",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
@ -382,7 +387,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"include-media": "^1.4.9",
|
||||
"jest": "^28.1.2",
|
||||
"jest": "^28.1.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
@ -393,7 +398,7 @@
|
||||
"node-gyp": "^8.3.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"playwright": "^1.24.1",
|
||||
"playwright": "^1.24.2",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
@ -402,9 +407,10 @@
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"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",
|
||||
"sass": "^1.53.0",
|
||||
"sass": "^1.54.2",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sharp": "^0.30.7",
|
||||
"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 loggerInjectable from "../../common/logger.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import getRandomIdInjectable from "../../common/utils/get-random-id.injectable";
|
||||
|
||||
describe("clicking tray menu item originating from extension", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
@ -20,6 +21,7 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
mainDi.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
||||
mainDi.override(getRandomIdInjectable, () => () => "some-random-id");
|
||||
});
|
||||
|
||||
await applicationBuilder.render();
|
||||
@ -42,7 +44,7 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
|
||||
it("when item is clicked, triggers the click handler", () => {
|
||||
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();
|
||||
@ -55,13 +57,13 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
});
|
||||
|
||||
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", () => {
|
||||
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),
|
||||
);
|
||||
});
|
||||
@ -72,13 +74,13 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
clickMock.mockImplementation(() => Promise.reject("some-rejection"));
|
||||
|
||||
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", () => {
|
||||
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",
|
||||
);
|
||||
});
|
||||
@ -92,7 +94,7 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
it("does not have the tray menu item from extension", () => {
|
||||
expect(
|
||||
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();
|
||||
});
|
||||
@ -103,7 +105,7 @@ describe("clicking tray menu item originating from extension", () => {
|
||||
|
||||
expect(
|
||||
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();
|
||||
});
|
||||
|
||||
@ -13,6 +13,7 @@ describe("preferences: extension adding tray items", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let someObservableForVisibility: IObservableValue<boolean>;
|
||||
let someObservableForEnabled: IObservableValue<boolean>;
|
||||
let someObservableLabel: IObservableValue<string>;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
@ -25,6 +26,7 @@ describe("preferences: extension adding tray items", () => {
|
||||
|
||||
someObservableForVisibility = observable.box(false);
|
||||
someObservableForEnabled = observable.box(false);
|
||||
someObservableLabel = observable.box("Some label");
|
||||
|
||||
const testExtension = getExtensionFake({
|
||||
id: "some-extension-id",
|
||||
@ -33,38 +35,51 @@ describe("preferences: extension adding tray items", () => {
|
||||
mainOptions: {
|
||||
trayMenus: [
|
||||
{
|
||||
id: "some-controlled-visibility",
|
||||
label: "some-controlled-visibility",
|
||||
click: () => {},
|
||||
visible: computed(() => someObservableForVisibility.get()),
|
||||
},
|
||||
|
||||
{
|
||||
id: "some-uncontrolled-visibility",
|
||||
label: "some-uncontrolled-visibility",
|
||||
click: () => {},
|
||||
},
|
||||
|
||||
{
|
||||
id: "some-controlled-enabled",
|
||||
label: "some-controlled-enabled",
|
||||
click: () => {},
|
||||
enabled: computed(() => someObservableForEnabled.get()),
|
||||
},
|
||||
|
||||
{
|
||||
id: "some-uncontrolled-enabled",
|
||||
label: "some-uncontrolled-enabled",
|
||||
click: () => {},
|
||||
},
|
||||
|
||||
{
|
||||
id: "some-statically-enabled",
|
||||
label: "some-statically-enabled",
|
||||
click: () => {},
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: "some-statically-disabled",
|
||||
label: "some-statically-disabled",
|
||||
click: () => {},
|
||||
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);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
expect(
|
||||
builder.tray.get(
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
import { forRemoteCluster, KubeApi } from "../kube-api";
|
||||
import { KubeJsonApi } from "../kube-json-api";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import AbortController from "abort-controller";
|
||||
import { delay } from "../../utils/delay";
|
||||
import { PassThrough } from "stream";
|
||||
import { ApiManager } from "../api-manager";
|
||||
|
||||
@ -58,11 +58,11 @@ export interface ResourceMetricSource {
|
||||
}
|
||||
|
||||
export interface BaseHorizontalPodAutoscalerMetricSpec {
|
||||
resource: ResourceMetricSource;
|
||||
object: ObjectMetricSource;
|
||||
external: ExternalMetricSource;
|
||||
pods: PodsMetricSource;
|
||||
containerResource: ContainerResourceMetricSource;
|
||||
external: ExternalMetricSource;
|
||||
object: ObjectMetricSource;
|
||||
pods: PodsMetricSource;
|
||||
resource: ResourceMetricSource;
|
||||
}
|
||||
|
||||
export type HorizontalPodAutoscalerMetricSpec =
|
||||
@ -72,6 +72,55 @@ export type HorizontalPodAutoscalerMetricSpec =
|
||||
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
|
||||
| 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 {
|
||||
kind: string;
|
||||
name: string;
|
||||
@ -89,7 +138,7 @@ export interface HorizontalPodAutoscalerStatus {
|
||||
conditions?: BaseKubeObjectCondition[];
|
||||
currentReplicas: number;
|
||||
desiredReplicas: number;
|
||||
currentMetrics: HorizontalPodAutoscalerMetricSpec[];
|
||||
currentMetrics?: HorizontalPodAutoscalerMetricStatus[];
|
||||
}
|
||||
|
||||
interface MetricCurrentTarget {
|
||||
@ -142,114 +191,11 @@ export class HorizontalPodAutoscaler extends KubeObject<
|
||||
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 {
|
||||
const {
|
||||
current = "unknown",
|
||||
target = "unknown",
|
||||
} = this.getMetricCurrentTarget(metric);
|
||||
} = getMetricCurrentTarget(metric, this.getCurrentMetrics());
|
||||
|
||||
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 { isDefined, noop, WrappedAbortController } from "../utils";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import type AbortController from "abort-controller";
|
||||
import type { AgentOptions } from "https";
|
||||
import { Agent } from "https";
|
||||
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 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`
|
||||
*/
|
||||
@ -717,7 +719,7 @@ export class KubeApi<
|
||||
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
|
||||
const watchUrl = this.getWatchUrl(namespace);
|
||||
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
||||
signal: abortController.signal,
|
||||
signal: abortController.signal as LegacyAbortSignal,
|
||||
timeout: 600_000,
|
||||
});
|
||||
|
||||
|
||||
@ -15,13 +15,15 @@ import { ItemStore } from "../item.store";
|
||||
import type { KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "./kube-api";
|
||||
import { parseKubeApi } from "./kube-api-parse";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import AbortController from "abort-controller";
|
||||
import type { Patch } from "rfc6902";
|
||||
import logger from "../logger";
|
||||
import assert from "assert";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
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 interface KubeObjectStoreLoadingParams {
|
||||
@ -477,7 +479,8 @@ export abstract class KubeObjectStore<
|
||||
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) => {
|
||||
if (!this.isLoaded || error?.type === "aborted") return;
|
||||
|
||||
@ -4,6 +4,11 @@
|
||||
*/
|
||||
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>>(
|
||||
injectable: T,
|
||||
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.
|
||||
* 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 {
|
||||
constructor(parent?: AbortController) {
|
||||
constructor(parent?: AbortController | undefined) {
|
||||
super();
|
||||
|
||||
parent?.signal.addEventListener("abort", () => {
|
||||
|
||||
@ -3,18 +3,38 @@
|
||||
* 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
|
||||
|
||||
const thousand = 1000;
|
||||
const million = thousand * thousand;
|
||||
const shortBillion = thousand * million;
|
||||
const unitConverters = new Map([
|
||||
["n", 1000 ** -3],
|
||||
["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 cpuNum = parseInt(cpu);
|
||||
const cpuUnitsRegex = TypedRegEx("^(?<digits>[+-]?[0-9.]+(e[-+]?[0-9]+)?)(?<unit>[EinumkKMGTP]*)$");
|
||||
|
||||
if (cpu.includes("m")) return cpuNum / thousand;
|
||||
if (cpu.includes("u")) return cpuNum / million;
|
||||
if (cpu.includes("n")) return cpuNum / shortBillion;
|
||||
export function cpuUnitsToNumber(value: string) {
|
||||
const match = cpuUnitsRegex.captures(value);
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import type { AbortController } from "abort-controller";
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -22,7 +22,6 @@ export * from "./hash-set";
|
||||
export * from "./n-fircate";
|
||||
export * from "./noop";
|
||||
export * from "./observable-crate/impl";
|
||||
export * from "./openBrowser";
|
||||
export * from "./paths";
|
||||
export * from "./promise-exec";
|
||||
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.
|
||||
*/
|
||||
|
||||
import type { AbortSignal } from "abort-controller";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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 { 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 { setImmediate } from "timers";
|
||||
import { TextEncoder, TextDecoder as TextDecoderNode } from "util";
|
||||
import glob from "glob";
|
||||
import path from "path";
|
||||
|
||||
// setup default configuration for external npm-packages
|
||||
configurePackages();
|
||||
@ -45,3 +47,13 @@ global.ResizeObserver = class {
|
||||
|
||||
jest.mock("./renderer/components/monaco-editor/monaco-editor");
|
||||
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 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 { afterApplicationIsLoadedInjectionToken } from "../../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
|
||||
|
||||
const startCheckingForUpdatesInjectable = getInjectable({
|
||||
id: "start-checking-for-updates",
|
||||
@ -23,7 +23,7 @@ const startCheckingForUpdatesInjectable = getInjectable({
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: afterRootFrameIsReadyInjectionToken,
|
||||
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||
});
|
||||
|
||||
export default startCheckingForUpdatesInjectable;
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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 { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||
|
||||
const stopCheckingForUpdatesInjectable = getInjectable({
|
||||
id: "stop-checking-for-updates",
|
||||
@ -21,7 +21,7 @@ const stopCheckingForUpdatesInjectable = getInjectable({
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfFrontEndInjectionToken,
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
});
|
||||
|
||||
export default stopCheckingForUpdatesInjectable;
|
||||
|
||||
@ -11,9 +11,8 @@ import { watch } from "chokidar";
|
||||
import type { Stats } from "fs";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type stream from "stream";
|
||||
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 type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
|
||||
@ -265,47 +264,35 @@ const diffChangedConfigFor = (dependencies: ComputeDiffDependencies) => ({ fileP
|
||||
return noop;
|
||||
}
|
||||
|
||||
// TODO: replace with an AbortController with fs.readFile when we upgrade to Node 16 (after it comes out)
|
||||
const fileReader = fs.createReadStream(filePath, {
|
||||
mode: fs.constants.O_RDONLY,
|
||||
const controller = new AbortController();
|
||||
const fileContentsP = fs.promises.readFile(filePath, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
const readStream: stream.Readable = fileReader;
|
||||
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||
let fileString = "";
|
||||
let closed = false;
|
||||
const cleanup = disposer(
|
||||
() => controller.abort(),
|
||||
);
|
||||
|
||||
const cleanup = () => {
|
||||
closed = true;
|
||||
fileReader.close(); // This may not close the stream.
|
||||
// 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();
|
||||
};
|
||||
fileContentsP
|
||||
.then((fileData) => {
|
||||
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||
|
||||
readStream
|
||||
.on("data", (chunk: Buffer) => {
|
||||
try {
|
||||
fileString += decoder.decode(chunk, { stream: true });
|
||||
const fileString = decoder.decode(fileData);
|
||||
|
||||
computeDiff(dependencies)(fileString, source, filePath);
|
||||
} catch (error) {
|
||||
logger.warn(`${logPrefix} skipping ${filePath}: ${error}`);
|
||||
source.clear();
|
||||
cleanup();
|
||||
}
|
||||
})
|
||||
.on("close", () => cleanup())
|
||||
.on("error", error => {
|
||||
cleanup();
|
||||
logger.warn(`${logPrefix} failed to read file: ${error}`, { filePath });
|
||||
})
|
||||
.on("end", () => {
|
||||
if (!closed) {
|
||||
computeDiff(dependencies)(fileString, source, filePath);
|
||||
.catch(error => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn(`${logPrefix} failed to read file: ${error}`, { filePath });
|
||||
cleanup();
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
|
||||
@ -96,8 +96,6 @@ export class DistributionDetector extends BaseClusterDetector {
|
||||
}
|
||||
|
||||
public async getKubernetesVersion() {
|
||||
if (this.cluster.version) return this.cluster.version;
|
||||
|
||||
const response = await this.k8sRequest("/version");
|
||||
|
||||
return response.gitVersion;
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import glob from "glob";
|
||||
import { kebabCase, memoize, noop, chunk } from "lodash/fp";
|
||||
import { kebabCase, noop, chunk } from "lodash/fp";
|
||||
import type { DiContainer, Injectable } 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";
|
||||
@ -101,6 +100,7 @@ import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
||||
import type { HotbarStore } from "../common/hotbars/store";
|
||||
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 } = {}) {
|
||||
const {
|
||||
@ -113,9 +113,9 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||
|
||||
const filePaths = getInjectableFilePaths();
|
||||
|
||||
const injectables = filePaths.map(filePath => require(filePath).default);
|
||||
const injectables: Injectable<any, any, any>[] = (global as any).mainInjectablePaths.map(
|
||||
(filePath: string) => require(filePath).default,
|
||||
);
|
||||
|
||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||
di.register(...chunkInjectables);
|
||||
@ -124,10 +124,8 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.preventSideEffects();
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
const globalOverrideFilePaths = getGlobalOverridePaths();
|
||||
|
||||
const globalOverrides = globalOverrideFilePaths.map(
|
||||
(filePath) => require(filePath).default,
|
||||
const globalOverrides: GlobalOverride[] = (global as any).mainGlobalOverridePaths.map(
|
||||
(filePath: string) => require(filePath).default,
|
||||
);
|
||||
|
||||
globalOverrides.forEach(globalOverride => {
|
||||
@ -215,20 +213,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
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
|
||||
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
||||
[
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { docsUrl, productName, supportUrl } from "../../common/vars";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import { openBrowser } from "../../common/utils";
|
||||
import type { MenuItemConstructorOptions } from "electron";
|
||||
import { webContents } from "electron";
|
||||
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 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 openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable";
|
||||
|
||||
function ignoreIf(check: boolean, menuItems: MenuItemOpts[]) {
|
||||
return check ? [] : menuItems;
|
||||
@ -54,6 +54,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
||||
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
|
||||
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
|
||||
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||
|
||||
logger.info(`[MENU]: autoUpdateEnabled=${updatingIsEnabled}`);
|
||||
|
||||
@ -260,7 +261,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
||||
label: "Documentation",
|
||||
id: "documentation",
|
||||
click: async () => {
|
||||
openBrowser(docsUrl).catch((error) => {
|
||||
openLinkInBrowser(docsUrl).catch((error) => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
@ -269,7 +270,7 @@ const applicationMenuItemsInjectable = getInjectable({
|
||||
label: "Support",
|
||||
id: "support",
|
||||
click: async () => {
|
||||
openBrowser(supportUrl).catch((error) => {
|
||||
openLinkInBrowser(supportUrl).catch((error) => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
|
||||
@ -8,6 +8,7 @@ import os from "os";
|
||||
import { app } from "electron";
|
||||
import logger from "./logger";
|
||||
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
|
||||
@ -25,7 +26,8 @@ export async function shellSync() {
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@ -6,10 +6,10 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import applicationWindowStateInjectable from "./application-window-state.injectable";
|
||||
import { BrowserWindow } from "electron";
|
||||
import { openBrowser } from "../../../../common/utils";
|
||||
import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable";
|
||||
import type { ElectronWindow } from "./create-lens-window.injectable";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
|
||||
|
||||
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
|
||||
|
||||
@ -46,6 +46,7 @@ const createElectronWindowInjectable = getInjectable({
|
||||
instantiate: (di): CreateElectronWindow => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const sendToChannelInLensWindow = di.inject(sendToChannelInElectronBrowserWindowInjectable);
|
||||
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||
|
||||
return (configuration) => {
|
||||
const applicationWindowState = di.inject(
|
||||
@ -76,9 +77,7 @@ const createElectronWindowInjectable = getInjectable({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInSubFrames: true,
|
||||
webviewTag: true,
|
||||
contextIsolation: false,
|
||||
nativeWindowOpen: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -88,20 +87,16 @@ const createElectronWindowInjectable = getInjectable({
|
||||
.on("focus", () => {
|
||||
configuration.onFocus?.();
|
||||
})
|
||||
|
||||
.on("blur", () => {
|
||||
configuration.onBlur?.();
|
||||
})
|
||||
|
||||
.on("closed", () => {
|
||||
configuration.onClose();
|
||||
applicationWindowState.unmanage();
|
||||
})
|
||||
|
||||
.webContents.on("dom-ready", () => {
|
||||
configuration.onDomReady?.();
|
||||
})
|
||||
|
||||
.on("did-fail-load", (_event, code, desc) => {
|
||||
logger.error(
|
||||
`[CREATE-ELECTRON-WINDOW]: Failed to load window "${configuration.id}"`,
|
||||
@ -111,54 +106,13 @@ const createElectronWindowInjectable = getInjectable({
|
||||
},
|
||||
);
|
||||
})
|
||||
|
||||
.on("did-finish-load", () => {
|
||||
logger.info(
|
||||
`[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) => {
|
||||
openBrowser(details.url).catch((error) => {
|
||||
openLinkInBrowser(details.url).catch((error) => {
|
||||
logger.error("[CREATE-ELECTRON-WINDOW]: failed to open browser", {
|
||||
error,
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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 ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
|
||||
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
||||
@ -27,7 +27,7 @@ const startKubeConfigSyncInjectable = getInjectable({
|
||||
|
||||
causesSideEffects: true,
|
||||
|
||||
injectionToken: afterRootFrameIsReadyInjectionToken,
|
||||
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||
});
|
||||
|
||||
export default startKubeConfigSyncInjectable;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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";
|
||||
|
||||
const stopKubeConfigSyncInjectable = getInjectable({
|
||||
@ -19,7 +19,7 @@ const stopKubeConfigSyncInjectable = getInjectable({
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfFrontEndInjectionToken,
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
});
|
||||
|
||||
export default stopKubeConfigSyncInjectable;
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { kebabCase } from "lodash/fp";
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
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 withErrorLoggingInjectable from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
|
||||
import { isBoolean } from "../../../common/utils";
|
||||
import { isBoolean, isString } from "../../../common/utils";
|
||||
|
||||
const trayMenuItemRegistratorInjectable = getInjectable({
|
||||
id: "tray-menu-item-registrator",
|
||||
@ -38,7 +37,7 @@ export default trayMenuItemRegistratorInjectable;
|
||||
|
||||
const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor: WithErrorLoggingFor, getRandomId: () => string) => {
|
||||
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 parentInjectable = getInjectable({
|
||||
@ -51,7 +50,18 @@ const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor:
|
||||
|
||||
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,
|
||||
|
||||
click: () => {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import type { IComputedValue } from "mobx";
|
||||
|
||||
export interface TrayMenuRegistration {
|
||||
label?: string;
|
||||
label?: string | IComputedValue<string>;
|
||||
click?: (menuItem: TrayMenuRegistration) => void;
|
||||
id?: string;
|
||||
type?: "normal" | "separator" | "submenu";
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import shellEnvironment from "shell-env";
|
||||
import logger from "../logger";
|
||||
|
||||
export type EnvironmentVariables = Record<string, string>;
|
||||
export type EnvironmentVariables = Partial<Record<string, string>>;
|
||||
|
||||
let shellSyncFailed = false;
|
||||
|
||||
|
||||
@ -63,27 +63,13 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
|
||||
case HpaMetricType.Resource: {
|
||||
const metricSpec = metric.resource ?? metric.containerResource;
|
||||
const addition = metricSpec.targetAverageUtilization
|
||||
? "(as a percentage of request)"
|
||||
? " (as a percentage of request)"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<>
|
||||
Resource
|
||||
{metricSpec.name}
|
||||
{" "}
|
||||
on Pods
|
||||
{addition}
|
||||
</>
|
||||
);
|
||||
return `Resource ${metricSpec.name} on Pods${addition}`;
|
||||
}
|
||||
case HpaMetricType.Pods:
|
||||
return (
|
||||
<>
|
||||
{metric.pods.metricName}
|
||||
{" "}
|
||||
on Pods
|
||||
</>
|
||||
);
|
||||
return `${metric.pods.metricName} on Pods`;
|
||||
|
||||
case HpaMetricType.Object: {
|
||||
return (
|
||||
@ -95,15 +81,7 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
|
||||
);
|
||||
}
|
||||
case HpaMetricType.External:
|
||||
return (
|
||||
<>
|
||||
{metric.external.metricName}
|
||||
{" "}
|
||||
on
|
||||
{" "}
|
||||
{JSON.stringify(metric.external.metricSelector)}
|
||||
</>
|
||||
);
|
||||
return `${metric.external.metricName} on ${JSON.stringify(metric.external.metricSelector)}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import logger from "../../../common/logger";
|
||||
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")) {
|
||||
return unitsToBytes(value);
|
||||
}
|
||||
@ -36,23 +36,38 @@ function renderQuotas(quota: ResourceQuota): JSX.Element[] {
|
||||
|
||||
return object.entries(hard)
|
||||
.filter(hasDefinedTupleValue)
|
||||
.map(([name, value]) => {
|
||||
const current = transformUnit(name, value);
|
||||
const max = transformUnit(name, value);
|
||||
const usage = max === 0 ? 100 : Math.ceil(current / max * 100); // special case 0 max as always 100% usage
|
||||
.map(([name, rawMax]) => {
|
||||
const rawCurrent = used[name] ?? "0";
|
||||
const current = transformUnit(name, rawCurrent);
|
||||
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 (
|
||||
<div key={name} className={cssNames("param", kebabCase(name))}>
|
||||
<span className="title">{name}</span>
|
||||
<span className="value">
|
||||
{`${used[name]} / ${value}`}
|
||||
{`${rawCurrent} / ${rawMax}`}
|
||||
</span>
|
||||
<LineProgress
|
||||
max={max}
|
||||
value={current}
|
||||
tooltip={(
|
||||
<p>
|
||||
{`Set: ${value}. Usage: ${usage}%`}
|
||||
{`Set: ${rawMax}. Usage: ${+usage.toFixed(2)}%`}
|
||||
</p>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
import React from "react";
|
||||
import { autoBind, cssNames } from "../../utils";
|
||||
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
|
||||
import { openPortForward } from "../../port-forward";
|
||||
import type { MenuActionsProps } from "../menu/menu-actions";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
import { MenuItem } from "../menu";
|
||||
@ -15,6 +14,8 @@ import { Notifications } from "../notifications";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
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 type { OpenPortForward } from "../../port-forward/open-port-forward.injectable";
|
||||
import openPortForwardInjectable from "../../port-forward/open-port-forward.injectable";
|
||||
|
||||
export interface PortForwardMenuProps extends MenuActionsProps {
|
||||
portForward: PortForwardItem;
|
||||
@ -24,6 +25,7 @@ export interface PortForwardMenuProps extends MenuActionsProps {
|
||||
interface Dependencies {
|
||||
portForwardStore: PortForwardStore;
|
||||
openPortForwardDialog: (item: PortForwardItem) => void;
|
||||
openPortForward: OpenPortForward;
|
||||
}
|
||||
|
||||
class NonInjectedPortForwardMenu<Props extends PortForwardMenuProps & Dependencies> extends React.Component<Props> {
|
||||
@ -94,7 +96,7 @@ class NonInjectedPortForwardMenu<Props extends PortForwardMenuProps & Dependenci
|
||||
return (
|
||||
<>
|
||||
{ portForward.status === "Active" && (
|
||||
<MenuItem onClick={() => openPortForward(portForward)}>
|
||||
<MenuItem onClick={() => this.props.openPortForward(portForward)}>
|
||||
<Icon
|
||||
material="open_in_browser"
|
||||
interactive={toolbar}
|
||||
@ -139,6 +141,7 @@ export const PortForwardMenu = withInjectables<Dependencies, PortForwardMenuProp
|
||||
getProps: (di, props) => ({
|
||||
portForwardStore: di.inject(portForwardStoreInjectable),
|
||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||
openPortForward: di.inject(openPortForwardInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -13,7 +13,7 @@ import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||
import { openPortForward, predictProtocol } from "../../port-forward";
|
||||
import { predictProtocol } from "../../port-forward";
|
||||
import { Spinner } from "../spinner";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
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 aboutPortForwardingInjectable from "../../port-forward/about-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 {
|
||||
service: Service;
|
||||
@ -32,6 +34,7 @@ interface Dependencies {
|
||||
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
||||
aboutPortForwarding: () => void;
|
||||
notifyErrorPortForwarding: (message: string) => void;
|
||||
openPortForward: OpenPortForward;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -88,7 +91,7 @@ class NonInjectedServicePortComponent extends React.Component<ServicePortCompone
|
||||
|
||||
@action
|
||||
async portForward() {
|
||||
const { service, port } = this.props;
|
||||
const { service, port, openPortForward } = this.props;
|
||||
let portForward: ForwardedPort = {
|
||||
kind: "service",
|
||||
name: service.getName(),
|
||||
@ -202,6 +205,7 @@ export const ServicePortComponent = withInjectables<Dependencies, ServicePortCom
|
||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||
openPortForward: di.inject(openPortForwardInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { workloadInjectionToken } from "../workload-injection-token";
|
||||
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 cronJobsStoreInjectable from "../../../+workloads-cronjobs/store.injectable";
|
||||
import { computed } from "mobx";
|
||||
@ -14,7 +14,7 @@ const cronJobsWorkloadInjectable = getInjectable({
|
||||
id: "cron-jobs-workload",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigate = di.inject(navigateToPodsInjectable);
|
||||
const navigate = di.inject(navigateToCronJobsInjectable);
|
||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||
const store = di.inject(cronJobsStoreInjectable);
|
||||
|
||||
|
||||
@ -5,16 +5,16 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { workloadInjectionToken } from "../workload-injection-token";
|
||||
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 replicasetsStoreInjectable from "../../../+workloads-replicasets/store.injectable";
|
||||
import { computed } from "mobx";
|
||||
import navigateToReplicasetsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable";
|
||||
|
||||
const replicasetsWorkloadInjectable = getInjectable({
|
||||
id: "replicasets-workload",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigate = di.inject(navigateToPodsInjectable);
|
||||
const navigate = di.inject(navigateToReplicasetsInjectable);
|
||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||
const store = di.inject(replicasetsStoreInjectable);
|
||||
|
||||
|
||||
@ -5,16 +5,16 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { workloadInjectionToken } from "../workload-injection-token";
|
||||
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 statefulsetsStoreInjectable from "../../../+workloads-statefulsets/store.injectable";
|
||||
import { computed } from "mobx";
|
||||
import navigateToStatefulsetsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable";
|
||||
|
||||
const statefulsetsWorkloadInjectable = getInjectable({
|
||||
id: "statefulsets-workload",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigate = di.inject(navigateToPodsInjectable);
|
||||
const navigate = di.inject(navigateToStatefulsetsInjectable);
|
||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||
const store = di.inject(statefulsetsStoreInjectable);
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||
import { openPortForward, predictProtocol } from "../../port-forward";
|
||||
import { predictProtocol } from "../../port-forward";
|
||||
import { Spinner } from "../spinner";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
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 aboutPortForwardingInjectable from "../../port-forward/about-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 {
|
||||
pod: Pod;
|
||||
@ -32,6 +34,7 @@ interface Dependencies {
|
||||
openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void;
|
||||
aboutPortForwarding: () => void;
|
||||
notifyErrorPortForwarding: (message: string) => void;
|
||||
openPortForward: OpenPortForward;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -86,7 +89,7 @@ class NonInjectedPodContainerPort extends React.Component<PodContainerPortProps
|
||||
|
||||
@action
|
||||
async portForward() {
|
||||
const { pod, port } = this.props;
|
||||
const { pod, port, openPortForward } = this.props;
|
||||
let portForward: ForwardedPort = {
|
||||
kind: "pod",
|
||||
name: pod.getName(),
|
||||
@ -200,6 +203,7 @@ export const PodContainerPort = withInjectables<Dependencies, PodContainerPortPr
|
||||
openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open,
|
||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||
openPortForward: di.inject(openPortForwardInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -82,7 +82,7 @@ export class PodStore extends KubeObjectStore<Pod, PodApi> {
|
||||
}
|
||||
|
||||
return {
|
||||
cpu: total.cpu + cpuUnitsToNumber(cpu),
|
||||
cpu: total.cpu + (cpuUnitsToNumber(cpu) ?? 0),
|
||||
memory: total.memory + unitsToBytes(memory),
|
||||
};
|
||||
}, empty);
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.badge:not(.isExpanded) {
|
||||
.badge:not(.scrollable):not(.isExpanded) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -115,6 +115,7 @@ export class ClusterFrameHandler {
|
||||
(view: LensView) => {
|
||||
logger.info(`[LENS-VIEW]: cluster id=${clusterId} should now be visible`);
|
||||
view.frame.classList.remove("hidden");
|
||||
view.frame.focus();
|
||||
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 terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.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;
|
||||
|
||||
@ -22,6 +24,8 @@ const createTerminalInjectable = getInjectable({
|
||||
terminalConfig: di.inject(terminalConfigInjectable),
|
||||
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
|
||||
themeStore: di.inject(themeStoreInjectable),
|
||||
isMac: di.inject(isMacInjectable),
|
||||
openLinkInBrowser: di.inject(openLinkInBrowserInjectable),
|
||||
};
|
||||
|
||||
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 { ThemeStore } from "../../../themes/store";
|
||||
import { disposer } from "../../../utils";
|
||||
import { isMac } from "../../../../common/vars";
|
||||
import { once } from "lodash";
|
||||
import { clipboard } from "electron";
|
||||
import logger from "../../../../common/logger";
|
||||
import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
|
||||
import assert from "assert";
|
||||
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 {
|
||||
readonly spawningPool: HTMLElement;
|
||||
readonly terminalConfig: IComputedValue<TerminalConfig>;
|
||||
readonly terminalCopyOnSelect: IComputedValue<boolean>;
|
||||
readonly themeStore: ThemeStore;
|
||||
readonly isMac: boolean;
|
||||
openLinkInBrowser: OpenLinkInBrowser;
|
||||
}
|
||||
|
||||
export interface TerminalArguments {
|
||||
@ -93,7 +96,6 @@ export class Terminal {
|
||||
this.xterm.loadAddon(this.fitAddon);
|
||||
|
||||
this.xterm.open(this.dependencies.spawningPool);
|
||||
this.xterm.registerLinkMatcher(/https?:\/\/[^\s]+/i, this.onClickLink);
|
||||
this.xterm.attachCustomKeyEventHandler(this.keyHandler);
|
||||
this.xterm.onSelectionChange(this.onSelectionChange);
|
||||
|
||||
@ -108,7 +110,16 @@ export class Terminal {
|
||||
this.api.on("data", this.onApiData);
|
||||
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.xterm.registerLinkProvider(linkProvider),
|
||||
reaction(() => this.theme, colors => this.xterm.setOption("theme", colors), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
@ -169,10 +180,6 @@ export class Terminal {
|
||||
this.viewport.scrollTop = this.scrollPos; // restore last scroll position
|
||||
};
|
||||
|
||||
onClickLink = (evt: MouseEvent, link: string) => {
|
||||
window.open(link, "_blank");
|
||||
};
|
||||
|
||||
onContextMenu = () => {
|
||||
if (
|
||||
// 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
|
||||
if (isMac && metaKey) {
|
||||
if (this.dependencies.isMac && metaKey) {
|
||||
switch (code) {
|
||||
case "KeyK":
|
||||
this.onClear();
|
||||
|
||||
@ -54,11 +54,10 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: $unit * 0.5;
|
||||
transition: width 150ms;
|
||||
height: 3px;
|
||||
transition: opacity 150ms;
|
||||
background: currentColor;
|
||||
color: var(--halfGray)
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@ -76,6 +75,7 @@
|
||||
left: 0;
|
||||
right: auto;
|
||||
color: var(--line-color-active);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import glob from "glob";
|
||||
import { memoize, noop, chunk } from "lodash/fp";
|
||||
import { noop, chunk } from "lodash/fp";
|
||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||
import {
|
||||
createContainer,
|
||||
@ -73,6 +72,7 @@ import forceUpdateModalRootFrameComponentInjectable from "./application-update/f
|
||||
import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.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 type { GlobalOverride } from "../common/test-utils/get-global-override";
|
||||
|
||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||
const {
|
||||
@ -85,9 +85,9 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
|
||||
|
||||
const filePaths = getInjectableFilePaths();
|
||||
|
||||
const injectables = filePaths.map(filePath => require(filePath).default);
|
||||
const injectables: Injectable<any, any, any>[] = (global as any).rendererInjectablePaths.map(
|
||||
(filePath: string) => require(filePath).default,
|
||||
);
|
||||
|
||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||
di.register(...chunkInjectables);
|
||||
@ -96,10 +96,8 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.preventSideEffects();
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
const globalOverrideFilePaths = getGlobalOverridePaths();
|
||||
|
||||
const globalOverrides = globalOverrideFilePaths.map(
|
||||
(filePath) => require(filePath).default,
|
||||
const globalOverrides: GlobalOverride[] = (global as any).rendererGlobalOverridePaths.map(
|
||||
(filePath: string) => require(filePath).default,
|
||||
);
|
||||
|
||||
globalOverrides.forEach(globalOverride => {
|
||||
@ -232,20 +230,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
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>[]) => {
|
||||
injectables.forEach(injectable => {
|
||||
di.override(injectable, () => () => {
|
||||
|
||||
@ -5,11 +5,14 @@
|
||||
import { comparer, reaction } from "mobx";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
import { disposer, getOrInsert, noop, WrappedAbortController } from "../../common/utils";
|
||||
import AbortController from "abort-controller";
|
||||
import { once } from "lodash";
|
||||
import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context";
|
||||
import logger from "../../common/logger";
|
||||
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
|
||||
// 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) => {
|
||||
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 }));
|
||||
} catch (error) {
|
||||
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 { cssNames } from "../utils";
|
||||
import type { PortForwardStore } from "./port-forward-store/port-forward-store";
|
||||
import { openPortForward } from "./port-forward-utils";
|
||||
import { Checkbox } from "../components/checkbox";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
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 aboutPortForwardingInjectable from "./about-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> {}
|
||||
|
||||
@ -31,6 +32,7 @@ interface Dependencies {
|
||||
model: PortForwardDialogModel;
|
||||
aboutPortForwarding: () => void;
|
||||
notifyErrorPortForwarding: (message: string) => void;
|
||||
openPortForward: OpenPortForward;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -89,7 +91,7 @@ class NonInjectedPortForwardDialog extends Component<PortForwardDialogProps & De
|
||||
}
|
||||
|
||||
if (portForward.status === "Active" && data.openInBrowser) {
|
||||
openPortForward(portForward);
|
||||
this.props.openPortForward(portForward);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[PORT-FORWARD-DIALOG]: ${error}`, portForward);
|
||||
@ -177,6 +179,7 @@ export const PortForwardDialog = withInjectables<Dependencies, PortForwardDialog
|
||||
model: di.inject(portForwardDialogModelInjectable),
|
||||
aboutPortForwarding: di.inject(aboutPortForwardingInjectable),
|
||||
notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable),
|
||||
openPortForward: di.inject(openPortForwardInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -3,34 +3,12 @@
|
||||
* 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 logger from "../../common/logger";
|
||||
|
||||
export function portForwardAddress(portForward: ForwardedPort) {
|
||||
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) {
|
||||
return name === "https" ? "https" : "http";
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user