From 9589175604aa690dddda414a801cda4203e6bfee Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Nov 2022 09:13:15 -0800 Subject: [PATCH] Make EntitySettingRegistry fully injectable (#6604) * Make EntitySettingRegistry fully injectable Signed-off-by: Sebastian Malton * Add behavioural tests Signed-off-by: Sebastian Malton * Fix lint Signed-off-by: Sebastian Malton * Revert tsconfig change Signed-off-by: Sebastian Malton * Fix type errors Signed-off-by: Sebastian Malton * Update snapshot Signed-off-by: Sebastian Malton * Improve naming Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../orderable/orderable.ts | 4 + .../extension-loader/extension-loader.ts | 1 - src/extensions/lens-renderer-extension.ts | 3 +- .../registries/entity-setting-registry.ts | 54 - src/extensions/registries/index.ts | 1 - ...-settings-for-correct-entity.test.tsx.snap | 1056 +++++++++++++++++ ...owing-settings-for-correct-entity.test.tsx | 226 ++++ .../navigate-to-preference-tab.injectable.ts | 10 +- .../preferences-navigation-tab.tsx | 26 +- .../preferences-navigation.tsx | 29 +- src/renderer/bootstrap.tsx | 3 - .../extensions-registrator.injectable.ts | 9 +- .../active-tabs.injectable.ts | 79 ++ .../current-entity.injectable.ts | 20 + .../+entity-settings/entity-settings.tsx | 220 ++-- .../extension-registrator.injectable.ts | 75 ++ .../has-settings.injectable.ts | 20 + .../components/+entity-settings/index.ts | 2 - .../general-settings.injectable.tsx | 71 ++ .../metrics-settings.injectable.tsx | 64 + .../namespace-settings.injectable.tsx | 55 + .../node-shell-settings.injectable.tsx | 55 + .../proxy-settings.injectable.tsx | 55 + .../terminal-settings.injectable.tsx | 55 + ...table.ts => route-component.injectable.ts} | 4 +- ...able.ts => route-parameters.injectable.ts} | 0 .../+entity-settings/settings.injectable.ts | 33 + .../components/+entity-settings/token.ts | 11 + .../cluster-local-terminal-settings.test.tsx | 14 +- ...mespaces.tsx => accessible-namespaces.tsx} | 8 +- .../cluster-settings/cluster-settings.tsx | 118 -- .../cluster-settings/components/index.ts | 15 - ...er-icon-settings.tsx => icon-settings.tsx} | 12 +- .../components/cluster-settings/index.ts | 6 - .../cluster-kubeconfig.tsx => kubeconfig.tsx} | 6 +- ...ttings.tsx => local-terminal-settings.tsx} | 29 +- ...etrics-setting.tsx => metrics-setting.tsx} | 12 +- ...ster-name-setting.tsx => name-setting.tsx} | 10 +- ...ell-setting.tsx => node-shell-setting.tsx} | 12 +- ...eus-setting.tsx => prometheus-setting.tsx} | 18 +- ...er-proxy-setting.tsx => proxy-setting.tsx} | 6 +- ...ster-show-metrics.tsx => show-metrics.tsx} | 8 +- .../get-entity-setting-commands.injectable.ts | 21 - .../internal-commands.injectable.tsx | 12 +- .../components/layout/tab-layout-2.tsx | 1 + src/renderer/components/layout/tab-layout.tsx | 2 +- src/renderer/components/tabs/tabs.tsx | 18 +- src/renderer/getDiForUnitTesting.tsx | 4 - .../initializers/entity-settings-registry.ts | 68 -- src/renderer/initializers/index.ts | 1 - src/renderer/initializers/registries.ts | 1 - 51 files changed, 2103 insertions(+), 540 deletions(-) delete mode 100644 src/extensions/registries/entity-setting-registry.ts create mode 100644 src/features/entity-settings/__snapshots__/showing-settings-for-correct-entity.test.tsx.snap create mode 100644 src/features/entity-settings/showing-settings-for-correct-entity.test.tsx create mode 100644 src/renderer/components/+entity-settings/active-tabs.injectable.ts create mode 100644 src/renderer/components/+entity-settings/current-entity.injectable.ts create mode 100644 src/renderer/components/+entity-settings/extension-registrator.injectable.ts create mode 100644 src/renderer/components/+entity-settings/has-settings.injectable.ts create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx create mode 100644 src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx rename src/renderer/components/+entity-settings/{entity-settings-route-component.injectable.ts => route-component.injectable.ts} (87%) rename src/renderer/components/+entity-settings/{entity-settings-route-parameters.injectable.ts => route-parameters.injectable.ts} (100%) create mode 100644 src/renderer/components/+entity-settings/settings.injectable.ts create mode 100644 src/renderer/components/+entity-settings/token.ts rename src/renderer/components/cluster-settings/{components => }/__tests__/cluster-local-terminal-settings.test.tsx (88%) rename src/renderer/components/cluster-settings/{components/cluster-accessible-namespaces.tsx => accessible-namespaces.tsx} (87%) delete mode 100644 src/renderer/components/cluster-settings/cluster-settings.tsx delete mode 100644 src/renderer/components/cluster-settings/components/index.ts rename src/renderer/components/cluster-settings/{components/cluster-icon-settings.tsx => icon-settings.tsx} (88%) delete mode 100644 src/renderer/components/cluster-settings/index.ts rename src/renderer/components/cluster-settings/{components/cluster-kubeconfig.tsx => kubeconfig.tsx} (82%) rename src/renderer/components/cluster-settings/{components/cluster-local-terminal-settings.tsx => local-terminal-settings.tsx} (83%) rename src/renderer/components/cluster-settings/{components/cluster-metrics-setting.tsx => metrics-setting.tsx} (89%) rename src/renderer/components/cluster-settings/{components/cluster-name-setting.tsx => name-setting.tsx} (80%) rename src/renderer/components/cluster-settings/{components/cluster-node-shell-setting.tsx => node-shell-setting.tsx} (89%) rename src/renderer/components/cluster-settings/{components/cluster-prometheus-setting.tsx => prometheus-setting.tsx} (90%) rename src/renderer/components/cluster-settings/{components/cluster-proxy-setting.tsx => proxy-setting.tsx} (89%) rename src/renderer/components/cluster-settings/{components/cluster-show-metrics.tsx => show-metrics.tsx} (90%) delete mode 100644 src/renderer/components/command-palette/registered-commands/get-entity-setting-commands.injectable.ts delete mode 100644 src/renderer/initializers/entity-settings-registry.ts diff --git a/src/common/utils/composable-responsibilities/orderable/orderable.ts b/src/common/utils/composable-responsibilities/orderable/orderable.ts index e0425cc0a7..2b4d95b3f4 100644 --- a/src/common/utils/composable-responsibilities/orderable/orderable.ts +++ b/src/common/utils/composable-responsibilities/orderable/orderable.ts @@ -19,3 +19,7 @@ export const orderByOrderNumber = (maybeOrderables: T[ : Number.MAX_SAFE_INTEGER, maybeOrderables, ); + +export const byOrderNumber = (left: T, right: T) => ( + left.orderNumber - right.orderNumber +); diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index 5c63451276..23840b67c4 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -261,7 +261,6 @@ export class ExtensionLoader { return this.autoInitExtensions(async (ext) => { const extension = ext as LensRendererExtension; const removeItems = [ - registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), ]; diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index d0a2a979e6..fb60d6aed8 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -29,6 +29,7 @@ import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/hand import type { AppPreferenceTabRegistration } from "../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-tab-registration"; import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration"; import type { ClusterFrameChildComponent } from "../renderer/frames/cluster-frame/cluster-frame-child-component-injection-token"; +import type { EntitySettingRegistration } from "../renderer/components/+entity-settings/extension-registrator.injectable"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -38,7 +39,7 @@ export class LensRendererExtension extends LensExtension; -} - -export interface EntitySettingRegistration { - apiVersions: string[]; - kind: string; - title: string; - components: EntitySettingComponents; - source?: string; - id?: string; - priority?: number; - group?: string; -} - -export interface RegisteredEntitySetting extends EntitySettingRegistration { - id: string; -} - -export class EntitySettingRegistry extends BaseRegistry { - getRegisteredItem(item: EntitySettingRegistration): RegisteredEntitySetting { - return { - id: item.id || item.title.toLowerCase(), - ...item, - }; - } - - getItemsForKind(kind: string, apiVersion: string, source?: string) { - let items = this.getItems().filter((item) => { - return item.kind === kind && item.apiVersions.includes(apiVersion); - }); - - if (source) { - items = items.filter((item) => { - return !item.source || item.source === source; - }); - } - - return items.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50)); - } -} diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index f916600903..2841b4c031 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -7,6 +7,5 @@ export * from "./page-registry"; export * from "./page-menu-registry"; -export * from "./entity-setting-registry"; export * from "./catalog-entity-detail-registry"; export * from "./protocol-handler"; diff --git a/src/features/entity-settings/__snapshots__/showing-settings-for-correct-entity.test.tsx.snap b/src/features/entity-settings/__snapshots__/showing-settings-for-correct-entity.test.tsx.snap new file mode 100644 index 0000000000..c00f2637b7 --- /dev/null +++ b/src/features/entity-settings/__snapshots__/showing-settings-for-correct-entity.test.tsx.snap @@ -0,0 +1,1056 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Showing correct entity settings renders 1`] = ` + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+
+ +
+
+

+ Welcome to some-product-name! +

+

+ To get you started we have auto-detected your clusters in your + + kubeconfig file and added them to the catalog, your centralized + + view for managing all your cloud-native resources. +
+
+ If you have any questions or feedback, please join our + + Lens Community slack channel + + . +

+ +
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+ +`; + +exports[`Showing correct entity settings when navigating to local cluster entity settings renders 1`] = ` + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ General +

+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+ +`; + +exports[`Showing correct entity settings when navigating to non-local cluster entity settings renders 1`] = ` + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Proxy +

+
+
+
+ HTTP Proxy + +
+
+ +
+
+ + HTTP Proxy server. Used for communicating with Kubernetes API. + +
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+ +`; + +exports[`Showing correct entity settings when navigating to weblink entity settings renders 1`] = ` + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+ No settings found for + + entity.k8slens.dev/v1alpha1 + / + WebLink +
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+ +`; diff --git a/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx new file mode 100644 index 0000000000..306582a188 --- /dev/null +++ b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx @@ -0,0 +1,226 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DiContainer } from "@ogre-tools/injectable"; +import type { RenderResult } from "@testing-library/react"; +import { KubernetesCluster, WebLink } from "../../common/catalog-entities"; +import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; +import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; +import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import createClusterInjectable from "../../renderer/create-cluster/create-cluster.injectable"; + +describe("Showing correct entity settings", () => { + let builder: ApplicationBuilder; + let rendered: RenderResult; + let windowDi: DiContainer; + let clusterEntity: KubernetesCluster; + let localClusterEntity: KubernetesCluster; + let otherEntity: WebLink; + let cluster: Cluster; + + beforeEach(async () => { + builder = getApplicationBuilder(); + + builder.afterWindowStart((windowDi) => { + const createCluster = windowDi.inject(createClusterInjectable); + + clusterEntity = new KubernetesCluster({ + metadata: { + labels: {}, + name: "some-kubernetes-cluster", + uid: "some-entity-id", + }, + spec: { + kubeconfigContext: "some-context", + kubeconfigPath: "/some/path/to/kubeconfig", + }, + status: { + phase: "connecting", + }, + }); + localClusterEntity = new KubernetesCluster({ + metadata: { + labels: {}, + name: "some-local-kubernetes-cluster", + uid: "some-entity-id-2", + source: "local", + }, + spec: { + kubeconfigContext: "some-context", + kubeconfigPath: "/some/path/to/local/kubeconfig", + }, + status: { + phase: "connecting", + }, + }); + otherEntity = new WebLink({ + metadata: { + labels: {}, + name: "some-weblink", + uid: "some-weblink-id", + }, + spec: { + url: "https://my-websome.com", + }, + status: { + phase: "available", + }, + }); + cluster = createCluster({ + contextName: clusterEntity.spec.kubeconfigContext, + id: clusterEntity.getId(), + kubeConfigPath: clusterEntity.spec.kubeconfigPath, + }, { + clusterServerUrl: "https://localhost:9999", + }); + + // TODO: remove once ClusterStore can be used without overriding it + windowDi.override(getClusterByIdInjectable, () => (clusterId) => { + if (clusterId === cluster.id) { + return cluster; + } + + return undefined; + }); + + // TODO: replace with proper entity source once syncing entities between main and windows is injectable + const catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable); + + catalogEntityRegistry.updateItems([clusterEntity, otherEntity, localClusterEntity]); + }); + + rendered = await builder.render(); + windowDi = builder.applicationWindow.only.di; + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("does not show entity settings page yet", () => { + expect(rendered.queryByTestId("entity-settings")).not.toBeInTheDocument(); + }); + + describe("when navigating to non-local cluster entity settings", () => { + beforeEach(() => { + const navigateToEntitySettings = windowDi.inject(navigateToEntitySettingsInjectable); + + navigateToEntitySettings(clusterEntity.getId()); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("shows entity settings page", () => { + expect(rendered.queryByTestId("entity-settings")).toBeInTheDocument(); + }); + + it("does not show the General setting tab header for non-local cluster", () => { + expect(rendered.queryByTestId("general-tab")).not.toBeInTheDocument(); + }); + + it("shows Proxy setting tab header", () => { + expect(rendered.queryByTestId("proxy-tab")).toBeInTheDocument(); + }); + + it("shows Terminal setting tab header", () => { + expect(rendered.queryByTestId("terminal-tab")).toBeInTheDocument(); + }); + + it("shows Namespaces setting tab header", () => { + expect(rendered.queryByTestId("namespace-tab")).toBeInTheDocument(); + }); + + it("shows Metrics setting tab header", () => { + expect(rendered.queryByTestId("metrics-tab")).toBeInTheDocument(); + }); + + it("shows Node Shell setting tab header", () => { + expect(rendered.queryByTestId("node-shell-tab")).toBeInTheDocument(); + }); + + it("shows the setting tabs in the correct order", () => { + expect(rendered.getByTestId("proxy-tab").nextSibling).toHaveAttribute("data-testid", "terminal-tab"); + expect(rendered.getByTestId("terminal-tab").nextSibling).toHaveAttribute("data-testid", "namespace-tab"); + expect(rendered.getByTestId("namespace-tab").nextSibling).toHaveAttribute("data-testid", "metrics-tab"); + expect(rendered.getByTestId("metrics-tab").nextSibling).toHaveAttribute("data-testid", "node-shell-tab"); + }); + }); + + describe("when navigating to local cluster entity settings", () => { + beforeEach(() => { + const navigateToEntitySettings = windowDi.inject(navigateToEntitySettingsInjectable); + + navigateToEntitySettings(localClusterEntity.getId()); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("shows entity settings page", () => { + expect(rendered.queryByTestId("entity-settings")).toBeInTheDocument(); + }); + + it("shows the General setting tab header", () => { + expect(rendered.queryByTestId("general-tab")).toBeInTheDocument(); + }); + + it("shows Proxy setting tab header", () => { + expect(rendered.queryByTestId("proxy-tab")).toBeInTheDocument(); + }); + + it("shows Terminal setting tab header", () => { + expect(rendered.queryByTestId("terminal-tab")).toBeInTheDocument(); + }); + + it("shows Namespaces setting tab header", () => { + expect(rendered.queryByTestId("namespace-tab")).toBeInTheDocument(); + }); + + it("shows Metrics setting tab header", () => { + expect(rendered.queryByTestId("metrics-tab")).toBeInTheDocument(); + }); + + it("shows Node Shell setting tab header", () => { + expect(rendered.queryByTestId("node-shell-tab")).toBeInTheDocument(); + }); + + it("shows the setting tabs in the correct order", () => { + expect(rendered.getByTestId("general-tab").nextSibling).toHaveAttribute("data-testid", "proxy-tab"); + expect(rendered.getByTestId("proxy-tab").nextSibling).toHaveAttribute("data-testid", "terminal-tab"); + expect(rendered.getByTestId("terminal-tab").nextSibling).toHaveAttribute("data-testid", "namespace-tab"); + expect(rendered.getByTestId("namespace-tab").nextSibling).toHaveAttribute("data-testid", "metrics-tab"); + expect(rendered.getByTestId("metrics-tab").nextSibling).toHaveAttribute("data-testid", "node-shell-tab"); + }); + }); + + describe("when navigating to weblink entity settings", () => { + beforeEach(() => { + const navigateToEntitySettings = windowDi.inject(navigateToEntitySettingsInjectable); + + navigateToEntitySettings(otherEntity.getId()); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("shows entity settings page", () => { + expect(rendered.queryByTestId("entity-settings")).toBeInTheDocument(); + }); + + it("does not show the unrelated settings", () => { + expect(rendered.queryByTestId("general-tab")).not.toBeInTheDocument(); + }); + + it("shows no settings page info", () => { + expect(rendered.baseElement.querySelector("[data-preference-page-does-not-exist-test='true']")).toBeInTheDocument(); + }); + }); +}); diff --git a/src/features/preferences/renderer/preference-navigation/navigate-to-preference-tab/navigate-to-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-navigation/navigate-to-preference-tab/navigate-to-preference-tab.injectable.ts index f4e34267e8..de5fbd46b4 100644 --- a/src/features/preferences/renderer/preference-navigation/navigate-to-preference-tab/navigate-to-preference-tab.injectable.ts +++ b/src/features/preferences/renderer/preference-navigation/navigate-to-preference-tab/navigate-to-preference-tab.injectable.ts @@ -6,14 +6,16 @@ import { getInjectable } from "@ogre-tools/injectable"; import { navigateToRouteInjectionToken } from "../../../../../common/front-end-routing/navigate-to-route-injection-token"; import preferencesRouteInjectable from "../../../common/preferences-route.injectable"; -const navigateToPreferenceTabInjectable = getInjectable({ - id: "navigate-to-preference-tab-2", +export type NavigateToPreferenceTab = (tabId: string) => void; - instantiate: (di) => { +const navigateToPreferenceTabInjectable = getInjectable({ + id: "navigate-to-preference-tab", + + instantiate: (di): NavigateToPreferenceTab => { const navigateToRoute = di.inject(navigateToRouteInjectionToken); const route = di.inject(preferencesRouteInjectable); - return (preferenceTabId: string) => { + return (preferenceTabId) => { navigateToRoute(route, { withoutAffectingBackButton: true, parameters: { preferenceTabId }, diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx index 1ee75298d0..fb8a7f3f7a 100644 --- a/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx +++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { Tab } from "../../../../renderer/components/tabs"; -import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab/navigate-to-preference-tab.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import { observer } from "mobx-react"; import type { PreferenceTab } from "../preference-items/preference-item-injection-token"; @@ -12,7 +11,6 @@ import preferenceTabIsActiveInjectable from "./navigate-to-preference-tab/prefer import React from "react"; interface Dependencies { - navigateToTab: (tabId: string) => void; tabIsActive: IComputedValue; } @@ -20,23 +18,21 @@ interface PreferenceNavigationTabProps { tab: PreferenceTab; } -const NonInjectedPreferencesNavigationTab = observer(({ navigateToTab, tabIsActive, tab } : Dependencies & PreferenceNavigationTabProps) => ( +const NonInjectedPreferencesNavigationTab = observer(({ + tabIsActive, + tab, +}: Dependencies & PreferenceNavigationTabProps) => ( navigateToTab(tab.pathId)} active={tabIsActive.get()} label={tab.label} data-preference-tab-link-test={tab.pathId} + value={tab.pathId} /> )); -export const PreferencesNavigationTab = withInjectables( - NonInjectedPreferencesNavigationTab, - - { - getProps: (di, props) => ({ - navigateToTab: di.inject(navigateToPreferenceTabInjectable), - tabIsActive: di.inject(preferenceTabIsActiveInjectable, props.tab.pathId), - ...props, - }), - }, -); +export const PreferencesNavigationTab = withInjectables(NonInjectedPreferencesNavigationTab, { + getProps: (di, props) => ({ + ...props, + tabIsActive: di.inject(preferenceTabIsActiveInjectable, props.tab.pathId), + }), +}); diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx index 8d54e69d09..861dc1fbff 100644 --- a/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx +++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx @@ -16,26 +16,33 @@ import { compositeHasDescendant } from "../../../../common/utils/composite/compo import type { PreferenceTabsRoot } from "../preference-items/preference-tab-root"; import { Icon } from "../../../../renderer/components/icon"; import { checkThatAllDiscriminablesAreExhausted } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable"; +import type { NavigateToPreferenceTab } from "./navigate-to-preference-tab/navigate-to-preference-tab.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab/navigate-to-preference-tab.injectable"; interface Dependencies { composite: IComputedValue>; + navigateToPreferenceTab: NavigateToPreferenceTab; } -const NonInjectedPreferencesNavigation = observer(({ composite }: Dependencies) => ( - +const NonInjectedPreferencesNavigation = observer(({ + composite, + navigateToPreferenceTab, +}: Dependencies) => ( + + className="flex column" + scrollable={false} + onChange={navigateToPreferenceTab} + > {toNavigationHierarchy(composite.get())} )); -export const PreferencesNavigation = withInjectables( - NonInjectedPreferencesNavigation, - - { - getProps: (di) => ({ - composite: di.inject(preferencesCompositeInjectable), - }), - }, -); +export const PreferencesNavigation = withInjectables(NonInjectedPreferencesNavigation, { + getProps: (di) => ({ + composite: di.inject(preferencesCompositeInjectable), + navigateToPreferenceTab: di.inject(navigateToPreferenceTabInjectable), + }), +}); const toNavigationHierarchy = (composite: Composite) => { const value = composite.value; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 53e2913583..6cfb6dfb60 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -97,9 +97,6 @@ export async function bootstrap(di: DiContainer) { logger.info(`${logPrefix} initializing Registries`); initializers.initRegistries(); - logger.info(`${logPrefix} initializing EntitySettingsRegistry`); - initializers.initEntitySettingsRegistry(); - logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`); initializers.initCatalogEntityDetailRegistry(); diff --git a/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts b/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts index c4223da3ea..7482f216ee 100644 --- a/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts +++ b/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { ExtensionRegistrator } from "../../../../extensions/extension-loader/extension-registrator-injection-token"; import { extensionRegistratorInjectionToken } from "../../../../extensions/extension-loader/extension-registrator-injection-token"; import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; import type { AdditionalCategoryColumnRegistration } from "../custom-category-columns"; @@ -11,12 +10,10 @@ import { customCatalogCategoryColumnInjectionToken } from "./custom-token"; const customCategoryColumnsRegistratorInjectable = getInjectable({ id: "custom-category-columns-registrator", - instantiate: (): ExtensionRegistrator => { - return (ext) => { - const extension = ext as LensRendererExtension; + instantiate: () => (ext) => { + const extension = ext as LensRendererExtension; - return extension.additionalCategoryColumns.map(getInjectableForColumnRegistrationFor(extension)); - }; + return extension.additionalCategoryColumns.map(getInjectableForColumnRegistrationFor(extension)); }, injectionToken: extensionRegistratorInjectionToken, }); diff --git a/src/renderer/components/+entity-settings/active-tabs.injectable.ts b/src/renderer/components/+entity-settings/active-tabs.injectable.ts new file mode 100644 index 0000000000..1c9e22e803 --- /dev/null +++ b/src/renderer/components/+entity-settings/active-tabs.injectable.ts @@ -0,0 +1,79 @@ +/** + * 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 { action } from "mobx"; +import { byOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import observableHistoryInjectable from "../../navigation/observable-history.injectable"; +import type { RegisteredEntitySetting } from "./extension-registrator.injectable"; +import catalogEntitySettingItemsInjectable from "./settings.injectable"; + +export interface SettingTabs { + title: string; + id: string; +} + +export interface SettingGroup { + title: string; + tabs: SettingTabs[]; +} + +export interface ActiveEntitySettingDetails { + tabId: string | undefined; + setting: RegisteredEntitySetting | undefined; + groups: SettingGroup[]; +} + +export interface ActiveEntitySettings { + get: () => ActiveEntitySettingDetails; + set: (tabId: string) => void; +} + +const settingsGroup = "Settings"; +const defaultExtensionsGroup = "Extensions"; + +const getSettingGroups = (items: RegisteredEntitySetting[]): SettingGroup[] => { + const groupNames = new Set(items.map(({ group }) => group)); + const titles = [...groupNames].sort((left, right) => { + if (left === settingsGroup) return -1; + if (left === defaultExtensionsGroup) return 1; + + return left <= right ? -1 : 1; + }); + + return titles.map(title => ({ + title, + tabs: items + .filter(({ group }) => group === title) + .sort(byOrderNumber), + })); +}; + +const activeEntitySettingsTabInjectable = getInjectable({ + id: "active-entity-settings-tab", + instantiate: (di, entity): ActiveEntitySettings => { + const observableHistory = di.inject(observableHistoryInjectable); + const items = di.inject(catalogEntitySettingItemsInjectable, entity); + + return { + get: () => { + const defaultTabId = items.get()[0]?.id; + const tabId = observableHistory.location.hash.slice(1) || defaultTabId; + const setting = items.get().find(({ id }) => id === tabId); + const groups = getSettingGroups(items.get()); + + return { tabId, setting, groups }; + }, + set: action((tabId) => { + observableHistory.location.hash = tabId; + }), + }; + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, entity: CatalogEntity) => `${entity.apiVersion}/${entity.kind}[${entity.metadata.source ?? ""}]`, + }), +}); + +export default activeEntitySettingsTabInjectable; diff --git a/src/renderer/components/+entity-settings/current-entity.injectable.ts b/src/renderer/components/+entity-settings/current-entity.injectable.ts new file mode 100644 index 0000000000..fded6d096a --- /dev/null +++ b/src/renderer/components/+entity-settings/current-entity.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; +import entitySettingsRouteParametersInjectable from "./route-parameters.injectable"; + +const currentCatalogEntityForSettingsInjectable = getInjectable({ + id: "current-catalog-entity-for-settings", + instantiate: (di) => { + const { entityId } = di.inject(entitySettingsRouteParametersInjectable); + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + + return computed(() => catalogEntityRegistry.getById(entityId.get())); + }, +}); + +export default currentCatalogEntityForSettingsInjectable; diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index b1c672ae2e..8b7fd9f4ce 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -7,155 +7,129 @@ import styles from "./entity-settings.module.scss"; import React from "react"; import type { IComputedValue } from "mobx"; -import { observable, makeObservable, computed } from "mobx"; import { observer } from "mobx-react"; import { Tabs, Tab } from "../tabs"; import type { CatalogEntity } from "../../api/catalog-entity"; -import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; -import { EntitySettingRegistry } from "../../../extensions/registries"; -import { groupBy } from "lodash"; import { SettingLayout } from "../layout/setting-layout"; -import logger from "../../../common/logger"; import { Avatar } from "../avatar"; import { withInjectables } from "@ogre-tools/injectable-react"; -import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable"; -import type { ObservableHistory } from "mobx-observable-history"; -import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; -import observableHistoryInjectable from "../../navigation/observable-history.injectable"; +import currentCatalogEntityForSettingsInjectable from "./current-entity.injectable"; +import type { ActiveEntitySettings } from "./active-tabs.injectable"; +import activeEntitySettingsTabInjectable from "./active-tabs.injectable"; interface Dependencies { - entityId: IComputedValue; - entityRegistry: CatalogEntityRegistry; - observableHistory: ObservableHistory; + entity: IComputedValue; } -@observer -class NonInjectedEntitySettings extends React.Component { - @observable activeTab?: string; +const NonInjectedEntitySettings = observer((props: Dependencies) => { + const entity = props.entity.get(); - constructor(props: Dependencies) { - super(props); - makeObservable(this); - - const { hash } = this.props.observableHistory.location; - - if (hash) { - const menuId = hash.slice(1); - const item = this.menuItems.find((item) => item.id === menuId); - - if (item) { - this.activeTab = item.id; - } - } + if (!entity) { + return null; } - @computed - get entityId() { - return this.props.entityId.get(); - } + return ; +}); - get entity() { - return this.props.entityRegistry.getById(this.entityId); - } +export const EntitySettingsRouteComponent = withInjectables(NonInjectedEntitySettings, { + getProps: (di) => ({ + entity: di.inject(currentCatalogEntityForSettingsInjectable), + }), +}); - get menuItems() { - if (!this.entity) return []; +interface CatalogEntitySettingsProps { + entity: CatalogEntity; +} - return EntitySettingRegistry.getInstance().getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source); - } +interface CatalogEntitySettingsDeps { + activeEntitySettingsTab: ActiveEntitySettings; +} - get activeSetting() { - this.activeTab ||= this.menuItems[0]?.id; +const NonInjectedCatalogEntitySettings = observer((props: CatalogEntitySettingsProps & CatalogEntitySettingsDeps) => { + const { + activeEntitySettingsTab, + entity, + } = props; + const { tabId, setting, groups } = activeEntitySettingsTab.get(); - return this.menuItems.find((setting) => setting.id === this.activeTab); - } - - onTabChange = (tabId: string) => { - this.activeTab = tabId; - }; - - renderNavigation(entity: CatalogEntity) { - const groups = Object.entries(groupBy(this.menuItems, (item) => item.group || "Extensions")); - - groups.sort((a, b) => { - if (a[0] === "Settings") return -1; - if (a[0] === "Extensions") return 1; - - return a[0] <= b[0] ? -1 : 1; - }); - - return ( - <> -
- -
- {entity.getName()} -
+ const renderNavigation = () => ( + <> +
+ +
+ {entity.getName()}
- - { groups.map((group, groupIndex) => ( - -
-
{group[0]}
- { group[1].map((setting, index) => ( - - ))} -
- ))} -
- - ); - } - - render() { - const { activeSetting, entity } = this; - - if (!entity) { - logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId); - - return null; - } - - return ( - + { - activeSetting && ( + groups.map(({ tabs, title }) => ( + +
+
{title}
+ { + tabs.map((setting) => ( + + )) + } +
+ )) + } +
+ + ); + + return ( + + { + tabId && setting + ? (
-

{activeSetting.title}

+

{setting.title}

- +
) - } -
- ); - } -} + : ( +
+ No settings found for + {" "} + {entity.apiVersion} + / + {entity.kind} +
+ ) + } +
+ ); +}); -export const EntitySettings = withInjectables(NonInjectedEntitySettings, { - getProps: (di) => ({ - ...di.inject(entitySettingsRouteParametersInjectable), - entityRegistry: di.inject(catalogEntityRegistryInjectable), - observableHistory: di.inject(observableHistoryInjectable), +const CatalogEntitySettings = withInjectables(NonInjectedCatalogEntitySettings, { + getProps: (di, props) => ({ + ...props, + activeEntitySettingsTab: di.inject(activeEntitySettingsTabInjectable, props.entity), }), }); diff --git a/src/renderer/components/+entity-settings/extension-registrator.injectable.ts b/src/renderer/components/+entity-settings/extension-registrator.injectable.ts new file mode 100644 index 0000000000..19187d921d --- /dev/null +++ b/src/renderer/components/+entity-settings/extension-registrator.injectable.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import { entitySettingInjectionToken } from "./token"; + +export interface EntitySettingViewProps { + entity: CatalogEntity; +} + +export interface EntitySettingComponents { + View: React.ComponentType; +} + +export interface EntitySettingRegistration { + apiVersions: string[]; + kind: string; + title: string; + components: EntitySettingComponents; + source?: string; + id?: string; + priority?: number; + group?: string; +} + +export interface RegisteredEntitySetting { + id: string; + orderNumber: number; + apiVersions: Set; + kind: string; + title: string; + components: EntitySettingComponents; + source?: string; + group: string; +} + +const entitySettingExtensionRegistratorInjectable = getInjectable({ + id: "entity-setting-extension-registrator", + instantiate: () => (ext) => { + const extension = ext as LensRendererExtension; + + return extension.entitySettings.map(getInjectableForEntitySettingRegistrationFor(extension)); + }, + injectionToken: extensionRegistratorInjectionToken, +}); + +export default entitySettingExtensionRegistratorInjectable; + +const getInjectableForEntitySettingRegistrationFor = (extension: LensRendererExtension) => ({ + apiVersions, + components, + kind, + title, + group = "Extensions", + id = title.toLowerCase(), + priority, + source, +}: EntitySettingRegistration) => getInjectable({ + id: `${extension.manifest.name}:${group}/${kind}:${id}`, + instantiate: () => ({ + apiVersions: new Set(apiVersions), + components, + id, + kind, + orderNumber: priority ?? 50, + title, + group, + source, + }), + injectionToken: entitySettingInjectionToken, +}); diff --git a/src/renderer/components/+entity-settings/has-settings.injectable.ts b/src/renderer/components/+entity-settings/has-settings.injectable.ts new file mode 100644 index 0000000000..5e8d2ad5b6 --- /dev/null +++ b/src/renderer/components/+entity-settings/has-settings.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import catalogEntitySettingItemsInjectable from "./settings.injectable"; + +export type HasCatalogEntitySettingItems = (entity: CatalogEntity) => boolean; + +const hasCatalogEntitySettingItemsInjectable = getInjectable({ + id: "has-catalog-entity-setting-items", + instantiate: (di): HasCatalogEntitySettingItems => (entity) => { + const items = di.inject(catalogEntitySettingItemsInjectable, entity); + + return items.get().length > 0; + }, +}); + +export default hasCatalogEntitySettingItemsInjectable; diff --git a/src/renderer/components/+entity-settings/index.ts b/src/renderer/components/+entity-settings/index.ts index ef8ac0d38a..aeab50e325 100644 --- a/src/renderer/components/+entity-settings/index.ts +++ b/src/renderer/components/+entity-settings/index.ts @@ -3,6 +3,4 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import "../cluster-settings"; - export * from "./entity-settings"; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx new file mode 100644 index 0000000000..059f8c15eb --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx @@ -0,0 +1,71 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { KubernetesCluster } from "../../../../common/catalog-entities"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterIconSetting } from "../../cluster-settings/icon-settings"; +import { ClusterKubeconfig } from "../../cluster-settings/kubeconfig"; +import { ClusterNameSetting } from "../../cluster-settings/name-setting"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedGeneralKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ ); +} + +const GeneralKubernetesClusterSettings = withInjectables(NonInjectedGeneralKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const generalKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "general-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + source: "local", + title: "General", + group: "Settings", + id: "general", + orderNumber: 0, + components: { + View: GeneralKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default generalKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx new file mode 100644 index 0000000000..1be5f7e78a --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterMetricsSetting } from "../../cluster-settings/metrics-setting"; +import { ClusterPrometheusSetting } from "../../cluster-settings/prometheus-setting"; +import { ShowMetricsSetting } from "../../cluster-settings/show-metrics"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedMetricsKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+
+ +
+
+
+ + +
+
+ ); +} + +const MetricsKubernetesClusterSettings = withInjectables(NonInjectedMetricsKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const metricsKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "metrics-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + title: "Metrics", + group: "Settings", + id: "metrics", + orderNumber: 40, + components: { + View: MetricsKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default metricsKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx new file mode 100644 index 0000000000..a7c86265b8 --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterAccessibleNamespaces } from "../../cluster-settings/accessible-namespaces"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedNamespaceKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); +} + +const NamespaceKubernetesClusterSettings = withInjectables(NonInjectedNamespaceKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const namespaceKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "namespace-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + title: "Namespace", + group: "Settings", + id: "namespace", + orderNumber: 30, + components: { + View: NamespaceKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default namespaceKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx new file mode 100644 index 0000000000..ff899095d6 --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterNodeShellSetting } from "../../cluster-settings/node-shell-setting"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedNodeShellKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); +} + +const NodeShellKubernetesClusterSettings = withInjectables(NonInjectedNodeShellKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const nodeShellKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "node-shell-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + title: "Node Shell", + group: "Settings", + id: "node-shell", + orderNumber: 45, + components: { + View: NodeShellKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default nodeShellKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx new file mode 100644 index 0000000000..4479a04c2a --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterProxySetting } from "../../cluster-settings/proxy-setting"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedProxyKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); +} + +const ProxyKubernetesClusterSettings = withInjectables(NonInjectedProxyKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const proxyKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "proxy-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + title: "Proxy", + group: "Settings", + id: "proxy", + orderNumber: 10, + components: { + View: ProxyKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default proxyKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx new file mode 100644 index 0000000000..e778c97728 --- /dev/null +++ b/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import React from "react"; +import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { ClusterLocalTerminalSetting } from "../../cluster-settings/local-terminal-settings"; +import type { EntitySettingViewProps } from "../extension-registrator.injectable"; +import { entitySettingInjectionToken } from "../token"; + +interface Dependencies { + getClusterById: GetClusterById; +} + +function NonInjectedTerminalKubernetesClusterSettings({ entity, getClusterById }: EntitySettingViewProps & Dependencies) { + const cluster = getClusterById(entity.getId()); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); +} + +const TerminalKubernetesClusterSettings = withInjectables(NonInjectedTerminalKubernetesClusterSettings, { + getProps: (di, props) => ({ + ...props, + getClusterById: di.inject(getClusterByIdInjectable), + }), +}); + +const terminalKubernetesClusterEntitySettingsInjectable = getInjectable({ + id: "terminal-kubernetes-cluster-entity-settings", + instantiate: () => ({ + apiVersions: new Set(["entity.k8slens.dev/v1alpha1"]), + kind: "KubernetesCluster", + title: "Terminal", + group: "Settings", + id: "terminal", + orderNumber: 20, + components: { + View: TerminalKubernetesClusterSettings, + }, + }), + injectionToken: entitySettingInjectionToken, +}); + +export default terminalKubernetesClusterEntitySettingsInjectable; diff --git a/src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts b/src/renderer/components/+entity-settings/route-component.injectable.ts similarity index 87% rename from src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts rename to src/renderer/components/+entity-settings/route-component.injectable.ts index 5e3a2da43f..deedaca53a 100644 --- a/src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts +++ b/src/renderer/components/+entity-settings/route-component.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { EntitySettings } from "./entity-settings"; +import { EntitySettingsRouteComponent } from "./entity-settings"; import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; import entitySettingsRouteInjectable from "../../../common/front-end-routing/routes/entity-settings/entity-settings-route.injectable"; @@ -12,7 +12,7 @@ const entitySettingsRouteComponentInjectable = getInjectable({ instantiate: (di) => ({ route: di.inject(entitySettingsRouteInjectable), - Component: EntitySettings, + Component: EntitySettingsRouteComponent, }), injectionToken: routeSpecificComponentInjectionToken, diff --git a/src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts b/src/renderer/components/+entity-settings/route-parameters.injectable.ts similarity index 100% rename from src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts rename to src/renderer/components/+entity-settings/route-parameters.injectable.ts diff --git a/src/renderer/components/+entity-settings/settings.injectable.ts b/src/renderer/components/+entity-settings/settings.injectable.ts new file mode 100644 index 0000000000..c70063f2b6 --- /dev/null +++ b/src/renderer/components/+entity-settings/settings.injectable.ts @@ -0,0 +1,33 @@ +/** + * 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 { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; +import { computed } from "mobx"; +import { byOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import { entitySettingInjectionToken } from "./token"; + +const catalogEntitySettingItemsInjectable = getInjectable({ + id: "catalog-entity-setting-items", + instantiate: (di, entity) => { + const computedInjectMany = di.inject(computedInjectManyInjectable); + const items = computedInjectMany(entitySettingInjectionToken); + + return computed(() => ( + items.get() + .filter(item => ( + item.apiVersions.has(entity.apiVersion) + && item.kind === entity.kind + && (!item.source || item.source === entity.metadata.source) + )) + .sort(byOrderNumber) + )); + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, entity: CatalogEntity) => `${entity.apiVersion}/${entity.kind}[${entity.metadata.source ?? ""}]`, + }), +}); + +export default catalogEntitySettingItemsInjectable; diff --git a/src/renderer/components/+entity-settings/token.ts b/src/renderer/components/+entity-settings/token.ts new file mode 100644 index 0000000000..187cb0c635 --- /dev/null +++ b/src/renderer/components/+entity-settings/token.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { RegisteredEntitySetting } from "./extension-registrator.injectable"; + +export const entitySettingInjectionToken = getInjectionToken({ + id: "entity-setting", +}); diff --git a/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx b/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx similarity index 88% rename from src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx rename to src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx index b4a78a5dc0..f583f2fb2a 100644 --- a/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx +++ b/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx @@ -5,15 +5,15 @@ import React from "react"; import { waitFor } from "@testing-library/react"; -import { ClusterLocalTerminalSetting } from "../cluster-local-terminal-settings"; +import { ClusterLocalTerminalSetting } from "../local-terminal-settings"; import userEvent from "@testing-library/user-event"; import type { Stats } from "fs"; -import type { Cluster } from "../../../../../common/cluster/cluster"; -import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; -import type { DiRender } from "../../../test-utils/renderFor"; -import { renderFor } from "../../../test-utils/renderFor"; -import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable"; -import statInjectable from "../../../../../common/fs/stat/stat.injectable"; +import type { Cluster } from "../../../../common/cluster/cluster"; +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import type { DiRender } from "../../test-utils/renderFor"; +import { renderFor } from "../../test-utils/renderFor"; +import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable"; +import statInjectable from "../../../../common/fs/stat/stat.injectable"; describe("ClusterLocalTerminalSettings", () => { let render: DiRender; diff --git a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx b/src/renderer/components/cluster-settings/accessible-namespaces.tsx similarity index 87% rename from src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx rename to src/renderer/components/cluster-settings/accessible-namespaces.tsx index 8ef03aa6b1..479cc8f443 100644 --- a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/accessible-namespaces.tsx @@ -5,11 +5,11 @@ import React from "react"; import { observer } from "mobx-react"; -import type { Cluster } from "../../../../common/cluster/cluster"; -import { SubTitle } from "../../layout/sub-title"; -import { EditableList } from "../../editable-list"; +import type { Cluster } from "../../../common/cluster/cluster"; +import { SubTitle } from "../layout/sub-title"; +import { EditableList } from "../editable-list"; import { observable, makeObservable } from "mobx"; -import { systemName } from "../../input/input_validators"; +import { systemName } from "../input/input_validators"; export interface ClusterAccessibleNamespacesProps { cluster: Cluster; diff --git a/src/renderer/components/cluster-settings/cluster-settings.tsx b/src/renderer/components/cluster-settings/cluster-settings.tsx deleted file mode 100644 index e89a600095..0000000000 --- a/src/renderer/components/cluster-settings/cluster-settings.tsx +++ /dev/null @@ -1,118 +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 type { KubernetesCluster } from "../../../common/catalog-entities"; -import { ClusterStore } from "../../../common/cluster-store/cluster-store"; -import type { EntitySettingViewProps } from "../../../extensions/registries"; -import type { CatalogEntity } from "../../api/catalog-entity"; -import * as components from "./components"; - -function getClusterForEntity(entity: CatalogEntity) { - return ClusterStore.getInstance().getById(entity.getId()); -} - -export function GeneralSettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if (!cluster) { - return null; - } - - return ( -
-
-
-
- -
-
- -
-
-
-
- -
-
- ); -} - -export function ProxySettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if (!cluster) { - return null; - } - - return ( -
- -
- ); -} - -export function TerminalSettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if (!cluster) { - return null; - } - - return ( -
- -
- ); -} - -export function NamespacesSettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if (!cluster) { - return null; - } - - return ( -
- -
- ); -} - -export function MetricsSettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if (!cluster) { - return null; - } - - return ( -
-
- -
-
-
- - -
-
- ); -} - -export function NodeShellSettings({ entity }: EntitySettingViewProps) { - const cluster = getClusterForEntity(entity); - - if(!cluster) { - return null; - } - - return ( -
- -
- ); -} diff --git a/src/renderer/components/cluster-settings/components/index.ts b/src/renderer/components/cluster-settings/components/index.ts deleted file mode 100644 index 8c878bc415..0000000000 --- a/src/renderer/components/cluster-settings/components/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./cluster-accessible-namespaces"; -export * from "./cluster-local-terminal-settings"; -export * from "./cluster-kubeconfig"; -export * from "./cluster-metrics-setting"; -export * from "./cluster-name-setting"; -export * from "./cluster-prometheus-setting"; -export * from "./cluster-proxy-setting"; -export * from "./cluster-show-metrics"; -export * from "./cluster-icon-settings"; -export * from "./cluster-node-shell-setting"; diff --git a/src/renderer/components/cluster-settings/components/cluster-icon-settings.tsx b/src/renderer/components/cluster-settings/icon-settings.tsx similarity index 88% rename from src/renderer/components/cluster-settings/components/cluster-icon-settings.tsx rename to src/renderer/components/cluster-settings/icon-settings.tsx index 4f5c6abd78..55e4b21828 100644 --- a/src/renderer/components/cluster-settings/components/cluster-icon-settings.tsx +++ b/src/renderer/components/cluster-settings/icon-settings.tsx @@ -4,14 +4,14 @@ */ import React from "react"; -import type { Cluster } from "../../../../common/cluster/cluster"; -import { autoBind } from "../../../utils"; +import type { Cluster } from "../../../common/cluster/cluster"; +import { autoBind } from "../../utils"; import { observable } from "mobx"; import { observer } from "mobx-react"; -import type { KubernetesCluster } from "../../../../common/catalog-entities"; -import { FilePicker, OverSizeLimitStyle } from "../../file-picker"; -import { MenuActions, MenuItem } from "../../menu"; -import { Avatar } from "../../avatar"; +import type { KubernetesCluster } from "../../../common/catalog-entities"; +import { FilePicker, OverSizeLimitStyle } from "../file-picker"; +import { MenuActions, MenuItem } from "../menu"; +import { Avatar } from "../avatar"; enum GeneralInputStatus { CLEAN = "clean", diff --git a/src/renderer/components/cluster-settings/index.ts b/src/renderer/components/cluster-settings/index.ts deleted file mode 100644 index 3699309256..0000000000 --- a/src/renderer/components/cluster-settings/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 "./cluster-settings"; diff --git a/src/renderer/components/cluster-settings/components/cluster-kubeconfig.tsx b/src/renderer/components/cluster-settings/kubeconfig.tsx similarity index 82% rename from src/renderer/components/cluster-settings/components/cluster-kubeconfig.tsx rename to src/renderer/components/cluster-settings/kubeconfig.tsx index cc94ab4548..251185ba89 100644 --- a/src/renderer/components/cluster-settings/components/cluster-kubeconfig.tsx +++ b/src/renderer/components/cluster-settings/kubeconfig.tsx @@ -4,11 +4,11 @@ */ import React from "react"; -import type { Cluster } from "../../../../common/cluster/cluster"; +import type { Cluster } from "../../../common/cluster/cluster"; import { observer } from "mobx-react"; -import { SubTitle } from "../../layout/sub-title"; +import { SubTitle } from "../layout/sub-title"; import { shell } from "electron"; -import { Notice } from "../../+extensions/notice"; +import { Notice } from "../+extensions/notice"; export interface ClusterKubeconfigProps { cluster: Cluster; diff --git a/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx b/src/renderer/components/cluster-settings/local-terminal-settings.tsx similarity index 83% rename from src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx rename to src/renderer/components/cluster-settings/local-terminal-settings.tsx index 639623e08f..9a37381b15 100644 --- a/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx +++ b/src/renderer/components/cluster-settings/local-terminal-settings.tsx @@ -5,20 +5,20 @@ import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import type { Cluster } from "../../../../common/cluster/cluster"; -import { Input } from "../../input"; -import { SubTitle } from "../../layout/sub-title"; -import type { ShowNotification } from "../../notifications"; -import { Icon } from "../../icon"; -import { PathPicker } from "../../path-picker"; -import { isWindows } from "../../../../common/vars"; +import type { Cluster } from "../../../common/cluster/cluster"; +import { Input } from "../input"; +import { SubTitle } from "../layout/sub-title"; +import type { ShowNotification } from "../notifications"; +import { Icon } from "../icon"; +import { PathPicker } from "../path-picker"; import { withInjectables } from "@ogre-tools/injectable-react"; -import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable"; -import type { ValidateDirectory } from "../../../../common/fs/validate-directory.injectable"; -import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable"; -import type { ResolveTilde } from "../../../../common/path/resolve-tilde.injectable"; -import resolveTildeInjectable from "../../../../common/path/resolve-tilde.injectable"; -import Gutter from "../../gutter/gutter"; +import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable"; +import type { ValidateDirectory } from "../../../common/fs/validate-directory.injectable"; +import validateDirectoryInjectable from "../../../common/fs/validate-directory.injectable"; +import type { ResolveTilde } from "../../../common/path/resolve-tilde.injectable"; +import resolveTildeInjectable from "../../../common/path/resolve-tilde.injectable"; +import Gutter from "../gutter/gutter"; +import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; export interface ClusterLocalTerminalSettingProps { cluster: Cluster; @@ -27,6 +27,7 @@ interface Dependencies { showErrorNotification: ShowNotification; validateDirectory: ValidateDirectory; resolveTilde: ResolveTilde; + isWindows: boolean; } const NonInjectedClusterLocalTerminalSetting = observer(({ @@ -34,6 +35,7 @@ const NonInjectedClusterLocalTerminalSetting = observer(({ showErrorNotification, validateDirectory, resolveTilde, + isWindows, }: Dependencies & ClusterLocalTerminalSettingProps) => { if (!cluster) { return null; @@ -165,5 +167,6 @@ export const ClusterLocalTerminalSetting = withInjectables RegisteredEntitySetting[]; - -const getEntitySettingCommandsInjectable = getInjectable({ - id: "get-entity-setting-commands", - instantiate: (): GetEntitySettingCommands => { - const reg = EntitySettingRegistry.getInstance(); - - return (kind, apiVersion, source) => reg.getItemsForKind(kind, apiVersion, source); - }, - causesSideEffects: true, -}); - -export default getEntitySettingCommandsInjectable; diff --git a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx index 27bbc4aab7..7026d8882b 100644 --- a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx +++ b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx @@ -4,7 +4,6 @@ */ import React from "react"; -import type { RegisteredEntitySetting } from "../../../../extensions/registries"; import { HotbarAddCommand } from "../../hotbar/hotbar-add-command"; import { HotbarRemoveCommand } from "../../hotbar/hotbar-remove-command"; import { HotbarSwitchCommand } from "../../hotbar/hotbar-switch-command"; @@ -37,10 +36,11 @@ import navigateToJobsInjectable from "../../../../common/front-end-routing/route import navigateToCronJobsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable"; import navigateToCustomResourcesInjectable from "../../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable"; import navigateToEntitySettingsInjectable from "../../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; -import getEntitySettingCommandsInjectable from "./get-entity-setting-commands.injectable"; // TODO: Importing from features is not OK. Make commands to comply with Open Closed Principle to allow moving implementation under a feature import navigateToPreferencesInjectable from "../../../../features/preferences/common/navigate-to-preferences.injectable"; +import type { HasCatalogEntitySettingItems } from "../../+entity-settings/has-settings.injectable"; +import hasCatalogEntitySettingItemsInjectable from "../../+entity-settings/has-settings.injectable"; export function isKubernetesClusterActive(context: CommandContext): boolean { return context.entity?.kind === "KubernetesCluster"; @@ -48,9 +48,8 @@ export function isKubernetesClusterActive(context: CommandContext): boolean { interface Dependencies { openCommandDialog: (component: React.ReactElement) => void; - getEntitySettingItems: (kind: string, apiVersion: string, source?: string) => RegisteredEntitySetting[]; + hasCatalogEntitySettingItems: HasCatalogEntitySettingItems; createTerminalTab: () => DockTabCreate; - navigateToPreferences: () => void; navigateToHelmCharts: () => void; navigateToHelmReleases: () => void; @@ -214,8 +213,7 @@ function getInternalCommands(dependencies: Dependencies): CommandRegistration[] title: ({ entity }) => `${entity.kind}/${entity.getName()}: View Settings`, action: ({ entity }) => dependencies.navigateToEntitySettings(entity.getId()), isActive: ({ entity }) => ( - entity - && dependencies.getEntitySettingItems(entity.kind, entity.apiVersion, entity.metadata.source).length > 0 + entity && dependencies.hasCatalogEntitySettingItems(entity) ), }, { @@ -257,7 +255,7 @@ const internalCommandsInjectable = getInjectable({ instantiate: (di) => getInternalCommands({ openCommandDialog: di.inject(commandOverlayInjectable).open, - getEntitySettingItems: di.inject(getEntitySettingCommandsInjectable), + hasCatalogEntitySettingItems: di.inject(hasCatalogEntitySettingItemsInjectable), createTerminalTab: di.inject(createTerminalTabInjectable), navigateToPreferences: di.inject(navigateToPreferencesInjectable), navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable), diff --git a/src/renderer/components/layout/tab-layout-2.tsx b/src/renderer/components/layout/tab-layout-2.tsx index a0348383c1..935e4b9368 100644 --- a/src/renderer/components/layout/tab-layout-2.tsx +++ b/src/renderer/components/layout/tab-layout-2.tsx @@ -43,6 +43,7 @@ export const TabLayout = observer( active={active} data-is-active-test={active} data-testid={`tab-link-for-${registration.id}`} + value={undefined} /> ); })} diff --git a/src/renderer/components/layout/tab-layout.tsx b/src/renderer/components/layout/tab-layout.tsx index 19f8db4d6d..07684ef28d 100644 --- a/src/renderer/components/layout/tab-layout.tsx +++ b/src/renderer/components/layout/tab-layout.tsx @@ -39,7 +39,7 @@ export const TabLayout = observer(({ className, contentClass, tabs = [], childre return (
{hasTabs && ( - navigate(url)}> + center onChange={(url) => navigate(url)}> {tabs.map(({ title, routePath, url = routePath, exact }) => ( ({}); +const TabsContext = React.createContext>({}); -interface TabsContextValue { +interface TabsContextValue { autoFocus?: boolean; withBorder?: boolean; value?: D; onChange?(value: D): void; } -export interface TabsProps extends TabsContextValue, Omit, "onChange"> { +export interface TabsProps extends TabsContextValue, Omit, "onChange"> { className?: string; center?: boolean; wrap?: boolean; scrollable?: boolean; } -export class Tabs extends React.PureComponent { +export class Tabs extends React.PureComponent> { public elem: HTMLDivElement | null = null; render() { @@ -49,22 +49,22 @@ export class Tabs extends React.PureComponent { } } -export interface TabProps extends DOMAttributes { +export interface TabProps extends DOMAttributes { id?: string; className?: string; active?: boolean; disabled?: boolean; icon?: React.ReactNode | string; // material-ui name or custom icon label?: React.ReactNode; - value?: D; + value: D; } -export class Tab extends React.PureComponent { +export class Tab extends React.PureComponent> { static contextType = TabsContext; - declare context: TabsContextValue; + declare context: TabsContextValue; public ref = React.createRef(); - constructor(props: TabProps) { + constructor(props: TabProps) { super(props); autoBind(this); } diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index 8f9e72c167..07c7afb007 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -47,7 +47,6 @@ import setupSystemCaInjectable from "./frames/root-frame/setup-system-ca.injecta import extensionShouldBeEnabledForClusterFrameInjectable from "./extension-loader/extension-should-be-enabled-for-cluster-frame.injectable"; import { asyncComputed } from "@ogre-tools/injectable-react"; import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injectable"; -import getEntitySettingCommandsInjectable from "./components/command-palette/registered-commands/get-entity-setting-commands.injectable"; import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable"; import environmentVariablesInjectable from "../common/utils/environment-variables.injectable"; import type { GlobalOverride } from "../common/test-utils/get-global-override"; @@ -113,9 +112,6 @@ export const getDiForUnitTesting = ( di.override(requestAnimationFrameInjectable, () => (callback) => callback()); di.override(lensResourcesDirInjectable, () => "/irrelevant"); - // TODO: remove when entity settings registry is refactored - di.override(getEntitySettingCommandsInjectable, () => () => []); - // TODO: Remove after "LensRendererExtension.isEnabledForCluster" is removed di.override(extensionShouldBeEnabledForClusterFrameInjectable, () => asyncComputed({ getValueFromObservedPromise: async () => true, valueWhenPending: true }), diff --git a/src/renderer/initializers/entity-settings-registry.ts b/src/renderer/initializers/entity-settings-registry.ts deleted file mode 100644 index 867368fafa..0000000000 --- a/src/renderer/initializers/entity-settings-registry.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { EntitySettingRegistry } from "../../extensions/registries"; -import * as clusterSettings from "../components/cluster-settings"; - -export function initEntitySettingsRegistry() { - EntitySettingRegistry.getInstance() - .add([ - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - source: "local", - title: "General", - group: "Settings", - components: { - View: clusterSettings.GeneralSettings, - }, - }, - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Proxy", - group: "Settings", - components: { - View: clusterSettings.ProxySettings, - }, - }, - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Terminal", - group: "Settings", - components: { - View: clusterSettings.TerminalSettings, - }, - }, - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Namespaces", - group: "Settings", - components: { - View: clusterSettings.NamespacesSettings, - }, - }, - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Metrics", - group: "Settings", - components: { - View: clusterSettings.MetricsSettings, - }, - }, - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Node Shell", - group: "Settings", - components: { - View: clusterSettings.NodeShellSettings, - }, - }, - ]); -} diff --git a/src/renderer/initializers/index.ts b/src/renderer/initializers/index.ts index 7653a6147a..6fb131e507 100644 --- a/src/renderer/initializers/index.ts +++ b/src/renderer/initializers/index.ts @@ -5,7 +5,6 @@ export * from "./catalog-entity-detail-registry"; export * from "./catalog"; -export * from "./entity-settings-registry"; export * from "./ipc"; export * from "./registries"; export * from "./catalog-category-registry"; diff --git a/src/renderer/initializers/registries.ts b/src/renderer/initializers/registries.ts index 5db6be77f2..6d9b7a075e 100644 --- a/src/renderer/initializers/registries.ts +++ b/src/renderer/initializers/registries.ts @@ -7,5 +7,4 @@ import * as registries from "../../extensions/registries"; export function initRegistries() { registries.CatalogEntityDetailRegistry.createInstance(); - registries.EntitySettingRegistry.createInstance(); }