From 4504283041026da31e66122633202b659fcfee5d Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Nov 2022 09:21:03 -0500 Subject: [PATCH] Extract ClusterContext into deps for KubeObjectStore to fix circular import Signed-off-by: Sebastian Malton --- .../allowed-resources-injection-token.ts | 5 +- ...thorization-namespace-review.injectable.ts | 89 ----------- src/common/cluster/cluster.ts | 143 +++++++----------- .../cluster/list-api-resources.injectable.ts | 99 ------------ .../request-api-resources.injectable.ts | 83 ++++++++++ ...t-namespace-list-permissions.injectable.ts | 75 +++++++++ .../config-maps-route.injectable.ts | 20 +-- ...zontal-pod-autoscalers-route.injectable.ts | 19 ++- .../config/leases/leases-route.injectable.ts | 18 +-- .../limit-ranges-route.injectable.ts | 21 +-- ...pod-disruption-budgets-route.injectable.ts | 19 ++- .../priority-classes-route.injectable.ts | 19 ++- .../resource-quotas-route.injectable.ts | 18 +-- .../runtime-classes-route.injectable.ts | 19 ++- .../secrets/secrets-route.injectable.ts | 18 +-- .../cluster/events/events-route.injectable.ts | 18 +-- .../namespaces/namespaces-route.injectable.ts | 18 +-- .../endpoints/endpoints-route.injectable.ts | 18 +-- .../ingresses/ingresses-route.injectable.ts | 26 ++-- .../network-policies-route.injectable.ts | 19 ++- .../services/services-route.injectable.ts | 18 +-- .../cluster/nodes/nodes-route.injectable.ts | 18 +-- .../cluster-overview-route.injectable.ts | 18 +-- ...rsistent-volume-claims-route.injectable.ts | 18 +-- .../persistent-volumes-route.injectable.ts | 18 +-- .../storage-classes-route.injectable.ts | 19 ++- .../cluster-role-bindings-route.injectable.ts | 19 ++- .../cluster-roles-route.injectable.ts | 19 ++- .../pod-security-policies-route.injectable.ts | 9 +- .../role-bindings-route.injectable.ts | 9 +- .../roles/roles-route.injectable.ts | 19 ++- .../service-accounts-route.injectable.ts | 18 +-- .../cron-jobs/cron-jobs-route.injectable.ts | 19 ++- .../daemonsets/daemonsets-route.injectable.ts | 19 ++- .../deployments-route.injectable.ts | 19 ++- .../workloads/jobs/jobs-route.injectable.ts | 19 ++- .../workloads/pods/pods-route.injectable.ts | 18 +-- .../replicasets-route.injectable.ts | 19 ++- .../statefulsets-route.injectable.ts | 19 ++- .../k8s-api/__tests__/api-manager.test.ts | 10 +- .../__tests__/kube-object.store.test.ts | 24 ++- .../auto-registration.injectable.ts | 7 +- .../k8s-api/api-manager/resource.store.ts | 5 +- src/common/k8s-api/cluster-context.ts | 13 -- src/common/k8s-api/kube-object.store.ts | 96 +++++------- src/common/rbac.ts | 13 +- src/common/utils/computed-or.ts | 11 ++ .../utils/is-allowed-resource.injectable.ts | 26 ---- src/extensions/common-api/k8s-api.ts | 32 +++- src/extensions/renderer-api/k8s-api.ts | 20 ++- .../visibility-of-sidebar-items.test.tsx | 23 +-- src/main/__test__/cluster.test.ts | 7 +- .../allowed-resources.injectable.ts | 4 +- .../create-cluster.injectable.ts | 6 +- .../allowed-resources.injectable.ts | 25 --- .../cluster-frame-context.injectable.ts | 25 --- .../cluster-frame-context.ts | 49 ++---- ...for-cluster-scoped-resources.injectable.ts | 29 ++++ .../for-namespaced-resources.injectable.ts | 65 ++++++++ .../should-show-resource.injectable.ts | 27 ++++ .../cluster-overview-store.injectable.ts | 2 + .../cluster-overview-store.ts | 5 +- .../+config-autoscalers/store.injectable.ts | 5 +- .../+config-leases/store.injectable.ts | 5 +- .../+config-limit-ranges/store.injectable.ts | 5 +- .../+config-maps/store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../+config-secrets/store.injectable.ts | 5 +- .../definition.store.injectable.ts | 2 + .../+custom-resources/definition.store.ts | 6 +- .../+helm-releases/releases.injectable.ts | 4 +- .../+namespaces/store.injectable.ts | 2 + src/renderer/components/+namespaces/store.ts | 16 +- .../+network-endpoints/store.injectable.ts | 5 +- .../+network-ingresses/store.injectable.ts | 5 +- .../+network-policies/store.injectable.ts | 5 +- .../+network-services/store.injectable.ts | 5 +- .../components/+nodes/store.injectable.ts | 5 +- src/renderer/components/+nodes/store.ts | 6 +- .../store.injectable.ts | 5 +- .../+storage-classes/store.injectable.ts | 2 + .../components/+storage-classes/store.ts | 6 +- .../store.injectable.ts | 5 +- .../+storage-volumes/store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../+cluster-roles/store.injectable.ts | 5 +- .../+role-bindings/store.injectable.ts | 5 +- .../+roles/store.injectable.ts | 5 +- .../+service-accounts/store.injectable.ts | 5 +- .../+workloads-cronjobs/store.injectable.ts | 2 + .../components/+workloads-cronjobs/store.ts | 6 +- .../+workloads-daemonsets/store.injectable.ts | 2 + .../components/+workloads-daemonsets/store.ts | 6 +- .../store.injectable.ts | 2 + .../+workloads-deployments/store.ts | 6 +- .../+workloads-overview/overview-statuses.tsx | 3 +- .../+workloads-overview/overview.tsx | 4 +- .../cron-jobs-workload.injectable.ts | 8 +- .../daemonsets-workload.injectable.ts | 5 +- .../deployments-workload.injectable.ts | 5 +- .../jobs-workload.injectable.ts | 5 +- .../pods-workload.injectable.ts | 4 +- .../replicasets-workload.injectable.ts | 5 +- .../statefulsets-workload.injectable.ts | 5 +- .../workloads/workload-injection-token.ts | 3 +- .../workloads/workloads.injectable.ts | 32 +--- .../+workloads-pods/store.injectable.ts | 2 + .../components/+workloads-pods/store.ts | 6 +- .../store.injectable.ts | 2 + .../+workloads-replicasets/store.ts | 8 +- .../store.injectable.ts | 2 + .../+workloads-statefulsets/store.ts | 6 +- .../accessible-namespaces.tsx | 4 +- .../kube-object-list-layout.tsx | 4 +- .../test-utils/get-application-builder.tsx | 28 ++-- .../create-cluster.injectable.ts | 4 +- .../cluster-frame/cluster-frame.test.tsx | 6 +- .../init-cluster-frame.injectable.ts | 6 +- .../init-cluster-frame/init-cluster-frame.ts | 22 +-- src/renderer/initializers/workload-events.tsx | 6 +- .../kube-watch-api.injectable.ts | 4 +- 123 files changed, 993 insertions(+), 991 deletions(-) delete mode 100644 src/common/cluster/authorization-namespace-review.injectable.ts delete mode 100644 src/common/cluster/list-api-resources.injectable.ts create mode 100644 src/common/cluster/request-api-resources.injectable.ts create mode 100644 src/common/cluster/request-namespace-list-permissions.injectable.ts delete mode 100644 src/common/k8s-api/cluster-context.ts create mode 100644 src/common/utils/computed-or.ts delete mode 100644 src/common/utils/is-allowed-resource.injectable.ts delete mode 100644 src/renderer/cluster-frame-context/allowed-resources.injectable.ts delete mode 100644 src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts create mode 100644 src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts create mode 100644 src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts create mode 100644 src/renderer/cluster-frame-context/should-show-resource.injectable.ts diff --git a/src/common/cluster-store/allowed-resources-injection-token.ts b/src/common/cluster-store/allowed-resources-injection-token.ts index 353d0b309c..5b71038d04 100644 --- a/src/common/cluster-store/allowed-resources-injection-token.ts +++ b/src/common/cluster-store/allowed-resources-injection-token.ts @@ -5,7 +5,8 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; +import type { KubeApiResourceDescriptor } from "../rbac"; -export const allowedResourcesInjectionToken = getInjectionToken>>({ - id: "allowed-resources", +export const shouldShowResourceInjectionToken = getInjectionToken, KubeApiResourceDescriptor>({ + id: "should-show-resource", }); diff --git a/src/common/cluster/authorization-namespace-review.injectable.ts b/src/common/cluster/authorization-namespace-review.injectable.ts deleted file mode 100644 index 0a9f4a7789..0000000000 --- a/src/common/cluster/authorization-namespace-review.injectable.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { KubeConfig } from "@kubernetes/client-node"; -import { AuthorizationV1Api } from "@kubernetes/client-node"; -import { getInjectable } from "@ogre-tools/injectable"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource } from "../rbac"; - -/** - * Requests the permissions for actions on the kube cluster - * @param namespace The namespace of the resources - * @param availableResources List of available resources in the cluster to resolve glob values fir api groups - * @returns list of allowed resources names - */ -export type RequestNamespaceResources = (namespace: string, availableResources: KubeApiResource[]) => Promise; - -/** - * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster - */ -export type AuthorizationNamespaceReview = (proxyConfig: KubeConfig) => RequestNamespaceResources; - -const authorizationNamespaceReviewInjectable = getInjectable({ - id: "authorization-namespace-review", - instantiate: (di): AuthorizationNamespaceReview => { - const logger = di.inject(loggerInjectable); - - return (proxyConfig) => { - const api = proxyConfig.makeApiClient(AuthorizationV1Api); - - return async (namespace, availableResources) => { - try { - const { body: { status }} = await api.createSelfSubjectRulesReview({ - apiVersion: "authorization.k8s.io/v1", - kind: "SelfSubjectRulesReview", - spec: { namespace }, - }); - - const allowedResources = new Set(); - - if (!status || status.incomplete) { - logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`); - - return availableResources.map(r => r.apiName); - } - - for (const { verbs, resources, apiGroups } of status.resourceRules) { - if ( - !resources - || (!verbs.includes("*") && !verbs.includes("list")) - ) { - continue; - } - - if (resources[0] !== "*" || !apiGroups) { - for (const resource of resources) { - allowedResources.add(resource); - } - continue; - } - - if (apiGroups[0] === "*") { - for (const resource of availableResources) { - allowedResources.add(resource.apiName); - } - continue; - } - - for (const resource of availableResources) { - if (apiGroups.includes(resource.group || "")) { - allowedResources.add(resource.apiName); - } - } - } - - return [...allowedResources]; - } catch (error) { - logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace }); - - return []; - } - }; - }; - }, -}); - -export default authorizationNamespaceReviewInjectable; diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index fe66c9fe1b..cdc442782b 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -9,8 +9,8 @@ import type { KubeConfig } from "@kubernetes/client-node"; import { HttpError } from "@kubernetes/client-node"; import type { Kubectl } from "../../main/kubectl/kubectl"; import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager"; -import type { KubeApiResource, KubeResource } from "../rbac"; -import { apiResourceRecord, apiResources } from "../rbac"; +import type { KubeApiResource, KubeApiResourceDescriptor } from "../rbac"; +import { formatKubeApiResource } from "../rbac"; import type { VersionDetector } from "../../main/cluster-detectors/version-detector"; import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry"; import plimit from "p-limit"; @@ -25,8 +25,8 @@ import assert from "assert"; import type { Logger } from "../logger"; import type { BroadcastMessage } from "../ipc/broadcast-message.injectable"; import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable"; -import type { RequestNamespaceResources } from "./authorization-namespace-review.injectable"; -import type { RequestListApiResources } from "./list-api-resources.injectable"; +import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable"; +import type { RequestApiResources } from "./request-api-resources.injectable"; export interface ClusterDependencies { readonly directoryForKubeConfigs: string; @@ -36,8 +36,8 @@ export interface ClusterDependencies { createContextHandler: (cluster: Cluster) => ClusterContextHandler; createKubectl: (clusterVersion: string) => Kubectl; createAuthorizationReview: (config: KubeConfig) => CanI; - createAuthorizationNamespaceReview: (config: KubeConfig) => RequestNamespaceResources; - createListApiResources: (cluster: Cluster) => RequestListApiResources; + requestApiResources: RequestApiResources; + requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor; createListNamespaces: (config: KubeConfig) => ListNamespaces; createVersionDetector: (cluster: Cluster) => VersionDetector; broadcastMessage: BroadcastMessage; @@ -49,7 +49,7 @@ export interface ClusterDependencies { * * @beta */ -export class Cluster implements ClusterModel, ClusterState { +export class Cluster implements ClusterModel { /** Unique id for a cluster */ public readonly id: ClusterId; private kubeCtl: Kubectl | undefined; @@ -62,7 +62,6 @@ export class Cluster implements ClusterModel, ClusterState { protected readonly _proxyKubeconfigManager: KubeconfigManager | undefined; protected readonly eventsDisposer = disposer(); protected activated = false; - private readonly resourceAccessStatuses = new Map(); public get contextHandler() { // TODO: remove these once main/renderer are seperate classes @@ -163,25 +162,30 @@ export class Cluster implements ClusterModel, ClusterState { * @observable */ @observable metadata: ClusterMetadata = {}; + /** * List of allowed namespaces verified via K8S::SelfSubjectAccessReview api - * - * @observable */ - @observable allowedNamespaces: string[] = []; - /** - * List of allowed resources - * - * @observable - * @internal - */ - @observable allowedResources: string[] = []; + readonly allowedNamespaces = observable.array(); + /** * List of accessible namespaces provided by user in the Cluster Settings - * - * @observable */ - @observable accessibleNamespaces: string[] = []; + readonly accessibleNamespaces = observable.array(); + + private readonly knownResources = observable.array(); + + private readonly knownNamespacedResources = computed(() => ( + this.knownResources + .filter(r => r.namespaced === true) + )); + private readonly knownClusterscopedResources = computed(() => ( + this.knownResources + .filter(r => r.namespaced === false) + )); + + // The formatting of this is `group.name` or `name` (if in core) + private readonly allowedResources = observable.set(); /** * Labels for the catalog entity @@ -299,7 +303,7 @@ export class Cluster implements ClusterModel, ClusterState { } if (model.accessibleNamespaces) { - this.accessibleNamespaces = model.accessibleNamespaces; + this.accessibleNamespaces.replace(model.accessibleNamespaces); } if (model.labels) { @@ -433,8 +437,7 @@ export class Cluster implements ClusterModel, ClusterState { this.accessible = false; this.ready = false; this.activated = false; - this.allowedNamespaces = []; - this.resourceAccessStatuses.clear(); + this.allowedNamespaces.clear(); this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id }); } @@ -474,8 +477,7 @@ export class Cluster implements ClusterModel, ClusterState { this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta()); const proxyConfig = await this.getProxyKubeconfig(); const canI = this.dependencies.createAuthorizationReview(proxyConfig); - const requestNamespaceResources = this.dependencies.createAuthorizationNamespaceReview(proxyConfig); - const listApiResources = this.dependencies.createListApiResources(this); + const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig); this.isAdmin = await canI({ namespace: "kube-system", @@ -486,8 +488,9 @@ export class Cluster implements ClusterModel, ClusterState { verb: "watch", resource: "*", }); - this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig); - this.allowedResources = await this.getAllowedResources(listApiResources, requestNamespaceResources); + this.allowedNamespaces.replace(await this.requestAllowedNamespaces(proxyConfig)); + this.knownResources.replace(await this.dependencies.requestApiResources(this)); + this.allowedResources.replace(await this.getAllowedResources(requestNamespaceListPermissions)); this.ready = true; } @@ -600,7 +603,7 @@ export class Cluster implements ClusterModel, ClusterState { accessible: this.accessible, isAdmin: this.isAdmin, allowedNamespaces: this.allowedNamespaces, - allowedResources: this.allowedResources, + allowedResources: [...this.allowedResources], isGlobalWatchEnabled: this.isGlobalWatchEnabled, }); } @@ -611,8 +614,8 @@ export class Cluster implements ClusterModel, ClusterState { */ @action setState(state: ClusterState) { this.accessible = state.accessible; - this.allowedNamespaces = state.allowedNamespaces; - this.allowedResources = state.allowedResources; + this.allowedNamespaces.replace(state.allowedNamespaces); + this.allowedResources.replace(state.allowedResources); this.apiUrl = state.apiUrl; this.disconnected = state.disconnected; this.isAdmin = state.isAdmin; @@ -644,7 +647,7 @@ export class Cluster implements ClusterModel, ClusterState { this.dependencies.broadcastMessage(`cluster:${this.id}:connection-update`, update); } - protected async getAllowedNamespaces(proxyConfig: KubeConfig) { + protected async requestAllowedNamespaces(proxyConfig: KubeConfig) { if (this.accessibleNamespaces.length) { return this.accessibleNamespaces; } @@ -668,69 +671,35 @@ export class Cluster implements ClusterModel, ClusterState { } } - protected async getAllowedResources(listApiResources:RequestListApiResources, requestNamespaceResources: RequestNamespaceResources) { + protected async getAllowedResources(requestNamespaceListPermissions: RequestNamespaceListPermissions) { + if (!this.allowedNamespaces.length) { + return []; + } + try { - if (!this.allowedNamespaces.length) { - return []; - } + const canListClusterScopedResource = await requestNamespaceListPermissions(""); + const apiLimit = plimit(5); // 5 concurrent api requests + const canListNamespacedResourceCheckers = await Promise.all(( + this.allowedNamespaces.map(namespace => apiLimit(() => requestNamespaceListPermissions(namespace))) + )); + const canListNamespacedResource: CanListResource = (resource) => canListNamespacedResourceCheckers.some(fn => fn(resource)); - const unknownResources = new Map(apiResources.map(resource => ([resource.apiName, resource]))); + const allowedClusterScopedResources = this.knownClusterscopedResources + .get() + .filter(canListClusterScopedResource); + const allowedNamespaceScopedResources = this.knownNamespacedResources + .get() + .filter(canListNamespacedResource); - const availableResources = await listApiResources(); - const availableResourcesNames = new Set(availableResources.map(apiResource => apiResource.apiName)); - - [...unknownResources.values()].map(unknownResource => { - if (!availableResourcesNames.has(unknownResource.apiName)) { - this.resourceAccessStatuses.set(unknownResource, false); - unknownResources.delete(unknownResource.apiName); - } - }); - - if (unknownResources.size > 0) { - const apiLimit = plimit(5); // 5 concurrent api requests - - await Promise.all(this.allowedNamespaces.map(namespace => apiLimit(async () => { - if (unknownResources.size === 0) { - return; - } - - const namespaceResources = await requestNamespaceResources(namespace, availableResources); - - for (const resourceName of namespaceResources) { - const unknownResource = unknownResources.get(resourceName); - - if (unknownResource) { - this.resourceAccessStatuses.set(unknownResource, true); - unknownResources.delete(resourceName); - } - } - }))); - - for (const forbiddenResource of unknownResources.values()) { - this.resourceAccessStatuses.set(forbiddenResource, false); - } - } - - return apiResources - .filter((resource) => this.resourceAccessStatuses.get(resource)) - .map(apiResource => apiResource.apiName); + return [...allowedClusterScopedResources, ...allowedNamespaceScopedResources] + .map(formatKubeApiResource); } catch (error) { return []; } } - isAllowedResource(kind: string): boolean { - if ((kind as KubeResource) in apiResourceRecord) { - return this.allowedResources.includes(kind); - } - - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return this.allowedResources.includes(apiResource.apiName); - } - - return true; // allowed by default for other resources + shouldShowResource(resource: KubeApiResourceDescriptor): boolean { + return this.allowedResources.has(formatKubeApiResource(resource)); } isMetricHidden(resource: ClusterMetricsResourceType): boolean { diff --git a/src/common/cluster/list-api-resources.injectable.ts b/src/common/cluster/list-api-resources.injectable.ts deleted file mode 100644 index f744b192da..0000000000 --- a/src/common/cluster/list-api-resources.injectable.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { - V1APIGroupList, - V1APIResourceList, - V1APIVersions, -} from "@kubernetes/client-node"; -import { getInjectable } from "@ogre-tools/injectable"; -import k8SRequestInjectable from "../../main/k8s-request.injectable"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource } from "../rbac"; -import type { Cluster } from "./cluster"; -import plimit from "p-limit"; - -export type RequestListApiResources = () => Promise; - -/** - * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster - */ -export type ListApiResources = (cluster: Cluster) => RequestListApiResources; - -interface KubeResourceListGroup { - group: string; - path: string; -} - -const listApiResourcesInjectable = getInjectable({ - id: "list-api-resources", - instantiate: (di): ListApiResources => { - const k8sRequest = di.inject(k8SRequestInjectable); - const logger = di.inject(loggerInjectable); - - return (cluster) => { - const apiLimit = plimit(5); - - return async () => { - const kubeApiResources: KubeApiResource[] = []; - const resourceListGroups: KubeResourceListGroup[] = []; - - try { - await Promise.all([ - (async () => { - const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions; - - for (const version of versions) { - resourceListGroups.push({ - group: version, - path: `/api/${version}`, - }); - } - })(), - (async () => { - const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList; - - for (const { preferredVersion, name } of groups) { - const { groupVersion } = preferredVersion ?? {}; - - if (groupVersion) { - resourceListGroups.push({ - group: name, - path: `/apis/${groupVersion}`, - }); - } - } - })(), - ]); - - await Promise.all( - resourceListGroups.map(({ group, path }) => apiLimit(async () => { - const { resources } = await k8sRequest(cluster, path) as V1APIResourceList; - - for (const resource of resources) { - if (!resource.verbs.includes("list")) { - continue; - } - - kubeApiResources.push({ - apiName: resource.name, - kind: resource.kind, - group, - namespaced: resource.namespaced, - }); - } - })), - ); - } catch (error) { - logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); - } - - return kubeApiResources; - }; - }; - }, -}); - -export default listApiResourcesInjectable; diff --git a/src/common/cluster/request-api-resources.injectable.ts b/src/common/cluster/request-api-resources.injectable.ts new file mode 100644 index 0000000000..3e1a621f39 --- /dev/null +++ b/src/common/cluster/request-api-resources.injectable.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { V1APIGroupList, V1APIResourceList, V1APIVersions } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8SRequestInjectable from "../../main/k8s-request.injectable"; +import loggerInjectable from "../logger.injectable"; +import type { KubeApiResource } from "../rbac"; +import type { Cluster } from "./cluster"; +import plimit from "p-limit"; + +export type RequestApiResources = (cluster: Cluster) => Promise; + +interface KubeResourceListGroup { + group: string; + path: string; +} + +const requestApiResourcesInjectable = getInjectable({ + id: "request-api-resources", + instantiate: (di): RequestApiResources => { + const k8sRequest = di.inject(k8SRequestInjectable); + const logger = di.inject(loggerInjectable); + + return async (cluster) => { + const apiLimit = plimit(5); + const kubeApiResources: KubeApiResource[] = []; + const resourceListGroups: KubeResourceListGroup[] = []; + + try { + await Promise.all([ + (async () => { + const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions; + + for (const version of versions) { + resourceListGroups.push({ + group: version, + path: `/api/${version}`, + }); + } + })(), + (async () => { + const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList; + + for (const { preferredVersion, name } of groups) { + const { groupVersion } = preferredVersion ?? {}; + + if (groupVersion) { + resourceListGroups.push({ + group: name, + path: `/apis/${groupVersion}`, + }); + } + } + })(), + ]); + + await Promise.all( + resourceListGroups.map(({ group, path }) => apiLimit(async () => { + const { resources } = await k8sRequest(cluster, path) as V1APIResourceList; + + for (const resource of resources) { + kubeApiResources.push({ + apiName: resource.name, + kind: resource.kind, + group, + namespaced: resource.namespaced, + }); + } + })), + ); + } catch (error) { + logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); + } + + return kubeApiResources; + }; + }, +}); + +export default requestApiResourcesInjectable; diff --git a/src/common/cluster/request-namespace-list-permissions.injectable.ts b/src/common/cluster/request-namespace-list-permissions.injectable.ts new file mode 100644 index 0000000000..845152cda3 --- /dev/null +++ b/src/common/cluster/request-namespace-list-permissions.injectable.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeConfig } from "@kubernetes/client-node"; +import { AuthorizationV1Api } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../logger.injectable"; +import type { KubeApiResource } from "../rbac"; + +export type CanListResource = (resource: KubeApiResource) => boolean; + +/** + * Requests the permissions for actions on the kube cluster + * @param namespace The namespace of the resources + */ +export type RequestNamespaceListPermissions = (namespace: string) => Promise; + +/** + * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster + */ +export type RequestNamespaceListPermissionsFor = (proxyConfig: KubeConfig) => RequestNamespaceListPermissions; + +const requestNamespaceListPermissionsForInjectable = getInjectable({ + id: "request-namespace-list-permissions-for", + instantiate: (di): RequestNamespaceListPermissionsFor => { + const logger = di.inject(loggerInjectable); + + return (proxyConfig) => { + const api = proxyConfig.makeApiClient(AuthorizationV1Api); + + return async (namespace) => { + try { + const { body: { status }} = await api.createSelfSubjectRulesReview({ + apiVersion: "authorization.k8s.io/v1", + kind: "SelfSubjectRulesReview", + spec: { namespace }, + }); + + if (!status || status.incomplete) { + logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`); + + return () => true; + } + + const { resourceRules } = status; + + return (resource) => { + const resourceRule = resourceRules.find(rule => { + console.log(rule); + void resource; + + return true; + }); + + if (!resourceRule) { + return false; + } + + const { verbs } = resourceRule; + + return verbs.includes("*") || verbs.includes("list"); + }; + } catch (error) { + logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace }); + + return () => true; + } + }; + }; + }, +}); + +export default requestNamespaceListPermissionsForInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts index 3ab2bc515c..743c827cd6 100644 --- a/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts @@ -3,22 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import shouldShowResourceInjectable from "../../../../../../renderer/cluster-frame-context/should-show-resource.injectable"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const configMapsRouteInjectable = getInjectable({ id: "config-maps-route", - - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "configmaps"); - - return { - path: "/configmaps", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, - + instantiate: (di) => ({ + path: "/configmaps", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectable, { + apiName: "configmaps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts index fe1814734f..95384dc2c4 100644 --- a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import shouldShowResourceInjectable from "../../../../../../renderer/cluster-frame-context/should-show-resource.injectable"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const horizontalPodAutoscalersRouteInjectable = getInjectable({ id: "horizontal-pod-autoscalers-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "horizontalpodautoscalers"); - - return { - path: "/hpa", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/hpa", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectable, { + apiName: "horizontalpodautoscalers", + group: "autoscaling", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts index 65ee0e3ffa..4dd1a03692 100644 --- a/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const leasesRouteInjectable = getInjectable({ id: "leases", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "leases"); - - return { - path: "/leases", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/leases", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "leases", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts index bcf740113b..8d537d8048 100644 --- a/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts @@ -3,24 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const limitRangesRouteInjectable = getInjectable({ id: "limit-ranges-route", - instantiate: (di) => { - const limitRangesIsAllowed = di.inject( - isAllowedResourceInjectable, - "limitranges", - ); - - return { - path: "/limitranges", - clusterFrame: true, - isEnabled: limitRangesIsAllowed, - }; - }, + instantiate: (di) => ({ + path: "/limitranges", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "limitranges", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts index df6d89fb06..12ce0a2138 100644 --- a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podDisruptionBudgetsRouteInjectable = getInjectable({ id: "pod-disruption-budgets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "poddisruptionbudgets"); - - return { - path: "/poddisruptionbudgets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/poddisruptionbudgets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "poddisruptionbudgets", + group: "policy", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts index 1c3632c261..75194b0541 100644 --- a/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const priorityClassesRouteInjectable = getInjectable({ id: "priority-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "priorityclasses"); - - return { - path: "/priorityclasses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/priorityclasses", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "priorityclasses", + group: "scheduling.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts index 496a42ed0c..3c2b400f9a 100644 --- a/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const resourceQuotasRouteInjectable = getInjectable({ id: "resource-quotas-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "resourcequotas"); - - return { - path: "/resourcequotas", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/resourcequotas", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "resourcequotas", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts index 72934f9158..beab83754f 100644 --- a/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const runtimeClassesRouteInjectable = getInjectable({ id: "runtime-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "runtimeclasses"); - - return { - path: "/runtimeclasses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/runtimeclasses", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "runtimeclasses", + group: "node.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts index f451f51d0c..74cd6dfd5f 100644 --- a/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const secretsRouteInjectable = getInjectable({ id: "secrets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "secrets"); - - return { - path: "/secrets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/secrets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "secrets", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts index a53cb5d1f8..d548ad9a7e 100644 --- a/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const eventsRouteInjectable = getInjectable({ id: "events-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "events"); - - return { - path: "/events", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/events", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "events", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts index 395c128682..68243c0bfb 100644 --- a/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const namespacesRouteInjectable = getInjectable({ id: "namespaces-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "namespaces"); - - return { - path: "/namespaces", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/namespaces", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "namespaces", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts index e30df3123b..bdabc44bbf 100644 --- a/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const endpointsRouteInjectable = getInjectable({ id: "endpoints-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "endpoints"); - - return { - path: "/endpoints", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/endpoints", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "endpoints", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts index 12565deeaf..8e01646b82 100644 --- a/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts @@ -3,21 +3,27 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { computedOr } from "../../../../../utils/computed-or"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const ingressesRouteInjectable = getInjectable({ id: "ingresses-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "ingresses"); - - return { - path: "/ingresses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/ingresses", + clusterFrame: true, + isEnabled: computedOr( + di.inject(shouldShowResourceInjectionToken, { + apiName: "ingresses", + group: "networking.k8s.io", + }), + di.inject(shouldShowResourceInjectionToken, { + apiName: "ingresses", + group: "extensions", + }), + ), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts index ead62ee435..38a1b8a7e2 100644 --- a/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const networkPoliciesRouteInjectable = getInjectable({ id: "network-policies-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "networkpolicies"); - - return { - path: "/network-policies", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/network-policies", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "networkpolicies", + group: "networking.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts index 033c95673b..fda8830076 100644 --- a/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const servicesRouteInjectable = getInjectable({ id: "services-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "services"); - - return { - path: "/services", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/services", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "services", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts index febd733343..96990236f5 100644 --- a/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const nodesRouteInjectable = getInjectable({ id: "nodes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); - - return { - path: "/nodes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/nodes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "nodes", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts index 209fc113d8..54af37d666 100644 --- a/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const clusterOverviewRouteInjectable = getInjectable({ id: "cluster-overview-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); - - return { - path: "/overview", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/overview", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "nodes", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts index 8879541355..fc063947cd 100644 --- a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumeClaimsRouteInjectable = getInjectable({ id: "persistent-volume-claims-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumeclaims"); - - return { - path: "/persistent-volume-claims", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/persistent-volume-claims", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "persistentvolumeclaims", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts index e6549ea45b..46aeebd9d0 100644 --- a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumesRouteInjectable = getInjectable({ id: "persistent-volumes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumes"); - - return { - path: "/persistent-volumes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/persistent-volumes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "persistentvolumes", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts index 69f2b5d4ee..8702ab1602 100644 --- a/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const storageClassesRouteInjectable = getInjectable({ id: "storage-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "storageclasses"); - - return { - path: "/storage-classes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/storage-classes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "storageclasses", + group: "storage.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts index f19491ee72..0903d5fced 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRoleBindingsRouteInjectable = getInjectable({ id: "cluster-role-bindings-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterrolebindings"); - - return { - path: "/cluster-role-bindings", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cluster-role-bindings", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "clusterrolebindings", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts index d21c2c33a4..9fce206667 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRolesRouteInjectable = getInjectable({ id: "cluster-roles-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterroles"); - - return { - path: "/cluster-roles", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cluster-roles", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "clusterroles", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts index 14cfcbedc5..2f35986916 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts @@ -3,19 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podSecurityPoliciesRouteInjectable = getInjectable({ id: "pod-security-policies-route", instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "podsecuritypolicies"); - return { path: "/pod-security-policies", clusterFrame: true, - isEnabled: isAllowedResource, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "podsecuritypolicies", + group: "policy", + }), }; }, diff --git a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts index 0f908e5876..759c1b8eda 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts @@ -3,19 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const roleBindingsRouteInjectable = getInjectable({ id: "role-bindings-route", instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "rolebindings"); - return { path: "/role-bindings", clusterFrame: true, - isEnabled: isAllowedResource, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "rolebindings", + group: "rbac.authorization.k8s.io", + }), }; }, diff --git a/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts index 94db156fa4..efe4cad810 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const rolesRouteInjectable = getInjectable({ id: "roles-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "roles"); - - return { - path: "/roles", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/roles", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "roles", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts index 4d79258c54..8b3c3ff5d3 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const serviceAccountsRouteInjectable = getInjectable({ id: "service-accounts-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "serviceaccounts"); - - return { - path: "/service-accounts", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/service-accounts", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "serviceaccounts", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts index 735ea94642..33453a2247 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const cronJobsRouteInjectable = getInjectable({ id: "cron-jobs-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "cronjobs"); - - return { - path: "/cronjobs", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cronjobs", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "cronjobs", + group: "batch", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts index 55729813e8..f1ec2008fa 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const daemonsetsRouteInjectable = getInjectable({ id: "daemonsets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "daemonsets"); - - return { - path: "/daemonsets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/daemonsets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "daemonsets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts index b9ff072e66..84c059780f 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const deploymentsRouteInjectable = getInjectable({ id: "deployments-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "deployments"); - - return { - path: "/deployments", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/deployments", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "deployments", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts index d9190a7ea8..39cc89e88f 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const jobsRouteInjectable = getInjectable({ id: "jobs-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "jobs"); - - return { - path: "/jobs", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/jobs", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "jobs", + group: "batch", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts index e9fb2a2b16..e524bb6739 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts @@ -3,21 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podsRouteInjectable = getInjectable({ id: "pods-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "pods"); - - return { - path: "/pods", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/pods", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "pods", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts index 0319d27550..b790ce13ec 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const replicasetsRouteInjectable = getInjectable({ id: "replicasets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "replicasets"); - - return { - path: "/replicasets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/replicasets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "replicasets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts index a3089fa62f..72c81b3bee 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const statefulsetsRouteInjectable = getInjectable({ id: "statefulsets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "statefulsets"); - - return { - path: "/statefulsets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/statefulsets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "statefulsets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/k8s-api/__tests__/api-manager.test.ts b/src/common/k8s-api/__tests__/api-manager.test.ts index 3e411b6584..4bca310dac 100644 --- a/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/src/common/k8s-api/__tests__/api-manager.test.ts @@ -3,6 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { DiContainer } from "@ogre-tools/injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import type { ApiManager } from "../api-manager"; import apiManagerInjectable from "../api-manager/manager.injectable"; @@ -22,10 +24,10 @@ class TestStore extends KubeObjectStore { describe("ApiManager", () => { let apiManager: ApiManager; + let di: DiContainer; beforeEach(() => { - const di = getDiForUnitTesting({ doGeneralOverrides: true }); - + di = getDiForUnitTesting({ doGeneralOverrides: true }); apiManager = di.inject(apiManagerInjectable); }); @@ -40,7 +42,9 @@ describe("ApiManager", () => { fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, }); - const kubeStore = new TestStore(kubeApi); + const kubeStore = new TestStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, kubeApi); apiManager.registerApi(apiBase, kubeApi); diff --git a/src/common/k8s-api/__tests__/kube-object.store.test.ts b/src/common/k8s-api/__tests__/kube-object.store.test.ts index 91ed80fbde..6a21fa69ca 100644 --- a/src/common/k8s-api/__tests__/kube-object.store.test.ts +++ b/src/common/k8s-api/__tests__/kube-object.store.test.ts @@ -3,27 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Cluster } from "../../cluster/cluster"; -import type { ClusterContext } from "../cluster-context"; import type { KubeApi } from "../kube-api"; import { KubeObject } from "../kube-object"; import type { KubeObjectStoreLoadingParams } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; class FakeKubeObjectStore extends KubeObjectStore { - _context = { - allNamespaces: [], - contextNamespaces: [], - hasSelectedAll: false, - cluster: {} as Cluster, - } as ClusterContext; - - get context() { - return this._context; - } - constructor(private readonly _loadItems: (params: KubeObjectStoreLoadingParams) => KubeObject[], api: Partial>) { - super(api as KubeApi); + super({ + context: { + allNamespaces: [], + contextNamespaces: [], + hasSelectedAll: false, + isGlobalWatchEnabled: () => true, + isLoadingAll: () => true, + isNamespaceListStatic: () => false, + }, + }, api as KubeApi); } async loadItems(params: KubeObjectStoreLoadingParams) { diff --git a/src/common/k8s-api/api-manager/auto-registration.injectable.ts b/src/common/k8s-api/api-manager/auto-registration.injectable.ts index 0cf1a3055d..178426650f 100644 --- a/src/common/k8s-api/api-manager/auto-registration.injectable.ts +++ b/src/common/k8s-api/api-manager/auto-registration.injectable.ts @@ -3,9 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; import type { CustomResourceDefinition } from "../endpoints"; import { KubeApi } from "../kube-api"; import { KubeObject } from "../kube-object"; +import type { KubeObjectStoreDependencies } from "../kube-object.store"; import autoRegistrationEmitterInjectable from "./auto-registration-emitter.injectable"; import apiManagerInjectable from "./manager.injectable"; import { CustomResourceStore } from "./resource.store"; @@ -16,6 +18,9 @@ const autoRegistrationInjectable = getInjectable({ const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; const beforeApiManagerInitializationApis: KubeApi[] = []; + const deps: KubeObjectStoreDependencies = { + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }; let initialized = false; const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { @@ -40,7 +45,7 @@ const autoRegistrationInjectable = getInjectable({ })(); if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); + apiManager.registerStore(new CustomResourceStore(deps, api)); } }; const autoInitKubeApi = (api: KubeApi) => { diff --git a/src/common/k8s-api/api-manager/resource.store.ts b/src/common/k8s-api/api-manager/resource.store.ts index 63ccdcf93d..c81ce7daec 100644 --- a/src/common/k8s-api/api-manager/resource.store.ts +++ b/src/common/k8s-api/api-manager/resource.store.ts @@ -4,11 +4,12 @@ */ import type { KubeApi } from "../kube-api"; +import type { KubeObjectStoreDependencies } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; import type { KubeObject } from "../kube-object"; export class CustomResourceStore extends KubeObjectStore> { - constructor(api: KubeApi) { - super(api); + constructor(deps: KubeObjectStoreDependencies, api: KubeApi) { + super(deps, api); } } diff --git a/src/common/k8s-api/cluster-context.ts b/src/common/k8s-api/cluster-context.ts deleted file mode 100644 index 098d92642d..0000000000 --- a/src/common/k8s-api/cluster-context.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { Cluster } from "../cluster/cluster"; - -export interface ClusterContext { - cluster: Cluster; - allNamespaces: string[]; // available / allowed namespaces from cluster.ts - contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) - hasSelectedAll: boolean; -} diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 309c183e42..a82590d758 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -3,11 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterContext } from "./cluster-context"; - -import { action, computed, makeObservable, observable, reaction, when } from "mobx"; +import { action, computed, makeObservable, observable, reaction } from "mobx"; import type { Disposer } from "../utils"; -import { waitUntilDefined, autoBind, includes, noop, rejectPromiseBy } from "../utils"; +import { waitUntilDefined, autoBind, includes, rejectPromiseBy } from "../utils"; import type { KubeJsonApiDataFor, KubeObject } from "./kube-object"; import { KubeStatus } from "./kube-object"; import type { IKubeWatchEvent } from "./kube-watch-event"; @@ -21,6 +19,7 @@ import assert from "assert"; import type { PartialDeep } from "type-fest"; import { entries } from "../utils/objects"; import AbortController from "abort-controller"; +import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context"; export type OnLoadFailure = (error: unknown) => void; @@ -85,38 +84,26 @@ export type KubeApiDataFrom = A extends KubeApi = KubeApi>, D extends KubeJsonApiDataFor = KubeApiDataFrom, > extends ItemStore { - static readonly defaultContext = observable.box(); // TODO: support multiple cluster contexts - - public readonly api!: A; public readonly limit: number | undefined; public readonly bufferSize: number; - @observable private loadedNamespaces: string[] | undefined = undefined; - get contextReady() { - return when(() => Boolean(this.context)); - } + private readonly loadedNamespaces = observable.box(); - get namespacesReady() { - return when(() => Boolean(this.loadedNamespaces)); - } - - constructor(api: A, opts?: KubeObjectStoreOptions); - /** - * @deprecated Supply API instance through constructor - */ - constructor(); - constructor(api?: A, opts?: KubeObjectStoreOptions) { + constructor( + protected readonly dependencies: KubeObjectStoreDependencies, + public readonly api: A, + opts?: KubeObjectStoreOptions, + ) { super(); - - if (api) { - this.api = api; - } - this.limit = opts?.limit; this.bufferSize = opts?.bufferSize ?? 50_000; @@ -125,13 +112,9 @@ export abstract class KubeObjectStore< this.bindWatchEventsUpdater(); } - get context(): ClusterContext | undefined { - return KubeObjectStore.defaultContext.get(); - } - // TODO: Circular dependency: KubeObjectStore -> ClusterFrameContext -> NamespaceStore -> KubeObjectStore @computed get contextItems(): K[] { - const namespaces = this.context?.contextNamespaces ?? []; + const namespaces = this.dependencies.context.contextNamespaces; return this.items.filter(item => { const itemNamespace = item.getNs(); @@ -202,17 +185,11 @@ export abstract class KubeObjectStore< } protected async loadItems({ namespaces, reqInit, onLoadFailure }: KubeObjectStoreLoadingParams): Promise { - if (!this.context?.cluster?.isAllowedResource(this.api.kind)) { - return []; - } - - const isLoadingAll = this.context.allNamespaces?.length > 1 - && this.context.cluster.accessibleNamespaces.length === 0 - && this.context.allNamespaces.every(ns => namespaces.includes(ns)); + const isLoadingAll = this.dependencies.context.isLoadingAll(namespaces); if (!this.api.isNamespaced || isLoadingAll) { if (this.api.isNamespaced) { - this.loadedNamespaces = []; + this.loadedNamespaces.set([]); } const res = this.api.list({ reqInit }, this.query); @@ -234,7 +211,7 @@ export abstract class KubeObjectStore< return await res ?? []; } - this.loadedNamespaces = namespaces; + this.loadedNamespaces.set(namespaces); const results = await Promise.allSettled( namespaces.map(namespace => this.api.list({ namespace, reqInit }, this.query)), @@ -266,9 +243,7 @@ export abstract class KubeObjectStore< @action async loadAll({ namespaces, merge = true, reqInit, onLoadFailure }: KubeObjectStoreLoadAllParams = {}): Promise { - const context = await waitUntilDefined(() => this.context); - - namespaces ??= context.contextNamespaces; + namespaces ??= this.dependencies.context.contextNamespaces; this.isLoading = true; try { @@ -425,7 +400,7 @@ export abstract class KubeObjectStore< } // collect items from watch-api events to avoid UI blowing up with huge streams of data - protected eventsBuffer = observable.array>([], { deep: false }); + protected readonly eventsBuffer = observable.array>([], { deep: false }); protected bindWatchEventsUpdater(delay = 1000) { reaction(() => [...this.eventsBuffer], this.updateFromEventsBuffer, { @@ -435,25 +410,24 @@ export abstract class KubeObjectStore< subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer { if (this.api.isNamespaced) { - Promise.race([ - rejectPromiseBy(abortController.signal), - Promise.all([ - waitUntilDefined(() => this.context), - this.namespacesReady, - ] as const), - ]) - .then(([context]) => { - assert(this.loadedNamespaces); + void (async () => { + try { + const [loadedNamespaces] = await Promise.race([ + rejectPromiseBy(abortController.signal), + waitUntilDefined(() => this.loadedNamespaces.get()), + ]); - if (context.cluster?.isGlobalWatchEnabled && this.loadedNamespaces.length === 0) { - return this.watchNamespace("", abortController, { onLoadFailure }); + if (this.dependencies.context.isGlobalWatchEnabled() && loadedNamespaces.length === 0) { + this.watchNamespace("", abortController, { onLoadFailure }); + } else { + for (const namespace of loadedNamespaces) { + this.watchNamespace(namespace, abortController, { onLoadFailure }); + } } - - for (const namespace of this.loadedNamespaces) { - this.watchNamespace(namespace, abortController, { onLoadFailure }); - } - }) - .catch(noop); // ignore DOMExceptions + } catch { + // ignore + } + })(); } else { this.watchNamespace("", abortController, { onLoadFailure }); } diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 4315e899fb..8f41ec223a 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -11,11 +11,22 @@ export type KubeResource = "priorityclasses" | "runtimeclasses" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; -export interface KubeApiResource extends KubeApiResourceData { +export interface KubeApiResource extends KubeApiResourceData, KubeApiResourceDescriptor { apiName: string; namespaced: boolean; } +export interface KubeApiResourceDescriptor { + apiName: string; + group?: string; +} + +export const formatKubeApiResource = (res: KubeApiResourceDescriptor) => ( + res.group + ? `${res.group}/${res.apiName}` + : res.apiName +); + export interface KubeApiResourceData { kind: string; // resource type (e.g. "Namespace") group?: string; // api-group diff --git a/src/common/utils/computed-or.ts b/src/common/utils/computed-or.ts new file mode 100644 index 0000000000..4e93394924 --- /dev/null +++ b/src/common/utils/computed-or.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { IComputedValue } from "mobx"; +import { computed } from "mobx"; + +export const computedOr = (...values: IComputedValue[]) => computed(( + () => values.some(value => value.get()) +)); diff --git a/src/common/utils/is-allowed-resource.injectable.ts b/src/common/utils/is-allowed-resource.injectable.ts deleted file mode 100644 index 8841a8f0cc..0000000000 --- a/src/common/utils/is-allowed-resource.injectable.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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 { KubeResource } from "../rbac"; -import { allowedResourcesInjectionToken } from "../cluster-store/allowed-resources-injection-token"; - -export type IsAllowedResource = (resource: KubeResource) => boolean; - -const isAllowedResourceInjectable = getInjectable({ - id: "is-allowed-resource", - - instantiate: (di, resourceName: string) => { - const allowedResources = di.inject(allowedResourcesInjectionToken); - - return computed(() => allowedResources.get().has(resourceName)); - }, - - lifecycle: lifecycleEnum.keyedSingleton({ - getInstanceKey: (di, resource: string) => resource, - }), -}); - -export default isAllowedResourceInjectable; diff --git a/src/extensions/common-api/k8s-api.ts b/src/extensions/common-api/k8s-api.ts index e5c7013bc7..129dff3ad9 100644 --- a/src/extensions/common-api/k8s-api.ts +++ b/src/extensions/common-api/k8s-api.ts @@ -15,6 +15,11 @@ import type { ResourceApplyingStack } from "../../common/k8s/resource-stack"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; import type { KubernetesCluster } from "./catalog"; +import type { KubeApiDataFrom, KubeObjectStoreOptions } from "../../common/k8s-api/kube-object.store"; +import { KubeObjectStore as InternalKubeObjectStore } from "../../common/k8s-api/kube-object.store"; +import type { KubeJsonApiDataFor, KubeObject } from "../../common/k8s-api/kube-object"; +import type { KubeApi } from "../../common/k8s-api/kube-api"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; export const apiManager = asLegacyGlobalForExtensionApi(apiManagerInjectable); export const forCluster = asLegacyGlobalFunctionForExtensionApi(createKubeApiForClusterInjectable); @@ -72,8 +77,33 @@ export { type KubeJsonApiData, } from "../../common/k8s-api/kube-json-api"; +export abstract class KubeObjectStore< + K extends KubeObject = KubeObject, + A extends KubeApi = KubeApi>, + D extends KubeJsonApiDataFor = KubeApiDataFrom, +> extends InternalKubeObjectStore { + get context() { + return this.dependencies.context; + } + + constructor(api: A, opts?: KubeObjectStoreOptions); + /** + * @deprecated Supply API instance through constructor + */ + constructor(); + constructor(api?: A, opts?: KubeObjectStoreOptions) { + super( + { + context: asLegacyGlobalForExtensionApi(clusterFrameContextForNamespacedResourcesInjectable), + }, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + api!, + opts, + ); + } +} + export { - KubeObjectStore, type JsonPatch, type KubeObjectStoreLoadAllParams, type KubeObjectStoreLoadingParams, diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index 3401d979f7..6335559409 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -3,8 +3,7 @@ * 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 { castArray } from "lodash/fp"; +import { apiResourceRecord } from "../../common/rbac"; import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import clusterRoleBindingApiInjectable from "../../common/k8s-api/endpoints/cluster-role-binding.api.injectable"; import clusterRoleApiInjectable from "../../common/k8s-api/endpoints/cluster-role.api.injectable"; @@ -37,13 +36,22 @@ import namespaceApiInjectable from "../../common/k8s-api/endpoints/namespace.api import kubeEventApiInjectable from "../../common/k8s-api/endpoints/events.api.injectable"; import roleBindingApiInjectable from "../../common/k8s-api/endpoints/role-binding.api.injectable"; import customResourceDefinitionApiInjectable from "../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; -export function isAllowedResource(resource: KubeResource | KubeResource[]) { - const resources = castArray(resource); +export function isAllowedResource(resources: KubeResource | KubeResource[]) { const di = getLegacyGlobalDiForExtensionApi(); - return resources.every((resourceName: any) => { - const _isAllowedResource = di.inject(isAllowedResourceInjectable, resourceName); + return [resources].flat().every((resourceName) => { + const resource = apiResourceRecord[resourceName]; + + if (!resource) { + return true; + } + + const _isAllowedResource = di.inject(shouldShowResourceInjectionToken, { + apiName: resourceName, + group: resource.group, + }); // Note: Legacy isAllowedResource does not advertise reactivity return _isAllowedResource.get(); diff --git a/src/features/cluster/visibility-of-sidebar-items.test.tsx b/src/features/cluster/visibility-of-sidebar-items.test.tsx index 0e09f0efa8..44d6f7218f 100644 --- a/src/features/cluster/visibility-of-sidebar-items.test.tsx +++ b/src/features/cluster/visibility-of-sidebar-items.test.tsx @@ -9,11 +9,11 @@ import { sidebarItemsInjectionToken } from "../../renderer/components/layout/sid import { computed, runInAction } from "mobx"; import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token"; import React from "react"; -import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token"; import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; describe("cluster - visibility of sidebar items", () => { let builder: ApplicationBuilder; @@ -69,20 +69,13 @@ describe("cluster - visibility of sidebar items", () => { const testRouteInjectable = getInjectable({ id: "some-route-injectable-id", - instantiate: (di) => { - const someKubeResourceName = "namespaces"; - - const kubeResourceIsAllowed = di.inject( - isAllowedResourceInjectable, - someKubeResourceName, - ); - - return { - path: "/some-child-page", - isEnabled: kubeResourceIsAllowed, - clusterFrame: true, - }; - }, + instantiate: (di) => ({ + path: "/some-child-page", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "namespaces", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/main/__test__/cluster.test.ts b/src/main/__test__/cluster.test.ts index 4a9b257623..d9e48fb3d9 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -10,7 +10,7 @@ import { getDiForUnitTesting } from "../getDiForUnitTesting"; import type { CreateCluster } from "../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; -import authorizationNamespaceReviewInjectable from "../../common/cluster/authorization-namespace-review.injectable"; +import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import type { ClusterContextHandler } from "../context-handler/context-handler"; @@ -20,8 +20,6 @@ import directoryForTempInjectable from "../../common/app-paths/directory-for-tem import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; -import { apiResourceRecord, apiResources } from "../../common/rbac"; -import listApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable"; import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable"; import pathExistsInjectable from "../../common/fs/path-exists.injectable"; import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable"; @@ -46,8 +44,7 @@ describe("create clusters", () => { di.override(normalizedPlatformInjectable, () => "darwin"); di.override(broadcastMessageInjectable, () => async () => {}); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); - di.override(authorizationNamespaceReviewInjectable, () => () => () => Promise.resolve(Object.keys(apiResourceRecord))); - di.override(listApiResourcesInjectable, () => () => () => Promise.resolve(apiResources)); + di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(createContextHandlerInjectable, () => (cluster) => ({ restartServer: jest.fn(), diff --git a/src/main/create-cluster/allowed-resources.injectable.ts b/src/main/create-cluster/allowed-resources.injectable.ts index d614ca3d17..8492a122f6 100644 --- a/src/main/create-cluster/allowed-resources.injectable.ts +++ b/src/main/create-cluster/allowed-resources.injectable.ts @@ -4,13 +4,13 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { allowedResourcesInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; // TODO: Figure out implementation for this later. const allowedResourcesInjectable = getInjectable({ id: "allowed-resources", instantiate: () => computed(() => new Set()), - injectionToken: allowedResourcesInjectionToken, + injectionToken: shouldShowResourceInjectionToken, }); export default allowedResourcesInjectable; diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index e1782ec6c9..cf735ebc5f 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -11,9 +11,9 @@ import createKubectlInjectable from "../kubectl/create-kubectl.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; -import createAuthorizationNamespaceReview from "../../common/cluster/authorization-namespace-review.injectable"; +import createAuthorizationNamespaceReview from "../../common/cluster/request-namespace-list-permissions.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; -import createListApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable"; +import createListApiResourcesInjectable from "../../common/cluster/request-api-resources.injectable"; import loggerInjectable from "../../common/logger.injectable"; import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; @@ -31,7 +31,7 @@ const createClusterInjectable = getInjectable({ createContextHandler: di.inject(createContextHandlerInjectable), createAuthorizationReview: di.inject(authorizationReviewInjectable), createAuthorizationNamespaceReview: di.inject(createAuthorizationNamespaceReview), - createListApiResources: di.inject(createListApiResourcesInjectable), + requestApiResources: di.inject(createListApiResourcesInjectable), createListNamespaces: di.inject(listNamespacesInjectable), logger: di.inject(loggerInjectable), detectorRegistry: di.inject(detectorRegistryInjectable), diff --git a/src/renderer/cluster-frame-context/allowed-resources.injectable.ts b/src/renderer/cluster-frame-context/allowed-resources.injectable.ts deleted file mode 100644 index 646d931367..0000000000 --- a/src/renderer/cluster-frame-context/allowed-resources.injectable.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 { getInjectable } from "@ogre-tools/injectable"; -import { comparer, computed } from "mobx"; -import hostedClusterInjectable from "./hosted-cluster.injectable"; -import { allowedResourcesInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; - -const allowedResourcesInjectable = getInjectable({ - id: "allowed-resources", - - 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), - }); - }, - - injectionToken: allowedResourcesInjectionToken, -}); - -export default allowedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts b/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts deleted file mode 100644 index a353a30b40..0000000000 --- a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.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 { getInjectable } from "@ogre-tools/injectable"; -import { ClusterFrameContext } from "./cluster-frame-context"; -import namespaceStoreInjectable from "../components/+namespaces/store.injectable"; -import hostedClusterInjectable from "./hosted-cluster.injectable"; -import assert from "assert"; - -const clusterFrameContextInjectable = getInjectable({ - id: "cluster-frame-context", - - instantiate: (di) => { - const cluster = di.inject(hostedClusterInjectable); - - assert(cluster, "This can only be injected within a cluster frame"); - - return new ClusterFrameContext(cluster, { - namespaceStore: di.inject(namespaceStoreInjectable), - }); - }, -}); - -export default clusterFrameContextInjectable; diff --git a/src/renderer/cluster-frame-context/cluster-frame-context.ts b/src/renderer/cluster-frame-context/cluster-frame-context.ts index 1f1625e9e1..6257bdbea6 100755 --- a/src/renderer/cluster-frame-context/cluster-frame-context.ts +++ b/src/renderer/cluster-frame-context/cluster-frame-context.ts @@ -3,44 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Cluster } from "../../common/cluster/cluster"; -import type { NamespaceStore } from "../components/+namespaces/store"; -import type { ClusterContext } from "../../common/k8s-api/cluster-context"; -import { computed, makeObservable } from "mobx"; +/** + * This type is used for KubeObjectStores + */ +export interface ClusterContext { + readonly allNamespaces: string[]; // available / allowed namespaces from cluster.ts + readonly contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) + readonly hasSelectedAll: boolean; -interface Dependencies { - namespaceStore: NamespaceStore; -} - -export class ClusterFrameContext implements ClusterContext { - constructor(public cluster: Cluster, private dependencies: Dependencies) { - makeObservable(this); - } - - @computed get allNamespaces(): string[] { - // user given list of namespaces - if (this.cluster.accessibleNamespaces.length) { - return this.cluster.accessibleNamespaces; - } - - if (this.dependencies.namespaceStore.items.length > 0) { - // namespaces from kubernetes api - return this.dependencies.namespaceStore.items.map((namespace) => namespace.getName()); - } else { - // fallback to cluster resolved namespaces because we could not load list - return this.cluster.allowedNamespaces || []; - } - } - - @computed get contextNamespaces(): string[] { - return this.dependencies.namespaceStore.contextNamespaces; - } - - @computed get hasSelectedAll(): boolean { - const namespaces = new Set(this.contextNamespaces); - - return this.allNamespaces?.length > 1 - && this.cluster.accessibleNamespaces.length === 0 - && this.allNamespaces.every(ns => namespaces.has(ns)); - } + isNamespaceListStatic(): boolean; + isLoadingAll(namespaces: string[]): boolean; + isGlobalWatchEnabled(): boolean; } diff --git a/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts b/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts new file mode 100644 index 0000000000..db13bc92d1 --- /dev/null +++ b/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import assert from "assert"; +import type { ClusterContext } from "./cluster-frame-context"; +import hostedClusterInjectable from "./hosted-cluster.injectable"; + +const clusterFrameContextForClusterScopedResourcesInjectable = getInjectable({ + id: "cluster-frame-context-for-cluster-scoped-resources", + instantiate: (di): ClusterContext => { + const cluster = di.inject(hostedClusterInjectable); + + assert(cluster, "This can only be injected within a cluster frame"); + + return { + isGlobalWatchEnabled: () => cluster.isGlobalWatchEnabled, + // This is always the case for cluster scoped resources + isLoadingAll: () => true, + isNamespaceListStatic: () => cluster.accessibleNamespaces.length > 0, + allNamespaces: [], + contextNamespaces: [], // This value is used as a sentinal + hasSelectedAll: true, + }; + }, +}); + +export default clusterFrameContextForClusterScopedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts b/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts new file mode 100644 index 0000000000..61452ec7af --- /dev/null +++ b/src/renderer/cluster-frame-context/for-namespaced-resources.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 } from "@ogre-tools/injectable"; +import type { ClusterContext } from "./cluster-frame-context"; +import namespaceStoreInjectable from "../components/+namespaces/store.injectable"; +import hostedClusterInjectable from "./hosted-cluster.injectable"; +import assert from "assert"; +import { computed } from "mobx"; + +const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ + id: "cluster-frame-context-for-namespaced-resources", + + instantiate: (di): ClusterContext => { + const cluster = di.inject(hostedClusterInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + + assert(cluster, "This can only be injected within a cluster frame"); + + const allNamespaces = computed(() => { + // user given list of namespaces + if (cluster.accessibleNamespaces.length) { + return cluster.accessibleNamespaces.slice(); + } + + if (namespaceStore.items.length > 0) { + // namespaces from kubernetes api + return namespaceStore.items.map((namespace) => namespace.getName()); + } + + // fallback to cluster resolved namespaces because we could not load list + return cluster.allowedNamespaces.slice(); + }); + const contextNamespaces = computed(() => namespaceStore.contextNamespaces); + const hasSelectedAll = computed(() => { + const namespaces = new Set(contextNamespaces.get()); + + return allNamespaces.get().length > 1 + && cluster.accessibleNamespaces.length === 0 + && allNamespaces.get().every(ns => namespaces.has(ns)); + }); + + return { + isNamespaceListStatic: () => cluster.accessibleNamespaces.length > 0, + isLoadingAll: (namespaces) => ( + allNamespaces.get().length > 1 + && cluster.accessibleNamespaces.length === 0 + && allNamespaces.get().every(ns => namespaces.includes(ns)) + ), + isGlobalWatchEnabled: () => cluster.isGlobalWatchEnabled, + get allNamespaces() { + return allNamespaces.get(); + }, + get contextNamespaces() { + return contextNamespaces.get(); + }, + get hasSelectedAll() { + return hasSelectedAll.get(); + }, + }; + }, +}); + +export default clusterFrameContextForNamespacedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/should-show-resource.injectable.ts b/src/renderer/cluster-frame-context/should-show-resource.injectable.ts new file mode 100644 index 0000000000..2ec4132045 --- /dev/null +++ b/src/renderer/cluster-frame-context/should-show-resource.injectable.ts @@ -0,0 +1,27 @@ +/** + * 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 hostedClusterInjectable from "./hosted-cluster.injectable"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import type { KubeApiResourceDescriptor } from "../../common/rbac"; +import { formatKubeApiResource } from "../../common/rbac"; + +const shouldShowResourceInjectable = getInjectable({ + id: "should-show-resource", + instantiate: (di, resource) => { + const cluster = di.inject(hostedClusterInjectable); + + return cluster + ? computed(() => cluster.shouldShowResource(resource)) + : computed(() => false); + }, + injectionToken: shouldShowResourceInjectionToken, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, resource: KubeApiResourceDescriptor) => formatKubeApiResource(resource), + }), +}); + +export default shouldShowResourceInjectable; diff --git a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts index 6389009785..a4666adcef 100644 --- a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts +++ b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts @@ -13,6 +13,7 @@ import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-cre import assert from "assert"; import nodeStoreInjectable from "../../+nodes/store.injectable"; import requestClusterMetricsByNodeNamesInjectable from "../../../../common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const clusterOverviewStoreInjectable = getInjectable({ id: "cluster-overview-store", @@ -32,6 +33,7 @@ const clusterOverviewStoreInjectable = getInjectable({ ), nodeStore: di.inject(nodeStoreInjectable), requestClusterMetricsByNodeNames: di.inject(requestClusterMetricsByNodeNamesInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, clusterApi); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts index 2669cb71a6..943b14dbf9 100644 --- a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts +++ b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts @@ -4,6 +4,7 @@ */ import { action, observable, reaction, when, makeObservable } from "mobx"; +import type { KubeObjectStoreDependencies } from "../../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import type { Cluster, ClusterApi } from "../../../../common/k8s-api/endpoints"; import type { StorageLayer } from "../../../utils"; @@ -28,7 +29,7 @@ export interface ClusterOverviewStorageState { metricNodeRole: MetricNodeRole; } -interface ClusterOverviewStoreDependencies { +interface ClusterOverviewStoreDependencies extends KubeObjectStoreDependencies { readonly storage: StorageLayer; readonly nodeStore: NodeStore; requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames; @@ -58,7 +59,7 @@ export class ClusterOverviewStore extends KubeObjectStore i } constructor(protected readonly dependencies: ClusterOverviewStoreDependencies, api: ClusterApi) { - super(api); + super(dependencies, api); makeObservable(this); autoBind(this); diff --git a/src/renderer/components/+config-autoscalers/store.injectable.ts b/src/renderer/components/+config-autoscalers/store.injectable.ts index a2d271df5d..4d4a3adc21 100644 --- a/src/renderer/components/+config-autoscalers/store.injectable.ts +++ b/src/renderer/components/+config-autoscalers/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import horizontalPodAutoscalerApiInjectable from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { HorizontalPodAutoscalerStore } from "./store"; @@ -16,7 +17,9 @@ const horizontalPodAutoscalerStoreInjectable = getInjectable({ const api = di.inject(horizontalPodAutoscalerApiInjectable); - return new HorizontalPodAutoscalerStore(api); + return new HorizontalPodAutoscalerStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-leases/store.injectable.ts b/src/renderer/components/+config-leases/store.injectable.ts index b9dda24b07..eb9676ce90 100644 --- a/src/renderer/components/+config-leases/store.injectable.ts +++ b/src/renderer/components/+config-leases/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import leaseApiInjectable from "../../../common/k8s-api/endpoints/lease.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { LeaseStore } from "./store"; @@ -16,7 +17,9 @@ const leaseStoreInjectable = getInjectable({ const api = di.inject(leaseApiInjectable); - return new LeaseStore(api); + return new LeaseStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-limit-ranges/store.injectable.ts b/src/renderer/components/+config-limit-ranges/store.injectable.ts index 4f1224131a..494968b60c 100644 --- a/src/renderer/components/+config-limit-ranges/store.injectable.ts +++ b/src/renderer/components/+config-limit-ranges/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import limitRangeApiInjectable from "../../../common/k8s-api/endpoints/limit-range.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { LimitRangeStore } from "./store"; @@ -16,7 +17,9 @@ const limitRangeStoreInjectable = getInjectable({ const api = di.inject(limitRangeApiInjectable); - return new LimitRangeStore(api); + return new LimitRangeStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-maps/store.injectable.ts b/src/renderer/components/+config-maps/store.injectable.ts index d16da8318e..7cbd59e4a5 100644 --- a/src/renderer/components/+config-maps/store.injectable.ts +++ b/src/renderer/components/+config-maps/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import configMapApiInjectable from "../../../common/k8s-api/endpoints/config-map.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ConfigMapStore } from "./store"; @@ -16,7 +17,9 @@ const configMapStoreInjectable = getInjectable({ const api = di.inject(configMapApiInjectable); - return new ConfigMapStore(api); + return new ConfigMapStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts b/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts index 5314584274..697fd5c444 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts +++ b/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import podDisruptionBudgetApiInjectable from "../../../common/k8s-api/endpoints/pod-disruption-budget.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PodDisruptionBudgetStore } from "./store"; @@ -16,7 +17,9 @@ const podDisruptionBudgetStoreInjectable = getInjectable({ const api = di.inject(podDisruptionBudgetApiInjectable); - return new PodDisruptionBudgetStore(api); + return new PodDisruptionBudgetStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-priority-classes/store.injectable.ts b/src/renderer/components/+config-priority-classes/store.injectable.ts index 68677a3b5f..b01e69602c 100644 --- a/src/renderer/components/+config-priority-classes/store.injectable.ts +++ b/src/renderer/components/+config-priority-classes/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import priorityClassApiInjectable from "../../../common/k8s-api/endpoints/priority-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PriorityClassStore } from "./store"; @@ -16,7 +17,9 @@ const priorityClassStoreInjectable = getInjectable({ const api = di.inject(priorityClassApiInjectable); - return new PriorityClassStore(api); + return new PriorityClassStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-resource-quotas/store.injectable.ts b/src/renderer/components/+config-resource-quotas/store.injectable.ts index d8294c8132..4999ca9517 100644 --- a/src/renderer/components/+config-resource-quotas/store.injectable.ts +++ b/src/renderer/components/+config-resource-quotas/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import resourceQuotaApiInjectable from "../../../common/k8s-api/endpoints/resource-quota.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ResourceQuotaStore } from "./store"; @@ -16,7 +17,9 @@ const resourceQuotaStoreInjectable = getInjectable({ const api = di.inject(resourceQuotaApiInjectable); - return new ResourceQuotaStore(api); + return new ResourceQuotaStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-secrets/store.injectable.ts b/src/renderer/components/+config-secrets/store.injectable.ts index 9e84f20696..2af7bf13f6 100644 --- a/src/renderer/components/+config-secrets/store.injectable.ts +++ b/src/renderer/components/+config-secrets/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import secretApiInjectable from "../../../common/k8s-api/endpoints/secret.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { SecretStore } from "./store"; @@ -16,7 +17,9 @@ const secretStoreInjectable = getInjectable({ const api = di.inject(secretApiInjectable); - return new SecretStore(api); + return new SecretStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+custom-resources/definition.store.injectable.ts b/src/renderer/components/+custom-resources/definition.store.injectable.ts index ee4719cc97..b93f656df9 100644 --- a/src/renderer/components/+custom-resources/definition.store.injectable.ts +++ b/src/renderer/components/+custom-resources/definition.store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { CustomResourceDefinitionStore } from "./definition.store"; @@ -19,6 +20,7 @@ const customResourceDefinitionStoreInjectable = getInjectable({ return new CustomResourceDefinitionStore({ autoRegistration: di.inject(autoRegistrationEmitterInjectable), + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+custom-resources/definition.store.ts b/src/renderer/components/+custom-resources/definition.store.ts index 778f895214..bef5c2323e 100644 --- a/src/renderer/components/+custom-resources/definition.store.ts +++ b/src/renderer/components/+custom-resources/definition.store.ts @@ -4,7 +4,7 @@ */ import { computed, reaction, makeObservable } from "mobx"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { autoBind } from "../../utils"; import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; @@ -12,7 +12,7 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type TypedEventEmitter from "typed-emitter"; import type { LegacyAutoRegistration } from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; -export interface CustomResourceDefinitionStoreDependencies { +export interface CustomResourceDefinitionStoreDependencies extends KubeObjectStoreDependencies { readonly autoRegistration: TypedEventEmitter; } @@ -22,7 +22,7 @@ export class CustomResourceDefinitionStore extends KubeObjectStore { - const clusterContext = di.inject(clusterFrameContextInjectable); + const clusterContext = di.inject(clusterFrameContextForNamespacedResourcesInjectable); const releaseSecrets = di.inject(releaseSecretsInjectable); const requestHelmReleases = di.inject(requestHelmReleasesInjectable); const toHelmRelease = di.inject(toHelmReleaseInjectable); diff --git a/src/renderer/components/+namespaces/store.injectable.ts b/src/renderer/components/+namespaces/store.injectable.ts index 68d611e837..9c835e0139 100644 --- a/src/renderer/components/+namespaces/store.injectable.ts +++ b/src/renderer/components/+namespaces/store.injectable.ts @@ -9,6 +9,7 @@ import createStorageInjectable from "../../utils/create-storage/create-storage.i import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable"; import assert from "assert"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; const namespaceStoreInjectable = getInjectable({ id: "namespace-store", @@ -20,6 +21,7 @@ const namespaceStoreInjectable = getInjectable({ const api = di.inject(namespaceApiInjectable); return new NamespaceStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), storage: createStorage("selected_namespaces", undefined), }, api); }, diff --git a/src/renderer/components/+namespaces/store.ts b/src/renderer/components/+namespaces/store.ts index 1b2342facf..8c44390cf7 100644 --- a/src/renderer/components/+namespaces/store.ts +++ b/src/renderer/components/+namespaces/store.ts @@ -7,18 +7,18 @@ import type { IReactionDisposer } from "mobx"; import { action, comparer, computed, makeObservable, reaction } from "mobx"; import type { StorageLayer } from "../../utils"; import { autoBind, noop, toggle } from "../../utils"; -import type { KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api"; import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api"; -interface Dependencies { - storage: StorageLayer; +interface Dependencies extends KubeObjectStoreDependencies { + readonly storage: StorageLayer; } export class NamespaceStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: NamespaceApi) { - super(api); + super(dependencies, api); makeObservable(this); autoBind(this); @@ -26,7 +26,6 @@ export class NamespaceStore extends KubeObjectStore { } private async init() { - await this.contextReady; await this.dependencies.storage.whenReady; this.selectNamespaces(this.initialNamespaces); @@ -75,10 +74,7 @@ export class NamespaceStore extends KubeObjectStore { } @computed get allowedNamespaces(): string[] { - return Array.from(new Set([ - ...(this.context?.allNamespaces ?? []), // allowed namespaces from cluster (main), updating every 30s - ...this.items.map(item => item.getName()), // loaded namespaces from k8s api - ].flat())); + return this.items.map(item => item.getName()); } /** @@ -114,7 +110,7 @@ export class NamespaceStore extends KubeObjectStore { * if user has given static list of namespaces let's not start watches * because watch adds stuff that's not wanted or will just fail */ - if ((this.context?.cluster.accessibleNamespaces.length ?? 0) > 0) { + if (this.dependencies.context.isNamespaceListStatic()) { return noop; } diff --git a/src/renderer/components/+network-endpoints/store.injectable.ts b/src/renderer/components/+network-endpoints/store.injectable.ts index a63014ea70..7531b933b3 100644 --- a/src/renderer/components/+network-endpoints/store.injectable.ts +++ b/src/renderer/components/+network-endpoints/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import endpointsApiInjectable from "../../../common/k8s-api/endpoints/endpoint.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { EndpointsStore } from "./store"; @@ -16,7 +17,9 @@ const endpointsStoreInjectable = getInjectable({ const api = di.inject(endpointsApiInjectable); - return new EndpointsStore(api); + return new EndpointsStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-ingresses/store.injectable.ts b/src/renderer/components/+network-ingresses/store.injectable.ts index 9b95939e9f..0084a006a2 100644 --- a/src/renderer/components/+network-ingresses/store.injectable.ts +++ b/src/renderer/components/+network-ingresses/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import ingressApiInjectable from "../../../common/k8s-api/endpoints/ingress.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { IngressStore } from "./store"; @@ -16,7 +17,9 @@ const ingressStoreInjectable = getInjectable({ const api = di.inject(ingressApiInjectable); - return new IngressStore(api); + return new IngressStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-policies/store.injectable.ts b/src/renderer/components/+network-policies/store.injectable.ts index a666c04cb4..ad79a89b54 100644 --- a/src/renderer/components/+network-policies/store.injectable.ts +++ b/src/renderer/components/+network-policies/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import networkPolicyApiInjectable from "../../../common/k8s-api/endpoints/network-policy.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { NetworkPolicyStore } from "./store"; @@ -16,7 +17,9 @@ const networkPolicyStoreInjectable = getInjectable({ const api = di.inject(networkPolicyApiInjectable); - return new NetworkPolicyStore(api); + return new NetworkPolicyStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-services/store.injectable.ts b/src/renderer/components/+network-services/store.injectable.ts index 6013a97f15..e54f994162 100644 --- a/src/renderer/components/+network-services/store.injectable.ts +++ b/src/renderer/components/+network-services/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import serviceApiInjectable from "../../../common/k8s-api/endpoints/service.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ServiceStore } from "./store"; @@ -16,7 +17,9 @@ const serviceStoreInjectable = getInjectable({ const api = di.inject(serviceApiInjectable); - return new ServiceStore(api); + return new ServiceStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+nodes/store.injectable.ts b/src/renderer/components/+nodes/store.injectable.ts index adb69a99ad..ac149210bd 100644 --- a/src/renderer/components/+nodes/store.injectable.ts +++ b/src/renderer/components/+nodes/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import nodeApiInjectable from "../../../common/k8s-api/endpoints/node.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { NodeStore } from "./store"; @@ -16,7 +17,9 @@ const nodeStoreInjectable = getInjectable({ const api = di.inject(nodeApiInjectable); - return new NodeStore(api); + return new NodeStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+nodes/store.ts b/src/renderer/components/+nodes/store.ts index 9f4be4b620..87fafa9256 100644 --- a/src/renderer/components/+nodes/store.ts +++ b/src/renderer/components/+nodes/store.ts @@ -6,13 +6,13 @@ import { sum } from "lodash"; import { computed, makeObservable } from "mobx"; import type { Node, NodeApi } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { autoBind } from "../../utils"; export class NodeStore extends KubeObjectStore { - constructor(api: NodeApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + constructor(dependencies: KubeObjectStoreDependencies, api: NodeApi, opts?: KubeObjectStoreOptions) { + super(dependencies, api, opts); makeObservable(this); autoBind(this); diff --git a/src/renderer/components/+pod-security-policies/store.injectable.ts b/src/renderer/components/+pod-security-policies/store.injectable.ts index b010b60c97..44ab372d70 100644 --- a/src/renderer/components/+pod-security-policies/store.injectable.ts +++ b/src/renderer/components/+pod-security-policies/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import podSecurityPolicyApiInjectable from "../../../common/k8s-api/endpoints/pod-security-policy.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PodSecurityPolicyStore } from "./store"; @@ -16,7 +17,9 @@ const podSecurityPolicyStoreInjectable = getInjectable({ const api = di.inject(podSecurityPolicyApiInjectable); - return new PodSecurityPolicyStore(api); + return new PodSecurityPolicyStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+storage-classes/store.injectable.ts b/src/renderer/components/+storage-classes/store.injectable.ts index ab4ae8c784..17f6da93db 100644 --- a/src/renderer/components/+storage-classes/store.injectable.ts +++ b/src/renderer/components/+storage-classes/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getPersistentVolumesByStorageClassInjectable from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import storageClassApiInjectable from "../../../common/k8s-api/endpoints/storage-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { StorageClassStore } from "./store"; @@ -19,6 +20,7 @@ const storageClassStoreInjectable = getInjectable({ return new StorageClassStore({ getPersistentVolumesByStorageClass: di.inject(getPersistentVolumesByStorageClassInjectable), + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+storage-classes/store.ts b/src/renderer/components/+storage-classes/store.ts index 8037a8d67d..8c01c145b3 100644 --- a/src/renderer/components/+storage-classes/store.ts +++ b/src/renderer/components/+storage-classes/store.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { StorageClass, StorageClassApi, StorageClassData } from "../../../common/k8s-api/endpoints/storage-class.api"; import type { GetPersistentVolumesByStorageClass } from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable"; -export interface StorageClassStoreDependencies { +export interface StorageClassStoreDependencies extends KubeObjectStoreDependencies { getPersistentVolumesByStorageClass: GetPersistentVolumesByStorageClass; } @@ -18,7 +18,7 @@ export class StorageClassStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: CronJobApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getStatuses(cronJobs?: CronJob[]) { diff --git a/src/renderer/components/+workloads-daemonsets/store.injectable.ts b/src/renderer/components/+workloads-daemonsets/store.injectable.ts index 3b7611dc43..5711ad625f 100644 --- a/src/renderer/components/+workloads-daemonsets/store.injectable.ts +++ b/src/renderer/components/+workloads-daemonsets/store.injectable.ts @@ -9,6 +9,7 @@ import daemonSetApiInjectable from "../../../common/k8s-api/endpoints/daemon-set import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { DaemonSetStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const daemonSetStoreInjectable = getInjectable({ id: "daemon-set-store", @@ -19,6 +20,7 @@ const daemonSetStoreInjectable = getInjectable({ return new DaemonSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-daemonsets/store.ts b/src/renderer/components/+workloads-daemonsets/store.ts index 66216e3f76..1345468b62 100644 --- a/src/renderer/components/+workloads-daemonsets/store.ts +++ b/src/renderer/components/+workloads-daemonsets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { DaemonSet, DaemonSetApi, Pod } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -export interface DaemonSetStoreDependencies { +export interface DaemonSetStoreDependencies extends KubeObjectStoreDependencies { readonly getPodsByOwnerId: GetPodsByOwnerId; } export class DaemonSetStore extends KubeObjectStore { constructor(protected readonly dependencies: DaemonSetStoreDependencies, api: DaemonSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(daemonSet: DaemonSet): Pod[] { diff --git a/src/renderer/components/+workloads-deployments/store.injectable.ts b/src/renderer/components/+workloads-deployments/store.injectable.ts index 02eb3da48c..b77d9aa496 100644 --- a/src/renderer/components/+workloads-deployments/store.injectable.ts +++ b/src/renderer/components/+workloads-deployments/store.injectable.ts @@ -9,6 +9,7 @@ import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manag import { storesAndApisCanBeCreatedInjectionToken } from "../../../common/k8s-api/stores-apis-can-be-created.token"; import deploymentApiInjectable from "../../../common/k8s-api/endpoints/deployment.api.injectable"; import { DeploymentStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const deploymentStoreInjectable = getInjectable({ id: "deployment-store", @@ -19,6 +20,7 @@ const deploymentStoreInjectable = getInjectable({ return new DeploymentStore({ podStore: di.inject(podStoreInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-deployments/store.ts b/src/renderer/components/+workloads-deployments/store.ts index 014f1c2abc..af5d085594 100644 --- a/src/renderer/components/+workloads-deployments/store.ts +++ b/src/renderer/components/+workloads-deployments/store.ts @@ -6,7 +6,7 @@ import type { PodStore } from "../+workloads-pods/store"; import type { Deployment, DeploymentApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; // This needs to be disables because of https://github.com/microsoft/TypeScript/issues/15300 @@ -17,13 +17,13 @@ export type DeploymentStatuses = { pending: number; }; -export interface DeploymentStoreDependencies { +export interface DeploymentStoreDependencies extends KubeObjectStoreDependencies { readonly podStore: PodStore; } export class DeploymentStore extends KubeObjectStore { constructor(protected readonly dependencies: DeploymentStoreDependencies, api: DeploymentApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } protected sortItems(items: Deployment[]) { diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index 2430409d30..19c81712a9 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -12,6 +12,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import workloadsInjectable from "./workloads/workloads.injectable"; import type { Workload } from "./workloads/workload-injection-token"; +import { formatKubeApiResource } from "../../../common/rbac"; export interface OverviewStatusesProps {} @@ -24,7 +25,7 @@ const NonInjectedOverviewStatuses = observer(
{workloads.get().map((workload) => ( -
+
{`${workload.title} (${workload.amountOfItems.get()})`} diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 7c93bd429c..584485bcaf 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -17,7 +17,7 @@ import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import workloadOverviewDetailsInjectable from "./workload-overview-details/workload-overview-details.injectable"; @@ -123,7 +123,7 @@ class NonInjectedWorkloadsOverview extends React.Component { export const WorkloadsOverview = withInjectables(NonInjectedWorkloadsOverview, { getProps: (di) => ({ detailComponents: di.inject(workloadOverviewDetailsInjectable), - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), subscribeStores: di.inject(subscribeStoresInjectable), daemonSetStore: di.inject(daemonSetStoreInjectable), podStore: di.inject(podStoreInjectable), diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts index b4e22d64ba..7e8ab5cf6e 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts @@ -19,17 +19,17 @@ const cronJobsWorkloadInjectable = getInjectable({ const store = di.inject(cronJobsStoreInjectable); return { - resourceName: "cronjobs", + resource: { + apiName: "cronjobs", + group: "batch", + }, open: navigate, - amountOfItems: computed( () => store.getAllByNs(namespaceStore.contextNamespaces).length, ), - status: computed(() => store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), ), - title: ResourceNames.cronjobs, orderNumber: 70, }; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts index 0124c0d1ce..7b6d7d750f 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts @@ -19,7 +19,10 @@ const daemonsetsWorkloadInjectable = getInjectable({ const store = di.inject(daemonsetsStoreInjectable); return { - resourceName: "daemonsets", + resource: { + apiName: "daemonsets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts index 27ca74fe97..b41e4ea5ca 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts @@ -19,7 +19,10 @@ const deploymentsWorkloadInjectable = getInjectable({ const store = di.inject(deploymentsStoreInjectable); return { - resourceName: "deployments", + resource: { + apiName: "deployments", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts index 3442aabaf7..e68fd88c1c 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts @@ -19,7 +19,10 @@ const jobsWorkloadInjectable = getInjectable({ const store = di.inject(jobStoreInjectable); return { - resourceName: "jobs", + resource: { + apiName: "jobs", + group: "batch", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts index 2a2afefe3a..644fccaf08 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts @@ -19,7 +19,9 @@ const podsWorkloadInjectable = getInjectable({ const store = di.inject(podStoreInjectable); return { - resourceName: "pods", + resource: { + apiName: "pods", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts index 6102e70b19..86189e4634 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts @@ -19,7 +19,10 @@ const replicasetsWorkloadInjectable = getInjectable({ const store = di.inject(replicasetsStoreInjectable); return { - resourceName: "replicasets", + resource: { + apiName: "replicasets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts index 8a12f3c0af..19b85c4fa0 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts @@ -19,7 +19,10 @@ const statefulsetsWorkloadInjectable = getInjectable({ const store = di.inject(statefulsetsStoreInjectable); return { - resourceName: "statefulsets", + resource: { + apiName: "statefulsets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts index decb5a1f7c..fca19148a7 100644 --- a/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts +++ b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts @@ -4,10 +4,11 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; +import type { KubeApiResourceDescriptor } from "../../../../common/rbac"; import type { WorkloadStatus } from "../overview-workload-status"; export interface Workload { - resourceName: string; + resource: KubeApiResourceDescriptor; open: () => void; amountOfItems: IComputedValue; status: IComputedValue; diff --git a/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts index aeaa62ed4a..d6b23b582f 100644 --- a/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts @@ -2,18 +2,11 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { pipeline } from "@ogre-tools/fp"; import { getInjectable } from "@ogre-tools/injectable"; -import { filter, sortBy as sortByWithBadTyping } from "lodash/fp"; import { computed } from "mobx"; -import type { Workload } from "./workload-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../common/cluster-store/allowed-resources-injection-token"; +import { byOrderNumber } from "../../../../common/utils/composable-responsibilities/orderable/orderable"; import { workloadInjectionToken } from "./workload-injection-token"; -import isAllowedResourceInjectable from "../../../../common/utils/is-allowed-resource.injectable"; - -const sortBy = - (propertyPath: string) => - (collection: Collection[]) => - sortByWithBadTyping(propertyPath, collection); const workloadsInjectable = getInjectable({ id: "workloads", @@ -21,22 +14,11 @@ const workloadsInjectable = getInjectable({ instantiate: (di) => { const workloads = di.injectMany(workloadInjectionToken); - const isAllowedResource = (resourceName: string) => - di.inject(isAllowedResourceInjectable, resourceName); - - return computed(() => - pipeline( - workloads, - - filter((workload: Workload) => { - const isAllowed = isAllowedResource(workload.resourceName); - - return isAllowed.get(); - }), - - sortBy("orderNumber"), - ), - ); + return computed(() => ( + workloads + .filter(w => di.inject(shouldShowResourceInjectionToken, w.resource).get()) + .sort(byOrderNumber) + )); }, }); diff --git a/src/renderer/components/+workloads-pods/store.injectable.ts b/src/renderer/components/+workloads-pods/store.injectable.ts index 59b91b8c27..a1bf527bef 100644 --- a/src/renderer/components/+workloads-pods/store.injectable.ts +++ b/src/renderer/components/+workloads-pods/store.injectable.ts @@ -9,6 +9,7 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { PodStore } from "./store"; import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const podStoreInjectable = getInjectable({ id: "pod-store", @@ -19,6 +20,7 @@ const podStoreInjectable = getInjectable({ return new PodStore({ podMetricsApi: di.inject(podMetricsApiInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-pods/store.ts b/src/renderer/components/+workloads-pods/store.ts index 146eb0b390..5ce9dca041 100644 --- a/src/renderer/components/+workloads-pods/store.ts +++ b/src/renderer/components/+workloads-pods/store.ts @@ -5,13 +5,13 @@ import countBy from "lodash/countBy"; import { observable } from "mobx"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { cpuUnitsToNumber, unitsToBytes } from "../../utils"; import type { Pod, PodMetrics, PodApi, PodMetricsApi } from "../../../common/k8s-api/endpoints"; import type { KubeObject, NamespaceScopedMetadata } from "../../../common/k8s-api/kube-object"; -export interface PodStoreDependencies { +export interface PodStoreDependencies extends KubeObjectStoreDependencies { readonly podMetricsApi: PodMetricsApi; } @@ -21,7 +21,7 @@ export class PodStore extends KubeObjectStore { api: PodApi, opts?: KubeObjectStoreOptions, ) { - super(api, opts); + super(dependencies, api, opts); } readonly kubeMetrics = observable.array([]); diff --git a/src/renderer/components/+workloads-replicasets/store.injectable.ts b/src/renderer/components/+workloads-replicasets/store.injectable.ts index 73da498071..c961e504ad 100644 --- a/src/renderer/components/+workloads-replicasets/store.injectable.ts +++ b/src/renderer/components/+workloads-replicasets/store.injectable.ts @@ -9,6 +9,7 @@ import replicaSetApiInjectable from "../../../common/k8s-api/endpoints/replica-s import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { ReplicaSetStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const replicaSetStoreInjectable = getInjectable({ id: "replica-set-store", @@ -19,6 +20,7 @@ const replicaSetStoreInjectable = getInjectable({ return new ReplicaSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-replicasets/store.ts b/src/renderer/components/+workloads-replicasets/store.ts index d782846cd0..1a6ac96610 100644 --- a/src/renderer/components/+workloads-replicasets/store.ts +++ b/src/renderer/components/+workloads-replicasets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { Deployment, ReplicaSet, ReplicaSetApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints/pod.api"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -interface Dependencies { +export interface ReplicaSetStoreDependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class ReplicaSetStore extends KubeObjectStore { - constructor(protected readonly dependencies: Dependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + constructor(protected readonly dependencies: ReplicaSetStoreDependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) { + super(dependencies, api, opts); } getChildPods(replicaSet: ReplicaSet) { diff --git a/src/renderer/components/+workloads-statefulsets/store.injectable.ts b/src/renderer/components/+workloads-statefulsets/store.injectable.ts index 0e440d969e..5931b5dfaf 100644 --- a/src/renderer/components/+workloads-statefulsets/store.injectable.ts +++ b/src/renderer/components/+workloads-statefulsets/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getPodsByOwnerIdInjectable from "../+workloads-pods/get-pods-by-owner-id.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import statefulSetApiInjectable from "../../../common/k8s-api/endpoints/stateful-set.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { StatefulSetStore } from "./store"; @@ -19,6 +20,7 @@ const statefulSetStoreInjectable = getInjectable({ return new StatefulSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-statefulsets/store.ts b/src/renderer/components/+workloads-statefulsets/store.ts index 0732ae5680..bdedcdcaf7 100644 --- a/src/renderer/components/+workloads-statefulsets/store.ts +++ b/src/renderer/components/+workloads-statefulsets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { StatefulSet, StatefulSetApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class StatefulSetStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: StatefulSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(statefulSet: StatefulSet) { diff --git a/src/renderer/components/cluster-settings/accessible-namespaces.tsx b/src/renderer/components/cluster-settings/accessible-namespaces.tsx index 479cc8f443..2f3b9b930d 100644 --- a/src/renderer/components/cluster-settings/accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/accessible-namespaces.tsx @@ -32,13 +32,13 @@ export class ClusterAccessibleNamespaces extends React.Component { this.namespaces.add(newNamespace); - this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); + this.props.cluster.accessibleNamespaces.replace([...this.namespaces]); }} validators={systemName} items={Array.from(this.namespaces)} remove={({ oldItem: oldNamespace }) => { this.namespaces.delete(oldNamespace); - this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); + this.props.cluster.accessibleNamespaces.replace([...this.namespaces]); }} inputTheme="round-black" /> diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index 707351c231..e1d6596a2c 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -21,7 +21,7 @@ import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import type { SubscribableStore, SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import type { KubeApi } from "../../../common/k8s-api/kube-api"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; @@ -177,7 +177,7 @@ export const KubeObjectListLayout = withInjectables< >(NonInjectedKubeObjectListLayout, { getProps: (di, props) => ({ ...props, - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), subscribeToStores: di.inject(subscribeStoresInjectable), kubeSelectedUrlParam: di.inject(kubeSelectedUrlParamInjectable), toggleKubeDetailsPane: di.inject(toggleKubeDetailsPaneInjectable), diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 0a27fe6d01..4ce5f4af1b 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -9,10 +9,10 @@ import type { IComputedValue, ObservableMap } from "mobx"; import { action, computed, observable, runInAction } from "mobx"; import React from "react"; import { Router } from "react-router"; -import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable"; import type { RenderResult } from "@testing-library/react"; import { fireEvent, queryByText } from "@testing-library/react"; -import type { KubeResource } from "../../../common/rbac"; +import type { KubeApiResourceDescriptor } from "../../../common/rbac"; +import { formatKubeApiResource } from "../../../common/rbac"; import type { DiContainer, Injectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable"; import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable"; @@ -24,8 +24,7 @@ import navigateToHelmChartsInjectable from "../../../common/front-end-routing/ro import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; import type { Cluster } from "../../../common/cluster/cluster"; -import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable"; import startFrameInjectable from "../../start-frame/start-frame.injectable"; import type { NamespaceStore } from "../+namespaces/store"; @@ -114,7 +113,7 @@ export interface ApplicationBuilder { create: (id: string) => LensWindowWithHelpers; }; - allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder; + allowKubeResource: (resource: KubeApiResourceDescriptor) => ApplicationBuilder; beforeApplicationStart: (callback: Callback) => ApplicationBuilder; afterApplicationStart: (callback: Callback) => ApplicationBuilder; beforeWindowStart: (callback: Callback) => ApplicationBuilder; @@ -213,7 +212,7 @@ export const getApplicationBuilder = () => { }, })); - const allowedResourcesState = observable.array(); + const allowedResourcesState = observable.set(); const windowHelpers = new Map RenderResult }>(); @@ -501,14 +500,10 @@ export const getApplicationBuilder = () => { environment = environments.clusterFrame; builder.beforeWindowStart((windowDi) => { - windowDi.override(allowedResourcesInjectable, () => - computed(() => new Set([...allowedResourcesState])), - ); - const clusterStub = { id: "some-cluster-id", - accessibleNamespaces: [], - isAllowedResource: (kind) => allowedResourcesState.includes(kind), + accessibleNamespaces: observable.array(), + shouldShowResource: (kind) => allowedResourcesState.has(formatKubeApiResource(kind)), } as Partial as Cluster; windowDi.override(activeKubernetesClusterInjectable, () => @@ -547,10 +542,7 @@ export const getApplicationBuilder = () => { windowDi.override(namespaceStoreInjectable, () => namespaceStoreStub); windowDi.override(hostedClusterInjectable, () => clusterStub); - windowDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake); - - // Todo: get rid of global state. - KubeObjectStore.defaultContext.set(clusterFrameContextFake); + windowDi.override(clusterFrameContextForNamespacedResourcesInjectable, () => clusterFrameContextFake); }); return builder; @@ -634,11 +626,11 @@ export const getApplicationBuilder = () => { }, }, - allowKubeResource: (resourceName) => { + allowKubeResource: (resource) => { environment.onAllowKubeResource(); runInAction(() => { - allowedResourcesState.push(resourceName); + allowedResourcesState.add(formatKubeApiResource(resource)); }); return builder; diff --git a/src/renderer/create-cluster/create-cluster.injectable.ts b/src/renderer/create-cluster/create-cluster.injectable.ts index e0a9f51656..385dfe8d66 100644 --- a/src/renderer/create-cluster/create-cluster.injectable.ts +++ b/src/renderer/create-cluster/create-cluster.injectable.ts @@ -27,9 +27,9 @@ const createClusterInjectable = getInjectable({ createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");}, createContextHandler: () => undefined as never, createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, - createAuthorizationNamespaceReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, + requestNamespaceListPermissionsFor: () => { throw new Error("Tried to access back-end feature in front-end."); }, createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); }, - createListApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); }, + requestApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); }, detectorRegistry: undefined as never, createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); }, }; diff --git a/src/renderer/frames/cluster-frame/cluster-frame.test.tsx b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx index f95a80fee2..cf5dd8434d 100644 --- a/src/renderer/frames/cluster-frame/cluster-frame.test.tsx +++ b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx @@ -21,7 +21,6 @@ import directoryForUserDataInjectable from "../../../common/app-paths/directory- import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import legacyOnChannelListenInjectable from "../../ipc/legacy-channel-listen.injectable"; import currentRouteComponentInjectable from "../../routes/current-route-component.injectable"; -import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable"; import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluster-id.injectable"; import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import { useFakeTime } from "../../../common/test-utils/use-fake-time"; @@ -69,7 +68,8 @@ describe("", () => { describe("given cluster with list nodes and namespaces permissions", () => { beforeEach(() => { - di.override(allowedResourcesInjectable, () => computed(() => new Set(["nodes", "namespaces"]))); + // TODO: replace with not using private info + (cluster as any).allowedResources.replace(["nodes", "namespaces"]); }); it("renders", () => { @@ -110,7 +110,7 @@ describe("", () => { describe("given cluster without list nodes, but with namespaces permissions", () => { beforeEach(() => { - di.override(allowedResourcesInjectable, () => computed(() => new Set(["namespaces"]))); + (cluster as any).allowedResources.replace(["namespaces"]); }); it("renders", () => { 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 909834a047..c640264ee3 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,10 +7,11 @@ import { initClusterFrame } from "./init-cluster-frame"; import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable"; import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable"; import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable"; -import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable"; import assert from "assert"; import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import loadExtensionsInjectable from "../../load-extensions.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -26,7 +27,8 @@ const initClusterFrameInjectable = getInjectable({ catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), frameRoutingId: di.inject(frameRoutingIdInjectable), emitAppEvent: di.inject(emitAppEventInjectable), - clusterFrameContext: di.inject(clusterFrameContextInjectable), + logger: di.inject(loggerInjectable), + showErrorNotification: di.inject(showErrorNotificationInjectable), }); }, }); diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts index 9476393366..109ae0f0bc 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts @@ -4,13 +4,11 @@ */ import type { Cluster } from "../../../../common/cluster/cluster"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry"; -import logger from "../../../../main/logger"; -import { Notifications } from "../../../components/notifications"; +import type { ShowNotification } from "../../../components/notifications"; import { when } from "mobx"; -import type { ClusterFrameContext } from "../../../cluster-frame-context/cluster-frame-context"; -import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import { requestSetClusterFrameId } from "../../../ipc"; import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.injectable"; +import type { Logger } from "../../../../common/logger"; interface Dependencies { hostedCluster: Cluster; @@ -18,9 +16,8 @@ interface Dependencies { catalogEntityRegistry: CatalogEntityRegistry; frameRoutingId: number; emitAppEvent: EmitAppEvent; - - // TODO: This dependency belongs to KubeObjectStore - clusterFrameContext: ClusterFrameContext; + logger: Logger; + showErrorNotification: ShowNotification; } const logPrefix = "[CLUSTER-FRAME]:"; @@ -31,7 +28,8 @@ export const initClusterFrame = ({ catalogEntityRegistry, frameRoutingId, emitAppEvent, - clusterFrameContext, + logger, + showErrorNotification, }: Dependencies) => async (unmountRoot: () => void) => { // TODO: Make catalogEntityRegistry already initialized when passed as dependency @@ -55,14 +53,12 @@ export const initClusterFrame = ({ { timeout: 15_000, onError: (error) => { - console.warn( + logger.warn( "[CLUSTER-FRAME]: error from activeEntity when()", error, ); - Notifications.error( - "Failed to get KubernetesCluster for this view. Extensions will not be loaded.", - ); + showErrorNotification("Failed to get KubernetesCluster for this view. Extensions will not be loaded."); }, }, ); @@ -84,6 +80,4 @@ export const initClusterFrame = ({ unmountRoot(); }; - // TODO: Make context dependency of KubeObjectStore - KubeObjectStore.defaultContext.set(clusterFrameContext); }; diff --git a/src/renderer/initializers/workload-events.tsx b/src/renderer/initializers/workload-events.tsx index 734b5105f0..426bb875c1 100644 --- a/src/renderer/initializers/workload-events.tsx +++ b/src/renderer/initializers/workload-events.tsx @@ -7,7 +7,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import React from "react"; -import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; +import shouldShowResourceInjectable from "../cluster-frame-context/should-show-resource.injectable"; import { Events } from "../components/+events/events"; export interface WorkloadEventsProps {} @@ -32,7 +32,9 @@ const NonInjectedWorkloadEvents = observer(({ workloadEventsAreAllowed }: Depend export const WorkloadEvents = withInjectables(NonInjectedWorkloadEvents, { getProps: (di, props) => ({ - workloadEventsAreAllowed: di.inject(isAllowedResourceInjectable, "events"), + workloadEventsAreAllowed: di.inject(shouldShowResourceInjectable, { + apiName: "events", + }), ...props, }), }); diff --git a/src/renderer/kube-watch-api/kube-watch-api.injectable.ts b/src/renderer/kube-watch-api/kube-watch-api.injectable.ts index 3fc8aa60d3..df6e5df56a 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.injectable.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.injectable.ts @@ -3,14 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import clusterFrameContextInjectable from "../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../cluster-frame-context/for-namespaced-resources.injectable"; import { KubeWatchApi } from "./kube-watch-api"; const kubeWatchApiInjectable = getInjectable({ id: "kube-watch-api", instantiate: (di) => new KubeWatchApi({ - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }), });