From 4ec36a154d70d42e92d639fe0ef50855bf24a2cc Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 1 Feb 2022 01:05:45 -0500 Subject: [PATCH 01/22] select all on helm releases now selects all or none (toggles) (#4742) * select all on helm releases now selects all (or none if all were already selected) Signed-off-by: Jim Ehrismann * Fix isSelectedAll when no items are visibile Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- .../components/+apps-releases/releases.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index 34f9798783..4d38cb5a20 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -109,11 +109,23 @@ class NonInjectedHelmReleases extends Component { item.toggle(); }, - isSelectedAll: () => - releases.get().every((release) => release.isSelected), + isSelectedAll: (visibleItems: RemovableHelmRelease[]) => ( + visibleItems.length > 0 + && visibleItems.every((release) => release.isSelected) + ), - toggleSelectionAll: () => { - releases.get().forEach((release) => release.toggle()); + toggleSelectionAll: (visibleItems: RemovableHelmRelease[]) => { + let selected = false; + + if (!legacyReleaseStore.isSelectedAll(visibleItems)) { + selected = true; + } + + visibleItems.forEach((release) => { + if (release.isSelected !== selected) { + release.toggle(); + } + }); }, isSelected: (item) => item.isSelected, @@ -197,7 +209,7 @@ class NonInjectedHelmReleases extends Component { })} onDetails={this.onDetails} /> - + From dd4aa30b8d38ff37bf1139b60efa8654373335e3 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Feb 2022 07:33:39 -0500 Subject: [PATCH 02/22] Update Node.getRoleLabels() to work with kube 1.16's defaults (#4771) Signed-off-by: Sebastian Malton --- src/common/k8s-api/__tests__/nodes.test.ts | 131 +++++++++++++++++++++ src/common/k8s-api/endpoints/nodes.api.ts | 32 +++-- 2 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 src/common/k8s-api/__tests__/nodes.test.ts diff --git a/src/common/k8s-api/__tests__/nodes.test.ts b/src/common/k8s-api/__tests__/nodes.test.ts new file mode 100644 index 0000000000..4b109a8f96 --- /dev/null +++ b/src/common/k8s-api/__tests__/nodes.test.ts @@ -0,0 +1,131 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { Node } from "../endpoints"; + +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +describe("Nodes tests", () => { + describe("getRoleLabels()", () => { + it("should return empty string if labels is not present", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + }, + }); + + expect(node.getRoleLabels()).toBe(""); + }); + + it("should return empty string if labels is empty object", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: {}, + }, + }); + + expect(node.getRoleLabels()).toBe(""); + }); + + it("should return rest of keys with substring node-role.kubernetes.io/", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: { + "node-role.kubernetes.io/foobar": "bat", + "hellonode-role.kubernetes.io/foobar1": "bat", + }, + }, + }); + + expect(node.getRoleLabels()).toBe("foobar, foobar1"); + }); + + it("should return rest of keys with substring node-role.kubernetes.io/ after last /", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: { + "node-role.kubernetes.io/foobar": "bat", + "hellonode-role.kubernetes.io//////foobar1": "bat", + }, + }, + }); + + expect(node.getRoleLabels()).toBe("foobar, foobar1"); + }); + + it("should return value of label kubernetes.io/role if present", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: { + "kubernetes.io/role": "master", + }, + }, + }); + + expect(node.getRoleLabels()).toBe("master"); + }); + + it("should return value of label node.kubernetes.io/role if present", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: { + "node.kubernetes.io/role": "master", + }, + }, + }); + + expect(node.getRoleLabels()).toBe("master"); + }); + + it("all sources should be joined together", () => { + const node = new Node({ + apiVersion: "foo", + kind: "Node", + metadata: { + name: "bar", + resourceVersion: "1", + uid: "bat", + labels: { + "aksjhdkjahsdnode-role.kubernetes.io/foobar": "bat", + "kubernetes.io/role": "master", + "node.kubernetes.io/role": "master-v2-max", + }, + }, + }); + + expect(node.getRoleLabels()).toBe("foobar, master, master-v2-max"); + }); + }); +}); diff --git a/src/common/k8s-api/endpoints/nodes.api.ts b/src/common/k8s-api/endpoints/nodes.api.ts index 34e1dd0863..afe379f3b1 100644 --- a/src/common/k8s-api/endpoints/nodes.api.ts +++ b/src/common/k8s-api/endpoints/nodes.api.ts @@ -140,6 +140,12 @@ function* getTrueConditionTypes(conditions: IterableIterator | It } } +/** + * This regex is used in the `getRoleLabels()` method bellow, but placed here + * as factoring out regexes is best practice. + */ +const nodeRoleLabelKeyMatcher = /^.*node-role.kubernetes.io\/+(?.+)$/; + export class Node extends KubeObject { static kind = "Node"; static namespaced = false; @@ -165,17 +171,29 @@ export class Node extends KubeObject { return this.spec.taints || []; } - getRoleLabels() { - if (!this.metadata?.labels || typeof this.metadata.labels !== "object") { + getRoleLabels(): string { + const { labels } = this.metadata; + + if (!labels || typeof labels !== "object") { return ""; } - const roleLabels = Object.keys(this.metadata.labels) - .filter(key => key.includes("node-role.kubernetes.io")) - .map(key => key.match(/([^/]+$)/)[0]); // all after last slash + const roleLabels: string[] = []; - if (this.metadata.labels["kubernetes.io/role"] != undefined) { - roleLabels.push(this.metadata.labels["kubernetes.io/role"]); + for (const labelKey of Object.keys(labels)) { + const match = nodeRoleLabelKeyMatcher.exec(labelKey); + + if (match) { + roleLabels.push(match.groups.role); + } + } + + if (typeof labels["kubernetes.io/role"] === "string") { + roleLabels.push(labels["kubernetes.io/role"]); + } + + if (typeof labels["node.kubernetes.io/role"] === "string") { + roleLabels.push(labels["node.kubernetes.io/role"]); } return roleLabels.join(", "); From c1d557aeee34b69cf372a3fab24bdb39f671cb02 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 1 Feb 2022 08:47:03 -0500 Subject: [PATCH 03/22] release v5.4.0-alpha.0 (#4780) Signed-off-by: Jim Ehrismann --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebb8d290e6..64bd8fee19 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.3.0", + "version": "5.4.0-alpha.0", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", From e8205a64b98cf2d6f6fa03c62cf21754b1660a05 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Tue, 1 Feb 2022 15:18:43 +0100 Subject: [PATCH 04/22] Fix bugs in helm releases (#4785) * Fix opening of upgrade release tab Signed-off-by: Janne Savolainen * Bring back ability to rollback to specific revision Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Fix helm releases not updating after rollback Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Fix helm releases not updating after upgrade Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- src/main/helm/helm-release-manager.ts | 5 ++-- src/main/helm/helm-service.ts | 4 +--- src/main/routes/helm-route.ts | 4 ++-- .../update-release.injectable.ts | 23 +++++++++++++++++-- .../components/dock/upgrade-chart/view.tsx | 4 ++-- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 879bfed8ec..9c1d78bcfc 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -179,12 +179,13 @@ export async function getHistory(name: string, namespace: string, kubeconfigPath } export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) { - return JSON.parse(await execHelm([ + await execHelm([ "rollback", name, + `${revision}`, "--namespace", namespace, "--kubeconfig", kubeconfigPath, - ])); + ]); } async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) { diff --git a/src/main/helm/helm-service.ts b/src/main/helm/helm-service.ts index fbb22eb6fc..d3a17d3f63 100644 --- a/src/main/helm/helm-service.ts +++ b/src/main/helm/helm-service.ts @@ -102,9 +102,7 @@ class HelmService { const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); logger.debug("Rollback release"); - const output = rollback(releaseName, namespace, revision, proxyKubeconfig); - - return { message: output }; + await rollback(releaseName, namespace, revision, proxyKubeconfig); } } diff --git a/src/main/routes/helm-route.ts b/src/main/routes/helm-route.ts index 0f0905a675..958f729228 100644 --- a/src/main/routes/helm-route.ts +++ b/src/main/routes/helm-route.ts @@ -71,9 +71,9 @@ export class HelmApiRoute { const { cluster, params, payload, response } = request; try { - const result = await helmService.rollback(cluster, params.release, params.namespace, payload.revision); + await helmService.rollback(cluster, params.release, params.namespace, payload.revision); - respondJson(response, result); + response.end(); } catch (error) { logger.debug(error); respondText(response, error?.toString() || "Error rolling back chart", 422); diff --git a/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts b/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts index 2e5a681f51..06b1949da2 100644 --- a/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts +++ b/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts @@ -3,10 +3,29 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { updateRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import { + IReleaseUpdatePayload, + updateRelease, +} from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import releasesInjectable from "../releases.injectable"; const updateReleaseInjectable = getInjectable({ - instantiate: () => updateRelease, + instantiate: (di) => { + const releases = di.inject(releasesInjectable); + + return async ( + name: string, + namespace: string, + payload: IReleaseUpdatePayload, + ) => { + const result = await updateRelease(name, namespace, payload); + + releases.invalidate(); + + return result; + }; + }, + lifecycle: lifecycleEnum.singleton, }); diff --git a/src/renderer/components/dock/upgrade-chart/view.tsx b/src/renderer/components/dock/upgrade-chart/view.tsx index bff0491610..9e5b0496a4 100644 --- a/src/renderer/components/dock/upgrade-chart/view.tsx +++ b/src/renderer/components/dock/upgrade-chart/view.tsx @@ -136,8 +136,8 @@ export class NonInjectedUpgradeChart extends React.Component; + if (!release || !this.props.upgradeChartTabStore.isReady(tabId) || !version) { + return ; } const currentVersion = release.getVersion(); const controlsAndInfo = ( From 11c78414a0b630061d81bba987ff73646aa19e4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 08:34:48 -0500 Subject: [PATCH 05/22] Bump @types/react-table from 7.7.8 to 7.7.9 (#4530) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 64bd8fee19..e85d38c6d3 100644 --- a/package.json +++ b/package.json @@ -304,7 +304,7 @@ "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.3.2", "@types/react-select": "3.1.2", - "@types/react-table": "^7.7.8", + "@types/react-table": "^7.7.9", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", "@types/readable-stream": "^2.3.12", diff --git a/yarn.lock b/yarn.lock index 226a290633..1d8e0af6f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,10 +1762,10 @@ "@types/react-dom" "*" "@types/react-transition-group" "*" -"@types/react-table@^7.7.8": - version "7.7.8" - resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.8.tgz#b1aa5fb7a54432969262d2306b87fdbb9a5ee647" - integrity sha512-OMhbPlf+uUGte3M1WdArEKeBkyQ1XJxKvFYs+o1dGGGyaSVIqxPPQmBZ6Skkw0V9y0F/kOY7rnTD8r9GbfpBOg== +"@types/react-table@^7.7.9": + version "7.7.9" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.9.tgz#ea82875775fc6ee71a28408dcc039396ae067c92" + integrity sha512-ejP/J20Zlj9VmuLh73YgYkW2xOSFTW39G43rPH93M4mYWdMmqv66lCCr+axZpkdtlNLGjvMG2CwzT4S6abaeGQ== dependencies: "@types/react" "*" From 576135f7233f44a22cd716935e9a56ffb5cef2e8 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 2 Feb 2022 16:46:33 +0300 Subject: [PATCH 06/22] Fix quota entries layout in Add Resouce Quota dialog (#4789) Signed-off-by: Alex Andreev --- .../+config-resource-quotas/add-quota-dialog.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/+config-resource-quotas/add-quota-dialog.scss b/src/renderer/components/+config-resource-quotas/add-quota-dialog.scss index 8d2e6a0ed7..941f1c9a49 100644 --- a/src/renderer/components/+config-resource-quotas/add-quota-dialog.scss +++ b/src/renderer/components/+config-resource-quotas/add-quota-dialog.scss @@ -9,19 +9,23 @@ } .quota-entries { - margin: -$margin * 0.5; + display: flex; + gap: 8px; + margin-left: -1px; + flex-wrap: wrap; &:empty { display: none; } .quota { - --flex-gap: #{$padding}; border: 1px solid var(--halfGray); border-radius: $radius; - margin: $margin * 0.5; padding: $padding * 0.5 $padding; transition: all 150ms ease; + display: flex; + align-items: center; + gap: 8px; &:hover { box-shadow: inset 0 0 0 1px var(--borderColor); From 334ab56f9d3b864a2a5866314a3f76a3ba51b21e Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 2 Feb 2022 16:47:33 +0300 Subject: [PATCH 07/22] Fix dialog icons hover color (#4791) Signed-off-by: Alex Andreev --- src/renderer/components/+catalog/catalog.module.scss | 2 ++ src/renderer/components/icon/icon.scss | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/+catalog/catalog.module.scss b/src/renderer/components/+catalog/catalog.module.scss index 5948d64a62..e285fb3ced 100644 --- a/src/renderer/components/+catalog/catalog.module.scss +++ b/src/renderer/components/+catalog/catalog.module.scss @@ -43,6 +43,8 @@ } .pinIcon { + --color-active: var(--textColorAccent); + transition: none; opacity: 0; margin-left: var(--padding); diff --git a/src/renderer/components/icon/icon.scss b/src/renderer/components/icon/icon.scss index b8d97cc3c8..de8d6fbdf2 100644 --- a/src/renderer/components/icon/icon.scss +++ b/src/renderer/components/icon/icon.scss @@ -111,7 +111,7 @@ } &.active { - color: var(--textColorAccent); + color: var(--color-active); box-shadow: 0 0 0 2px var(--iconActiveBackground); background-color: var(--iconActiveBackground); } From e58d9b631aa1f52d0f49c37314835d7c075dc02a Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Wed, 2 Feb 2022 15:46:07 +0100 Subject: [PATCH 08/22] Fix bugs in ItemListLayouts/KubeObjectListLayouts (#4792) * Fix virtual list behaviour in item list layout by making table rows observer Signed-off-by: Janne Savolainen * Make selecting item in ItemListLayout not scroll to top Signed-off-by: Janne Savolainen * Fix scrolling to top when selecting all items in item list layout Signed-off-by: Janne Savolainen * Fix refreshing release values when release is unselected Signed-off-by: Janne Savolainen * Remove noisy debugging Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Fix sorting of table rows Signed-off-by: Janne Savolainen * Replace avoidable asyncComputed with normal computed Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Revert unnecessary code style changes Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Fix render-storm in Events Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen * Fix missing key in React array Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- .../release-details.injectable.ts | 2 +- .../release-details/release-details.tsx | 6 +- .../release-values.injectable.ts | 8 +- .../release-details/release.injectable.ts | 4 +- src/renderer/components/+events/events.tsx | 4 +- .../components/item-object-list/content.tsx | 140 ++++++++++-------- .../components/kube-detail-params/params.ts | 1 - 7 files changed, 95 insertions(+), 70 deletions(-) diff --git a/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts index 1dac3b716f..c726e0429c 100644 --- a/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts +++ b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts @@ -10,7 +10,7 @@ import releaseInjectable from "./release.injectable"; const releaseDetailsInjectable = getInjectable({ instantiate: (di) => asyncComputed(async () => { - const release = di.inject(releaseInjectable).value.get(); + const release = di.inject(releaseInjectable).get(); return await getRelease(release.name, release.namespace); }), diff --git a/src/renderer/components/+apps-releases/release-details/release-details.tsx b/src/renderer/components/+apps-releases/release-details/release-details.tsx index 30d34e1857..a41d0cae16 100644 --- a/src/renderer/components/+apps-releases/release-details/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details/release-details.tsx @@ -7,7 +7,7 @@ import "./release-details.scss"; import React, { Component } from "react"; import groupBy from "lodash/groupBy"; -import { computed, makeObservable, observable } from "mobx"; +import { computed, IComputedValue, makeObservable, observable } from "mobx"; import { Link } from "react-router-dom"; import kebabCase from "lodash/kebabCase"; import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api"; @@ -39,7 +39,7 @@ interface Props { } interface Dependencies { - release: IAsyncComputed + release: IComputedValue releaseDetails: IAsyncComputed releaseValues: IAsyncComputed updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise @@ -59,7 +59,7 @@ class NonInjectedReleaseDetails extends Component { } @computed get release() { - return this.props.release.value.get(); + return this.props.release.get(); } @computed get details() { diff --git a/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts index d7cc2c965c..965db1324f 100644 --- a/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts +++ b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts @@ -12,7 +12,13 @@ import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-sho const releaseValuesInjectable = getInjectable({ instantiate: (di) => asyncComputed(async () => { - const release = di.inject(releaseInjectable).value.get(); + const release = di.inject(releaseInjectable).get(); + + // TODO: Figure out way to get rid of defensive code + if (!release) { + return ""; + } + const userSuppliedValuesAreShown = di.inject(userSuppliedValuesAreShownInjectable).value; try { diff --git a/src/renderer/components/+apps-releases/release-details/release.injectable.ts b/src/renderer/components/+apps-releases/release-details/release.injectable.ts index 6da2295765..75b821890a 100644 --- a/src/renderer/components/+apps-releases/release-details/release.injectable.ts +++ b/src/renderer/components/+apps-releases/release-details/release.injectable.ts @@ -6,14 +6,14 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { matches } from "lodash/fp"; import releasesInjectable from "../releases.injectable"; import releaseRouteParametersInjectable from "./release-route-parameters.injectable"; -import { asyncComputed } from "@ogre-tools/injectable-react"; +import { computed } from "mobx"; const releaseInjectable = getInjectable({ instantiate: (di) => { const releases = di.inject(releasesInjectable); const releaseRouteParameters = di.inject(releaseRouteParametersInjectable); - return asyncComputed(async () => { + return computed(() => { const { name, namespace } = releaseRouteParameters.get(); if (!name || !namespace) { diff --git a/src/renderer/components/+events/events.tsx b/src/renderer/components/+events/events.tsx index 8b8ae46ccb..033189ea7d 100644 --- a/src/renderer/components/+events/events.tsx +++ b/src/renderer/components/+events/events.tsx @@ -124,7 +124,7 @@ export class Events extends React.Component { }; render() { - const { store, visibleItems } = this; + const { store } = this; const { compact, compactLimit, className, ...layoutProps } = this.props; const events = ( @@ -137,7 +137,7 @@ export class Events extends React.Component { renderHeaderTitle="Events" customizeHeader={this.customizeHeader} isSelectable={false} - items={visibleItems} + getItems={() => this.visibleItems} virtual={!compact} tableProps={{ sortSyncWithUrl: false, diff --git a/src/renderer/components/item-object-list/content.tsx b/src/renderer/components/item-object-list/content.tsx index ccfdddd7d3..773f70d74f 100644 --- a/src/renderer/components/item-object-list/content.tsx +++ b/src/renderer/components/item-object-list/content.tsx @@ -7,7 +7,7 @@ import "./item-list-layout.scss"; import React, { ReactNode } from "react"; import { computed, makeObservable } from "mobx"; -import { observer } from "mobx-react"; +import { Observer, observer } from "mobx-react"; import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog"; import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallbacks } from "../table"; import { boundMethod, cssNames, IClassName, isReactNode, prevDefault, stopPropagation } from "../../utils"; @@ -70,58 +70,68 @@ export class ItemListLayoutContent extends React.Component @boundMethod getRow(uid: string) { - const { - isSelectable, renderTableHeader, renderTableContents, renderItemMenu, - store, hasDetailsView, onDetails, - copyClassNameFromHeadCells, customizeTableRowProps, detailsItem, - } = this.props; - const { isSelected } = store; - const item = this.props.getItems().find(item => item.getId() == uid); - - if (!item) return null; - const itemId = item.getId(); - return ( - onDetails(item)) : undefined} - {...customizeTableRowProps(item)} - > - {isSelectable && ( - store.toggleSelection(item))} - /> - )} - { - renderTableContents(item).map((content, index) => { - const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content; - const headCell = renderTableHeader?.[index]; +
+ + {() => { + const { + isSelectable, renderTableHeader, renderTableContents, renderItemMenu, + store, hasDetailsView, onDetails, + copyClassNameFromHeadCells, customizeTableRowProps, detailsItem, + } = this.props; + const { isSelected } = store; + const item = this.props.getItems().find(item => item.getId() == uid); - if (copyClassNameFromHeadCells && headCell) { - cellProps.className = cssNames(cellProps.className, headCell.className); - } + if (!item) return null; + const itemId = item.getId(); - if (!headCell || this.showColumn(headCell)) { - return ; - } + return ( + onDetails(item)) : undefined} + {...customizeTableRowProps(item)} + > + {isSelectable && ( + store.toggleSelection(item))} + /> + )} + {renderTableContents(item).map((content, index) => { + const cellProps: TableCellProps = isReactNode(content) + ? { children: content } + : content; + const headCell = renderTableHeader?.[index]; - return null; - }) - } - {renderItemMenu && ( - -
- {renderItemMenu(item, store)} -
-
- )} -
+ if (copyClassNameFromHeadCells && headCell) { + cellProps.className = cssNames( + cellProps.className, + headCell.className, + ); + } + + if (!headCell || this.showColumn(headCell)) { + return ; + } + + return null; + })} + {renderItemMenu && ( + +
+ {renderItemMenu(item, store)} +
+
+ )} + + ); + }} +
+
); } @@ -190,11 +200,15 @@ export class ItemListLayoutContent extends React.Component return ( {isSelectable && ( - store.toggleSelectionAll(enabledItems))} - /> + + {() => ( + store.toggleSelectionAll(enabledItems))} + /> + )} + )} {renderTableHeader.map((cellProps, index) => ( this.showColumn(cellProps) && ( @@ -213,7 +227,6 @@ export class ItemListLayoutContent extends React.Component store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem, className, tableProps = {}, tableId, } = this.props; - const { selectedItems } = store; const selectedItemId = detailsItem && detailsItem.getId(); const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type); @@ -234,11 +247,18 @@ export class ItemListLayoutContent extends React.Component {this.renderTableHeader()} {this.renderItems()} - + + + {() => ( + + )} + ); } diff --git a/src/renderer/components/kube-detail-params/params.ts b/src/renderer/components/kube-detail-params/params.ts index 953b666406..9eeb141e3f 100644 --- a/src/renderer/components/kube-detail-params/params.ts +++ b/src/renderer/components/kube-detail-params/params.ts @@ -46,7 +46,6 @@ export function hideDetails() { } export function getDetailsUrl(selfLink: string, resetSelected = false, mergeGlobals = true) { - console.debug("getDetailsUrl", { selfLink, resetSelected, mergeGlobals }); const params = new URLSearchParams(mergeGlobals ? navigation.searchParams : ""); params.set(kubeDetailsUrlParam.name, selfLink); From 534889458501dabf242b2eee6ab584f9c9dccd1b Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Wed, 2 Feb 2022 11:52:13 -0500 Subject: [PATCH 09/22] release v5.4.0-alpha.1 (#4797) Signed-off-by: Jim Ehrismann --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e85d38c6d3..302511f3aa 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.4.0-alpha.0", + "version": "5.4.0-alpha.1", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", From 6ebfd766445c5a3129a793c2909a7f99ca8e404f Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 3 Feb 2022 07:37:21 +0200 Subject: [PATCH 10/22] Use system-node-critical priorityClassName for node shell (#4800) Signed-off-by: Jari Kolehmainen --- src/main/shell-session/node-shell-session/node-shell-session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/shell-session/node-shell-session/node-shell-session.ts b/src/main/shell-session/node-shell-session/node-shell-session.ts index 1fa05a4ae2..8c5861bff5 100644 --- a/src/main/shell-session/node-shell-session/node-shell-session.ts +++ b/src/main/shell-session/node-shell-session/node-shell-session.ts @@ -94,6 +94,7 @@ export class NodeShellSession extends ShellSession { tolerations: [{ operator: "Exists", }], + priorityClassName: "system-node-critical", containers: [{ name: "shell", image: this.cluster.nodeShellImage, From 09824a6e016e9f33ddb2a136e7727f738620c74d Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Thu, 3 Feb 2022 12:26:14 +0100 Subject: [PATCH 11/22] Prevent creation of dynamic components where withInjectables use type parameters to make React not re-mount because of dynamic components being different (#4808) Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- .../kube-object-list-layout.tsx | 30 ++++++++-------- .../kube-object-menu/kube-object-menu.tsx | 34 +++++++++---------- src/renderer/components/table/table.tsx | 22 ++++++------ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index de1253fd65..ec0245c512 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -138,23 +138,25 @@ class NonInjectedKubeObjectListLayout extends React.Compon } } +const InjectedKubeObjectListLayout = withInjectables< + Dependencies, + KubeObjectListLayoutProps +>( + NonInjectedKubeObjectListLayout, + + { + getProps: (di, props) => ({ + clusterFrameContext: di.inject(clusterFrameContextInjectable), + subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, + ...props, + }), + }, +); + + export function KubeObjectListLayout( props: KubeObjectListLayoutProps, ) { - const InjectedKubeObjectListLayout = withInjectables< - Dependencies, - KubeObjectListLayoutProps - >( - NonInjectedKubeObjectListLayout, - - { - getProps: (di, props) => ({ - clusterFrameContext: di.inject(clusterFrameContextInjectable), - subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, - ...props, - }), - }, - ); return ; } diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 6d425ab9c3..83cba3ade7 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -108,25 +108,25 @@ class NonInjectedKubeObjectMenu extends React.Co } } +const InjectedKubeObjectMenu = withInjectables>( + NonInjectedKubeObjectMenu, + { + getProps: (di, props) => ({ + clusterName: di.inject(clusterNameInjectable), + apiManager: di.inject(apiManagerInjectable), + createEditResourceTab: di.inject(createEditResourceTabInjectable), + hideDetails: di.inject(hideDetailsInjectable), + + kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, { + kubeObject: props.object, + }), + ...props, + }), + }, +); + export function KubeObjectMenu( props: KubeObjectMenuProps, ) { - const InjectedKubeObjectMenu = withInjectables>( - NonInjectedKubeObjectMenu, - { - getProps: (di, props) => ({ - clusterName: di.inject(clusterNameInjectable), - apiManager: di.inject(apiManagerInjectable), - createEditResourceTab: di.inject(createEditResourceTabInjectable), - hideDetails: di.inject(hideDetailsInjectable), - - kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, { - kubeObject: props.object, - }), - ...props, - }), - }, - ); - return ; } diff --git a/src/renderer/components/table/table.tsx b/src/renderer/components/table/table.tsx index 4823626094..be9a6f6ac8 100644 --- a/src/renderer/components/table/table.tsx +++ b/src/renderer/components/table/table.tsx @@ -246,18 +246,18 @@ class NonInjectedTable extends React.Component & Dependen } } +const InjectedTable = withInjectables>( + NonInjectedTable, + + { + getProps: (di, props) => ({ + model: di.inject(tableModelInjectable), + ...props, + }), + }, +); + export function Table(props: TableProps) { - const InjectedTable = withInjectables>( - NonInjectedTable, - - { - getProps: (di, props) => ({ - model: di.inject(tableModelInjectable), - ...props, - }), - }, - ); - return ; } From 00314aabc00b2d8da16e8116ac090c076a1b3ee0 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 3 Feb 2022 14:57:55 +0300 Subject: [PATCH 12/22] Fix unresponsive kubectl download binaries switch (#4807) * Add observer wrapper to KubectlBinaries Signed-off-by: Alex Andreev * Add missing terminal title Signed-off-by: Alex Andreev --- src/renderer/components/+preferences/kubectl-binaries.tsx | 3 ++- src/renderer/components/+preferences/terminal.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx index fdd0ab4131..c39dd333c3 100644 --- a/src/renderer/components/+preferences/kubectl-binaries.tsx +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -4,6 +4,7 @@ */ import React, { useState } from "react"; +import { observer } from "mobx-react"; import { Input, InputValidators } from "../input"; import { SubTitle } from "../layout/sub-title"; import { UserStore } from "../../../common/user-store"; @@ -19,7 +20,7 @@ interface Dependencies { defaultPathForKubectlBinaries: string } -const NonInjectedKubectlBinaries: React.FC = (({ defaultPathForKubectlBinaries }) => { +const NonInjectedKubectlBinaries: React.FC = observer(({ defaultPathForKubectlBinaries }) => { const userStore = UserStore.getInstance(); const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || ""); const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || ""); diff --git a/src/renderer/components/+preferences/terminal.tsx b/src/renderer/components/+preferences/terminal.tsx index b76f2ba567..f9f1226609 100644 --- a/src/renderer/components/+preferences/terminal.tsx +++ b/src/renderer/components/+preferences/terminal.tsx @@ -24,7 +24,9 @@ export const Terminal = observer(() => { : "System default shell" ); - return (
+ return (
+

Terminal

+
{ onChange={(value) => userStore.terminalConfig.fontFamily=value} />
-
); + ); }); From 76f48df56b536a02779699aafed0a0912921550f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 3 Feb 2022 09:11:28 -0500 Subject: [PATCH 13/22] Fix rerendering of KubeObject views every 30s (#4801) Co-authored-by: Mikko Aspiala Co-authored-by: Janne Savolainen --- integration/__tests__/cluster-pages.tests.ts | 6 +- .../allowed-resources.injectable.ts | 21 ++ .../hosted-cluster.injectable.ts | 4 +- .../k8s-api/endpoints/helm-releases.api.ts | 2 +- src/common/k8s-api/kube-api.ts | 17 +- src/common/k8s-api/kube-object.store.ts | 4 +- src/common/routes/helm-charts.ts | 4 +- src/common/routes/{apps.ts => helm.ts} | 6 +- src/common/routes/index.ts | 2 +- src/common/routes/releases.ts | 4 +- src/common/utils/abort-controller.ts | 15 ++ src/common/utils/allowed-resource.ts | 25 -- src/common/utils/index.ts | 1 + .../utils/is-allowed-resource.injectable.ts | 22 ++ src/extensions/main-api/k8s-api.ts | 9 +- src/extensions/renderer-api/k8s-api.ts | 13 +- .../cluster-frame-context.injectable.ts | 2 +- src/renderer/components/+apps/apps.tsx | 38 --- src/renderer/components/+apps/index.ts | 6 - .../components/+cluster/sidebar-item.tsx | 37 +++ src/renderer/components/+config/index.ts | 6 - .../{config.tsx => route-tabs.injectable.ts} | 43 ++-- src/renderer/components/+config/route.tsx | 31 +++ .../components/+config/sidebar-item.tsx | 45 ++++ .../+custom-resources/custom-resources.tsx | 38 --- .../grouped-custom-resources.injectable.ts | 34 +++ .../route-tabs.injectable.ts | 65 +++++ .../components/+custom-resources/route.tsx | 43 ++++ .../+custom-resources/sidebar-item.tsx | 76 ++++++ src/renderer/components/+events/index.ts | 1 - .../components/+events/sidebar-item.tsx | 37 +++ .../helm-chart-details.scss | 0 .../helm-chart-details.tsx | 0 .../helm-chart.store.ts | 0 .../helm-charts.scss | 0 .../helm-charts.tsx | 0 .../helm-placeholder.svg | 0 .../index.ts | 0 .../create-release.injectable.ts | 0 .../delete-release.injectable.ts | 0 .../index.ts | 0 .../release-details.injectable.ts | 0 .../release-details/release-details.scss | 0 .../release-details/release-details.tsx | 0 .../release-route-parameters.injectable.ts | 0 .../release-values.injectable.ts | 0 .../release-details/release.injectable.ts | 0 ...er-supplied-values-are-shown.injectable.ts | 0 .../release-menu.tsx | 0 ...elease-rollback-dialog-model.injectable.ts | 0 .../release-rollback-dialog-model.ts | 0 .../release-rollback-dialog.scss | 0 .../release-rollback-dialog.tsx | 0 .../release.mixins.scss | 0 .../releases.injectable.ts | 0 .../releases.scss | 0 .../releases.tsx | 0 .../removable-releases.injectable.ts | 0 .../removable-releases.ts | 0 .../rollback-release.injectable.ts | 0 .../update-release.injectable.ts | 0 .../components/+helm/route-tabs.injectable.ts | 34 +++ src/renderer/components/+helm/route.tsx | 31 +++ .../components/+helm/sidebar-item.tsx | 44 ++++ src/renderer/components/+namespaces/index.ts | 1 - .../components/+namespaces/namespaces.tsx | 96 -------- src/renderer/components/+namespaces/route.tsx | 89 +++++++ .../components/+namespaces/sidebar-item.tsx | 37 +++ src/renderer/components/+network/index.ts | 6 - .../{network.tsx => route-tabs.injectable.ts} | 38 +-- src/renderer/components/+network/route.tsx | 33 +++ .../components/+network/sidebar-item.tsx | 45 ++++ ...-resources.scss => details-resources.scss} | 0 ...ls-resources.tsx => details-resources.tsx} | 2 +- .../{node-details.scss => details.scss} | 0 .../+nodes/{node-details.tsx => details.tsx} | 4 +- src/renderer/components/+nodes/index.ts | 7 - .../+nodes/{nodes.tsx => route.tsx} | 2 +- .../components/+nodes/sidebar-item.tsx | 37 +++ src/renderer/components/+storage/index.ts | 6 - .../{storage.tsx => route-tabs.injectable.ts} | 38 +-- src/renderer/components/+storage/route.tsx | 34 +++ .../components/+storage/sidebar-item.tsx | 45 ++++ .../components/+user-management/index.ts | 6 - ...anagement.tsx => route-tabs.injectable.ts} | 62 ++--- .../components/+user-management/route.tsx | 31 +++ .../+user-management/sidebar-item.tsx | 45 ++++ .../+workloads-overview/overview-statuses.tsx | 96 +++----- .../workloads.injectable.ts | 40 ++++ .../+workloads-overview/workloads.ts | 39 +++ src/renderer/components/+workloads/index.ts | 7 - ...workloads.tsx => route-tabs.injectable.ts} | 36 +-- src/renderer/components/+workloads/route.tsx | 34 +++ .../components/+workloads/sidebar-item.tsx | 45 ++++ .../components/+workloads/workloads.stores.ts | 25 -- .../components/dock/install-chart/view.tsx | 2 +- .../components/dock/upgrade-chart/view.tsx | 6 +- src/renderer/components/layout/sidebar.tsx | 223 +++--------------- .../layout/tab-routes-sidebar-items.tsx | 37 +++ .../frames/cluster-frame/cluster-frame.tsx | 63 ++--- .../init-cluster-frame.injectable.ts | 2 +- .../kube-object-detail-registry.tsx | 2 +- src/renderer/initializers/workload-events.tsx | 38 +++ .../workloads-overview-detail-registry.tsx | 8 +- src/renderer/kube-watch-api/kube-watch-api.ts | 18 +- 105 files changed, 1442 insertions(+), 709 deletions(-) create mode 100644 src/common/cluster-store/allowed-resources.injectable.ts rename src/common/cluster-store/{hosted-cluster => }/hosted-cluster.injectable.ts (81%) rename src/common/routes/{apps.ts => helm.ts} (70%) create mode 100644 src/common/utils/abort-controller.ts delete mode 100644 src/common/utils/allowed-resource.ts create mode 100644 src/common/utils/is-allowed-resource.injectable.ts delete mode 100644 src/renderer/components/+apps/apps.tsx delete mode 100644 src/renderer/components/+apps/index.ts create mode 100644 src/renderer/components/+cluster/sidebar-item.tsx delete mode 100644 src/renderer/components/+config/index.ts rename src/renderer/components/+config/{config.tsx => route-tabs.injectable.ts} (75%) create mode 100644 src/renderer/components/+config/route.tsx create mode 100644 src/renderer/components/+config/sidebar-item.tsx delete mode 100644 src/renderer/components/+custom-resources/custom-resources.tsx create mode 100644 src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts create mode 100644 src/renderer/components/+custom-resources/route-tabs.injectable.ts create mode 100644 src/renderer/components/+custom-resources/route.tsx create mode 100644 src/renderer/components/+custom-resources/sidebar-item.tsx create mode 100644 src/renderer/components/+events/sidebar-item.tsx rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-chart-details.scss (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-chart-details.tsx (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-chart.store.ts (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-charts.scss (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-charts.tsx (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/helm-placeholder.svg (100%) rename src/renderer/components/{+apps-helm-charts => +helm-charts}/index.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/create-release/create-release.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/delete-release/delete-release.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/index.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release-details.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release-details.scss (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release-details.tsx (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release-route-parameters.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release-values.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/release.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-details/user-supplied-values-are-shown.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-menu.tsx (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-rollback-dialog-model/release-rollback-dialog-model.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-rollback-dialog-model/release-rollback-dialog-model.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-rollback-dialog.scss (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release-rollback-dialog.tsx (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/release.mixins.scss (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/releases.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/releases.scss (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/releases.tsx (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/removable-releases.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/removable-releases.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/rollback-release/rollback-release.injectable.ts (100%) rename src/renderer/components/{+apps-releases => +helm-releases}/update-release/update-release.injectable.ts (100%) create mode 100644 src/renderer/components/+helm/route-tabs.injectable.ts create mode 100644 src/renderer/components/+helm/route.tsx create mode 100644 src/renderer/components/+helm/sidebar-item.tsx delete mode 100644 src/renderer/components/+namespaces/namespaces.tsx create mode 100644 src/renderer/components/+namespaces/route.tsx create mode 100644 src/renderer/components/+namespaces/sidebar-item.tsx delete mode 100644 src/renderer/components/+network/index.ts rename src/renderer/components/+network/{network.tsx => route-tabs.injectable.ts} (68%) create mode 100644 src/renderer/components/+network/route.tsx create mode 100644 src/renderer/components/+network/sidebar-item.tsx rename src/renderer/components/+nodes/{node-details-resources.scss => details-resources.scss} (100%) rename src/renderer/components/+nodes/{node-details-resources.tsx => details-resources.tsx} (98%) rename src/renderer/components/+nodes/{node-details.scss => details.scss} (100%) rename src/renderer/components/+nodes/{node-details.tsx => details.tsx} (98%) delete mode 100644 src/renderer/components/+nodes/index.ts rename src/renderer/components/+nodes/{nodes.tsx => route.tsx} (99%) create mode 100644 src/renderer/components/+nodes/sidebar-item.tsx delete mode 100644 src/renderer/components/+storage/index.ts rename src/renderer/components/+storage/{storage.tsx => route-tabs.injectable.ts} (61%) create mode 100644 src/renderer/components/+storage/route.tsx create mode 100644 src/renderer/components/+storage/sidebar-item.tsx delete mode 100644 src/renderer/components/+user-management/index.ts rename src/renderer/components/+user-management/{user-management.tsx => route-tabs.injectable.ts} (67%) create mode 100644 src/renderer/components/+user-management/route.tsx create mode 100644 src/renderer/components/+user-management/sidebar-item.tsx create mode 100644 src/renderer/components/+workloads-overview/workloads.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads.ts delete mode 100644 src/renderer/components/+workloads/index.ts rename src/renderer/components/+workloads/{workloads.tsx => route-tabs.injectable.ts} (76%) create mode 100644 src/renderer/components/+workloads/route.tsx create mode 100644 src/renderer/components/+workloads/sidebar-item.tsx delete mode 100644 src/renderer/components/+workloads/workloads.stores.ts create mode 100644 src/renderer/components/layout/tab-routes-sidebar-items.tsx create mode 100644 src/renderer/initializers/workload-events.tsx diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 375321aaaf..d9f91d3dc8 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -233,16 +233,16 @@ const commonPageTests: CommonPageTest[] = [{ }, }, { - drawerId: "apps", + drawerId: "helm", pages: [ { name: "Charts", - href: "/apps/charts", + href: "/helm/charts", expectedSelector: "div.HelmCharts input", }, { name: "Releases", - href: "/apps/releases", + href: "/helm/releases", expectedSelector: "h5.title", expectedText: "Releases", }, diff --git a/src/common/cluster-store/allowed-resources.injectable.ts b/src/common/cluster-store/allowed-resources.injectable.ts new file mode 100644 index 0000000000..3e4d4e67f5 --- /dev/null +++ b/src/common/cluster-store/allowed-resources.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { comparer, computed } from "mobx"; +import hostedClusterInjectable from "./hosted-cluster.injectable"; + +const allowedResourcesInjectable = getInjectable({ + instantiate: (di) => { + const cluster = di.inject(hostedClusterInjectable); + + return computed(() => new Set(cluster.allowedResources), { + // This needs to be here so that during refresh changes are only propogated when necessary + equals: (cur, prev) => comparer.structural(cur, prev), + }); + }, + lifecycle: lifecycleEnum.singleton, +}); + +export default allowedResourcesInjectable; diff --git a/src/common/cluster-store/hosted-cluster/hosted-cluster.injectable.ts b/src/common/cluster-store/hosted-cluster.injectable.ts similarity index 81% rename from src/common/cluster-store/hosted-cluster/hosted-cluster.injectable.ts rename to src/common/cluster-store/hosted-cluster.injectable.ts index 6a11c80a0e..2d093a1600 100644 --- a/src/common/cluster-store/hosted-cluster/hosted-cluster.injectable.ts +++ b/src/common/cluster-store/hosted-cluster.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { getHostedClusterId } from "../../utils"; -import clusterStoreInjectable from "../cluster-store.injectable"; +import { getHostedClusterId } from "../utils"; +import clusterStoreInjectable from "./cluster-store.injectable"; const hostedClusterInjectable = getInjectable({ instantiate: (di) => { diff --git a/src/common/k8s-api/endpoints/helm-releases.api.ts b/src/common/k8s-api/endpoints/helm-releases.api.ts index b0da1ff3cb..3f46e343cd 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api.ts @@ -7,7 +7,7 @@ import yaml from "js-yaml"; import { formatDuration } from "../../utils"; import capitalize from "lodash/capitalize"; import { apiBase } from "../index"; -import { helmChartStore } from "../../../renderer/components/+apps-helm-charts/helm-chart.store"; +import { helmChartStore } from "../../../renderer/components/+helm-charts/helm-chart.store"; import type { ItemObject } from "../../item.store"; import { KubeObject } from "../kube-object"; import type { JsonApiData } from "../json-api"; diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index db7f2516b6..64f4620c17 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -16,12 +16,9 @@ import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object"; import byline from "byline"; import type { IKubeWatchEvent } from "./kube-watch-event"; import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api"; -import { noop } from "../utils"; +import { noop, WrappedAbortController } from "../utils"; import type { RequestInit } from "node-fetch"; - -// BUG: https://github.com/mysticatea/abort-controller/pull/22 -// eslint-disable-next-line import/no-named-as-default -import AbortController from "abort-controller"; +import type AbortController from "abort-controller"; import { Agent, AgentOptions } from "https"; import type { Patch } from "rfc6902"; @@ -582,14 +579,7 @@ export class KubeApi { const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts; // Create AbortController for this request - const abortController = new AbortController(); - - // If caller aborts, abort using request's abortController - if (opts.abortController) { - opts.abortController.signal.addEventListener("abort", () => { - abortController.abort(); - }); - } + const abortController = new WrappedAbortController(opts.abortController); abortController.signal.addEventListener("abort", () => { logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`); @@ -687,7 +677,6 @@ export class KubeApi { protected modifyWatchEvent(event: IKubeWatchEvent) { if (event.type === "ERROR") { return; - } ensureObjectSelfLink(this, event.object); diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 8f0d5351f0..360229c79f 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -6,7 +6,7 @@ import type { ClusterContext } from "./cluster-context"; import { action, computed, makeObservable, observable, reaction, when } from "mobx"; -import { autoBind, noop, rejectPromiseBy } from "../utils"; +import { autoBind, Disposer, noop, rejectPromiseBy } from "../utils"; import { KubeObject, KubeStatus } from "./kube-object"; import type { IKubeWatchEvent } from "./kube-watch-event"; import { ItemStore } from "../item.store"; @@ -378,7 +378,7 @@ export abstract class KubeObjectStore extends ItemStore }); } - subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}) { + subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer { if (this.api.isNamespaced) { Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])]) .then(() => { diff --git a/src/common/routes/helm-charts.ts b/src/common/routes/helm-charts.ts index 6d36e0d06c..3777ac0ebd 100644 --- a/src/common/routes/helm-charts.ts +++ b/src/common/routes/helm-charts.ts @@ -5,10 +5,10 @@ import type { RouteProps } from "react-router"; import { buildURL } from "../utils/buildUrl"; -import { appsRoute } from "./apps"; +import { helmRoute } from "./helm"; export const helmChartsRoute: RouteProps = { - path: `${appsRoute.path}/charts/:repo?/:chartName?`, + path: `${helmRoute.path}/charts/:repo?/:chartName?`, }; export interface HelmChartsRouteParams { diff --git a/src/common/routes/apps.ts b/src/common/routes/helm.ts similarity index 70% rename from src/common/routes/apps.ts rename to src/common/routes/helm.ts index 304fbaf255..bb5d5b6914 100644 --- a/src/common/routes/apps.ts +++ b/src/common/routes/helm.ts @@ -6,8 +6,8 @@ import type { RouteProps } from "react-router"; import { buildURL } from "../utils/buildUrl"; -export const appsRoute: RouteProps = { - path: "/apps", +export const helmRoute: RouteProps = { + path: "/helm", }; -export const appsURL = buildURL(appsRoute.path); +export const helmURL = buildURL(helmRoute.path); diff --git a/src/common/routes/index.ts b/src/common/routes/index.ts index cb8a1d1653..916806fa16 100644 --- a/src/common/routes/index.ts +++ b/src/common/routes/index.ts @@ -4,7 +4,6 @@ */ export * from "./add-cluster"; -export * from "./apps"; export * from "./catalog"; export * from "./cluster-view"; export * from "./cluster"; @@ -16,6 +15,7 @@ export * from "./entity-settings"; export * from "./events"; export * from "./extensions"; export * from "./helm-charts"; +export * from "./helm"; export * from "./hpa"; export * from "./ingresses"; export * from "./limit-ranges"; diff --git a/src/common/routes/releases.ts b/src/common/routes/releases.ts index 092ec1ab63..191d186570 100644 --- a/src/common/routes/releases.ts +++ b/src/common/routes/releases.ts @@ -5,10 +5,10 @@ import type { RouteProps } from "react-router"; import { buildURL } from "../utils/buildUrl"; -import { appsRoute } from "./apps"; +import { helmRoute } from "./helm"; export const releaseRoute: RouteProps = { - path: `${appsRoute.path}/releases/:namespace?/:name?`, + path: `${helmRoute.path}/releases/:namespace?/:name?`, }; export interface ReleaseRouteParams { diff --git a/src/common/utils/abort-controller.ts b/src/common/utils/abort-controller.ts new file mode 100644 index 0000000000..e115fd5868 --- /dev/null +++ b/src/common/utils/abort-controller.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import AbortController from "abort-controller"; + +export class WrappedAbortController extends AbortController { + constructor(parent?: AbortController) { + super(); + + parent?.signal.addEventListener("abort", () => { + this.abort(); + }); + } +} diff --git a/src/common/utils/allowed-resource.ts b/src/common/utils/allowed-resource.ts deleted file mode 100644 index d1be86b720..0000000000 --- a/src/common/utils/allowed-resource.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { ClusterStore } from "../cluster-store/cluster-store"; -import type { KubeResource } from "../rbac"; -import { getHostedClusterId } from "./cluster-id-url-parsing"; - -export function isAllowedResource(resource: KubeResource | KubeResource[]) { - const resources = [resource].flat(); - const cluster = ClusterStore.getInstance().getById(getHostedClusterId()); - - if (!cluster?.allowedResources) { - return false; - } - - if (resources.length === 0) { - return true; - } - - const allowedResources = new Set(cluster.allowedResources); - - return resources.every(resource => allowedResources.has(resource)); -} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index d8a07f31f6..d653214c68 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -10,6 +10,7 @@ export function noop(...args: T): void { return void args; } +export * from "./abort-controller"; export * from "./app-version"; export * from "./autobind"; export * from "./camelCase"; diff --git a/src/common/utils/is-allowed-resource.injectable.ts b/src/common/utils/is-allowed-resource.injectable.ts new file mode 100644 index 0000000000..0b9d07d5bb --- /dev/null +++ b/src/common/utils/is-allowed-resource.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import allowedResourcesInjectable from "../cluster-store/allowed-resources.injectable"; +import type { KubeResource } from "../rbac"; + +export type IsAllowedResource = (resource: KubeResource) => boolean; + +// TODO: This injectable obscures MobX de-referencing. Make it more apparent in usage. +const isAllowedResourceInjectable = getInjectable({ + instantiate: (di) => { + const allowedResources = di.inject(allowedResourcesInjectable); + + return (resource: KubeResource) => allowedResources.get().has(resource); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default isAllowedResourceInjectable; diff --git a/src/extensions/main-api/k8s-api.ts b/src/extensions/main-api/k8s-api.ts index cc54ab0134..e9ea0650ac 100644 --- a/src/extensions/main-api/k8s-api.ts +++ b/src/extensions/main-api/k8s-api.ts @@ -3,7 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export { isAllowedResource } from "../../common/utils/allowed-resource"; +/** + * @deprecated This function never works + * @returns false + */ +export function isAllowedResource(...args: any[]) { + return Boolean(void args); +} + export { ResourceStack } from "../../common/k8s/resource-stack"; export { apiManager } from "../../common/k8s-api/api-manager"; export { KubeApi, forCluster, forRemoteCluster } from "../../common/k8s-api/kube-api"; diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index 47a17e4427..1f3829f0cc 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -2,8 +2,19 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { KubeResource } from "../../common/rbac"; +import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; +import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import { castArray } from "lodash/fp"; + +export function isAllowedResource(resource: KubeResource | KubeResource[]) { + const _isAllowedResource = asLegacyGlobalFunctionForExtensionApi(isAllowedResourceInjectable); + + const resources = castArray(resource); + + return resources.every(x => _isAllowedResource(x)); +} -export { isAllowedResource } from "../../common/utils/allowed-resource"; export { ResourceStack } from "../../common/k8s/resource-stack"; export { apiManager } from "../../common/k8s-api/api-manager"; export { KubeObjectStore } from "../../common/k8s-api/kube-object.store"; diff --git a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts b/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts index 1d15174f04..da139cf8b0 100644 --- a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts +++ b/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { ClusterFrameContext } from "./cluster-frame-context"; import namespaceStoreInjectable from "../components/+namespaces/namespace-store/namespace-store.injectable"; -import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster/hosted-cluster.injectable"; +import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster.injectable"; const clusterFrameContextInjectable = getInjectable({ instantiate: (di) => { diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx deleted file mode 100644 index 99f5130113..0000000000 --- a/src/renderer/components/+apps/apps.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { HelmCharts } from "../+apps-helm-charts"; -import { HelmReleases } from "../+apps-releases"; -import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes"; - -@observer -export class Apps extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { - - return [ - { - title: "Charts", - component: HelmCharts, - url: helmChartsURL(), - routePath: helmChartsRoute.path.toString(), - }, - { - title: "Releases", - component: HelmReleases, - url: releaseURL(), - routePath: releaseRoute.path.toString(), - }, - ]; - } - - render() { - return ( - - ); - } -} diff --git a/src/renderer/components/+apps/index.ts b/src/renderer/components/+apps/index.ts deleted file mode 100644 index 07b7a6bcdc..0000000000 --- a/src/renderer/components/+apps/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./apps"; diff --git a/src/renderer/components/+cluster/sidebar-item.tsx b/src/renderer/components/+cluster/sidebar-item.tsx new file mode 100644 index 0000000000..597118eb02 --- /dev/null +++ b/src/renderer/components/+cluster/sidebar-item.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import React from "react"; +import { clusterRoute, clusterURL } from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; + +export interface ClusterSidebarItemProps {} + +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +const NonInjectedClusterSidebarItem = observer(({ isAllowedResource }: Dependencies & ClusterSidebarItemProps) => ( + } + /> +)); + +export const ClusterSidebarItem = withInjectables(NonInjectedClusterSidebarItem, { + getProps: (di, props) => ({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+config/index.ts b/src/renderer/components/+config/index.ts deleted file mode 100644 index 5fa22d8bf5..0000000000 --- a/src/renderer/components/+config/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./config"; diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/route-tabs.injectable.ts similarity index 75% rename from src/renderer/components/+config/config.tsx rename to src/renderer/components/+config/route-tabs.injectable.ts index 0cd471ae2d..d715d4ac4b 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/route-tabs.injectable.ts @@ -2,22 +2,24 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { ConfigMaps } from "../+config-maps"; -import { Secrets } from "../+config-secrets"; -import { ResourceQuotas } from "../+config-resource-quotas"; -import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; import { HorizontalPodAutoscalers } from "../+config-autoscalers"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; import { LimitRanges } from "../+config-limit-ranges"; +import { ConfigMaps } from "../+config-maps"; +import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; +import { ResourceQuotas } from "../+config-resource-quotas"; +import { Secrets } from "../+config-secrets"; +import isAllowedResourceInjectable, { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import type { TabLayoutRoute } from "../layout/tab-layout"; import * as routes from "../../../common/routes"; -@observer -export class Config extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +function getRouteTabs({ isAllowedResource }: Dependencies) { + return computed(() => { const tabs: TabLayoutRoute[] = []; if (isAllowedResource("configmaps")) { @@ -75,11 +77,14 @@ export class Config extends React.Component { } return tabs; - } - - render() { - return ( - - ); - } + }); } + +const configRoutesInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default configRoutesInjectable; diff --git a/src/renderer/components/+config/route.tsx b/src/renderer/components/+config/route.tsx new file mode 100644 index 0000000000..44b0934bc6 --- /dev/null +++ b/src/renderer/components/+config/route.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { observer } from "mobx-react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import configRoutesInjectable from "./route-tabs.injectable"; + +export interface ConfigRouteProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedConfigRoute = observer(({ routes }: Dependencies & ConfigRouteProps) => ( + +)); + +export const ConfigRoute = withInjectables(NonInjectedConfigRoute, { + getProps: (di, props) => ({ + routes: di.inject(configRoutesInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+config/sidebar-item.tsx b/src/renderer/components/+config/sidebar-item.tsx new file mode 100644 index 0000000000..5298ae251a --- /dev/null +++ b/src/renderer/components/+config/sidebar-item.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { configRoute, configURL } from "../../../common/routes"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import configRoutesInjectable from "./route-tabs.injectable"; + +export interface ConfigSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedConfigSidebarItem = observer(({ routes }: Dependencies & ConfigSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const ConfigSidebarItem = withInjectables(NonInjectedConfigSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(configRoutesInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+custom-resources/custom-resources.tsx b/src/renderer/components/+custom-resources/custom-resources.tsx deleted file mode 100644 index 825e2b6908..0000000000 --- a/src/renderer/components/+custom-resources/custom-resources.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { CrdList } from "./crd-list"; -import { CrdResources } from "./crd-resources"; -import { crdURL, crdDefinitionsRoute, crdResourcesRoute } from "../../../common/routes"; - -@observer -export class CustomResources extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { - return [ - { - title: "Definitions", - component: CustomResources, - url: crdURL(), - routePath: String(crdDefinitionsRoute.path), - }, - ]; - } - - render() { - return ( - - - - - - - - ); - } -} diff --git a/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts b/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts new file mode 100644 index 0000000000..190bd544f0 --- /dev/null +++ b/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed, IComputedValue } from "mobx"; +import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; +import { getOrInsert } from "../../utils"; +import customResourceDefinitionsInjectable from "./custom-resources.injectable"; + +interface Dependencies { + definitions: IComputedValue; +} + +function getGroupedCustomResourceDefinitions({ definitions }: Dependencies) { + return computed(() => { + const groups = new Map(); + + for (const crd of definitions.get()) { + getOrInsert(groups, crd.getGroup(), []).push(crd); + } + + return groups; + }); +} + +const groupedCustomResourceDefinitionsInjectable = getInjectable({ + instantiate: (di) => getGroupedCustomResourceDefinitions({ + definitions: di.inject(customResourceDefinitionsInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default groupedCustomResourceDefinitionsInjectable; diff --git a/src/renderer/components/+custom-resources/route-tabs.injectable.ts b/src/renderer/components/+custom-resources/route-tabs.injectable.ts new file mode 100644 index 0000000000..9030077388 --- /dev/null +++ b/src/renderer/components/+custom-resources/route-tabs.injectable.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed, IComputedValue } from "mobx"; +import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; +import { crdURL, crdDefinitionsRoute } from "../../../common/routes"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { CrdList } from "./crd-list"; +import { CrdResources } from "./crd-resources"; +import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable"; + +export interface CustomResourceTabLayoutRoute extends TabLayoutRoute { + id: string; +} + +export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute { + subRoutes?: CustomResourceTabLayoutRoute[]; +} + +interface Dependencies { + customResourcesDefinitions: IComputedValue>; +} + +function getRouteTabs({ customResourcesDefinitions }: Dependencies) { + return computed(() => { + const tabs: CustomResourceGroupTabLayoutRoute[] = [ + { + id: "definitions", + title: "Definitions", + component: CrdList, + url: crdURL(), + routePath: String(crdDefinitionsRoute.path), + exact: true, + }, + ]; + + for (const [group, definitions] of customResourcesDefinitions.get()) { + tabs.push({ + id: `crd-group:${group}`, + title: group, + routePath: crdURL({ query: { groups: group }}), + component: CrdResources, + subRoutes: definitions.map(crd => ({ + id: `crd-resource:${crd.getResourceApiBase()}`, + title: crd.getResourceKind(), + routePath: crd.getResourceUrl(), + component: CrdResources, + })), + }); + } + + return tabs; + }); +} + +const customResourcesRouteTabsInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default customResourcesRouteTabsInjectable; diff --git a/src/renderer/components/+custom-resources/route.tsx b/src/renderer/components/+custom-resources/route.tsx new file mode 100644 index 0000000000..a8f3da4388 --- /dev/null +++ b/src/renderer/components/+custom-resources/route.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { observer } from "mobx-react"; +import { Redirect, Route, Switch } from "react-router"; +import { TabLayout } from "../layout/tab-layout"; +import { crdURL } from "../../../common/routes"; +import type { IComputedValue } from "mobx"; +import type { CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import customResourcesRouteTabsInjectable from "./route-tabs.injectable"; + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedCustomResourcesRoute = observer(({ routes }: Dependencies) => ( + + + { + routes.get().map(({ id, component, routePath, exact }) => ( + + )) + } + + + +)); + +export const CustomResourcesRoute = withInjectables(NonInjectedCustomResourcesRoute, { + getProps: (di, props) => ({ + routes: di.inject(customResourcesRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+custom-resources/sidebar-item.tsx b/src/renderer/components/+custom-resources/sidebar-item.tsx new file mode 100644 index 0000000000..40fb76b34a --- /dev/null +++ b/src/renderer/components/+custom-resources/sidebar-item.tsx @@ -0,0 +1,76 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React, { useEffect } from "react"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { crdURL, crdRoute } from "../../../common/routes"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; +import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; +import { crdStore } from "./crd.store"; +import { Spinner } from "../spinner"; + +export interface CustomResourcesSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; + isAllowedResource: IsAllowedResource; + subscribeStores: SubscribeStores; +} + +const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResource, subscribeStores }: Dependencies & CustomResourcesSidebarItemProps) => { + useEffect(() => subscribeStores([ + crdStore, + ]), []); + + return ( + } + > + {routes.get().map((route) => ( + + {route.subRoutes?.map((subRoute) => ( + + ))} + + ))} + {crdStore.isLoading && ( +
+ +
+ )} +
+ ); +}); + +export const CustomResourcesSidebarItem = withInjectables(NonInjectedCustomResourcesSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(customResourcesRouteTabsInjectable), + isAllowedResource: di.inject(isAllowedResourceInjectable), + subscribeStores: di.inject(subscribeStoresInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+events/index.ts b/src/renderer/components/+events/index.ts index 3eaea4bdae..03a60b7266 100644 --- a/src/renderer/components/+events/index.ts +++ b/src/renderer/components/+events/index.ts @@ -3,5 +3,4 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export * from "./events"; export * from "./event-details"; diff --git a/src/renderer/components/+events/sidebar-item.tsx b/src/renderer/components/+events/sidebar-item.tsx new file mode 100644 index 0000000000..50ffddcefc --- /dev/null +++ b/src/renderer/components/+events/sidebar-item.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import React from "react"; +import { eventRoute, eventsURL } from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; + +export interface EventsSidebarItemProps {} + +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +const NonInjectedEventsSidebarItem = observer(({ isAllowedResource }: Dependencies & EventsSidebarItemProps) => ( + } + /> +)); + +export const EventsSidebarItem = withInjectables(NonInjectedEventsSidebarItem, { + getProps: (di, props) => ({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.scss b/src/renderer/components/+helm-charts/helm-chart-details.scss similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-chart-details.scss rename to src/renderer/components/+helm-charts/helm-chart-details.scss diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx b/src/renderer/components/+helm-charts/helm-chart-details.tsx similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-chart-details.tsx rename to src/renderer/components/+helm-charts/helm-chart-details.tsx diff --git a/src/renderer/components/+apps-helm-charts/helm-chart.store.ts b/src/renderer/components/+helm-charts/helm-chart.store.ts similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-chart.store.ts rename to src/renderer/components/+helm-charts/helm-chart.store.ts diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.scss b/src/renderer/components/+helm-charts/helm-charts.scss similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-charts.scss rename to src/renderer/components/+helm-charts/helm-charts.scss diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.tsx b/src/renderer/components/+helm-charts/helm-charts.tsx similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-charts.tsx rename to src/renderer/components/+helm-charts/helm-charts.tsx diff --git a/src/renderer/components/+apps-helm-charts/helm-placeholder.svg b/src/renderer/components/+helm-charts/helm-placeholder.svg similarity index 100% rename from src/renderer/components/+apps-helm-charts/helm-placeholder.svg rename to src/renderer/components/+helm-charts/helm-placeholder.svg diff --git a/src/renderer/components/+apps-helm-charts/index.ts b/src/renderer/components/+helm-charts/index.ts similarity index 100% rename from src/renderer/components/+apps-helm-charts/index.ts rename to src/renderer/components/+helm-charts/index.ts diff --git a/src/renderer/components/+apps-releases/create-release/create-release.injectable.ts b/src/renderer/components/+helm-releases/create-release/create-release.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/create-release/create-release.injectable.ts rename to src/renderer/components/+helm-releases/create-release/create-release.injectable.ts diff --git a/src/renderer/components/+apps-releases/delete-release/delete-release.injectable.ts b/src/renderer/components/+helm-releases/delete-release/delete-release.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/delete-release/delete-release.injectable.ts rename to src/renderer/components/+helm-releases/delete-release/delete-release.injectable.ts diff --git a/src/renderer/components/+apps-releases/index.ts b/src/renderer/components/+helm-releases/index.ts similarity index 100% rename from src/renderer/components/+apps-releases/index.ts rename to src/renderer/components/+helm-releases/index.ts diff --git a/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts b/src/renderer/components/+helm-releases/release-details/release-details.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release-details.injectable.ts rename to src/renderer/components/+helm-releases/release-details/release-details.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-details/release-details.scss b/src/renderer/components/+helm-releases/release-details/release-details.scss similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release-details.scss rename to src/renderer/components/+helm-releases/release-details/release-details.scss diff --git a/src/renderer/components/+apps-releases/release-details/release-details.tsx b/src/renderer/components/+helm-releases/release-details/release-details.tsx similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release-details.tsx rename to src/renderer/components/+helm-releases/release-details/release-details.tsx diff --git a/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts b/src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts rename to src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts b/src/renderer/components/+helm-releases/release-details/release-values.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release-values.injectable.ts rename to src/renderer/components/+helm-releases/release-details/release-values.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-details/release.injectable.ts b/src/renderer/components/+helm-releases/release-details/release.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-details/release.injectable.ts rename to src/renderer/components/+helm-releases/release-details/release.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts b/src/renderer/components/+helm-releases/release-details/user-supplied-values-are-shown.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts rename to src/renderer/components/+helm-releases/release-details/user-supplied-values-are-shown.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-menu.tsx b/src/renderer/components/+helm-releases/release-menu.tsx similarity index 100% rename from src/renderer/components/+apps-releases/release-menu.tsx rename to src/renderer/components/+helm-releases/release-menu.tsx diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog-model/release-rollback-dialog-model.injectable.ts b/src/renderer/components/+helm-releases/release-rollback-dialog-model/release-rollback-dialog-model.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-rollback-dialog-model/release-rollback-dialog-model.injectable.ts rename to src/renderer/components/+helm-releases/release-rollback-dialog-model/release-rollback-dialog-model.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog-model/release-rollback-dialog-model.ts b/src/renderer/components/+helm-releases/release-rollback-dialog-model/release-rollback-dialog-model.ts similarity index 100% rename from src/renderer/components/+apps-releases/release-rollback-dialog-model/release-rollback-dialog-model.ts rename to src/renderer/components/+helm-releases/release-rollback-dialog-model/release-rollback-dialog-model.ts diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog.scss b/src/renderer/components/+helm-releases/release-rollback-dialog.scss similarity index 100% rename from src/renderer/components/+apps-releases/release-rollback-dialog.scss rename to src/renderer/components/+helm-releases/release-rollback-dialog.scss diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog.tsx b/src/renderer/components/+helm-releases/release-rollback-dialog.tsx similarity index 100% rename from src/renderer/components/+apps-releases/release-rollback-dialog.tsx rename to src/renderer/components/+helm-releases/release-rollback-dialog.tsx diff --git a/src/renderer/components/+apps-releases/release.mixins.scss b/src/renderer/components/+helm-releases/release.mixins.scss similarity index 100% rename from src/renderer/components/+apps-releases/release.mixins.scss rename to src/renderer/components/+helm-releases/release.mixins.scss diff --git a/src/renderer/components/+apps-releases/releases.injectable.ts b/src/renderer/components/+helm-releases/releases.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/releases.injectable.ts rename to src/renderer/components/+helm-releases/releases.injectable.ts diff --git a/src/renderer/components/+apps-releases/releases.scss b/src/renderer/components/+helm-releases/releases.scss similarity index 100% rename from src/renderer/components/+apps-releases/releases.scss rename to src/renderer/components/+helm-releases/releases.scss diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+helm-releases/releases.tsx similarity index 100% rename from src/renderer/components/+apps-releases/releases.tsx rename to src/renderer/components/+helm-releases/releases.tsx diff --git a/src/renderer/components/+apps-releases/removable-releases.injectable.ts b/src/renderer/components/+helm-releases/removable-releases.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/removable-releases.injectable.ts rename to src/renderer/components/+helm-releases/removable-releases.injectable.ts diff --git a/src/renderer/components/+apps-releases/removable-releases.ts b/src/renderer/components/+helm-releases/removable-releases.ts similarity index 100% rename from src/renderer/components/+apps-releases/removable-releases.ts rename to src/renderer/components/+helm-releases/removable-releases.ts diff --git a/src/renderer/components/+apps-releases/rollback-release/rollback-release.injectable.ts b/src/renderer/components/+helm-releases/rollback-release/rollback-release.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/rollback-release/rollback-release.injectable.ts rename to src/renderer/components/+helm-releases/rollback-release/rollback-release.injectable.ts diff --git a/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts b/src/renderer/components/+helm-releases/update-release/update-release.injectable.ts similarity index 100% rename from src/renderer/components/+apps-releases/update-release/update-release.injectable.ts rename to src/renderer/components/+helm-releases/update-release/update-release.injectable.ts diff --git a/src/renderer/components/+helm/route-tabs.injectable.ts b/src/renderer/components/+helm/route-tabs.injectable.ts new file mode 100644 index 0000000000..34422f7c4b --- /dev/null +++ b/src/renderer/components/+helm/route-tabs.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { HelmCharts } from "../+helm-charts"; +import { HelmReleases } from "../+helm-releases"; +import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes"; + +function getRouteTabs() { + return computed((): TabLayoutRoute[] => [ + { + title: "Charts", + component: HelmCharts, + url: helmChartsURL(), + routePath: helmChartsRoute.path.toString(), + }, + { + title: "Releases", + component: HelmReleases, + url: releaseURL(), + routePath: releaseRoute.path.toString(), + }, + ]); +} + +const helmRoutesInjectable = getInjectable({ + instantiate: () => getRouteTabs(), + lifecycle: lifecycleEnum.singleton, +}); + +export default helmRoutesInjectable; diff --git a/src/renderer/components/+helm/route.tsx b/src/renderer/components/+helm/route.tsx new file mode 100644 index 0000000000..da2983a8ba --- /dev/null +++ b/src/renderer/components/+helm/route.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { observer } from "mobx-react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import helmRoutesInjectable from "./route-tabs.injectable"; + +export interface HelmRouteProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedHelmRoute = observer(({ routes }: Dependencies & HelmRouteProps) => ( + +)); + +export const HelmRoute = withInjectables(NonInjectedHelmRoute, { + getProps: (di, props) => ({ + routes: di.inject(helmRoutesInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+helm/sidebar-item.tsx b/src/renderer/components/+helm/sidebar-item.tsx new file mode 100644 index 0000000000..2893d369a9 --- /dev/null +++ b/src/renderer/components/+helm/sidebar-item.tsx @@ -0,0 +1,44 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import { helmRoute, helmURL } from "../../../common/routes"; +import networkRouteTabsInjectable from "./route-tabs.injectable"; + +export interface HelmSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedHelmSidebarItem = observer(({ routes }: Dependencies & HelmSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const HelmSidebarItem = withInjectables(NonInjectedHelmSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(networkRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+namespaces/index.ts b/src/renderer/components/+namespaces/index.ts index ce705883ea..1ce2e35264 100644 --- a/src/renderer/components/+namespaces/index.ts +++ b/src/renderer/components/+namespaces/index.ts @@ -3,6 +3,5 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export * from "./namespaces"; export * from "./namespace-details"; export * from "./add-namespace-dialog"; diff --git a/src/renderer/components/+namespaces/namespaces.tsx b/src/renderer/components/+namespaces/namespaces.tsx deleted file mode 100644 index f997613c17..0000000000 --- a/src/renderer/components/+namespaces/namespaces.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./namespaces.scss"; - -import React from "react"; -import { NamespaceStatus } from "../../../common/k8s-api/endpoints"; -import { AddNamespaceDialog } from "./add-namespace-dialog"; -import { TabLayout } from "../layout/tab-layout"; -import { Badge } from "../badge"; -import type { RouteComponentProps } from "react-router"; -import { KubeObjectListLayout } from "../kube-object-list-layout"; -import type { NamespaceStore } from "./namespace-store/namespace.store"; -import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { NamespacesRouteParams } from "../../../common/routes"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable"; -import addNamespaceDialogModelInjectable - from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable"; - -enum columnId { - name = "name", - labels = "labels", - age = "age", - status = "status", -} - -interface Props extends RouteComponentProps { -} - -interface Dependencies { - namespaceStore: NamespaceStore - openAddNamespaceDialog: () => void -} - -class NonInjectedNamespaces extends React.Component { - render() { - return ( - - ns.getName(), - [columnId.labels]: ns => ns.getLabels(), - [columnId.age]: ns => ns.getTimeDiffFromNow(), - [columnId.status]: ns => ns.getStatus(), - }} - searchFilters={[ - item => item.getSearchFields(), - item => item.getStatus(), - ]} - renderHeaderTitle="Namespaces" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { className: "warning", showWithColumn: columnId.name }, - { title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, - ]} - renderTableContents={item => [ - item.getName(), - , - item.getLabels().map(label => ), - item.getAge(), - { title: item.getStatus(), className: item.getStatus().toLowerCase() }, - ]} - addRemoveButtons={{ - addTooltip: "Add Namespace", - onAdd: () => this.props.openAddNamespaceDialog(), - }} - customizeTableRowProps={item => ({ - disabled: item.getStatus() === NamespaceStatus.TERMINATING, - })} - /> - - - ); - } -} - -export const Namespaces = withInjectables( - NonInjectedNamespaces, - - { - getProps: (di, props) => ({ - namespaceStore: di.inject(namespaceStoreInjectable), - openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open, - ...props, - }), - }, -); diff --git a/src/renderer/components/+namespaces/route.tsx b/src/renderer/components/+namespaces/route.tsx new file mode 100644 index 0000000000..a05231dc05 --- /dev/null +++ b/src/renderer/components/+namespaces/route.tsx @@ -0,0 +1,89 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./namespaces.scss"; + +import React from "react"; +import { NamespaceStatus } from "../../../common/k8s-api/endpoints"; +import { AddNamespaceDialog } from "./add-namespace-dialog"; +import { TabLayout } from "../layout/tab-layout"; +import { Badge } from "../badge"; +import type { RouteComponentProps } from "react-router"; +import { KubeObjectListLayout } from "../kube-object-list-layout"; +import type { NamespaceStore } from "./namespace-store/namespace.store"; +import { KubeObjectStatusIcon } from "../kube-object-status-icon"; +import type { NamespacesRouteParams } from "../../../common/routes"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable"; +import addNamespaceDialogModelInjectable + from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable"; + +enum columnId { + name = "name", + labels = "labels", + age = "age", + status = "status", +} + +export interface NamespacesRouteProps extends RouteComponentProps { +} + +interface Dependencies { + namespaceStore: NamespaceStore + openAddNamespaceDialog: () => void +} + +export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies & NamespacesRouteProps) => ( + + ns.getName(), + [columnId.labels]: ns => ns.getLabels(), + [columnId.age]: ns => ns.getTimeDiffFromNow(), + [columnId.status]: ns => ns.getStatus(), + }} + searchFilters={[ + item => item.getSearchFields(), + item => item.getStatus(), + ]} + renderHeaderTitle="Namespaces" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, + ]} + renderTableContents={item => [ + item.getName(), + , + item.getLabels().map(label => ), + item.getAge(), + { title: item.getStatus(), className: item.getStatus().toLowerCase() }, + ]} + addRemoveButtons={{ + addTooltip: "Add Namespace", + onAdd: openAddNamespaceDialog, + }} + customizeTableRowProps={item => ({ + disabled: item.getStatus() === NamespaceStatus.TERMINATING, + })} + /> + + +); + + +export const NamespacesRoute = withInjectables(NonInjectedNamespacesRoute, { + getProps: (di, props) => ({ + namespaceStore: di.inject(namespaceStoreInjectable), + openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open, + ...props, + }), +}); diff --git a/src/renderer/components/+namespaces/sidebar-item.tsx b/src/renderer/components/+namespaces/sidebar-item.tsx new file mode 100644 index 0000000000..78bbc8a3ae --- /dev/null +++ b/src/renderer/components/+namespaces/sidebar-item.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import React from "react"; +import { namespacesRoute, namespacesURL } from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; + +export interface NamespacesSidebarItemProps {} + +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +const NonInjectedNamespacesSidebarItem = observer(({ isAllowedResource }: Dependencies & NamespacesSidebarItemProps) => ( + } + /> +)); + +export const NamespacesSidebarItem = withInjectables(NonInjectedNamespacesSidebarItem, { + getProps: (di, props) => ({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+network/index.ts b/src/renderer/components/+network/index.ts deleted file mode 100644 index 37d73526e3..0000000000 --- a/src/renderer/components/+network/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./network"; diff --git a/src/renderer/components/+network/network.tsx b/src/renderer/components/+network/route-tabs.injectable.ts similarity index 68% rename from src/renderer/components/+network/network.tsx rename to src/renderer/components/+network/route-tabs.injectable.ts index 2bdad62d8d..95476fe17f 100644 --- a/src/renderer/components/+network/network.tsx +++ b/src/renderer/components/+network/route-tabs.injectable.ts @@ -2,23 +2,24 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -import "./network.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { TabLayoutRoute } from "../layout/tab-layout"; import { Services } from "../+network-services"; import { Endpoints } from "../+network-endpoints"; import { Ingresses } from "../+network-ingresses"; import { NetworkPolicies } from "../+network-policies"; import { PortForwards } from "../+network-port-forwards"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; import * as routes from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -@observer -export class Network extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +function getRouteTabs({ isAllowedResource }: Dependencies) { + return computed(() => { const tabs: TabLayoutRoute[] = []; if (isAllowedResource("services")) { @@ -65,11 +66,14 @@ export class Network extends React.Component { }); return tabs; - } - - render() { - return ( - - ); - } + }); } + +const networkRouteTabsInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default networkRouteTabsInjectable; diff --git a/src/renderer/components/+network/route.tsx b/src/renderer/components/+network/route.tsx new file mode 100644 index 0000000000..1abf1ca445 --- /dev/null +++ b/src/renderer/components/+network/route.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./network.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import networkRouteTabsInjectable from "./route-tabs.injectable"; + +export interface NetworksRouteProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedNetworksRoute = observer(({ routes }: Dependencies & NetworksRouteProps) => ( + +)); + +export const NetworkRoute = withInjectables(NonInjectedNetworksRoute, { + getProps: (di, props) => ({ + routes: di.inject(networkRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+network/sidebar-item.tsx b/src/renderer/components/+network/sidebar-item.tsx new file mode 100644 index 0000000000..2dcf056a97 --- /dev/null +++ b/src/renderer/components/+network/sidebar-item.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import { networkRoute, networkURL } from "../../../common/routes"; +import networkRouteTabsInjectable from "./route-tabs.injectable"; + +export interface NetworkSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedNetworkSidebarItem = observer(({ routes }: Dependencies & NetworkSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const NetworkSidebarItem = withInjectables(NonInjectedNetworkSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(networkRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+nodes/node-details-resources.scss b/src/renderer/components/+nodes/details-resources.scss similarity index 100% rename from src/renderer/components/+nodes/node-details-resources.scss rename to src/renderer/components/+nodes/details-resources.scss diff --git a/src/renderer/components/+nodes/node-details-resources.tsx b/src/renderer/components/+nodes/details-resources.tsx similarity index 98% rename from src/renderer/components/+nodes/node-details-resources.tsx rename to src/renderer/components/+nodes/details-resources.tsx index 98dab82d91..50d3a7cb7c 100644 --- a/src/renderer/components/+nodes/node-details-resources.tsx +++ b/src/renderer/components/+nodes/details-resources.tsx @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import "./node-details-resources.scss"; +import "./details-resources.scss"; import { Table } from "../table/table"; import { TableHead } from "../table/table-head"; diff --git a/src/renderer/components/+nodes/node-details.scss b/src/renderer/components/+nodes/details.scss similarity index 100% rename from src/renderer/components/+nodes/node-details.scss rename to src/renderer/components/+nodes/details.scss diff --git a/src/renderer/components/+nodes/node-details.tsx b/src/renderer/components/+nodes/details.tsx similarity index 98% rename from src/renderer/components/+nodes/node-details.tsx rename to src/renderer/components/+nodes/details.tsx index 82580224ec..9af106f43f 100644 --- a/src/renderer/components/+nodes/node-details.tsx +++ b/src/renderer/components/+nodes/details.tsx @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import "./node-details.scss"; +import "./details.scss"; import React from "react"; import upperFirst from "lodash/upperFirst"; @@ -21,7 +21,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object-meta"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; -import { NodeDetailsResources } from "./node-details-resources"; +import { NodeDetailsResources } from "./details-resources"; import { DrawerTitle } from "../drawer/drawer-title"; import { boundMethod, Disposer } from "../../utils"; import logger from "../../../common/logger"; diff --git a/src/renderer/components/+nodes/index.ts b/src/renderer/components/+nodes/index.ts deleted file mode 100644 index 78b0f3b279..0000000000 --- a/src/renderer/components/+nodes/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./nodes"; -export * from "./node-details"; diff --git a/src/renderer/components/+nodes/nodes.tsx b/src/renderer/components/+nodes/route.tsx similarity index 99% rename from src/renderer/components/+nodes/nodes.tsx rename to src/renderer/components/+nodes/route.tsx index 43a3b3a84e..8801e6e832 100644 --- a/src/renderer/components/+nodes/nodes.tsx +++ b/src/renderer/components/+nodes/route.tsx @@ -50,7 +50,7 @@ interface UsageArgs { } @observer -export class Nodes extends React.Component { +export class NodesRoute extends React.Component { @observable.ref metrics: Partial = {}; private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes()); diff --git a/src/renderer/components/+nodes/sidebar-item.tsx b/src/renderer/components/+nodes/sidebar-item.tsx new file mode 100644 index 0000000000..1a384cc0fc --- /dev/null +++ b/src/renderer/components/+nodes/sidebar-item.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import React from "react"; +import { nodesRoute, nodesURL } from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; + +export interface NodeSidebarItemProps {} + +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +const NonInjectedNodeSidebarItem = observer(({ isAllowedResource }: Dependencies & NodeSidebarItemProps) => ( + } + /> +)); + +export const NodesSidebarItem = withInjectables(NonInjectedNodeSidebarItem, { + getProps: (di, props) => ({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+storage/index.ts b/src/renderer/components/+storage/index.ts deleted file mode 100644 index 90fa5c7eea..0000000000 --- a/src/renderer/components/+storage/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./storage"; diff --git a/src/renderer/components/+storage/storage.tsx b/src/renderer/components/+storage/route-tabs.injectable.ts similarity index 61% rename from src/renderer/components/+storage/storage.tsx rename to src/renderer/components/+storage/route-tabs.injectable.ts index 882160cb4e..512e0ddc09 100644 --- a/src/renderer/components/+storage/storage.tsx +++ b/src/renderer/components/+storage/route-tabs.injectable.ts @@ -2,21 +2,22 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -import "./storage.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { TabLayoutRoute } from "../layout/tab-layout"; import { PersistentVolumes } from "../+storage-volumes"; import { StorageClasses } from "../+storage-classes"; import { PersistentVolumeClaims } from "../+storage-volume-claims"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; import * as routes from "../../../common/routes"; -@observer -export class Storage extends React.Component { - static get tabRoutes() { +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +function getRouteTabs({ isAllowedResource }: Dependencies) { + return computed(() => { const tabs: TabLayoutRoute[] = []; if (isAllowedResource("persistentvolumeclaims")) { @@ -47,11 +48,14 @@ export class Storage extends React.Component { } return tabs; - } - - render() { - return ( - - ); - } + }); } + +const storageRouteTabsInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default storageRouteTabsInjectable; diff --git a/src/renderer/components/+storage/route.tsx b/src/renderer/components/+storage/route.tsx new file mode 100644 index 0000000000..b11d643186 --- /dev/null +++ b/src/renderer/components/+storage/route.tsx @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./storage.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import storageRouteTabsInjectable from "./route-tabs.injectable"; + +export interface StorageRouteProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedStorageRoute = observer(({ routes }: Dependencies & StorageRouteProps) => ( + +)); + +export const StorageRoute = withInjectables(NonInjectedStorageRoute, { + getProps: (di, props) => ({ + routes: di.inject(storageRouteTabsInjectable), + ...props, + }), +}); + diff --git a/src/renderer/components/+storage/sidebar-item.tsx b/src/renderer/components/+storage/sidebar-item.tsx new file mode 100644 index 0000000000..11c598a622 --- /dev/null +++ b/src/renderer/components/+storage/sidebar-item.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { storageRoute, storageURL } from "../../../common/routes"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import storageRouteTabsInjectable from "./route-tabs.injectable"; + +export interface StorageSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedStorageSidebarItem = observer(({ routes }: Dependencies & StorageSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const StorageSidebarItem = withInjectables(NonInjectedStorageSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(storageRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+user-management/index.ts b/src/renderer/components/+user-management/index.ts deleted file mode 100644 index 8d292d0a2d..0000000000 --- a/src/renderer/components/+user-management/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./user-management"; diff --git a/src/renderer/components/+user-management/user-management.tsx b/src/renderer/components/+user-management/route-tabs.injectable.ts similarity index 67% rename from src/renderer/components/+user-management/user-management.tsx rename to src/renderer/components/+user-management/route-tabs.injectable.ts index 1d39eba153..a33249d5c6 100644 --- a/src/renderer/components/+user-management/user-management.tsx +++ b/src/renderer/components/+user-management/route-tabs.injectable.ts @@ -2,28 +2,29 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -import "./user-management.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { PodSecurityPolicies } from "../+pod-security-policies"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; import * as routes from "../../../common/routes"; +import { PodSecurityPolicies } from "../+pod-security-policies"; import { ClusterRoleBindings } from "./+cluster-role-bindings"; -import { ServiceAccounts } from "./+service-accounts"; -import { Roles } from "./+roles"; -import { RoleBindings } from "./+role-bindings"; import { ClusterRoles } from "./+cluster-roles"; +import { RoleBindings } from "./+role-bindings"; +import { Roles } from "./+roles"; +import { ServiceAccounts } from "./+service-accounts"; -@observer -export class UserManagement extends React.Component { - static get tabRoutes() { - const tabRoutes: TabLayoutRoute[] = []; +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +function getRouteTabs({ isAllowedResource }: Dependencies) { + return computed(() => { + const tabs: TabLayoutRoute[] = []; if (isAllowedResource("serviceaccounts")) { - tabRoutes.push({ + tabs.push({ title: "Service Accounts", component: ServiceAccounts, url: routes.serviceAccountsURL(), @@ -32,7 +33,7 @@ export class UserManagement extends React.Component { } if (isAllowedResource("clusterroles")) { - tabRoutes.push({ + tabs.push({ title: "Cluster Roles", component: ClusterRoles, url: routes.clusterRolesURL(), @@ -41,7 +42,7 @@ export class UserManagement extends React.Component { } if (isAllowedResource("roles")) { - tabRoutes.push({ + tabs.push({ title: "Roles", component: Roles, url: routes.rolesURL(), @@ -50,7 +51,7 @@ export class UserManagement extends React.Component { } if (isAllowedResource("clusterrolebindings")) { - tabRoutes.push({ + tabs.push({ title: "Cluster Role Bindings", component: ClusterRoleBindings, url: routes.clusterRoleBindingsURL(), @@ -59,7 +60,7 @@ export class UserManagement extends React.Component { } if (isAllowedResource("rolebindings")) { - tabRoutes.push({ + tabs.push({ title: "Role Bindings", component: RoleBindings, url: routes.roleBindingsURL(), @@ -68,7 +69,7 @@ export class UserManagement extends React.Component { } if (isAllowedResource("podsecuritypolicies")) { - tabRoutes.push({ + tabs.push({ title: "Pod Security Policies", component: PodSecurityPolicies, url: routes.podSecurityPoliciesURL(), @@ -76,12 +77,15 @@ export class UserManagement extends React.Component { }); } - return tabRoutes; - } - - render() { - return ( - - ); - } + return tabs; + }); } + +const userManagementRouteTabsInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default userManagementRouteTabsInjectable; diff --git a/src/renderer/components/+user-management/route.tsx b/src/renderer/components/+user-management/route.tsx new file mode 100644 index 0000000000..360b0f57d0 --- /dev/null +++ b/src/renderer/components/+user-management/route.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./user-management.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import userManagementRouteTabsInjectable from "./route-tabs.injectable"; + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedUserManagementRoute = observer(({ routes }: Dependencies) => ( + +)); + +export const UserManagementRoute = withInjectables(NonInjectedUserManagementRoute, { + getProps: (di, props) => ({ + routes: di.inject(userManagementRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+user-management/sidebar-item.tsx b/src/renderer/components/+user-management/sidebar-item.tsx new file mode 100644 index 0000000000..47977cc3c1 --- /dev/null +++ b/src/renderer/components/+user-management/sidebar-item.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { usersManagementRoute, usersManagementURL } from "../../../common/routes"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import userManagementRouteTabsInjectable from "./route-tabs.injectable"; + +export interface UserManagementSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedUserManagementSidebarItem = observer(({ routes }: Dependencies & UserManagementSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const UserManagementSidebarItem = withInjectables(NonInjectedUserManagementSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(userManagementRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index ad2f02c5ca..6862695ccf 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -9,73 +9,49 @@ import React from "react"; import { observer } from "mobx-react"; import { OverviewWorkloadStatus } from "./overview-workload-status"; import { Link } from "react-router-dom"; -import { workloadStores } from "../+workloads"; -import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store"; import type { KubeResource } from "../../../common/rbac"; -import { ResourceNames } from "../../utils/rbac"; -import { boundMethod } from "../../utils"; -import { workloadURL } from "../../../common/routes"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; import { withInjectables } from "@ogre-tools/injectable-react"; -import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; +import type { IComputedValue } from "mobx"; +import workloadsInjectable from "./workloads.injectable"; -const resources: KubeResource[] = [ - "pods", - "deployments", - "statefulsets", - "daemonsets", - "replicasets", - "jobs", - "cronjobs", -]; +export interface OverviewStatusesProps {} + +interface Workload { + resource: KubeResource; + amountOfItems: number; + href: string; + status: Record; + title: string +} interface Dependencies { - namespaceStore: NamespaceStore + workloads: IComputedValue; } -@observer -class NonInjectedOverviewStatuses extends React.Component { - @boundMethod - renderWorkload(resource: KubeResource): React.ReactElement { - const store = workloadStores.get(resource); +const NonInjectedOverviewStatuses = observer( + ({ workloads }: Dependencies & OverviewStatusesProps) => ( +
+
+ {workloads.get() + .map(({ resource, title, href, status, amountOfItems }) => ( +
+
+ + {title} ({amountOfItems}) + +
- if (!store) { - return null; - } - - const items = store.getAllByNs(this.props.namespaceStore.contextNamespaces); - - return ( -
-
- {ResourceNames[resource]} ({items.length}) -
- + +
+ ))}
- ); - } - - render() { - const workloads = resources - .filter(isAllowedResource) - .map(this.renderWorkload); - - return ( -
-
- {workloads} -
-
- ); - } -} - -export const OverviewStatuses = withInjectables( - NonInjectedOverviewStatuses, - - { - getProps: (di) => ({ - namespaceStore: di.inject(namespaceStoreInjectable), - }), - }, +
+ ), ); + +export const OverviewStatuses = withInjectables(NonInjectedOverviewStatuses, { + getProps: (di, props) => ({ + workloads: di.inject(workloadsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+workloads-overview/workloads.injectable.ts b/src/renderer/components/+workloads-overview/workloads.injectable.ts new file mode 100644 index 0000000000..013a195816 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads.injectable.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import type { KubeResource } from "../../../common/rbac"; +import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import { podsStore } from "../+workloads-pods/pods.store"; +import { deploymentStore } from "../+workloads-deployments/deployments.store"; +import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store"; +import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; +import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; +import { jobStore } from "../+workloads-jobs/job.store"; +import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; +import { workloads } from "./workloads"; + +const workloadsInjectable = getInjectable({ + instantiate: (di) => + workloads({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + namespaceStore: di.inject(namespaceStoreInjectable), + + workloadStores: new Map>([ + ["pods", podsStore], + ["deployments", deploymentStore], + ["daemonsets", daemonSetStore], + ["statefulsets", statefulSetStore], + ["replicasets", replicaSetStore], + ["jobs", jobStore], + ["cronjobs", cronJobStore], + ]), + }), + + lifecycle: lifecycleEnum.singleton, +}); + +export default workloadsInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads.ts b/src/renderer/components/+workloads-overview/workloads.ts new file mode 100644 index 0000000000..f3db713e63 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { computed } from "mobx"; +import type { KubeResource } from "../../../common/rbac"; +import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import { workloadURL } from "../../../common/routes"; +import { ResourceNames } from "../../utils/rbac"; +import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; + +interface Dependencies { + workloadStores: Map>; + isAllowedResource: IsAllowedResource; + namespaceStore: NamespaceStore; +} + +export const workloads = ({ + workloadStores, + isAllowedResource, + namespaceStore, +}: Dependencies) => + computed(() => + [...workloadStores.entries()] + .filter(([resource]) => isAllowedResource(resource)) + .map(([resource, store]) => { + const items = store.getAllByNs(namespaceStore.contextNamespaces); + + return { + resource, + href: workloadURL[resource](), + amountOfItems: items.length, + status: store.getStatuses(items), + title: ResourceNames[resource], + }; + }), + ); diff --git a/src/renderer/components/+workloads/index.ts b/src/renderer/components/+workloads/index.ts deleted file mode 100644 index 502ff98107..0000000000 --- a/src/renderer/components/+workloads/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./workloads"; -export * from "./workloads.stores"; diff --git a/src/renderer/components/+workloads/workloads.tsx b/src/renderer/components/+workloads/route-tabs.injectable.ts similarity index 76% rename from src/renderer/components/+workloads/workloads.tsx rename to src/renderer/components/+workloads/route-tabs.injectable.ts index aaa9ba6209..e31c171d38 100644 --- a/src/renderer/components/+workloads/workloads.tsx +++ b/src/renderer/components/+workloads/route-tabs.injectable.ts @@ -2,11 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -import "./workloads.scss"; - -import React from "react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { TabLayoutRoute } from "../layout/tab-layout"; import { WorkloadsOverview } from "../+workloads-overview/overview"; import { Pods } from "../+workloads-pods"; import { Deployments } from "../+workloads-deployments"; @@ -14,12 +12,17 @@ import { DaemonSets } from "../+workloads-daemonsets"; import { StatefulSets } from "../+workloads-statefulsets"; import { Jobs } from "../+workloads-jobs"; import { CronJobs } from "../+workloads-cronjobs"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; import { ReplicaSets } from "../+workloads-replicasets"; import * as routes from "../../../common/routes"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -export class Workloads extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +function getRouteTabs({ isAllowedResource }: Dependencies) { + return computed(() => { const tabs: TabLayoutRoute[] = [ { title: "Overview", @@ -93,11 +96,14 @@ export class Workloads extends React.Component { } return tabs; - } - - render() { - return ( - - ); - } + }); } + +const workloadsRouteTabsInjectable = getInjectable({ + instantiate: (di) => getRouteTabs({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + }), + lifecycle: lifecycleEnum.singleton, +}); + +export default workloadsRouteTabsInjectable; diff --git a/src/renderer/components/+workloads/route.tsx b/src/renderer/components/+workloads/route.tsx new file mode 100644 index 0000000000..9778ef5ee9 --- /dev/null +++ b/src/renderer/components/+workloads/route.tsx @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./workloads.scss"; + +import React from "react"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import workloadsRouteTabsInjectable from "./route-tabs.injectable"; + +export interface WorkloadsRouteProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedWorkloadsRoute = observer(({ routes }: Dependencies & WorkloadsRouteProps) => ( + +)); + +export const WorkloadsRoute = withInjectables(NonInjectedWorkloadsRoute, { + getProps: (di, props) => ({ + routes: di.inject(workloadsRouteTabsInjectable), + ...props, + }), +}); + diff --git a/src/renderer/components/+workloads/sidebar-item.tsx b/src/renderer/components/+workloads/sidebar-item.tsx new file mode 100644 index 0000000000..36b9728bc7 --- /dev/null +++ b/src/renderer/components/+workloads/sidebar-item.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import { workloadsRoute, workloadsURL } from "../../../common/routes"; +import { isActiveRoute } from "../../navigation"; +import { Icon } from "../icon"; +import { SidebarItem } from "../layout/sidebar-item"; +import type { TabLayoutRoute } from "../layout/tab-layout"; +import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; +import workloadsRouteTabsInjectable from "./route-tabs.injectable"; + +export interface WorkloadSidebarItemProps {} + +interface Dependencies { + routes: IComputedValue; +} + +const NonInjectedWorkloadsSidebarItem = observer(({ routes }: Dependencies & WorkloadSidebarItemProps) => { + const tabRoutes = routes.get(); + + return ( + } + > + + + ); +}); + +export const WorkloadsSidebarItem = withInjectables(NonInjectedWorkloadsSidebarItem, { + getProps: (di, props) => ({ + routes: di.inject(workloadsRouteTabsInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/+workloads/workloads.stores.ts b/src/renderer/components/+workloads/workloads.stores.ts deleted file mode 100644 index 8fb00c5a17..0000000000 --- a/src/renderer/components/+workloads/workloads.stores.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import { podsStore } from "../+workloads-pods/pods.store"; -import { deploymentStore } from "../+workloads-deployments/deployments.store"; -import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store"; -import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; -import { jobStore } from "../+workloads-jobs/job.store"; -import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; -import type { KubeResource } from "../../../common/rbac"; -import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; - -export const workloadStores = new Map>([ - ["pods", podsStore], - ["deployments", deploymentStore], - ["daemonsets", daemonSetStore], - ["statefulsets", statefulSetStore], - ["replicasets", replicaSetStore], - ["jobs", jobStore], - ["cronjobs", cronJobStore], -]); diff --git a/src/renderer/components/dock/install-chart/view.tsx b/src/renderer/components/dock/install-chart/view.tsx index f162ae13a3..90a02a4a79 100644 --- a/src/renderer/components/dock/install-chart/view.tsx +++ b/src/renderer/components/dock/install-chart/view.tsx @@ -27,7 +27,7 @@ import type { IReleaseCreatePayload, IReleaseUpdateDetails } from "../../../../c import { withInjectables } from "@ogre-tools/injectable-react"; import installChartTabStoreInjectable from "./store.injectable"; import dockStoreInjectable from "../dock/store.injectable"; -import createReleaseInjectable from "../../+apps-releases/create-release/create-release.injectable"; +import createReleaseInjectable from "../../+helm-releases/create-release/create-release.injectable"; import { Notifications } from "../../notifications"; interface Props { diff --git a/src/renderer/components/dock/upgrade-chart/view.tsx b/src/renderer/components/dock/upgrade-chart/view.tsx index 9e5b0496a4..6309262195 100644 --- a/src/renderer/components/dock/upgrade-chart/view.tsx +++ b/src/renderer/components/dock/upgrade-chart/view.tsx @@ -15,13 +15,13 @@ import type { UpgradeChartTabStore } from "./store"; import { Spinner } from "../../spinner"; import { Badge } from "../../badge"; import { EditorPanel } from "../editor-panel"; -import { helmChartStore, IChartVersion } from "../../+apps-helm-charts/helm-chart.store"; +import { helmChartStore, IChartVersion } from "../../+helm-charts/helm-chart.store"; import type { HelmRelease, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api"; import { Select, SelectOption } from "../../select"; import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react"; import upgradeChartTabStoreInjectable from "./store.injectable"; -import updateReleaseInjectable from "../../+apps-releases/update-release/update-release.injectable"; -import releasesInjectable from "../../+apps-releases/releases.injectable"; +import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable"; +import releasesInjectable from "../../+helm-releases/releases.injectable"; interface Props { className?: string; diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 3600eb482f..69d3cbfda4 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -7,99 +7,34 @@ import styles from "./sidebar.module.scss"; import type { TabLayoutRoute } from "./tab-layout"; import React from "react"; -import { disposeOnUnmount, observer } from "mobx-react"; -import { cssNames, Disposer } from "../../utils"; -import { Icon } from "../icon"; -import { Workloads } from "../+workloads"; -import { UserManagement } from "../+user-management"; -import { Storage } from "../+storage"; -import { Network } from "../+network"; -import { crdStore } from "../+custom-resources/crd.store"; -import { CustomResources } from "../+custom-resources/custom-resources"; +import { observer } from "mobx-react"; +import { cssNames } from "../../utils"; import { isActiveRoute } from "../../navigation"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; -import { Spinner } from "../spinner"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry, ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries"; import { SidebarItem } from "./sidebar-item"; -import { Apps } from "../+apps"; -import * as routes from "../../../common/routes"; -import { Config } from "../+config"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { SidebarCluster } from "./sidebar-cluster"; -import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import kubeWatchApiInjectable - from "../../kube-watch-api/kube-watch-api.injectable"; +import { TabRoutesSidebarItems } from "./tab-routes-sidebar-items"; +import { ConfigSidebarItem } from "../+config/sidebar-item"; +import { ClusterSidebarItem } from "../+cluster/sidebar-item"; +import { NodesSidebarItem } from "../+nodes/sidebar-item"; +import { WorkloadsSidebarItem } from "../+workloads/sidebar-item"; +import { NetworkSidebarItem } from "../+network/sidebar-item"; +import { StorageSidebarItem } from "../+storage/sidebar-item"; +import { NamespacesSidebarItem } from "../+namespaces/sidebar-item"; +import { EventsSidebarItem } from "../+events/sidebar-item"; +import { HelmSidebarItem } from "../+helm/sidebar-item"; +import { UserManagementSidebarItem } from "../+user-management/sidebar-item"; +import { CustomResourcesSidebarItem } from "../+custom-resources/sidebar-item"; -interface Props { +interface SidebarProps { className?: string; } -interface Dependencies { - subscribeStores: (stores: KubeObjectStore[]) => Disposer -} - @observer -class NonInjectedSidebar extends React.Component { +export class Sidebar extends React.Component { static displayName = "Sidebar"; - componentDidMount() { - disposeOnUnmount(this, [ - this.props.subscribeStores([ - crdStore, - ]), - ]); - } - - renderCustomResources() { - if (crdStore.isLoading) { - return ( -
- -
- ); - } - - return Object.entries(crdStore.groups).map(([group, crds]) => { - const id = `crd-group:${group}`; - const crdGroupsPageUrl = routes.crdURL({ query: { groups: group }}); - - return ( - - {crds.map((crd) => ( - - ))} - - ); - }); - } - - renderTreeFromTabRoutes(tabRoutes: TabLayoutRoute[] = []): React.ReactNode { - if (!tabRoutes.length) { - return null; - } - - return tabRoutes.map(({ title, routePath, url = routePath, exact = true }) => { - const subMenuItemId = `tab-route-item-${url}`; - - return ( - - ); - }); - } - getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] { if (!menu.id) { return []; @@ -169,7 +104,7 @@ class NonInjectedSidebar extends React.Component { text={menuItem.title} icon={} > - {this.renderTreeFromTabRoutes(tabRoutes)} + ); }); @@ -186,122 +121,20 @@ class NonInjectedSidebar extends React.Component {
- } - /> - } - /> - } - > - {this.renderTreeFromTabRoutes(Workloads.tabRoutes)} - - } - > - {this.renderTreeFromTabRoutes(Config.tabRoutes)} - - } - > - {this.renderTreeFromTabRoutes(Network.tabRoutes)} - - } - > - {this.renderTreeFromTabRoutes(Storage.tabRoutes)} - - } - /> - } - /> - } - > - {this.renderTreeFromTabRoutes(Apps.tabRoutes)} - - } - > - {this.renderTreeFromTabRoutes(UserManagement.tabRoutes)} - - } - > - {this.renderTreeFromTabRoutes(CustomResources.tabRoutes)} - {this.renderCustomResources()} - + + + + + + + + + + + {this.renderRegisteredMenus()}
); } } - -export const Sidebar = withInjectables( - NonInjectedSidebar, - - { - getProps: (di, props) => ({ - subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores, - ...props, - }), - }, -); diff --git a/src/renderer/components/layout/tab-routes-sidebar-items.tsx b/src/renderer/components/layout/tab-routes-sidebar-items.tsx new file mode 100644 index 0000000000..8445cae1f1 --- /dev/null +++ b/src/renderer/components/layout/tab-routes-sidebar-items.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { isActiveRoute } from "../../navigation"; +import { SidebarItem } from "./sidebar-item"; +import type { TabLayoutRoute } from "./tab-layout"; + +export interface SidebarTreeProps { + routes: TabLayoutRoute[]; +} + +function withId(src: TabLayoutRoute) { + return { + ...src, + id: `tab-route-item-${src.url ?? src.routePath}`, + }; +} + +export const TabRoutesSidebarItems = ({ routes }: SidebarTreeProps) => ( + <> + { + routes + .map(withId) + .map(({ title, routePath, url = routePath, exact = true, id }) => ( + + )) + } + +); diff --git a/src/renderer/frames/cluster-frame/cluster-frame.tsx b/src/renderer/frames/cluster-frame/cluster-frame.tsx index 9ef25ab844..9392b9f4c8 100755 --- a/src/renderer/frames/cluster-frame/cluster-frame.tsx +++ b/src/renderer/frames/cluster-frame/cluster-frame.tsx @@ -3,17 +3,16 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import React from "react"; -import { observable, makeObservable } from "mobx"; +import { makeObservable, computed } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { Redirect, Route, Router, Switch } from "react-router"; -import { UserManagement } from "../../components/+user-management/user-management"; +import { UserManagementRoute } from "../../components/+user-management/route"; import { ConfirmDialog } from "../../components/confirm-dialog"; import { ClusterOverview } from "../../components/+cluster/cluster-overview"; import { Events } from "../../components/+events/events"; import { DeploymentScaleDialog } from "../../components/+workloads-deployments/deployment-scale-dialog"; import { CronJobTriggerDialog } from "../../components/+workloads-cronjobs/cronjob-trigger-dialog"; -import { CustomResources } from "../../components/+custom-resources/custom-resources"; -import { isAllowedResource } from "../../../common/utils/allowed-resource"; +import { CustomResourcesRoute } from "../../components/+custom-resources/route"; import { ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries/page-registry"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../../../extensions/registries"; import { StatefulSetScaleDialog } from "../../components/+workloads-statefulsets/statefulset-scale-dialog"; @@ -28,13 +27,12 @@ import { KubeObjectDetails } from "../../components/kube-object-details"; import { KubeConfigDialog } from "../../components/kubeconfig-dialog"; import { Sidebar } from "../../components/layout/sidebar"; import { Dock } from "../../components/dock"; -import { Apps } from "../../components/+apps"; -import { Namespaces } from "../../components/+namespaces"; -import { Network } from "../../components/+network"; -import { Nodes } from "../../components/+nodes"; -import { Workloads } from "../../components/+workloads"; -import { Config } from "../../components/+config"; -import { Storage } from "../../components/+storage"; +import { NamespacesRoute } from "../../components/+namespaces/route"; +import { NetworkRoute } from "../../components/+network/route"; +import { NodesRoute } from "../../components/+nodes/route"; +import { WorkloadsRoute } from "../../components/+workloads/route"; +import { ConfigRoute } from "../../components/+config/route"; +import { StorageRoute } from "../../components/+storage/route"; import { watchHistoryState } from "../../remote-helpers/history-updater"; import { PortForwardDialog } from "../../port-forward"; import { DeleteClusterDialog } from "../../components/delete-cluster-dialog"; @@ -42,19 +40,24 @@ import type { NamespaceStore } from "../../components/+namespaces/namespace-stor import { withInjectables } from "@ogre-tools/injectable-react"; import namespaceStoreInjectable from "../../components/+namespaces/namespace-store/namespace-store.injectable"; import type { ClusterId } from "../../../common/cluster-types"; -import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable"; +import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable"; import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type { Disposer } from "../../../common/utils"; import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable"; import historyInjectable from "../../navigation/history.injectable"; import type { History } from "history"; +import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; +import { HelmRoute } from "../../components/+helm/route"; +import type { KubeResource } from "../../../common/rbac"; interface Dependencies { - history: History, - namespaceStore: NamespaceStore - hostedClusterId: ClusterId - subscribeStores: (stores: KubeObjectStore[]) => Disposer + history: History; + namespaceStore: NamespaceStore; + hostedClusterId: ClusterId; + subscribeStores: (stores: KubeObjectStore[]) => Disposer; + isAllowedResource: IsAllowedResource; } @observer @@ -75,7 +78,13 @@ class NonInjectedClusterFrame extends React.Component { ]); } - @observable startUrl = isAllowedResource(["events", "nodes", "pods"]) ? routes.clusterURL() : routes.workloadsURL(); + @computed get startUrl() { + const resources : KubeResource[] = ["events", "nodes", "pods"]; + + return resources.every(x => this.props.isAllowedResource(x)) + ? routes.clusterURL() + : routes.workloadsURL(); + } getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) { const routes: TabLayoutRoute[] = []; @@ -138,18 +147,17 @@ class NonInjectedClusterFrame extends React.Component { } footer={}> - - - - - - - + + + + + + - - - + + + {this.renderExtensionTabLayoutRoutes()} {this.renderExtensionRoutes()} @@ -185,5 +193,6 @@ export const ClusterFrame = withInjectables(NonInjectedClusterFram namespaceStore: di.inject(namespaceStoreInjectable), hostedClusterId: di.inject(hostedClusterInjectable).id, subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores, + isAllowedResource: di.inject(isAllowedResourceInjectable), }), }); diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index 7553877515..d8767fea27 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -7,7 +7,7 @@ import { initClusterFrame } from "./init-cluster-frame"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable"; import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable"; -import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable"; +import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster.injectable"; import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable"; diff --git a/src/renderer/initializers/kube-object-detail-registry.tsx b/src/renderer/initializers/kube-object-detail-registry.tsx index eef56b16b5..74dff0a7b5 100644 --- a/src/renderer/initializers/kube-object-detail-registry.tsx +++ b/src/renderer/initializers/kube-object-detail-registry.tsx @@ -19,7 +19,7 @@ import { EndpointDetails } from "../components/+network-endpoints"; import { IngressDetails } from "../components/+network-ingresses"; import { NetworkPolicyDetails } from "../components/+network-policies"; import { ServiceDetails } from "../components/+network-services"; -import { NodeDetails } from "../components/+nodes"; +import { NodeDetails } from "../components/+nodes/details"; import { PodSecurityPolicyDetails } from "../components/+pod-security-policies"; import { StorageClassDetails } from "../components/+storage-classes"; import { PersistentVolumeClaimDetails } from "../components/+storage-volume-claims"; diff --git a/src/renderer/initializers/workload-events.tsx b/src/renderer/initializers/workload-events.tsx new file mode 100644 index 0000000000..be58a5e39f --- /dev/null +++ b/src/renderer/initializers/workload-events.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { withInjectables } from "@ogre-tools/injectable-react"; +import { observer } from "mobx-react"; +import React from "react"; +import type { IsAllowedResource } from "../../common/utils/is-allowed-resource.injectable"; +import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; +import { Events } from "../components/+events/events"; + +export interface WorkloadEventsProps {} + +interface Dependencies { + isAllowedResource: IsAllowedResource; +} + +const NonInjectedWorkloadEvents = observer(({ isAllowedResource }: Dependencies & WorkloadEventsProps) => { + if (!isAllowedResource("events")) { + return null; + } + + return ( + + ); +}); + +export const WorkloadEvents = withInjectables(NonInjectedWorkloadEvents, { + getProps: (di, props) => ({ + isAllowedResource: di.inject(isAllowedResourceInjectable), + ...props, + }), +}); diff --git a/src/renderer/initializers/workloads-overview-detail-registry.tsx b/src/renderer/initializers/workloads-overview-detail-registry.tsx index 9ab0e17ccb..5654281b6b 100644 --- a/src/renderer/initializers/workloads-overview-detail-registry.tsx +++ b/src/renderer/initializers/workloads-overview-detail-registry.tsx @@ -3,11 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import React from "react"; -import { isAllowedResource } from "../../common/utils/allowed-resource"; import { WorkloadsOverviewDetailRegistry } from "../../extensions/registries"; -import { Events } from "../components/+events"; import { OverviewStatuses } from "../components/+workloads-overview/overview-statuses"; +import { WorkloadEvents } from "./workload-events"; export function initWorkloadsOverviewDetailRegistry() { WorkloadsOverviewDetailRegistry.getInstance() @@ -20,9 +18,7 @@ export function initWorkloadsOverviewDetailRegistry() { { priority: 5, components: { - Details: () => ( - isAllowedResource("events") && - ), + Details: WorkloadEvents, }, }, ]); diff --git a/src/renderer/kube-watch-api/kube-watch-api.ts b/src/renderer/kube-watch-api/kube-watch-api.ts index f38ffcec1c..6652d4f15f 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { comparer, reaction } from "mobx"; -import { disposer, Disposer, noop } from "../../common/utils"; +import { disposer, Disposer, noop, WrappedAbortController } from "../../common/utils"; import type { KubeObject } from "../../common/k8s-api/kube-object"; import AbortController from "abort-controller"; import { once } from "lodash"; @@ -14,16 +14,6 @@ import logger from "../../common/logger"; // Kubernetes watch-api client // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams -class WrappedAbortController extends AbortController { - constructor(protected parent: AbortController) { - super(); - - parent.signal.addEventListener("abort", () => { - this.abort(); - }); - } -} - interface SubscribeStoreParams { store: KubeObjectStore; parent: AbortController; @@ -58,7 +48,7 @@ class WatchCount { throw new Error(`Cannot dec count more times than it has been inc: ${store.api.objectConstructor.kind}`); } - logger.debug(`[KUBE-WATCH-API]: dec() count for ${store.api.objectConstructor.apiBase} is now ${newCount}`); + logger.info(`[KUBE-WATCH-API]: dec() count for ${store.api.objectConstructor.apiBase} is now ${newCount}`); this.#data.set(store, newCount); return newCount; @@ -83,6 +73,8 @@ interface Dependencies { clusterFrameContext: ClusterFrameContext } +export type SubscribeStores = (stores: KubeObjectStore[], opts?: KubeWatchSubscribeStoreOptions) => Disposer; + export class KubeWatchApi { #watch = new WatchCount(); @@ -153,7 +145,7 @@ export class KubeWatchApi { }; } - subscribeStores = (stores: KubeObjectStore[], { namespaces, onLoadFailure }: KubeWatchSubscribeStoreOptions = {}): Disposer => { + subscribeStores: SubscribeStores = (stores, { namespaces, onLoadFailure } = {}) => { const parent = new AbortController(); const unsubscribe = disposer( ...stores.map(store => this.subscribeStore({ From a4ce650b3db8a80eed96a72783880f5e62f68421 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 3 Feb 2022 16:12:51 +0200 Subject: [PATCH 14/22] Use electronegativity to identify misconfigurations and security issues (#4783) --- .github/workflows/electronegativity.yml | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/electronegativity.yml diff --git a/.github/workflows/electronegativity.yml b/.github/workflows/electronegativity.yml new file mode 100644 index 0000000000..6c52ed144f --- /dev/null +++ b/.github/workflows/electronegativity.yml @@ -0,0 +1,26 @@ +on: + push: + +name: Electronegativity +on: + - pull_request +jobs: + build_job: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + + - uses: doyensec/electronegativity-action@v1.1 + with: + input: src/ + electron-version: "14.2.4" + severity: medium + + - name: Upload sarif + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: ../results From 2f7994a16d9928bf791b9771c17b6e915228959f Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Thu, 3 Feb 2022 09:13:02 -0500 Subject: [PATCH 15/22] adding some rough notes to release guide to help with making changelogs (#4787) --- RELEASE_GUIDE.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/RELEASE_GUIDE.md b/RELEASE_GUIDE.md index 50f8b759e2..32dbfe8a74 100644 --- a/RELEASE_GUIDE.md +++ b/RELEASE_GUIDE.md @@ -6,7 +6,7 @@ Lens releases are built by CICD automatically on git tags. The typical release p 1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors. -This can be helped (if you have the `gh` CLI installed) by running: +This can be helped (if you have the `gh` CLI and `jq` installed) by running: ``` gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map(.merge_commit_sha) | join(" ")' ``` @@ -36,11 +36,12 @@ But you will probably need to verify that all the PRs have correct milestones. gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' | pbcopy ``` - And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` with `. != "bug && . != "enhancement"` for maintanence sections. + And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` instead of `any()` with `. != "bug and . != "enhancement"` for maintanence sections. ``` | select(any(.labels | map(.name)[]; . == "bug")) ``` + See notes below for ideas for building changelogs. 1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3. 1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like: @@ -64,3 +65,31 @@ Other tasks - announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process) - announce on lens-hq that master is open for PR merges for the next release (for major/minor releases) - update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases) + + + ### Rough Notes + + To get all of the PRs since a certain date you can use something like: + ``` + gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100&q=committer-date:>YYYY-MM-DD" > prs.json + ``` + +If there are more than 100 PRs you'll need to rerun for multiple pages: +``` + gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100&page=2&q=committer-date:>YYYY-MM-DD" >> prs.json +``` + +To get all the bug PRs: +``` +jq -r '[.[] | select((.merged_at | type) == "string") | select(any(.labels | map(.name)[]; . == "bug"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json +``` + +To get all the enhancement PRs: +``` +jq -r '[.[] | select((.merged_at | type) == "string") | select(any(.labels | map(.name)[]; . == "enhancement"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json +``` + +To get all of the maintenance PRs: +``` +jq -r '[.[] | select((.merged_at | type) == "string") | select(all(.labels | map(.name)[]; . != "bug" and . != "enhancement" and . != "skip-changelog"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json +``` \ No newline at end of file From 2b952e193ede738c47262c847abc106fc6db5157 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 3 Feb 2022 09:36:45 -0500 Subject: [PATCH 16/22] Fix electronegativity workflow config (#4809) --- .github/workflows/electronegativity.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/electronegativity.yml b/.github/workflows/electronegativity.yml index 6c52ed144f..059894530b 100644 --- a/.github/workflows/electronegativity.yml +++ b/.github/workflows/electronegativity.yml @@ -1,9 +1,11 @@ -on: - push: - name: Electronegativity on: - - pull_request + push: + branches: + - master + pull_request: + branches: + - master jobs: build_job: runs-on: ubuntu-latest @@ -12,7 +14,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: "14" - uses: doyensec/electronegativity-action@v1.1 with: From 0017f515ab011691af81d2eb5bd5dddd6d436ca5 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 3 Feb 2022 09:56:45 -0500 Subject: [PATCH 17/22] Fix 'mkdocs deploy master' workflow (#4810) --- .github/workflows/main.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84b8020605..300843633f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,9 @@ on: release: types: - published +concurrency: + group: publish-docs + cancel-in-progress: true jobs: verify-docs: name: Verify docs @@ -17,7 +20,7 @@ jobs: - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: "3.x" - name: Checkout Release from lens uses: actions/checkout@v2 @@ -31,8 +34,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: | @@ -49,7 +52,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: | @@ -64,8 +67,8 @@ jobs: - name: git config run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" - name: Using Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -74,13 +77,13 @@ jobs: - name: Generate Extensions API Reference using typedocs run: | - yarn install - yarn typedocs-extensions-api + yarn install + yarn typedocs-extensions-api - name: mkdocs deploy master if: contains(github.ref, 'refs/heads/master') run: | - mike deploy --push master + mike deploy --push master - name: Get the release version if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease @@ -90,5 +93,5 @@ jobs: - name: mkdocs deploy new release if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease run: | - mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest - mike set-default --push ${{ steps.get_version.outputs.VERSION }} + mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest + mike set-default --push ${{ steps.get_version.outputs.VERSION }} From d44b859c23946f937db03eddf5bd67b0d2f9ac39 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Thu, 3 Feb 2022 16:20:31 -0500 Subject: [PATCH 18/22] release v5.4.0-beta.0 (#4812) Signed-off-by: Jim Ehrismann --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 302511f3aa..86625a52ca 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.4.0-alpha.1", + "version": "5.4.0-beta.0", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", From 0c55fe8452e61446ae6b5833df9c2e1e6d63caf9 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:25:33 -0500 Subject: [PATCH 19/22] release v5.4.0-beta.1 (#4825) Signed-off-by: Jim Ehrismann --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86625a52ca..a224f215ce 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.4.0-beta.0", + "version": "5.4.0-beta.1", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", From 9ea3c94c5ab842832de5ccd75b8c30966ffc741e Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 7 Feb 2022 14:02:06 +0200 Subject: [PATCH 20/22] Upgrade injectable to fix observation of asyncComputed when invalidated before ever observed (#4828) Signed-off-by: Janne Savolainen --- package.json | 4 ++-- yarn.lock | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index a224f215ce..153b37d31d 100644 --- a/package.json +++ b/package.json @@ -194,8 +194,8 @@ "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.16.1", - "@ogre-tools/injectable": "3.2.0", - "@ogre-tools/injectable-react": "3.2.0", + "@ogre-tools/injectable": "3.2.1", + "@ogre-tools/injectable-react": "3.2.1", "@sentry/electron": "^2.5.4", "@sentry/integrations": "^6.15.0", "@types/circular-dependency-plugin": "5.0.4", diff --git a/yarn.lock b/yarn.lock index 1d8e0af6f8..86ba61eb62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -953,19 +953,19 @@ dependencies: lodash "^4.17.21" -"@ogre-tools/injectable-react@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-3.2.0.tgz#468542e846952deb8e7a4f6757da4813ee8f11fa" - integrity sha512-VU5l0uKe86psVzEPbXl1TLlflnoL+uSeOaOCy/mAGzau4nqRb+eA4RzYgzUs/D9tDYzJ7Es+LZWD9uSyXmpyXg== +"@ogre-tools/injectable-react@3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-3.2.1.tgz#b0bff8c32a2eb77653cc4b0496cd123c80cfaae5" + integrity sha512-ufC9bR5nnO/cQJ6TiIxKdg0j2liGPTb6XvuK/0QGeGwd534giMHHL5L4CxF6qRLX6QDQ3FejhOTUn7+DV5uugg== dependencies: "@ogre-tools/fp" "^3.0.0" - "@ogre-tools/injectable" "^3.2.0" + "@ogre-tools/injectable" "^3.2.1" lodash "^4.17.21" -"@ogre-tools/injectable@3.2.0", "@ogre-tools/injectable@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-3.2.0.tgz#7d8f653cb3a2c0253a29422bcffd5123308600a9" - integrity sha512-aRlRdvLefJMBvFu1tRlTGNgpMbqE250lwMXT8Y6/ruC88rCL+TygCWcsJELad1OgqX1cfHkCgCYeQeohV+G3Zg== +"@ogre-tools/injectable@3.2.1", "@ogre-tools/injectable@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-3.2.1.tgz#30ca60f47b84c91219acb333d4342df4ccd2827c" + integrity sha512-IEG3IIYTrTkKemDkxcI+qL8EDDQB3RS8k1qxy4obQ9g3SAURWUfB1j02dBbcIYmtOHEXWtP3AY57HE5sPG0prQ== dependencies: "@ogre-tools/fp" "^3.0.0" lodash "^4.17.21" From 06ec1b39a422c6f8ef52f27cd14eb88f29241aae Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 7 Feb 2022 09:07:09 -0500 Subject: [PATCH 21/22] Fix not being able to install extensions sometimes (#4794) --- .../attempt-install/attempt-install.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/+extensions/attempt-install/attempt-install.tsx b/src/renderer/components/+extensions/attempt-install/attempt-install.tsx index eccd75da3e..97b5f46ad2 100644 --- a/src/renderer/components/+extensions/attempt-install/attempt-install.tsx +++ b/src/renderer/components/+extensions/attempt-install/attempt-install.tsx @@ -12,7 +12,7 @@ import { Button } from "../../button"; import type { ExtensionLoader } from "../../../../extensions/extension-loader"; import type { LensExtensionId } from "../../../../extensions/lens-extension"; import React from "react"; -import fse from "fs-extra"; +import { remove as removeDir } from "fs-extra"; import { shell } from "electron"; import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate"; import type { InstallRequest } from "./install-request"; @@ -80,17 +80,12 @@ export const attemptInstall = } const extensionFolder = getExtensionDestFolder(name); - const folderExists = await fse.pathExists(extensionFolder); + const installedExtension = extensionLoader.getExtension(validatedRequest.id); - if (!folderExists) { - // install extension if not yet exists - await unpackExtension(validatedRequest, dispose); - } else { - const { - manifest: { version: oldVersion }, - } = extensionLoader.getExtension(validatedRequest.id); + if (installedExtension) { + const { version: oldVersion } = installedExtension.manifest; - // otherwise confirmation required (re-install / update) + // confirm to uninstall old version before installing new version const removeNotification = Notifications.info(
@@ -130,5 +125,11 @@ export const attemptInstall = onClose: dispose, }, ); + } else { + // clean up old data if still around + await removeDir(extensionFolder); + + // install extension if not yet exists + await unpackExtension(validatedRequest, dispose); } }; From 4abe0d6c200a05dc0d40d848140a112cecaa26bb Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 7 Feb 2022 10:40:20 -0500 Subject: [PATCH 22/22] Only group delete visible items (#4798) --- src/common/item.store.ts | 14 +++++--- src/common/k8s-api/kube-object.store.ts | 4 +++ .../components/+helm-releases/releases.tsx | 10 +++--- .../components/item-object-list/content.tsx | 32 +++++++++++++------ .../item-object-list/list-layout.tsx | 11 +++---- .../kube-object-list-layout.tsx | 27 +++++----------- .../port-forward-store/port-forward-store.ts | 4 +-- 7 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/common/item.store.ts b/src/common/item.store.ts index fee402491b..68ef6813b9 100644 --- a/src/common/item.store.ts +++ b/src/common/item.store.ts @@ -21,7 +21,7 @@ export abstract class ItemStore { @observable isLoading = false; @observable isLoaded = false; @observable items = observable.array([], { deep: false }); - @observable selectedItemsIds = observable.map(); + @observable selectedItemsIds = observable.set(); constructor() { makeObservable(this); @@ -29,7 +29,11 @@ export abstract class ItemStore { } @computed get selectedItems(): Item[] { - return this.items.filter(item => this.selectedItemsIds.get(item.getId())); + return this.pickOnlySelected(this.items); + } + + public pickOnlySelected(items: Item[]): Item[] { + return items.filter(item => this.selectedItemsIds.has(item.getId())); } public getItems(): Item[] { @@ -152,12 +156,12 @@ export abstract class ItemStore { } isSelected(item: Item) { - return !!this.selectedItemsIds.get(item.getId()); + return this.selectedItemsIds.has(item.getId()); } @action select(item: Item) { - this.selectedItemsIds.set(item.getId(), true); + this.selectedItemsIds.add(item.getId()); } @action @@ -207,6 +211,8 @@ export abstract class ItemStore { async removeSelectedItems?(): Promise; + async removeItems?(items: Item[]): Promise; + * [Symbol.iterator]() { yield* this.items; } diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 360229c79f..d1582e8b51 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -369,6 +369,10 @@ export abstract class KubeObjectStore extends ItemStore return Promise.all(this.selectedItems.map(this.remove)); } + async removeItems(items: T[]) { + await Promise.all(items.map(this.remove)); + } + // collect items from watch-api events to avoid UI blowing up with huge streams of data protected eventsBuffer = observable.array>([], { deep: false }); diff --git a/src/renderer/components/+helm-releases/releases.tsx b/src/renderer/components/+helm-releases/releases.tsx index 4d38cb5a20..0017fa1ba8 100644 --- a/src/renderer/components/+helm-releases/releases.tsx +++ b/src/renderer/components/+helm-releases/releases.tsx @@ -134,10 +134,12 @@ class NonInjectedHelmReleases extends Component { return releases.get().filter((release) => release.isSelected); }, - removeSelectedItems() { - return Promise.all( - releases.get().filter((release) => release.isSelected).map((release) => release.delete()), - ); + pickOnlySelected: (releases) => { + return releases.filter(release => release.isSelected); + }, + + removeItems: async (releases) => { + await Promise.all(releases.map(release => release.delete())); }, } as ItemStore; diff --git a/src/renderer/components/item-object-list/content.tsx b/src/renderer/components/item-object-list/content.tsx index 773f70d74f..8877f04e02 100644 --- a/src/renderer/components/item-object-list/content.tsx +++ b/src/renderer/components/item-object-list/content.tsx @@ -136,19 +136,27 @@ export class ItemListLayoutContent extends React.Component } @boundMethod - removeItemsDialog() { + removeItemsDialog(selectedItems: I[]) { const { customizeRemoveDialog, store } = this.props; - const { selectedItems, removeSelectedItems } = store; const visibleMaxNamesCount = 5; const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", "); const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {}; const selectedCount = selectedItems.length; - const tailCount = selectedCount > visibleMaxNamesCount ? selectedCount - visibleMaxNamesCount : 0; - const tail = tailCount > 0 ? <>, and {tailCount} more : null; - const message = selectedCount <= 1 ?

Remove item {selectedNames}?

:

Remove {selectedCount} items {selectedNames}{tail}?

; + const tailCount = selectedCount > visibleMaxNamesCount + ? selectedCount - visibleMaxNamesCount + : 0; + const tail = tailCount > 0 + ? <>, and {tailCount} more + : null; + const message = selectedCount <= 1 + ?

Remove item {selectedNames}?

+ :

Remove {selectedCount} items {selectedNames}{tail}?

; + const onConfirm = store.removeItems + ? () => store.removeItems(selectedItems) + : store.removeSelectedItems; ConfirmDialog.open({ - ok: removeSelectedItems, + ok: onConfirm, labelOk: "Remove", message, ...dialogCustomProps, @@ -225,10 +233,12 @@ export class ItemListLayoutContent extends React.Component render() { const { store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, - detailsItem, className, tableProps = {}, tableId, + detailsItem, className, tableProps = {}, tableId, getItems, } = this.props; const selectedItemId = detailsItem && detailsItem.getId(); const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type); + const items = getItems(); + const selectedItems = store.pickOnlySelected(items); return (
@@ -238,7 +248,7 @@ export class ItemListLayoutContent extends React.Component selectable={hasDetailsView} sortable={sortingCallbacks} getTableRow={this.getRow} - items={this.props.getItems()} + items={items} selectedItemId={selectedItemId} noItems={this.renderNoItems()} className={classNames} @@ -252,9 +262,11 @@ export class ItemListLayoutContent extends React.Component {() => ( 0 + ? () => this.removeItemsDialog(selectedItems) + : null } - removeTooltip={`Remove selected items (${store.selectedItems.length})`} + removeTooltip={`Remove selected items (${selectedItems.length})`} {...addRemoveButtons} /> )} diff --git a/src/renderer/components/item-object-list/list-layout.tsx b/src/renderer/components/item-object-list/list-layout.tsx index 959f814bce..ca8a4b1293 100644 --- a/src/renderer/components/item-object-list/list-layout.tsx +++ b/src/renderer/components/item-object-list/list-layout.tsx @@ -13,7 +13,7 @@ import { boundMethod, cssNames, IClassName, noop, StorageHelper } from "../../ut import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { ItemObject, ItemStore } from "../../../common/item.store"; import type { SearchInputUrlProps } from "../input"; -import { Filter, FilterType, pageFilters } from "./page-filters.store"; +import { FilterType, pageFilters } from "./page-filters.store"; import { PageFiltersList } from "./page-filters-list"; import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store"; import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; @@ -202,19 +202,18 @@ class NonInjectedItemListLayout extends React.Component(this.filters, ({ type }) => type); - + const filterGroups = groupBy(this.filters, ({ type }) => type); const filterItems: ItemsFilter[] = []; - Object.entries(filterGroups).forEach(([type, filtersGroup]) => { + for (const [type, filtersGroup] of Object.entries(filterGroups)) { const filterCallback = this.filterCallbacks[type] ?? this.props.filterCallbacks?.[type]; if (filterCallback && filtersGroup.length > 0) { filterItems.push(filterCallback); } - }); + } - const items = this.props.getItems ? this.props.getItems() : (this.props.items ?? this.props.store.items); + const items = this.props.getItems?.() ?? this.props.items ?? this.props.store.items; return applyFilters(filterItems.concat(this.props.filterItems), items); } diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index ec0245c512..5ecf4f1425 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -138,25 +138,14 @@ class NonInjectedKubeObjectListLayout extends React.Compon } } -const InjectedKubeObjectListLayout = withInjectables< - Dependencies, - KubeObjectListLayoutProps ->( - NonInjectedKubeObjectListLayout, - - { - getProps: (di, props) => ({ - clusterFrameContext: di.inject(clusterFrameContextInjectable), - subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, - ...props, - }), - }, -); - - -export function KubeObjectListLayout( - props: KubeObjectListLayoutProps, -) { +const InjectedKubeObjectListLayout = withInjectables>(NonInjectedKubeObjectListLayout, { + getProps: (di, props) => ({ + clusterFrameContext: di.inject(clusterFrameContextInjectable), + subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, + ...props, + }), +}); +export function KubeObjectListLayout(props: KubeObjectListLayoutProps) { return ; } diff --git a/src/renderer/port-forward/port-forward-store/port-forward-store.ts b/src/renderer/port-forward/port-forward-store/port-forward-store.ts index 070f6746b7..5ec58002e6 100644 --- a/src/renderer/port-forward/port-forward-store/port-forward-store.ts +++ b/src/renderer/port-forward/port-forward-store/port-forward-store.ts @@ -73,8 +73,8 @@ export class PortForwardStore extends ItemStore { }); } - async removeSelectedItems() { - return Promise.all(this.selectedItems.map(this.remove)); + async removeItems(items: PortForwardItem[]) { + await Promise.all(items.map(this.remove)); } getById(id: string) {