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({