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 aa78453569..0000000000 --- a/src/common/cluster/authorization-namespace-review.injectable.ts +++ /dev/null @@ -1,87 +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 type { Logger } from "../logger"; -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; - -interface Dependencies { - logger: Logger; -} - -const authorizationNamespaceReview = ({ logger }: Dependencies): AuthorizationNamespaceReview => { - return (proxyConfig) => { - - const api = proxyConfig.makeApiClient(AuthorizationV1Api); - - return async (namespace, availableResources) => { - try { - const { body } = await api.createSelfSubjectRulesReview({ - apiVersion: "authorization.k8s.io/v1", - kind: "SelfSubjectRulesReview", - spec: { namespace }, - }); - - const resources = new Set(); - - body.status?.resourceRules.forEach(resourceRule => { - if (!resourceRule.verbs.some(verb => ["*", "list"].includes(verb)) || !resourceRule.resources) { - return; - } - - const apiGroups = resourceRule.apiGroups; - - if (resourceRule.resources.length === 1 && resourceRule.resources[0] === "*" && apiGroups) { - if (apiGroups[0] === "*") { - availableResources.forEach(resource => resources.add(resource.apiName)); - } else { - availableResources.forEach((apiResource)=> { - if (apiGroups.includes(apiResource.group || "")) { - resources.add(apiResource.apiName); - } - }); - } - } else { - resourceRule.resources.forEach(resource => resources.add(resource)); - } - - }); - - return [...resources]; - } catch (error) { - logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace }); - - return []; - } - }; - }; -}; - -const authorizationNamespaceReviewInjectable = getInjectable({ - id: "authorization-namespace-review", - instantiate: (di) => { - const logger = di.inject(loggerInjectable); - - return authorizationNamespaceReview({ logger }); - }, -}); - -export default authorizationNamespaceReviewInjectable; diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index fe66c9fe1b..303ee89361 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,21 @@ 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(); + + // The formatting of this is `group.name` or `name` (if in core) + private readonly allowedResources = observable.set(); /** * Labels for the catalog entity @@ -299,7 +294,7 @@ export class Cluster implements ClusterModel, ClusterState { } if (model.accessibleNamespaces) { - this.accessibleNamespaces = model.accessibleNamespaces; + this.accessibleNamespaces.replace(model.accessibleNamespaces); } if (model.labels) { @@ -433,8 +428,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 +468,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 +479,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 +594,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 +605,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 +638,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 +662,28 @@ 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 apiLimit = plimit(5); // 5 concurrent api requests + const canListResourceCheckers = await Promise.all(( + this.allowedNamespaces.map(namespace => apiLimit(() => requestNamespaceListPermissions(namespace))) + )); + const canListNamespacedResource: CanListResource = (resource) => canListResourceCheckers.some(fn => fn(resource)); - const unknownResources = new Map(apiResources.map(resource => ([resource.apiName, resource]))); - - 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 this.knownResources + .filter(canListNamespacedResource) + .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/is-allowed-resource.ts b/src/common/cluster/is-allowed-resource.ts deleted file mode 100644 index 7a6a392f78..0000000000 --- a/src/common/cluster/is-allowed-resource.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { KubeResource } from "../rbac"; -import { apiResourceRecord, apiResources } from "../rbac"; - -export const isAllowedResource = (allowedResources: string[]) => (kind: string): boolean => { - if ((kind as KubeResource) in apiResourceRecord) { - return allowedResources.includes(kind); - } - - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return allowedResources.includes(apiResource.apiName); - } - - return true; // allowed by default for other resources -}; 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 ed9d5c9c39..0000000000 --- a/src/common/cluster/list-api-resources.injectable.ts +++ /dev/null @@ -1,91 +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 type { K8sRequest } from "../../main/k8s-request.injectable"; -import k8SRequestInjectable from "../../main/k8s-request.injectable"; -import type { Logger } from "../logger"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource, KubeResource } 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 Dependencies { - logger: Logger; - k8sRequest: K8sRequest; -} - -const listApiResources = ({ k8sRequest, logger }: Dependencies): ListApiResources => { - return (cluster) => { - const clusterRequest = (path: string) => k8sRequest(cluster, path); - const apiLimit = plimit(5); - - return async () => { - const resources: KubeApiResource[] = []; - - try { - const resourceListGroups:{ group:string;path:string }[] = []; - - await Promise.all( - [ - clusterRequest("/api").then((response:V1APIVersions)=>response.versions.forEach(version => resourceListGroups.push({ group:version, path:`/api/${version}` }))), - clusterRequest("/apis").then((response:V1APIGroupList) => response.groups.forEach(group => { - const preferredVersion = group.preferredVersion?.groupVersion; - - if (preferredVersion) { - resourceListGroups.push({ group:group.name, path:`/apis/${preferredVersion}` }); - } - })), - ], - ); - - await Promise.all( - resourceListGroups.map(({ group, path }) => apiLimit(async () => { - const apiResources:V1APIResourceList = await clusterRequest(path); - - if (apiResources.resources) { - resources.push( - ...apiResources.resources.filter(resource => resource.verbs.includes("list")).map((resource) => ({ - apiName: resource.name as KubeResource, - kind: resource.kind, - group, - })), - ); - } - }), - ), - ); - } catch (error) { - logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); - } - - return resources; - }; - }; -}; - -const listApiResourcesInjectable = getInjectable({ - id: "list-api-resources", - instantiate: (di) => { - const k8sRequest = di.inject(k8SRequestInjectable); - const logger = di.inject(loggerInjectable); - - return listApiResources({ k8sRequest, logger }); - }, -}); - -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..62d2477e42 --- /dev/null +++ b/src/common/cluster/request-namespace-list-permissions.injectable.ts @@ -0,0 +1,78 @@ +/** + * 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(({ + apiGroups = [], + resources = [], + }) => { + const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group); + const isAboutResource = resources.includes("*") || resources.includes(resource.apiName); + + return isAboutRelevantApiGroup && isAboutResource; + }); + + 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`, { namespace, error }); + + return () => true; + } + }; + }; + }, +}); + +export default requestNamespaceListPermissionsForInjectable; diff --git a/src/common/configure-packages.ts b/src/common/configure-packages.ts deleted file mode 100644 index ec48be44ce..0000000000 --- a/src/common/configure-packages.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import * as Mobx from "mobx"; -import * as Immer from "immer"; - -/** - * Setup default configuration for external npm-packages - */ -export default function configurePackages() { - // Docs: https://mobx.js.org/configuration.html - Mobx.configure({ - enforceActions: "never", - - // TODO: enable later (read more: https://mobx.js.org/migrating-from-4-or-5.html) - // computedRequiresReaction: true, - // reactionRequiresObservable: true, - // observableRequiresReaction: true, - }); - - // Docs: https://immerjs.github.io/immer/ - // Required in `utils/storage-helper.ts` - Immer.setAutoFreeze(false); // allow to merge mobx observables - Immer.enableMapSet(); // allow to merge maps and sets -} 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..6ea03fff08 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,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 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(shouldShowResourceInjectionToken, { + apiName: "configmaps", + group: "v1", + }), + }), 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..00002620ee 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 { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; 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(shouldShowResourceInjectionToken, { + 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..ea4eb2ae59 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,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 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", + group: "coordination.k8s.io", + }), + }), 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..8623f3520e 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,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 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", + group: "v1", + }), + }), 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..209f77e19a 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,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 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", + group: "v1", + }), + }), 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..079ddcbf83 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,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 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", + group: "v1", + }), + }), 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..b3df358ad8 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,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 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", + group: "v1", + }), + }), 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..2aa6c23efe 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,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 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", + group: "v1", + }), + }), 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..c88ec04714 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,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 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", + group: "v1", + }), + }), 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..53300ee241 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,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 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", + group: "v1", + }), + }), 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..81323843d5 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,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 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", + group: "v1", + }), + }), 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..8315fd7773 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,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 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", + group: "v1", + }), + }), 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..1b96933136 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,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 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", + group: "v1", + }), + }), 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..52f95b32c6 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,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 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", + group: "v1", + }), + }), 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..3bf6c1ec00 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,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 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", + group: "v1", + }), + }), 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..577f1c1a91 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,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 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", + group: "v1", + }), + }), 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..e99ac62018 100644 --- a/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/src/common/k8s-api/__tests__/api-manager.test.ts @@ -3,7 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { DiContainer } from "@ogre-tools/injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import type { ApiManager } from "../api-manager"; import apiManagerInjectable from "../api-manager/manager.injectable"; import { KubeApi } from "../kube-api"; @@ -22,9 +29,24 @@ class TestStore extends KubeObjectStore { describe("ApiManager", () => { let apiManager: ApiManager; + let di: DiContainer; beforeEach(() => { - const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di = getDiForUnitTesting({ doGeneralOverrides: true }); + + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); apiManager = di.inject(apiManagerInjectable); }); @@ -40,7 +62,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-api-version-detection.test.ts b/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts index eb13464716..e2caef39fa 100644 --- a/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts +++ b/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts @@ -3,43 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { KubeJsonApi } from "../kube-json-api"; -import { PassThrough } from "stream"; import type { ApiManager } from "../api-manager"; import { Ingress, IngressApi } from "../endpoints"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import apiManagerInjectable from "../api-manager/manager.injectable"; -import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { flushPromises } from "../../test-utils/flush-promises"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; -import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; - -const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: new PassThrough(), - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: data.length, - status: statusCode, - statusText: "some-text", - text: jest.fn(async () => data), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; +import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; +import { createMockResponseFromString } from "../../../test-utils/mock-responses"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; describe("KubeApi", () => { let request: KubeJsonApi; @@ -52,6 +32,20 @@ describe("KubeApi", () => { fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const createKubeJsonApi = di.inject(createKubeJsonApiInjectable); request = createKubeJsonApi({ @@ -60,7 +54,9 @@ describe("KubeApi", () => { }); registerApiSpy = jest.spyOn(di.inject(apiManagerInjectable), "registerApi"); - di.inject(autoRegistrationInjectable); + const setupAutoRegistration = di.inject(setupAutoRegistrationInjectable); + + setupAutoRegistration.run(); }); describe("on first call to IngressApi.get()", () => { diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index 4f67ed1401..a4f9fd5b21 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -8,7 +8,6 @@ import type { KubeJsonApi, KubeJsonApiData } from "../kube-json-api"; import { PassThrough } from "stream"; import { Deployment, DeploymentApi, NamespaceApi, Pod, PodApi } from "../endpoints"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; -import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable"; @@ -19,64 +18,14 @@ import { flushPromises } from "../../test-utils/flush-promises"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; import type { IKubeWatchEvent } from "../kube-watch-event"; import type { KubeJsonApiDataFor } from "../kube-object"; -import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; import AbortController from "abort-controller"; - -const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: new PassThrough(), - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: data.length, - status: statusCode, - statusText: "some-text", - text: jest.fn(async () => data), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; - -const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: stream, - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: 10, - status: statusCode, - statusText: "some-text", - text: jest.fn(() => { - const chunks: Buffer[] = []; - - return new Promise((resolve, reject) => { - stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); - stream.on("error", (err) => reject(err)); - stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); - }); - }), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; +import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; +import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; describe("createKubeApiForRemoteCluster", () => { let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster; @@ -85,6 +34,20 @@ describe("createKubeApiForRemoteCluster", () => { beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); @@ -174,6 +137,20 @@ describe("KubeApi", () => { beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); @@ -184,7 +161,9 @@ describe("KubeApi", () => { apiBase: "/api-kube", }); - di.inject(autoRegistrationInjectable); + const setupAutoRegistration = di.inject(setupAutoRegistrationInjectable); + + setupAutoRegistration.run(); }); describe("patching deployments", () => { 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..afe755a6ba 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,22 @@ * 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, + }, + }, 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 deleted file mode 100644 index 0cf1a3055d..0000000000 --- a/src/common/k8s-api/api-manager/auto-registration.injectable.ts +++ /dev/null @@ -1,74 +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 type { CustomResourceDefinition } from "../endpoints"; -import { KubeApi } from "../kube-api"; -import { KubeObject } from "../kube-object"; -import autoRegistrationEmitterInjectable from "./auto-registration-emitter.injectable"; -import apiManagerInjectable from "./manager.injectable"; -import { CustomResourceStore } from "./resource.store"; - -const autoRegistrationInjectable = getInjectable({ - id: "api-manager-auto-registration", - instantiate: (di) => { - const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); - const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; - const beforeApiManagerInitializationApis: KubeApi[] = []; - let initialized = false; - - const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { - const objectConstructor = class extends KubeObject { - static readonly kind = crd.getResourceKind(); - static readonly namespaced = crd.isNamespaced(); - static readonly apiBase = crd.getResourceApiBase(); - }; - - const api = (() => { - const rawApi = apiManager.getApi(objectConstructor.apiBase); - - if (rawApi) { - return rawApi; - } - - const api = new KubeApi({ objectConstructor }); - - apiManager.registerApi(api); - - return api; - })(); - - if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); - } - }; - const autoInitKubeApi = (api: KubeApi) => { - apiManager.registerApi(api); - }; - - autoRegistrationEmitter - .on("customResourceDefinition", (crd) => { - if (initialized) { - autoInitCustomResourceStore(crd); - } else { - beforeApiManagerInitializationCrds.push(crd); - } - }) - .on("kubeApi", (api) => { - if (initialized) { - autoInitKubeApi(api); - } else { - beforeApiManagerInitializationApis.push(api); - } - }); - - const apiManager = di.inject(apiManagerInjectable); - - beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); - beforeApiManagerInitializationApis.forEach(autoInitKubeApi); - initialized = true; - }, -}); - -export default autoRegistrationInjectable; 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/endpoints/resource-applier.api/request-patch.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts index 7290263a41..49271fb6d2 100644 --- a/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts +++ b/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts @@ -5,25 +5,44 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Patch } from "rfc6902"; import apiBaseInjectable from "../../api-base.injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import type { KubeJsonApiData } from "../../kube-json-api"; -export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise; +export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise>; const requestKubeObjectPatchInjectable = getInjectable({ id: "request-kube-object-patch", instantiate: (di): RequestKubeObjectPatch => { const apiBase = di.inject(apiBaseInjectable); - return (name, kind, ns, patch) => ( - apiBase.patch("/stack", { + return async (name, kind, ns, patch) => { + const result = await apiBase.patch("/stack", { data: { name, kind, ns, patch, }, - }) - ); + }) as AsyncResult; + + if (!result.callWasSuccessful) { + return result; + } + + try { + const response = JSON.parse(result.response); + + return { + callWasSuccessful: true, + response, + }; + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } + }; }, }); diff --git a/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts index 7d996253ee..1891a779cf 100644 --- a/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts +++ b/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts @@ -4,16 +4,37 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import apiBaseInjectable from "../../api-base.injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import type { KubeJsonApiData } from "../../kube-json-api"; -export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise; +export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise>; const requestKubeObjectCreationInjectable = getInjectable({ id: "request-kube-object-creation", instantiate: (di): RequestKubeObjectCreation => { const apiBase = di.inject(apiBaseInjectable); - return (data) => apiBase.post("/stack", { data }); + return async (data) => { + const result = await apiBase.post("/stack", { data }) as AsyncResult; + + if (!result.callWasSuccessful) { + return result; + } + + try { + const response = JSON.parse(result.response); + + return { + callWasSuccessful: true, + response, + }; + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } + }; }, }); diff --git a/src/common/k8s-api/json-api.ts b/src/common/k8s-api/json-api.ts index c3e07bfa94..988378641c 100644 --- a/src/common/k8s-api/json-api.ts +++ b/src/common/k8s-api/json-api.ts @@ -104,7 +104,7 @@ export class JsonApi = Js ); const { query } = params ?? {}; - if (query) { + if (query && Object.keys(query).length > 0) { const queryString = stringify(query as unknown as QueryParams); reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; @@ -171,7 +171,7 @@ export class JsonApi = Js reqInit.body = JSON.stringify(data); } - if (query) { + if (query && Object.keys(query).length > 0) { const queryString = stringify(query as unknown as QueryParams); reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 7ca2995906..6e3ad4a72a 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -403,10 +403,11 @@ export class KubeApi< /** * This method differs from {@link formatUrlForNotListing} because this treats `""` as "all namespaces" + * NOTE: This is also useful for watching * @param namespace The namespace to list in or `""` for all namespaces */ - formatUrlForListing(namespace: string) { - return createKubeApiURL({ + formatUrlForListing(namespace: string | undefined, query?: Partial) { + const resourcePath = createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, resource: this.apiResource, @@ -414,15 +415,15 @@ export class KubeApi< ? namespace ?? "default" : undefined, }); + + return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : ""); } /** * Format a URL pathname and query for acting upon a specific resource. */ - formatUrlForNotListing(resource?: Partial, query?: Partial): string; - - formatUrlForNotListing({ name, namespace }: Partial = {}, query?: Partial) { - const resourcePath = createKubeApiURL({ + formatUrlForNotListing({ name, namespace }: Partial = {}) { + return createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, resource: this.apiResource, @@ -431,15 +432,17 @@ export class KubeApi< : undefined, name, }); - - return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : ""); } /** - * @deprecated use {@link formatUrlForNotListing} instead + * @deprecated use {@link formatUrlForNotListing} or {@link formatUrlForListing} instead */ getUrl(resource?: Partial, query?: Partial) { - return this.formatUrlForNotListing(resource, query); + if (query) { + return this.formatUrlForListing(resource?.namespace, query); + } + + return this.formatUrlForNotListing(resource); } protected normalizeQuery(query: Partial = {}) { @@ -625,14 +628,14 @@ export class KubeApi< } getWatchUrl(namespace?: string, query: KubeApiQueryParams = {}) { - return this.formatUrlForNotListing({ namespace }, { + return this.formatUrlForListing(namespace, { watch: 1, resourceVersion: this.getResourceVersion(namespace), ...query, }); } - watch(opts?: KubeApiWatchOptions): () => void { + watch(opts?: KubeApiWatchOptions): Disposer { let errorReceived = false; let timedRetry: NodeJS.Timeout; const { diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 309c183e42..9e7c541b58 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 (error) { + console.error(`[KUBE-OBJECT-STORE]: failed to subscribe to ${this.api.apiBase}`, error); + } + })(); } else { this.watchNamespace("", abortController, { onLoadFailure }); } @@ -467,7 +441,7 @@ export abstract class KubeObjectStore< } let timedRetry: NodeJS.Timeout; - const watch = () => this.api.watch({ + const startNewWatch = () => this.api.watch({ namespace, abortController, callback, @@ -486,7 +460,7 @@ export abstract class KubeObjectStore< // not sure what to do, best to retry clearTimeout(timedRetry); - timedRetry = setTimeout(watch, 5000); + timedRetry = setTimeout(startNewWatch, 5000); } else if (error instanceof KubeStatus && error.code === 410) { clearTimeout(timedRetry); // resourceVersion has gone, let's try to reload @@ -495,11 +469,11 @@ export abstract class KubeObjectStore< namespace ? this.loadAll({ namespaces: [namespace], reqInit: { signal }, ...opts }) : this.loadAll({ merge: false, reqInit: { signal }, ...opts }) - ).then(watch); + ).then(startNewWatch); }, 1000); } else if (error) { // not sure what to do, best to retry clearTimeout(timedRetry); - timedRetry = setTimeout(watch, 5000); + timedRetry = setTimeout(startNewWatch, 5000); } if (data) { @@ -508,7 +482,7 @@ export abstract class KubeObjectStore< }; signal.addEventListener("abort", () => clearTimeout(timedRetry)); - watch(); + startNewWatch(); } @action diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index 24e34eff4b..53bc6defdf 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -645,8 +645,13 @@ export class KubeObject< } const requestKubeObjectPatch = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectPatchInjectable); + const result = await requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); - return requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); + if (!result.callWasSuccessful) { + throw new Error(result.error); + } + + return result.response; } /** @@ -665,7 +670,13 @@ export class KubeObject< ...data, }); - return requestKubeObjectCreation(descriptor); + const result = await requestKubeObjectCreation(descriptor); + + if (!result.callWasSuccessful) { + throw new Error(result.error); + } + + return result.response; } /** diff --git a/src/common/logger.injectable.ts b/src/common/logger.injectable.ts index e1a085f199..8e9dd2a6a7 100644 --- a/src/common/logger.injectable.ts +++ b/src/common/logger.injectable.ts @@ -9,13 +9,23 @@ import { loggerTransportInjectionToken } from "./logger/transports"; const loggerInjectable = getInjectable({ id: "logger", - instantiate: (di): Logger => createLogger({ - format: format.combine( - format.splat(), - format.simple(), - ), - transports: di.injectMany(loggerTransportInjectionToken), - }), + instantiate: (di): Logger => { + const baseLogger = createLogger({ + format: format.combine( + format.splat(), + format.simple(), + ), + transports: di.injectMany(loggerTransportInjectionToken), + }); + + return { + debug: (message, ...data) => baseLogger.debug(message, ...data), + info: (message, ...data) => baseLogger.info(message, ...data), + warn: (message, ...data) => baseLogger.warn(message, ...data), + error: (message, ...data) => baseLogger.error(message, ...data), + silly: (message, ...data) => baseLogger.silly(message, ...data), + }; + }, }); export default loggerInjectable; diff --git a/src/common/logger.ts b/src/common/logger.ts index 948404a6b9..0b460a48ff 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -17,4 +17,6 @@ export interface Logger { /** * @deprecated use `di.inject(loggerInjectable)` instead */ -export default asLegacyGlobalForExtensionApi(loggerInjectable); +const logger = asLegacyGlobalForExtensionApi(loggerInjectable); + +export default logger; diff --git a/src/common/rbac.ts b/src/common/rbac.ts index bfa04ef46b..99e564a377 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -11,51 +11,190 @@ export type KubeResource = "priorityclasses" | "runtimeclasses" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; -export interface KubeApiResource extends KubeApiResourceData { - apiName: KubeResource; // valid api resource name (e.g. "namespaces") +export interface KubeApiResource { + kind: string; + group: string; + apiName: string; + namespaced: boolean; } +export interface KubeApiResourceDescriptor { + apiName: string; + group: string; +} + +export const formatKubeApiResource = (res: KubeApiResourceDescriptor) => `${res.group}/${res.apiName}`; + export interface KubeApiResourceData { kind: string; // resource type (e.g. "Namespace") - group?: string; // api-group + group: string; // api-group, if empty then "core" + namespaced: boolean; } export const apiResourceRecord: Record = { - "clusterroles": { kind: "ClusterRole", group: "rbac.authorization.k8s.io" }, - "clusterrolebindings": { kind: "ClusterRoleBinding", group: "rbac.authorization.k8s.io" }, - "configmaps": { kind: "ConfigMap" }, //empty group means "core" - "cronjobs": { kind: "CronJob", group: "batch" }, - "customresourcedefinitions": { kind: "CustomResourceDefinition", group: "apiextensions.k8s.io" }, - "daemonsets": { kind: "DaemonSet", group: "apps" }, - "deployments": { kind: "Deployment", group: "apps" }, - "endpoints": { kind: "Endpoint" }, - "events": { kind: "Event" }, - "horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler", group: "autoscaling" }, - "ingresses": { kind: "Ingress", group: "networking.k8s.io" }, - "jobs": { kind: "Job", group: "batch" }, - "namespaces": { kind: "Namespace" }, - "limitranges": { kind: "LimitRange" }, - "leases": { kind: "Lease" }, - "networkpolicies": { kind: "NetworkPolicy", group: "networking.k8s.io" }, - "nodes": { kind: "Node" }, - "persistentvolumes": { kind: "PersistentVolume" }, - "persistentvolumeclaims": { kind: "PersistentVolumeClaim" }, - "pods": { kind: "Pod" }, - "poddisruptionbudgets": { kind: "PodDisruptionBudget", group: "policy" }, - "podsecuritypolicies": { kind: "PodSecurityPolicy", group: "policy" }, - "priorityclasses": { kind: "PriorityClass", group: "scheduling.k8s.io" }, - "runtimeclasses": { kind: "RuntimeClass", group: "node.k8s.io" }, - "resourcequotas": { kind: "ResourceQuota" }, - "replicasets": { kind: "ReplicaSet", group: "apps" }, - "roles": { kind: "Role", group: "rbac.authorization.k8s.io" }, - "rolebindings": { kind: "RoleBinding", group: "rbac.authorization.k8s.io" }, - "secrets": { kind: "Secret" }, - "serviceaccounts": { kind: "ServiceAccount" }, - "services": { kind: "Service" }, - "statefulsets": { kind: "StatefulSet", group: "apps" }, - "storageclasses": { kind: "StorageClass", group: "storage.k8s.io" }, + clusterroles: { + kind: "ClusterRole", + group: "rbac.authorization.k8s.io", + namespaced: false, + }, + clusterrolebindings: { + kind: "ClusterRoleBinding", + group: "rbac.authorization.k8s.io", + namespaced: false, + }, + configmaps: { + kind: "ConfigMap", + group: "v1", + namespaced: true, + }, + cronjobs: { + kind: "CronJob", + group: "batch", + namespaced: true, + }, + customresourcedefinitions: { + kind: "CustomResourceDefinition", + group: "apiextensions.k8s.io", + namespaced: false, + }, + daemonsets: { + kind: "DaemonSet", + group: "apps", + namespaced: true, + }, + deployments: { + kind: "Deployment", + group: "apps", + namespaced: true, + }, + endpoints: { + kind: "Endpoint", + group: "v1", + namespaced: true, + }, + events: { + kind: "Event", + group: "v1", + namespaced: true, + }, + horizontalpodautoscalers: { + kind: "HorizontalPodAutoscaler", + group: "autoscaling", + namespaced: true, + }, + ingresses: { + kind: "Ingress", + group: "networking.k8s.io", + namespaced: true, + }, + jobs: { + kind: "Job", + group: "batch", + namespaced: true, + }, + namespaces: { + kind: "Namespace", + group: "v1", + namespaced: false, + }, + limitranges: { + kind: "LimitRange", + group: "v1", + namespaced: true, + }, + leases: { + kind: "Lease", + group: "v1", + namespaced: true, + }, + networkpolicies: { + kind: "NetworkPolicy", + group: "networking.k8s.io", + namespaced: true, + }, + nodes: { + kind: "Node", + group: "v1", + namespaced: false, + }, + persistentvolumes: { + kind: "PersistentVolume", + group: "v1", + namespaced: false, + }, + persistentvolumeclaims: { + kind: "PersistentVolumeClaim", + group: "v1", + namespaced: true, + }, + pods: { + kind: "Pod", + group: "v1", + namespaced: true, + }, + poddisruptionbudgets: { + kind: "PodDisruptionBudget", + group: "policy", + namespaced: true, + }, + podsecuritypolicies: { + kind: "PodSecurityPolicy", + group: "policy", + namespaced: false, + }, + priorityclasses: { + kind: "PriorityClass", + group: "scheduling.k8s.io", + namespaced: false, + }, + runtimeclasses: { + kind: "RuntimeClass", + group: "node.k8s.io", + namespaced: false, + }, + resourcequotas: { + kind: "ResourceQuota", + group: "v1", + namespaced: true, + }, + replicasets: { + kind: "ReplicaSet", + group: "apps", + namespaced: true, + }, + roles: { + kind: "Role", + group: "rbac.authorization.k8s.io", + namespaced: true, + }, + rolebindings: { + kind: "RoleBinding", + group: "rbac.authorization.k8s.io", + namespaced: true, + }, + secrets: { + kind: "Secret", + group: "v1", + namespaced: true, + }, + serviceaccounts: { + kind: "ServiceAccount", + group: "v1", + namespaced: true, + }, + services: { + kind: "Service", + group: "v1", + namespaced: true, + }, + statefulsets: { + kind: "StatefulSet", + group: "apps", + namespaced: true, + }, + storageclasses: { + kind: "StorageClass", + group: "storage.k8s.io", + namespaced: false, + }, }; - -// TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) -export const apiResources: KubeApiResource[] = Object.entries(apiResourceRecord) - .map(([apiName, data]) => ({ apiName: apiName as KubeResource, ...data })); 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/common/utils/wait.ts b/src/common/utils/wait.ts index 7bcbd1688b..402d556b5d 100644 --- a/src/common/utils/wait.ts +++ b/src/common/utils/wait.ts @@ -8,26 +8,23 @@ import type { Disposer } from "./disposer"; export async function waitUntilDefined(getter: (() => T | null | undefined) | IComputedValue, opts?: { timeout?: number }): Promise { return new Promise((resolve, reject) => { - let res: T | null | undefined; - when( () => { - res = typeof getter === "function" + const res = typeof getter === "function" ? getter() : getter.get(); + const isDefined = res != null; - if (res != null) { + if (isDefined) { resolve(res); - - return true; } - return false; + return isDefined; }, () => {}, { onError: reject, - ...opts, + ...(opts ?? {}), }, ); }); diff --git a/src/common/vars.ts b/src/common/vars.ts index cbde12ff5d..d58e0871e6 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -6,26 +6,6 @@ // App's common configuration for any process (main, renderer, build pipeline, etc.) import type { ThemeId } from "../renderer/themes/lens-theme"; -/** - * @deprecated Switch to using isMacInjectable - */ -export const isMac = process.platform === "darwin"; - -/** - * @deprecated Switch to using isWindowsInjectable - */ -export const isWindows = process.platform === "win32"; - -/** - * @deprecated Switch to using isLinuxInjectable - */ -export const isLinux = process.platform === "linux"; - -/** - * @deprecated switch to using `isDebuggingInjectable` - */ -export const isDebugging = ["true", "1", "yes", "y", "on"].includes((process.env.DEBUG ?? "").toLowerCase()); - /** * @deprecated Switch to using isTestEnvInjectable */ diff --git a/src/extensions/common-api/k8s-api.ts b/src/extensions/common-api/k8s-api.ts index e5c7013bc7..9b62af7551 100644 --- a/src/extensions/common-api/k8s-api.ts +++ b/src/extensions/common-api/k8s-api.ts @@ -15,6 +15,12 @@ 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"; +import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context"; export const apiManager = asLegacyGlobalForExtensionApi(apiManagerInjectable); export const forCluster = asLegacyGlobalFunctionForExtensionApi(createKubeApiForClusterInjectable); @@ -72,8 +78,44 @@ 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 { + /** + * @deprecated This is no longer used and shouldn't have been every really used + */ + static readonly context = { + set: (ctx: ClusterContext) => { + console.warn("Setting KubeObjectStore.context is no longer supported"); + void ctx; + }, + get: () => asLegacyGlobalForExtensionApi(clusterFrameContextForNamespacedResourcesInjectable), + }; + + 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/catalog/opening-entity-details.test.tsx b/src/features/catalog/opening-entity-details.test.tsx index 2d548697ed..8ae49ff548 100644 --- a/src/features/catalog/opening-entity-details.test.tsx +++ b/src/features/catalog/opening-entity-details.test.tsx @@ -10,8 +10,8 @@ import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injec import type { Cluster } from "../../common/cluster/cluster"; import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; +import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import createClusterInjectable from "../../renderer/create-cluster/create-cluster.injectable"; describe("opening catalog entity details panel", () => { let builder: ApplicationBuilder; diff --git a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx index c96f2714e1..5c527edbb4 100644 --- a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx +++ b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx @@ -14,7 +14,6 @@ import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injec import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable"; import openDeleteClusterDialogInjectable, { type OpenDeleteClusterDialog } from "../../../renderer/components/delete-cluster-dialog/open.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder"; -import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; @@ -90,7 +89,6 @@ describe("Deleting a cluster", () => { }); builder.beforeWindowStart((windowDi) => { - windowDi.override(storesAndApisCanBeCreatedInjectable, () => true); openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable); }); diff --git a/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx b/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx index efe0cdf554..f37ada1736 100644 --- a/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx +++ b/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx @@ -98,7 +98,10 @@ describe("cluster/namespaces - edit namespace from new tab", () => { }); }); - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); describe("when navigating to namespaces", () => { diff --git a/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx b/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx index a5f834563b..4cf63ee353 100644 --- a/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx +++ b/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx @@ -42,7 +42,10 @@ describe("cluster/namespaces - edit namespaces from previously opened tab", () = windowDi.override(callForResourceInjectable, () => callForNamespaceMock); }); - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); describe("given tab was previously opened, when application is started", () => { diff --git a/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts index 93005543db..e4df4c7f8e 100644 --- a/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts +++ b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initClusterStoreInjectable from "../../store/renderer/init.injectable"; import requestInitialClusterStatesInjectable from "./request-initial.injectable"; @@ -23,7 +23,7 @@ const setupClusterStateSyncInjectable = getInjectable({ }, runAfter: di.inject(initClusterStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupClusterStateSyncInjectable; diff --git a/src/features/cluster/store/renderer/init.injectable.ts b/src/features/cluster/store/renderer/init.injectable.ts index 2c2795de5c..66d2e31e09 100644 --- a/src/features/cluster/store/renderer/init.injectable.ts +++ b/src/features/cluster/store/renderer/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; const initClusterStoreInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initClusterStoreInjectable = getInjectable({ }, runAfter: di.inject(initUserStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initClusterStoreInjectable; diff --git a/src/features/cluster/visibility-of-sidebar-items.test.tsx b/src/features/cluster/visibility-of-sidebar-items.test.tsx index 0e09f0efa8..2eb42b7fdf 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; @@ -50,7 +50,10 @@ describe("cluster - visibility of sidebar items", () => { describe("when kube resource becomes allowed", () => { beforeEach(() => { - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); it("renders", () => { @@ -69,20 +72,14 @@ 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", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/features/cluster/workload-overview.test.tsx b/src/features/cluster/workload-overview.test.tsx index 1a6750a8f9..205c837b47 100644 --- a/src/features/cluster/workload-overview.test.tsx +++ b/src/features/cluster/workload-overview.test.tsx @@ -13,7 +13,10 @@ describe("workload overview", () => { beforeEach(async () => { applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame(); - applicationBuilder.allowKubeResource("pods"); + applicationBuilder.allowKubeResource({ + apiName: "pods", + group: "v1", + }); rendered = await applicationBuilder.render(); }); diff --git a/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx index 306582a188..36844cb3af 100644 --- a/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx +++ b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx @@ -11,7 +11,7 @@ import type { Cluster } from "../../common/cluster/cluster"; import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import createClusterInjectable from "../../renderer/create-cluster/create-cluster.injectable"; +import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable"; describe("Showing correct entity settings", () => { let builder: ApplicationBuilder; diff --git a/src/features/file-system-provisioner/renderer/init-store.injectable.ts b/src/features/file-system-provisioner/renderer/init-store.injectable.ts index d241dcad38..0e64e49fd6 100644 --- a/src/features/file-system-provisioner/renderer/init-store.injectable.ts +++ b/src/features/file-system-provisioner/renderer/init-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../renderer/before-frame-starts/tokens"; const initFileSystemProvisionerStoreInjectable = getInjectable({ id: "init-file-system-provisioner-store", @@ -16,7 +16,7 @@ const initFileSystemProvisionerStoreInjectable = getInjectable({ store.load(); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initFileSystemProvisionerStoreInjectable; diff --git a/src/features/hotbar/store/renderer/init.injectable.ts b/src/features/hotbar/store/renderer/init.injectable.ts index 6d341f1ee8..c17e3a4858 100644 --- a/src/features/hotbar/store/renderer/init.injectable.ts +++ b/src/features/hotbar/store/renderer/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable"; const initHotbarStoreInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initHotbarStoreInjectable = getInjectable({ }, runAfter: di.inject(initClusterStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initHotbarStoreInjectable; diff --git a/src/features/theme/system-type/renderer/initialize.injectable.ts b/src/features/theme/system-type/renderer/initialize.injectable.ts index e6ed81f4f1..849c8328b2 100644 --- a/src/features/theme/system-type/renderer/initialize.injectable.ts +++ b/src/features/theme/system-type/renderer/initialize.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; import systemThemeConfigurationInjectable from "../../../../renderer/themes/system-theme.injectable"; import requestInitialSystemThemeTypeInjectable from "./request-initial.injectable"; @@ -20,7 +20,7 @@ const initializeSystemThemeTypeInjectable = getInjectable({ }, runAfter: di.inject(initUserStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initializeSystemThemeTypeInjectable; diff --git a/src/jest.setup.ts b/src/jest.setup.ts index b0acfba878..1e7d0a8ce3 100644 --- a/src/jest.setup.ts +++ b/src/jest.setup.ts @@ -3,22 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import configurePackages from "./common/configure-packages"; import { configure } from "mobx"; import { setImmediate } from "timers"; import { TextEncoder, TextDecoder as TextDecoderNode } from "util"; import glob from "glob"; import path from "path"; - -// setup default configuration for external npm-packages -configurePackages(); +import { enableMapSet, setAutoFreeze } from "immer"; configure({ // Needed because we want to use jest.spyOn() // ref https://github.com/mobxjs/mobx/issues/2784 safeDescriptors: false, + enforceActions: "never", }); +setAutoFreeze(false); // allow to merge mobx observables +enableMapSet(); // allow to merge maps and sets + // Mock __non_webpack_require__ for tests globalThis.__non_webpack_require__ = jest.fn(); 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/cluster/manager.ts b/src/main/cluster/manager.ts index d5c06c0619..83270941d4 100644 --- a/src/main/cluster/manager.ts +++ b/src/main/cluster/manager.ts @@ -210,7 +210,7 @@ export class ClusterManager { cluster.contextName = entity.spec.kubeconfigContext; if (entity.spec.accessibleNamespaces) { - cluster.accessibleNamespaces = entity.spec.accessibleNamespaces; + cluster.accessibleNamespaces.replace(entity.spec.accessibleNamespaces); } if (entity.spec.metrics) { diff --git a/src/main/create-cluster/allowed-resources.injectable.ts b/src/main/create-cluster/allowed-resources.injectable.ts index d614ca3d17..09beae9fd2 100644 --- a/src/main/create-cluster/allowed-resources.injectable.ts +++ b/src/main/create-cluster/allowed-resources.injectable.ts @@ -2,15 +2,20 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } 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"; +import type { KubeApiResourceDescriptor } from "../../common/rbac"; +import { formatKubeApiResource } from "../../common/rbac"; // TODO: Figure out implementation for this later. const allowedResourcesInjectable = getInjectable({ id: "allowed-resources", - instantiate: () => computed(() => new Set()), - injectionToken: allowedResourcesInjectionToken, + instantiate: () => computed(() => false), + injectionToken: shouldShowResourceInjectionToken, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, resource: KubeApiResourceDescriptor) => formatKubeApiResource(resource), + }), }); 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..f5b302300c 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -11,14 +11,14 @@ 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 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"; import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable"; +import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable"; const createClusterInjectable = getInjectable({ id: "create-cluster", @@ -30,8 +30,8 @@ const createClusterInjectable = getInjectable({ createKubectl: di.inject(createKubectlInjectable), createContextHandler: di.inject(createContextHandlerInjectable), createAuthorizationReview: di.inject(authorizationReviewInjectable), - createAuthorizationNamespaceReview: di.inject(createAuthorizationNamespaceReview), - createListApiResources: di.inject(createListApiResourcesInjectable), + requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable), + requestApiResources: di.inject(createListApiResourcesInjectable), createListNamespaces: di.inject(listNamespacesInjectable), logger: di.inject(loggerInjectable), detectorRegistry: di.inject(detectorRegistryInjectable), diff --git a/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts index 9d760a8bc2..d2d8f344eb 100644 --- a/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts @@ -5,7 +5,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { ContentSource, ElectronWindowTitleBarStyle } from "./create-electron-window.injectable"; import createElectronWindowForInjectable from "./create-electron-window.injectable"; -import assert from "assert"; import type { ClusterFrameInfo } from "../../../../common/cluster-frames"; export interface ElectronWindow { @@ -69,7 +68,9 @@ const createLensWindowInjectable = getInjectable({ let windowIsStarting = false; const showWindow = () => { - assert(browserWindow); + if (!browserWindow) { + throw new Error("Cannot show browserWindow, does not exist"); + } browserWindow.show(); windowIsShown = true; diff --git a/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts index 02a00523ad..ef85a84cd0 100644 --- a/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { customMonacoThemeInjectionToken } from "../../components/monaco-editor"; import addNewMonacoThemeInjectable from "../../monaco/add-new-theme.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const loadMonacoThemesInjectable = getInjectable({ id: "load-monaco-themes", @@ -18,7 +18,7 @@ const loadMonacoThemesInjectable = getInjectable({ customThemes.forEach(addNewMonacoTheme); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default loadMonacoThemesInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts index c2c58c22b0..ecfac3136a 100644 --- a/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts @@ -9,7 +9,9 @@ import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resourc import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; import { KubeApi } from "../../../common/k8s-api/kube-api"; import { KubeObject } from "../../../common/k8s-api/kube-object"; -import { beforeClusterFrameStartsInjectionToken } from "../tokens"; +import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens"; +import type { KubeObjectStoreDependencies } from "../../../common/k8s-api/kube-object.store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const setupAutoRegistrationInjectable = getInjectable({ id: "setup-auto-registration", @@ -19,6 +21,9 @@ const setupAutoRegistrationInjectable = 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) => { @@ -43,7 +48,7 @@ const setupAutoRegistrationInjectable = getInjectable({ })(); if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); + apiManager.registerStore(new CustomResourceStore(deps, api)); } }; const autoInitKubeApi = (api: KubeApi) => { @@ -66,6 +71,7 @@ const setupAutoRegistrationInjectable = getInjectable({ } }); + // NOTE: this MUST happen after the event emitter listeners are registered const apiManager = di.inject(apiManagerInjectable); beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); @@ -73,7 +79,7 @@ const setupAutoRegistrationInjectable = getInjectable({ initialized = true; }, }), - injectionToken: beforeClusterFrameStartsInjectionToken, + injectionToken: beforeClusterFrameStartsSecondInjectionToken, }); export default setupAutoRegistrationInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts index 978eae3d95..a89ba58ec2 100644 --- a/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts @@ -7,7 +7,7 @@ import { reaction } from "mobx"; import { currentClusterMessageChannel } from "../../../common/cluster/current-cluster-channel"; import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token"; import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; -import { beforeMainFrameStartsInjectionToken } from "../tokens"; +import { beforeMainFrameStartsFirstInjectionToken } from "../tokens"; const setupCurrentClusterBroadcastInjectable = getInjectable({ id: "setup-current-cluster-broadcast", @@ -26,7 +26,7 @@ const setupCurrentClusterBroadcastInjectable = getInjectable({ ); }, }), - injectionToken: beforeMainFrameStartsInjectionToken, + injectionToken: beforeMainFrameStartsFirstInjectionToken, }); export default setupCurrentClusterBroadcastInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts index 406b1641af..20566b5dac 100644 --- a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts @@ -9,7 +9,7 @@ import isLinuxInjectable from "../../../common/vars/is-linux.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable"; import addSyncEntriesInjectable from "../../initializers/add-sync-entries.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({ id: "setup-kubernetes-cluster-catalog-add-menu-listener", @@ -75,7 +75,7 @@ const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupKubernetesClusterCatalogAddMenuListenerInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts index 150620c22a..73cc988e2c 100644 --- a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts @@ -9,7 +9,7 @@ import readFileInjectable from "../../../common/fs/read-file.injectable"; import { loadConfigFromString } from "../../../common/kube-helpers"; import loggerInjectable from "../../../common/logger.injectable"; import openDeleteClusterDialogInjectable from "../../components/delete-cluster-dialog/open.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupKubernetesClusterContextMenuOpenInjectable = getInjectable({ id: "setup-kubernetes-cluster-context-menu-open", @@ -50,7 +50,7 @@ const setupKubernetesClusterContextMenuOpenInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupKubernetesClusterContextMenuOpenInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts index 59a684ccc9..56b9ce66fa 100644 --- a/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import isMacInjectable from "../../../common/vars/is-mac.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupRootMacClassnameInjectable = getInjectable({ id: "setup-root-mac-classname", @@ -17,7 +17,7 @@ const setupRootMacClassnameInjectable = getInjectable({ rootElem?.classList.toggle("is-mac", isMac); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupRootMacClassnameInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts index 401cec99d5..c6af3b7614 100644 --- a/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import initializeSentryReportingWithInjectable from "../../../common/error-reporting/initialize-sentry-reporting.injectable"; -import { beforeMainFrameStartsInjectionToken } from "../tokens"; +import { beforeMainFrameStartsFirstInjectionToken } from "../tokens"; import { init } from "@sentry/electron/renderer"; const setupSentryInjectable = getInjectable({ @@ -17,7 +17,7 @@ const setupSentryInjectable = getInjectable({ initializeSentryReportingWith(init); }, }), - injectionToken: beforeMainFrameStartsInjectionToken, + injectionToken: beforeMainFrameStartsFirstInjectionToken, }); export default setupSentryInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx b/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx index 4c027d6047..7bf8f29422 100644 --- a/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx +++ b/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx @@ -7,7 +7,7 @@ import React from "react"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; import { WeblinkAddCommand } from "../../components/catalog-entities/weblink-add-command"; import commandOverlayInjectable from "../../components/command-palette/command-overlay.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupWeblickContextMenuOpenInjectable = getInjectable({ id: "setup-weblick-context-menu-open", @@ -28,7 +28,7 @@ const setupWeblickContextMenuOpenInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupWeblickContextMenuOpenInjectable; diff --git a/src/renderer/before-frame-starts/tokens.ts b/src/renderer/before-frame-starts/tokens.ts index 77b0350315..460c44cde9 100644 --- a/src/renderer/before-frame-starts/tokens.ts +++ b/src/renderer/before-frame-starts/tokens.ts @@ -7,20 +7,30 @@ import type { Runnable } from "../../common/runnable/run-many-for"; // NOTE: these are run before any other token, mostly to set up things that all other runnables need export const beforeFrameStartsFirstInjectionToken = getInjectionToken({ - id: "even-before-frame-starts", + id: "before-frame-starts-first", }); // NOTE: these are only run when process.isMainFrame === true -export const beforeMainFrameStartsInjectionToken = getInjectionToken({ - id: "even-before-main-frame-starts", +export const beforeMainFrameStartsFirstInjectionToken = getInjectionToken({ + id: "before-main-frame-starts-first", }); // NOTE: these are only run when process.isMainFrame === false -export const beforeClusterFrameStartsInjectionToken = getInjectionToken({ - id: "even-before-cluster-frame-starts", +export const beforeClusterFrameStartsFirstInjectionToken = getInjectionToken({ + id: "before-cluster-frame-starts-first", }); -export const beforeFrameStartsInjectionToken = getInjectionToken({ - id: "before-frame-starts", +export const beforeFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-frame-starts-second", +}); + +// NOTE: these are only run when process.isMainFrame === true +export const beforeMainFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-main-frame-starts-second", +}); + +// NOTE: these are only run when process.isMainFrame === false +export const beforeClusterFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-cluster-frame-starts-second", }); 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..e23bac5fd4 100755 --- a/src/renderer/cluster-frame-context/cluster-frame-context.ts +++ b/src/renderer/cluster-frame-context/cluster-frame-context.ts @@ -3,44 +3,14 @@ * 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)); - } + 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..12e74cdc1f --- /dev/null +++ b/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ClusterContext } from "./cluster-frame-context"; + +const clusterFrameContextForClusterScopedResourcesInjectable = getInjectable({ + id: "cluster-frame-context-for-cluster-scoped-resources", + instantiate: (): ClusterContext => ({ + // This doesn't matter as it is an optimization for namespaced resources only + isGlobalWatchEnabled: () => true, + // This is always the case for cluster scoped resources + isLoadingAll: () => true, + allNamespaces: [], + contextNamespaces: [], + 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..1eb85b3a43 --- /dev/null +++ b/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import 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 { + 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/cluster/accessible-namespaces.injectable.ts b/src/renderer/cluster/accessible-namespaces.injectable.ts new file mode 100644 index 0000000000..50743cfa83 --- /dev/null +++ b/src/renderer/cluster/accessible-namespaces.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import hostedClusterInjectable from "../cluster-frame-context/hosted-cluster.injectable"; + +const clusterConfiguredAccessibleNamespacesInjectable = getInjectable({ + id: "cluster-configured-accessible-namespaces", + instantiate: (di) => { + const hostedCluster = di.inject(hostedClusterInjectable); + + return computed(() => [...hostedCluster?.accessibleNamespaces ?? []]); + }, +}); + +export default clusterConfiguredAccessibleNamespacesInjectable; diff --git a/src/renderer/create-cluster/create-cluster.injectable.ts b/src/renderer/cluster/create-cluster.injectable.ts similarity index 92% rename from src/renderer/create-cluster/create-cluster.injectable.ts rename to src/renderer/cluster/create-cluster.injectable.ts index e0a9f51656..385dfe8d66 100644 --- a/src/renderer/create-cluster/create-cluster.injectable.ts +++ b/src/renderer/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/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-runtime-classes/store.injectable.ts b/src/renderer/components/+config-runtime-classes/store.injectable.ts index 0a4a21d716..63e8d82526 100644 --- a/src/renderer/components/+config-runtime-classes/store.injectable.ts +++ b/src/renderer/components/+config-runtime-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 runtimeClassApiInjectable from "../../../common/k8s-api/endpoints/runtime-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { RuntimeClassStore } from "./store"; @@ -16,7 +17,9 @@ const runtimeClassStoreInjectable = getInjectable({ const api = di.inject(runtimeClassApiInjectable); - return new RuntimeClassStore(api); + return new RuntimeClassStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx index f1e3aad2b4..6a586bdd0c 100644 --- a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx +++ b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -9,6 +9,10 @@ import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { renderFor } from "../../test-utils/renderFor"; import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../../cluster/create-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; jest.mock("../../kube-object-meta/kube-object-meta", () => ({ KubeObjectMeta: () => null, @@ -19,8 +23,20 @@ describe("SecretDetails tests", () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); const render = renderFor(di); + di.override(directoryForUserDataInjectable, () => "/some-user-data"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const secret = new Secret({ apiVersion: "v1", kind: "secret", 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 { constructor( - protected readonly dependencies: Dependencies, + protected readonly dependencies: EventStoreDependencies, api: KubeEventApi, opts: KubeObjectStoreOptions = {}, ) { - super(api, { limit: 1000, ...opts }); + super(dependencies, api, { limit: 1000, ...opts }); autoBind(this); } diff --git a/src/renderer/components/+helm-releases/releases.injectable.ts b/src/renderer/components/+helm-releases/releases.injectable.ts index ed3d8d6b8e..7f4d2d286b 100644 --- a/src/renderer/components/+helm-releases/releases.injectable.ts +++ b/src/renderer/components/+helm-releases/releases.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { asyncComputed } 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 releaseSecretsInjectable from "./release-secrets.injectable"; import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable"; import toHelmReleaseInjectable from "./to-helm-release.injectable"; @@ -13,7 +13,7 @@ const releasesInjectable = getInjectable({ id: "releases", instantiate: (di) => { - 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/__snapshots__/namespace-select-filter.test.tsx.snap b/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap index 664b0daece..335cc1d159 100644 --- a/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap +++ b/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders 1`] = ` +exports[` once the subscribe resolves renders 1`] = `
renders 1`] = ` `; -exports[` when clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked renders 1`] = `
when clicked renders 1`] = ` class="Select__option Select__option--is-selected css-tr4s17-option" id="react-select-namespace-select-filter-option-2" tabindex="-1" - > -
- - - layers - - - - test-2 - - - - check - - -
-
-
-
- - - layers - - - - test-3 - - - - check - - -
-
-
-
- - - layers - - - - test-4 - - - - check - - -
-
-
-
- - - layers - - - - test-5 - - - - check - - -
-
-
-
- - - layers - - - - test-6 - - - - check - - -
-
-
-
- - - layers - - - - test-7 - - - - check - - -
-
-
-
- - - layers - - - - test-8 - - - - check - - -
-
-
-
- - - layers - - - - test-9 - - - - check - - -
-
-
when clicked renders 1`] = `
when clicked renders 1`] = `
when clicked renders 1`] = `
when clicked renders 1`] = `
+
+
+ + + layers + + + + test-2 + + + + check + + +
+
+
+
+ + + layers + + + + test-3 + + + + check + + +
+
+
+
+ + + layers + + + + test-4 + + + + check + + +
+
+
+
+ + + layers + + + + test-5 + + + + check + + +
+
+
+
+ + + layers + + + + test-6 + + + + check + + +
+
+
+
+ + + layers + + + + test-7 + + + + check + + +
+
+
+
+ + + layers + + + + test-8 + + + + check + + +
+
+
+
+ + + layers + + + + test-9 + + + + check + + +
+
`; -exports[` when clicked when 'test-2' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked renders 1`] = `
when clicked when 'test-2' is clicked renders `; -exports[` when clicked when 'test-2' is clicked when clicked again renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again renders 1`] = `
when clicked when 'test-2' is clicked when cl - test-3 + test-10
@@ -942,7 +942,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-4 + test-11
@@ -966,7 +966,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-5 + test-12
@@ -990,7 +990,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-6 + test-13
@@ -1014,7 +1014,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-7 + test-3
@@ -1038,7 +1038,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-8 + test-4 @@ -1062,7 +1062,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-9 + test-5 @@ -1086,7 +1086,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-10 + test-6 @@ -1110,7 +1110,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-11 + test-7 @@ -1134,7 +1134,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-12 + test-8 @@ -1158,7 +1158,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-13 + test-9 @@ -1168,7 +1168,7 @@ exports[` when clicked when 'test-2' is clicked when cl `; -exports[` when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
when clicked when 'test-2' is clicked when cl `; -exports[` when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = `
when clicked when 'test-2' is clicked when cl class="Select__option css-10wo9uf-option" id="react-select-namespace-select-filter-option-2" tabindex="-1" + > +
+ + + layers + + + + test-10 + +
+
+
+
+ + + layers + + + + test-11 + +
+
+
+
+ + + layers + + + + test-12 + +
+
+
+
+ + + layers + + + + test-13 + +
+
+
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
-
-
- - - layers - - - - test-10 - -
-
-
-
- - - layers - - - - test-11 - -
-
-
-
- - - layers - - - - test-12 - -
-
-
-
- - - layers - - - - test-13 - -
-
diff --git a/src/renderer/components/+namespaces/namespace-select-filter.test.tsx b/src/renderer/components/+namespaces/namespace-select-filter.test.tsx index a81e32e263..0a4626b251 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.test.tsx +++ b/src/renderer/components/+namespaces/namespace-select-filter.test.tsx @@ -3,14 +3,25 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; import type { DiContainer } from "@ogre-tools/injectable"; import type { RenderResult } from "@testing-library/react"; import { fireEvent } from "@testing-library/react"; import React from "react"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import type { Fetch } from "../../../common/fetch/fetch.injectable"; +import fetchInjectable from "../../../common/fetch/fetch.injectable"; import { Namespace } from "../../../common/k8s-api/endpoints"; +import { createMockResponseFromString } from "../../../test-utils/mock-responses"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; +import type { Disposer } from "../../utils"; +import { disposer } from "../../utils"; import { renderFor } from "../test-utils/renderFor"; import { NamespaceSelectFilter } from "./namespace-select-filter"; import type { NamespaceStore } from "./store"; @@ -32,147 +43,197 @@ function createNamespace(name: string): Namespace { describe("", () => { let di: DiContainer; let namespaceStore: NamespaceStore; + let fetchMock: AsyncFnMock; let result: RenderResult; + let cleanup: Disposer; beforeEach(() => { di = getDiForUnitTesting({ doGeneralOverrides: true }); - di.override(directoryForUserDataInjectable, () => "/some-directory"); + di.unoverride(subscribeStoresInjectable); + + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + namespaceStore = di.inject(namespaceStoreInjectable); - const render = renderFor(di); + const subscribeStores = di.inject(subscribeStoresInjectable); - namespaceStore.items.replace([ - createNamespace("test-1"), - createNamespace("test-2"), - createNamespace("test-3"), - createNamespace("test-4"), - createNamespace("test-5"), - createNamespace("test-6"), - createNamespace("test-7"), - createNamespace("test-8"), - createNamespace("test-9"), - createNamespace("test-10"), - createNamespace("test-11"), - createNamespace("test-12"), - createNamespace("test-13"), - ]); + cleanup = disposer(subscribeStores([namespaceStore])); + + fetchMock = asyncFn(); + di.override(fetchInjectable, () => fetchMock); + + const render = renderFor(di); result = render(( )); }); - it("renders", () => { - expect(result.baseElement).toMatchSnapshot(); + afterEach(() => { + cleanup(); }); - describe("when clicked", () => { - beforeEach(() => { - result.getByTestId("namespace-select-filter").click(); + describe("once the subscribe resolves", () => { + beforeEach(async () => { + await fetchMock.resolveSpecific([ + "http://127.0.0.1:12345/api-kube/api/v1/namespaces", + ], createMockResponseFromString("http://127.0.0.1:12345/api-kube/api/v1/namespaces", JSON.stringify({ + apiVersion: "v1", + kind: "NamespaceList", + metadata: {}, + items: [ + createNamespace("test-1"), + createNamespace("test-2"), + createNamespace("test-3"), + createNamespace("test-4"), + createNamespace("test-5"), + createNamespace("test-6"), + createNamespace("test-7"), + createNamespace("test-8"), + createNamespace("test-9"), + createNamespace("test-10"), + createNamespace("test-11"), + createNamespace("test-12"), + createNamespace("test-13"), + ], + }))); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("opens menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - - describe("when 'test-2' is clicked", () => { + describe("when clicked", () => { beforeEach(() => { - result.getByText("test-2").click(); + result.getByTestId("namespace-select-filter").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("has only 'test-2' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-2"]); + it("opens menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); - }); - - describe("when clicked again", () => { + describe("when 'test-2' is clicked", () => { beforeEach(() => { - result.getByTestId("namespace-select-filter").click(); + result.getByText("test-2").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("shows 'test-2' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull(); + it("has only 'test-2' is selected in the store", () => { + expect(namespaceStore.contextNamespaces).toEqual(["test-2"]); }); - it("does not show 'test-1' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull(); + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); }); - describe("when 'test-1' is clicked", () => { + describe("when clicked again", () => { beforeEach(() => { - result.getByText("test-1").click(); + result.getByTestId("namespace-select-filter").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("has only 'test-1' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-1"]); + it("shows 'test-2' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull(); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + it("does not show 'test-1' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull(); }); - describe("when clicked again, then holding down multi select key", () => { + describe("when 'test-1' is clicked", () => { beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - filter.click(); - fireEvent.keyDown(filter, { key: "Meta" }); + result.getByText("test-1").click(); }); - describe("when 'test-3' is clicked", () => { + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("has only 'test-1' is selected in the store", () => { + expect(namespaceStore.contextNamespaces).toEqual(["test-1"]); + }); + + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + }); + + describe("when clicked again, then holding down multi select key", () => { beforeEach(() => { - result.getByText("test-3").click(); + const filter = result.getByTestId("namespace-select-filter"); + + filter.click(); + fireEvent.keyDown(filter, { key: "Meta" }); }); - it("renders", () => { - expect(result.baseElement).toMatchSnapshot(); - }); - - it("has both 'test-1' and 'test-3' as selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); - }); - - it("keeps menu open", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - - it("does not show 'kube-system' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull(); - }); - - describe("when 'test-13' is clicked", () => { + describe("when 'test-3' is clicked", () => { beforeEach(() => { - result.getByText("test-13").click(); + result.getByText("test-3").click(); }); - it("has all of 'test-1', 'test-3', and 'test-13' selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); }); - it("'test-13' is not sorted to the top of the list", () => { - const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement; + it("has both 'test-1' and 'test-3' as selected in the store", () => { + expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); + }); - expect(topLevelElement.nextSibling).toBe(null); + it("keeps menu open", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); + }); + + it("does not show 'kube-system' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull(); + }); + + describe("when 'test-13' is clicked", () => { + beforeEach(() => { + result.getByText("test-13").click(); + }); + + it("has all of 'test-1', 'test-3', and 'test-13' selected in the store", () => { + expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); + }); + + it("'test-13' is not sorted to the top of the list", () => { + const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement; + + expect(topLevelElement.previousSibling).not.toBe(null); + }); + }); + + describe("when releasing multi select key", () => { + beforeEach(() => { + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyUp(filter, { key: "Meta" }); + }); + + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + }); }); }); @@ -183,46 +244,24 @@ describe("", () => { fireEvent.keyUp(filter, { key: "Meta" }); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + it("keeps menu open", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); }); }); }); - - describe("when releasing multi select key", () => { - beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyUp(filter, { key: "Meta" }); - }); - - it("keeps menu open", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - }); }); }); }); - }); - describe("when multi-selection key is pressed", () => { - beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyDown(filter, { key: "Meta" }); - }); - - it("should show placeholder text as 'All namespaces'", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces"); - }); - - describe("when 'test-2' is clicked", () => { + describe("when multi-selection key is pressed", () => { beforeEach(() => { - result.getByText("test-2").click(); + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyDown(filter, { key: "Meta" }); }); - it("should not show placeholder text as 'All namespaces'", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); + it("should show placeholder text as 'All namespaces'", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces"); }); describe("when 'test-2' is clicked", () => { @@ -230,20 +269,30 @@ describe("", () => { result.getByText("test-2").click(); }); - it("should not show placeholder as 'All namespaces'", () => { + it("should not show placeholder text as 'All namespaces'", () => { expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); }); - describe("when multi-selection key is raised", () => { + describe("when 'test-2' is clicked", () => { beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyUp(filter, { key: "Meta" }); + result.getByText("test-2").click(); }); - it("should show placeholder text as 'All namespaces'", () => { + it("should not show placeholder as 'All namespaces'", () => { expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); }); + + describe("when multi-selection key is raised", () => { + beforeEach(() => { + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyUp(filter, { key: "Meta" }); + }); + + it("should show placeholder text as 'All namespaces'", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); + }); + }); }); }); }); diff --git a/src/renderer/components/+namespaces/store.injectable.ts b/src/renderer/components/+namespaces/store.injectable.ts index 68d611e837..eb2a7e4a35 100644 --- a/src/renderer/components/+namespaces/store.injectable.ts +++ b/src/renderer/components/+namespaces/store.injectable.ts @@ -9,6 +9,8 @@ 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"; +import clusterConfiguredAccessibleNamespacesInjectable from "../../cluster/accessible-namespaces.injectable"; const namespaceStoreInjectable = getInjectable({ id: "namespace-store", @@ -20,7 +22,9 @@ const namespaceStoreInjectable = getInjectable({ const api = di.inject(namespaceApiInjectable); return new NamespaceStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), storage: createStorage("selected_namespaces", undefined), + clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+namespaces/store.ts b/src/renderer/components/+namespaces/store.ts index 1b2342facf..4c178a8a96 100644 --- a/src/renderer/components/+namespaces/store.ts +++ b/src/renderer/components/+namespaces/store.ts @@ -3,22 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { IReactionDisposer } from "mobx"; +import type { IComputedValue, 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; + readonly clusterConfiguredAccessibleNamespaces: IComputedValue; } export class NamespaceStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: NamespaceApi) { - super(api); + super(dependencies, api); makeObservable(this); autoBind(this); @@ -26,11 +27,21 @@ export class NamespaceStore extends KubeObjectStore { } private async init() { - await this.contextReady; await this.dependencies.storage.whenReady; - this.selectNamespaces(this.initialNamespaces); - this.autoLoadAllowedNamespaces(); + const { allowedNamespaces } = this; + const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load + + // return previously saved namespaces from local-storage (if any) + if (Array.isArray(selectedNamespaces)) { + this.selectNamespaces(selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace))); + } else if (allowedNamespaces.includes("default")) { + this.selectNamespaces(["default"]); + } else if (allowedNamespaces.length) { + this.selectNamespaces([allowedNamespaces[0]]); + } else { + this.selectNamespaces([]); + } } public onContextChange(callback: (namespaces: string[]) => void, opts: { fireImmediately?: boolean } = {}): IReactionDisposer { @@ -40,32 +51,6 @@ export class NamespaceStore extends KubeObjectStore { }); } - private autoLoadAllowedNamespaces(): IReactionDisposer { - return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), { - fireImmediately: true, - equals: comparer.shallow, - }); - } - - private get initialNamespaces(): string[] { - const { allowedNamespaces } = this; - const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load - - // return previously saved namespaces from local-storage (if any) - if (Array.isArray(selectedNamespaces)) { - return selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace)); - } - - // otherwise select "default" or first allowed namespace - if (allowedNamespaces.includes("default")) { - return ["default"]; - } else if (allowedNamespaces.length) { - return [allowedNamespaces[0]]; - } - - return []; - } - /** * @private * The current value (list of namespaces names) in the storage layer @@ -75,10 +60,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()); } /** @@ -110,11 +92,13 @@ export class NamespaceStore extends KubeObjectStore { } subscribe() { + const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get(); + /** * 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 (clusterConfiguredAccessibleNamespaces.length > 0) { return noop; } @@ -122,17 +106,13 @@ export class NamespaceStore extends KubeObjectStore { } protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { - const { allowedNamespaces } = this; + const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get(); - let namespaces = await super.loadItems(params).catch(() => []); - - namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName())); - - if (!namespaces.length && allowedNamespaces.length > 0) { - return allowedNamespaces.map(getDummyNamespace); + if (clusterConfiguredAccessibleNamespaces.length > 0) { + return clusterConfiguredAccessibleNamespaces.map(getDummyNamespace); } - return namespaces; + return super.loadItems(params); } @action selectNamespaces = (namespace: string | string[]) => { 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 { let render: DiRender; @@ -20,8 +23,19 @@ describe("ClusterRoleBindingDialog tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); - di.override(directoryForUserDataInjectable, () => "/some-path-for-user-data"); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); render = renderFor(di); diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts b/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts index afaf3e7400..a2e3223fa7 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts +++ b/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts @@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s- import clusterRoleBindingApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role-binding.api.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ClusterRoleBindingStore } from "./store"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable"; const clusterRoleBindingStoreInjectable = getInjectable({ id: "cluster-role-binding-store", @@ -16,7 +17,9 @@ const clusterRoleBindingStoreInjectable = getInjectable({ const api = di.inject(clusterRoleBindingApiInjectable); - return new ClusterRoleBindingStore(api); + return new ClusterRoleBindingStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts b/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts index bfd83f3b3b..9ae877f635 100644 --- a/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts +++ b/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts @@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s- import clusterRoleApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role.api.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ClusterRoleStore } from "./store"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable"; const clusterRoleStoreInjectable = getInjectable({ id: "cluster-role-store", @@ -16,7 +17,9 @@ const clusterRoleStoreInjectable = getInjectable({ const api = di.inject(clusterRoleApiInjectable); - return new ClusterRoleStore(api); + return new ClusterRoleStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx b/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx index 82e3a7c9d7..08067ed817 100644 --- a/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx +++ b/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx @@ -13,6 +13,9 @@ import { renderFor } from "../../../test-utils/renderFor"; import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable"; +import directoryForKubeConfigsInjectable from "../../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../../../cluster/create-cluster.injectable"; describe("RoleBindingDialog tests", () => { let render: DiRender; @@ -20,8 +23,19 @@ describe("RoleBindingDialog tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); - di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); render = renderFor(di); diff --git a/src/renderer/components/+user-management/+role-bindings/store.injectable.ts b/src/renderer/components/+user-management/+role-bindings/store.injectable.ts index 20122b47d6..a3f33e76bc 100644 --- a/src/renderer/components/+user-management/+role-bindings/store.injectable.ts +++ b/src/renderer/components/+user-management/+role-bindings/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 roleBindingApiInjectable from "../../../../common/k8s-api/endpoints/role-binding.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { RoleBindingStore } from "./store"; @@ -16,7 +17,9 @@ const roleBindingStoreInjectable = getInjectable({ const api = di.inject(roleBindingApiInjectable); - return new RoleBindingStore(api); + return new RoleBindingStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+roles/store.injectable.ts b/src/renderer/components/+user-management/+roles/store.injectable.ts index ac10dc831b..8b86ddb0ab 100644 --- a/src/renderer/components/+user-management/+roles/store.injectable.ts +++ b/src/renderer/components/+user-management/+roles/store.injectable.ts @@ -8,6 +8,7 @@ import roleApiInjectable from "../../../../common/k8s-api/endpoints/role.api.inj import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { RoleStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const roleStoreInjectable = getInjectable({ id: "role-store", @@ -16,7 +17,9 @@ const roleStoreInjectable = getInjectable({ const api = di.inject(roleApiInjectable); - return new RoleStore(api); + return new RoleStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+service-accounts/store.injectable.ts b/src/renderer/components/+user-management/+service-accounts/store.injectable.ts index 75b4b791d5..98570c9017 100644 --- a/src/renderer/components/+user-management/+service-accounts/store.injectable.ts +++ b/src/renderer/components/+user-management/+service-accounts/store.injectable.ts @@ -8,6 +8,7 @@ import serviceAccountApiInjectable from "../../../../common/k8s-api/endpoints/se import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ServiceAccountStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const serviceAccountStoreInjectable = getInjectable({ id: "service-account-store", @@ -16,7 +17,9 @@ const serviceAccountStoreInjectable = getInjectable({ const api = di.inject(serviceAccountApiInjectable); - return new ServiceAccountStore(api); + return new ServiceAccountStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+workloads-cronjobs/store.injectable.ts b/src/renderer/components/+workloads-cronjobs/store.injectable.ts index a71352685f..6b0fab2c9c 100644 --- a/src/renderer/components/+workloads-cronjobs/store.injectable.ts +++ b/src/renderer/components/+workloads-cronjobs/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getJobsByOwnerInjectable from "../+workloads-jobs/get-jobs-by-owner.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import cronJobApiInjectable from "../../../common/k8s-api/endpoints/cron-job.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { CronJobStore } from "./store"; @@ -19,6 +20,7 @@ const cronJobStoreInjectable = getInjectable({ return new CronJobStore({ getJobsByOwner: di.inject(getJobsByOwnerInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-cronjobs/store.ts b/src/renderer/components/+workloads-cronjobs/store.ts index 67e006ac90..fe6c765723 100644 --- a/src/renderer/components/+workloads-cronjobs/store.ts +++ b/src/renderer/components/+workloads-cronjobs/store.ts @@ -3,18 +3,18 @@ * 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 { CronJob, CronJobApi } from "../../../common/k8s-api/endpoints/cron-job.api"; import type { GetJobsByOwner } from "../+workloads-jobs/get-jobs-by-owner.injectable"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getJobsByOwner: GetJobsByOwner; } export class CronJobStore 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-jobs/store.injectable.ts b/src/renderer/components/+workloads-jobs/store.injectable.ts index 46e4d26fb4..8f394a1bf5 100644 --- a/src/renderer/components/+workloads-jobs/store.injectable.ts +++ b/src/renderer/components/+workloads-jobs/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 jobApiInjectable from "../../../common/k8s-api/endpoints/job.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { JobStore } from "./store"; @@ -19,6 +20,7 @@ const jobStoreInjectable = getInjectable({ return new JobStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-jobs/store.ts b/src/renderer/components/+workloads-jobs/store.ts index 47fc09d931..2f6f671aad 100644 --- a/src/renderer/components/+workloads-jobs/store.ts +++ b/src/renderer/components/+workloads-jobs/store.ts @@ -3,20 +3,20 @@ * 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 { Job, JobApi } from "../../../common/k8s-api/endpoints/job.api"; import type { CronJob, Pod } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class JobStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: JobApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(job: Job): Pod[] { 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..8b60676da2 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -17,8 +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 type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import workloadOverviewDetailsInjectable from "./workload-overview-details/workload-overview-details.injectable"; import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; @@ -35,10 +34,11 @@ import jobStoreInjectable from "../+workloads-jobs/store.injectable"; import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injectable"; import type { EventStore } from "../+events/store"; import eventStoreInjectable from "../+events/store.injectable"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; interface Dependencies { detailComponents: IComputedValue[]>; - clusterFrameContext: ClusterFrameContext; + clusterFrameContext: ClusterContext; subscribeStores: SubscribeStores; podStore: PodStore; daemonSetStore: DaemonSetStore; @@ -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..452f7903aa 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,10 @@ const podsWorkloadInjectable = getInjectable({ const store = di.inject(podStoreInjectable); return { - resourceName: "pods", + resource: { + apiName: "pods", + group: "v1", + }, 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/__tests__/cronjob.store.test.ts b/src/renderer/components/__tests__/cronjob.store.test.ts index 307c005aec..322e609953 100644 --- a/src/renderer/components/__tests__/cronjob.store.test.ts +++ b/src/renderer/components/__tests__/cronjob.store.test.ts @@ -7,6 +7,10 @@ import cronJobStoreInjectable from "../+workloads-cronjobs/store.injectable"; import { CronJob } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const scheduledCronJob = new CronJob({ apiVersion: "foo", @@ -119,8 +123,20 @@ describe("CronJob Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + cronJobStore = di.inject(cronJobStoreInjectable); }); diff --git a/src/renderer/components/__tests__/daemonset.store.test.ts b/src/renderer/components/__tests__/daemonset.store.test.ts index 76afb3188a..da5a167ffd 100644 --- a/src/renderer/components/__tests__/daemonset.store.test.ts +++ b/src/renderer/components/__tests__/daemonset.store.test.ts @@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable"; import { DaemonSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningDaemonSet = new DaemonSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("DaemonSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); daemonSetStore = di.inject(daemonSetStoreInjectable); diff --git a/src/renderer/components/__tests__/deployments.store.test.ts b/src/renderer/components/__tests__/deployments.store.test.ts index a478c03d2a..dc50f85ccf 100644 --- a/src/renderer/components/__tests__/deployments.store.test.ts +++ b/src/renderer/components/__tests__/deployments.store.test.ts @@ -11,6 +11,10 @@ import type { PodSpec } from "../../../common/k8s-api/endpoints"; import { Deployment, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const spec: PodSpec = { containers: [{ @@ -208,8 +212,20 @@ describe("Deployment Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); // Add pods to pod store diff --git a/src/renderer/components/__tests__/job.store.test.ts b/src/renderer/components/__tests__/job.store.test.ts index 6fdc63bd50..5a000d18ae 100644 --- a/src/renderer/components/__tests__/job.store.test.ts +++ b/src/renderer/components/__tests__/job.store.test.ts @@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable"; import { Job, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningJob = new Job({ apiVersion: "foo", @@ -173,8 +177,20 @@ describe("Job Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + jobStore = di.inject(jobStoreInjectable); const podStore = di.inject(podStoreInjectable); diff --git a/src/renderer/components/__tests__/pods.store.test.ts b/src/renderer/components/__tests__/pods.store.test.ts index 72e124a686..229c94c2d8 100644 --- a/src/renderer/components/__tests__/pods.store.test.ts +++ b/src/renderer/components/__tests__/pods.store.test.ts @@ -8,6 +8,10 @@ import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningPod = new Pod({ apiVersion: "foo", @@ -119,8 +123,20 @@ describe("Pod Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + podStore = di.inject(podStoreInjectable); }); diff --git a/src/renderer/components/__tests__/replicaset.store.test.ts b/src/renderer/components/__tests__/replicaset.store.test.ts index 29352c34e1..3186725981 100644 --- a/src/renderer/components/__tests__/replicaset.store.test.ts +++ b/src/renderer/components/__tests__/replicaset.store.test.ts @@ -10,6 +10,10 @@ import type { ReplicaSetStore } from "../+workloads-replicasets/store"; import { ReplicaSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningReplicaSet = new ReplicaSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("ReplicaSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); replicaSetStore = di.inject(replicasetsStoreInjectable); diff --git a/src/renderer/components/__tests__/statefulset.store.test.ts b/src/renderer/components/__tests__/statefulset.store.test.ts index 00471c8805..742955884c 100644 --- a/src/renderer/components/__tests__/statefulset.store.test.ts +++ b/src/renderer/components/__tests__/statefulset.store.test.ts @@ -10,6 +10,10 @@ import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injecta import { StatefulSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningStatefulSet = new StatefulSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("StatefulSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + statefulSetStore = di.inject(statefulSetStoreInjectable); const podStore = di.inject(podStoreInjectable); 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/dock/create-resource/view.tsx b/src/renderer/components/dock/create-resource/view.tsx index d84a823259..00221a7b32 100644 --- a/src/renderer/components/dock/create-resource/view.tsx +++ b/src/renderer/components/dock/create-resource/view.tsx @@ -13,8 +13,8 @@ import { observer } from "mobx-react"; import type { CreateResourceTabStore } from "./store"; import { EditorPanel } from "../editor-panel"; import { InfoPanel } from "../info-panel"; -import { Notifications } from "../../notifications"; -import logger from "../../../../common/logger"; +import type { ShowNotification } from "../../notifications"; +import type { Logger } from "../../../../common/logger"; import type { ApiManager } from "../../../../common/k8s-api/api-manager"; import { isObject, prevDefault } from "../../../utils"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -29,6 +29,10 @@ import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.in import navigateInjectable from "../../../navigation/navigate.injectable"; import type { RequestKubeObjectCreation } from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable"; import requestKubeObjectCreationInjectable from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import type { ShowCheckedErrorNotification } from "../../notifications/show-checked-error.injectable"; +import showSuccessNotificationInjectable from "../../notifications/show-success-notification.injectable"; +import showCheckedErrorNotificationInjectable from "../../notifications/show-checked-error.injectable"; export interface CreateResourceProps { tabId: string; @@ -38,9 +42,12 @@ interface Dependencies { createResourceTemplates: IComputedValue[]>; createResourceTabStore: CreateResourceTabStore; apiManager: ApiManager; + logger: Logger; navigate: Navigate; getDetailsUrl: GetDetailsUrl; requestKubeObjectCreation: RequestKubeObjectCreation; + showSuccessNotification: ShowNotification; + showCheckedErrorNotification: ShowCheckedErrorNotification; } @observer @@ -81,34 +88,38 @@ class NonInjectedCreateResource extends React.Component { - try { - const data = await requestKubeObjectCreation(dump(resource)); - const { kind, apiVersion, metadata: { name, namespace }} = data; + const result = await requestKubeObjectCreation(dump(resource)); - const showDetails = () => { - const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace }); + if (!result.callWasSuccessful) { + this.props.logger.warn("Failed to create resource", { resource }, result.error); + this.props.showCheckedErrorNotification(result.error, "Unknown error occured while creating resources"); - navigate(getDetailsUrl(resourceLink)); - close(); - }; - - const close = Notifications.ok( -

- {kind} - {" "} - - {name} - - {" successfully created."} -

, - ); - } catch (error) { - Notifications.checkedError(error, "Unknown error occured while creating resources"); + return; } + + const { kind, apiVersion, metadata: { name, namespace }} = result.response; + + const close = this.props.showSuccessNotification(( +

+ {kind} + {" "} + { + const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace }); + + navigate(getDetailsUrl(resourceLink)); + close(); + })} + > + {name} + + {" successfully created."} +

+ )); }); await Promise.allSettled(creatingResources); @@ -168,8 +179,11 @@ export const CreateResource = withInjectables createResourceTabStore: di.inject(createResourceTabStoreInjectable), createResourceTemplates: await di.inject(createResourceTemplatesInjectable), apiManager: di.inject(apiManagerInjectable), + logger: di.inject(loggerInjectable), getDetailsUrl: di.inject(getDetailsUrlInjectable), navigate: di.inject(navigateInjectable), requestKubeObjectCreation: di.inject(requestKubeObjectCreationInjectable), + showSuccessNotification: di.inject(showSuccessNotificationInjectable), + showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable), }), }); diff --git a/src/renderer/components/input/search-input.tsx b/src/renderer/components/input/search-input.tsx index 1c5a66bdc2..4aff4a6ffa 100644 --- a/src/renderer/components/input/search-input.tsx +++ b/src/renderer/components/input/search-input.tsx @@ -11,7 +11,8 @@ import { cssNames, autoBind } from "../../utils"; import { Icon } from "../icon"; import type { InputProps } from "./input"; import { Input } from "./input"; -import { isMac } from "../../../common/vars"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; export interface SearchInputProps extends InputProps { compact?: boolean; // show only search-icon when not focused @@ -27,13 +28,17 @@ const defaultProps: Partial = { placeholder: "Search...", }; +interface Dependencies { + isMac: boolean; +} + @observer -export class SearchInput extends React.Component { +class NonInjectedSearchInput extends React.Component { static defaultProps = defaultProps as object; private inputRef = createRef(); - constructor(props: SearchInputProps) { + constructor(props: SearchInputProps & Dependencies) { super(props); autoBind(this); } @@ -48,7 +53,7 @@ export class SearchInput extends React.Component { } onGlobalKey(evt: KeyboardEvent) { - if (evt.key === "f" && (isMac ? evt.metaKey : evt.ctrlKey)) { + if (evt.key === "f" && (this.props.isMac ? evt.metaKey : evt.ctrlKey)) { this.inputRef.current?.focus(); } } @@ -71,7 +76,7 @@ export class SearchInput extends React.Component { } render() { - const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, ...inputProps } = this.props; + const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, isMac, ...inputProps } = this.props; let rightIcon = ; if (showClearIcon && value) { @@ -97,3 +102,10 @@ export class SearchInput extends React.Component { ); } } + +export const SearchInput = withInjectables(NonInjectedSearchInput, { + getProps: (di, props) => ({ + ...props, + isMac: di.inject(isMacInjectable), + }), +}); 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..bd4fa3d81d 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 @@ -20,8 +20,7 @@ import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; 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"; @@ -29,6 +28,7 @@ import type { PageParam } from "../../navigation"; import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details.injectable"; import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable"; import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; export interface KubeObjectListLayoutProps< K extends KubeObject, @@ -43,7 +43,7 @@ export interface KubeObjectListLayoutProps< } interface Dependencies { - clusterFrameContext: ClusterFrameContext; + clusterFrameContext: ClusterContext; subscribeToStores: SubscribeStores; kubeSelectedUrlParam: PageParam; toggleKubeDetailsPane: ToggleKubeDetailsPane; @@ -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/layout/top-bar/start-state-sync.injectable.ts b/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts index 666966ddad..92488851c5 100644 --- a/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts +++ b/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { action } from "mobx"; -import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../before-frame-starts/tokens"; import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable"; import topBarStateInjectable from "./state.injectable"; @@ -26,7 +26,7 @@ const startTopbarStateSyncInjectable = getInjectable({ })); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, causesSideEffects: true, }); diff --git a/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json b/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json deleted file mode 100644 index 48696d8d0a..0000000000 --- a/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "name": "clouds-midnight", - "base": "vs-dark", - "inherit": true, - "rules": [ - { - "background": "191919", - "token": "" - }, - { - "foreground": "3c403b", - "token": "comment" - }, - { - "foreground": "5d90cd", - "token": "string" - }, - { - "foreground": "46a609", - "token": "constant.numeric" - }, - { - "foreground": "39946a", - "token": "constant.language" - }, - { - "foreground": "927c5d", - "token": "keyword" - }, - { - "foreground": "927c5d", - "token": "support.constant.property-value" - }, - { - "foreground": "927c5d", - "token": "constant.other.color" - }, - { - "foreground": "366f1a", - "token": "keyword.other.unit" - }, - { - "foreground": "a46763", - "token": "entity.other.attribute-name.html" - }, - { - "foreground": "4b4b4b", - "token": "keyword.operator" - }, - { - "foreground": "e92e2e", - "token": "storage" - }, - { - "foreground": "858585", - "token": "entity.other.inherited-class" - }, - { - "foreground": "606060", - "token": "entity.name.tag" - }, - { - "foreground": "a165ac", - "token": "constant.character.entity" - }, - { - "foreground": "a165ac", - "token": "support.class.js" - }, - { - "foreground": "606060", - "token": "entity.other.attribute-name" - }, - { - "foreground": "e92e2e", - "token": "meta.selector.css" - }, - { - "foreground": "e92e2e", - "token": "entity.name.tag.css" - }, - { - "foreground": "e92e2e", - "token": "entity.other.attribute-name.id.css" - }, - { - "foreground": "e92e2e", - "token": "entity.other.attribute-name.class.css" - }, - { - "foreground": "616161", - "token": "meta.property-name.css" - }, - { - "foreground": "e92e2e", - "token": "support.function" - }, - { - "foreground": "ffffff", - "background": "e92e2e", - "token": "invalid" - }, - { - "foreground": "e92e2e", - "token": "punctuation.section.embedded" - }, - { - "foreground": "606060", - "token": "punctuation.definition.tag" - }, - { - "foreground": "a165ac", - "token": "constant.other.color.rgb-value.css" - }, - { - "foreground": "a165ac", - "token": "support.constant.property-value.css" - } - ], - "colors": { - "editor.foreground": "#929292", - "editor.background": "#191919", - "editor.selectionBackground": "#000000", - "editor.lineHighlightBackground": "#D7D7D708", - "editorCursor.foreground": "#7DA5DC", - "editorWhitespace.foreground": "#BFBFBF" - } -} \ No newline at end of file diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 7e2dde5296..405a4e3179 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"; @@ -22,10 +22,7 @@ import navigateToPreferencesInjectable from "../../../features/preferences/commo import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; 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 startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable"; import startFrameInjectable from "../../start-frame/start-frame.injectable"; import type { NamespaceStore } from "../+namespaces/store"; @@ -52,7 +49,6 @@ import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluste import activeKubernetesClusterInjectable from "../../cluster-frame-context/active-kubernetes-cluster.injectable"; import { catalogEntityFromCluster } from "../../../main/cluster/manager"; import namespaceStoreInjectable from "../+namespaces/store.injectable"; -import { isAllowedResource } from "../../../common/cluster/is-allowed-resource"; import createApplicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-application-window.injectable"; import type { CreateElectronWindow } from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable"; import createElectronWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable"; @@ -115,7 +111,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; @@ -214,7 +210,7 @@ export const getApplicationBuilder = () => { }, })); - const allowedResourcesState = observable.array(); + const allowedResourcesState = observable.set(); const windowHelpers = new Map RenderResult }>(); @@ -502,15 +498,11 @@ export const getApplicationBuilder = () => { environment = environments.clusterFrame; builder.beforeWindowStart((windowDi) => { - windowDi.override(allowedResourcesInjectable, () => - computed(() => new Set([...allowedResourcesState])), - ); - const clusterStub = { id: "some-cluster-id", - accessibleNamespaces: [], - isAllowedResource: isAllowedResource(allowedResourcesState), - } as unknown as Cluster; + accessibleNamespaces: observable.array(), + shouldShowResource: (kind) => allowedResourcesState.has(formatKubeApiResource(kind)), + } as Partial as Cluster; windowDi.override(activeKubernetesClusterInjectable, () => computed(() => catalogEntityFromCluster(clusterStub)), @@ -538,20 +530,8 @@ export const getApplicationBuilder = () => { getTotalCount: () => namespaceItems.length, } as Partial as NamespaceStore; - const clusterFrameContextFake = new ClusterFrameContext( - clusterStub, - - { - namespaceStore: namespaceStoreStub, - }, - ); - windowDi.override(namespaceStoreInjectable, () => namespaceStoreStub); windowDi.override(hostedClusterInjectable, () => clusterStub); - windowDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake); - - // Todo: get rid of global state. - KubeObjectStore.defaultContext.set(clusterFrameContextFake); }); return builder; @@ -635,11 +615,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/frames/cluster-frame/cluster-frame.test.tsx b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx index f95a80fee2..b969291fd2 100644 --- a/src/renderer/frames/cluster-frame/cluster-frame.test.tsx +++ b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx @@ -15,13 +15,12 @@ import { ClusterFrame } from "./cluster-frame"; import historyInjectable from "../../navigation/history.injectable"; import { computed } from "mobx"; import type { Cluster } from "../../../common/cluster/cluster"; -import createClusterInjectable from "../../create-cluster/create-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; 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(["v1/nodes", "v1/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(["v1/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/frames/root-frame/setup-system-ca.injectable.ts b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts index 698a769be7..557d87c81f 100644 --- a/src/renderer/frames/root-frame/setup-system-ca.injectable.ts +++ b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import injectSystemCAsInjectable from "../../../common/certificate-authorities/inject-system-cas.injectable"; const setupSystemCaInjectable = getInjectable({ @@ -12,7 +12,7 @@ const setupSystemCaInjectable = getInjectable({ id: "setup-system-ca", run: di.inject(injectSystemCAsInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupSystemCaInjectable; diff --git a/src/renderer/initializers/workload-events.tsx b/src/renderer/initializers/workload-events.tsx index 734b5105f0..735b4e2c74 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 { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; import { Events } from "../components/+events/events"; export interface WorkloadEventsProps {} @@ -32,7 +32,10 @@ const NonInjectedWorkloadEvents = observer(({ workloadEventsAreAllowed }: Depend export const WorkloadEvents = withInjectables(NonInjectedWorkloadEvents, { getProps: (di, props) => ({ - workloadEventsAreAllowed: di.inject(isAllowedResourceInjectable, "events"), + workloadEventsAreAllowed: di.inject(shouldShowResourceInjectionToken, { + apiName: "events", + group: "v1", + }), ...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..23e53bf7cc 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), + clusterContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }), }); diff --git a/src/renderer/kube-watch-api/kube-watch-api.ts b/src/renderer/kube-watch-api/kube-watch-api.ts index c3eca74d29..4fc31bd1d5 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.ts @@ -6,10 +6,10 @@ import { comparer, reaction } from "mobx"; import type { Disposer } from "../../common/utils"; import { disposer, getOrInsert, noop, WrappedAbortController } from "../../common/utils"; import { once } from "lodash"; -import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context"; import logger from "../../common/logger"; import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store"; import AbortController from "abort-controller"; +import type { ClusterContext } from "../cluster-frame-context/cluster-frame-context"; // Kubernetes watch-api client // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams @@ -68,7 +68,7 @@ export interface KubeWatchSubscribeStoreOptions { } interface Dependencies { - clusterFrameContext: ClusterFrameContext; + readonly clusterContext: ClusterContext; } export interface SubscribableStore { @@ -86,7 +86,7 @@ export type SubscribeStores = (stores: SubscribableStore[], opts?: KubeWatchSubs export class KubeWatchApi { readonly #watch = new WatchCount(); - constructor(private dependencies: Dependencies) {} + constructor(private readonly dependencies: Dependencies) {} private subscribeStore({ store, parent, namespaces, onLoadFailure }: SubscribeStoreParams): Disposer { const isNamespaceFilterWatch = !namespaces; @@ -96,7 +96,7 @@ export class KubeWatchApi { return () => this.#watch.dec(store); } - namespaces ??= this.dependencies.clusterFrameContext?.contextNamespaces ?? []; + namespaces ??= this.dependencies.clusterContext?.contextNamespaces ?? []; let childController = new WrappedAbortController(parent); const unsubscribe = disposer(); @@ -123,7 +123,7 @@ export class KubeWatchApi { const cancelReloading = isNamespaceFilterWatch && store.api.isNamespaced ? reaction( // Note: must slice because reaction won't fire if it isn't there - () => [this.dependencies.clusterFrameContext.contextNamespaces.slice(), this.dependencies.clusterFrameContext.hasSelectedAll] as const, + () => [this.dependencies.clusterContext.contextNamespaces.slice(), this.dependencies.clusterContext.hasSelectedAll] as const, ([namespaces, curSelectedAll], [prevNamespaces, prevSelectedAll]) => { if (curSelectedAll && prevSelectedAll) { const action = namespaces.length > prevNamespaces.length ? "created" : "deleted"; diff --git a/src/renderer/kube-watch-api/subscribe-stores.injectable.ts b/src/renderer/kube-watch-api/subscribe-stores.injectable.ts index f34a3d21f4..81c38c387e 100644 --- a/src/renderer/kube-watch-api/subscribe-stores.injectable.ts +++ b/src/renderer/kube-watch-api/subscribe-stores.injectable.ts @@ -7,7 +7,6 @@ import kubeWatchApiInjectable from "./kube-watch-api.injectable"; const subscribeStoresInjectable = getInjectable({ id: "subscribe-stores", - causesSideEffects: true, instantiate: (di) => di.inject(kubeWatchApiInjectable).subscribeStores, }); diff --git a/src/renderer/navigation/events.ts b/src/renderer/navigation/events.ts index 7cacd1b313..452d16af9b 100644 --- a/src/renderer/navigation/events.ts +++ b/src/renderer/navigation/events.ts @@ -3,39 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { ipcRenderer } from "electron"; -import { reaction } from "mobx"; -import { broadcastMessage } from "../../common/ipc"; -import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import matchedClusterIdInjectable from "./matched-cluster-id.injectable"; - +// export const enum IpcRendererNavigationEvents { - CLUSTER_VIEW_CURRENT_ID = "renderer:cluster-id-of-active-view", NAVIGATE_IN_APP = "renderer:navigate", NAVIGATE_IN_CLUSTER = "renderer:navigate-in-cluster", LOADED = "renderer:loaded", } - -export function bindEvents() { - if (!ipcRenderer) { - return; - } - - if (process.isMainFrame) { - bindClusterManagerRouteEvents(); - } -} - -// Handle events only in main window renderer process (see also: cluster-manager.tsx) -function bindClusterManagerRouteEvents() { - const di = getLegacyGlobalDiForExtensionApi(); - - const matchedClusterId = di.inject(matchedClusterIdInjectable); - - // Keep track of active cluster-id for handling IPC/menus/etc. - reaction(() => matchedClusterId.get(), clusterId => { - broadcastMessage(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, clusterId); - }, { - fireImmediately: true, - }); -} diff --git a/src/renderer/start-frame/start-frame.injectable.ts b/src/renderer/start-frame/start-frame.injectable.ts index 60e8d590d0..3f1934aa1c 100644 --- a/src/renderer/start-frame/start-frame.injectable.ts +++ b/src/renderer/start-frame/start-frame.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { runManyFor } from "../../common/runnable/run-many-for"; -import { beforeFrameStartsInjectionToken, beforeClusterFrameStartsInjectionToken, beforeFrameStartsFirstInjectionToken, beforeMainFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import * as tokens from "../before-frame-starts/tokens"; import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable"; const startFrameInjectable = getInjectable({ @@ -13,22 +13,30 @@ const startFrameInjectable = getInjectable({ // TODO: Consolidate contents of bootstrap.tsx here instantiate: (di) => { const runMany = runManyFor(di); - const beforeFrameStartsFirst = runMany(beforeFrameStartsFirstInjectionToken); - const beforeMainFrameStarts = runMany(beforeMainFrameStartsInjectionToken); - const beforeClusterFrameStarts = runMany(beforeClusterFrameStartsInjectionToken); - const beforeFrameStarts = runMany(beforeFrameStartsInjectionToken); + const beforeFrameStartsFirst = runMany(tokens.beforeFrameStartsFirstInjectionToken); + const beforeMainFrameStartsFirst = runMany(tokens.beforeMainFrameStartsFirstInjectionToken); + const beforeClusterFrameStartsFirst = runMany(tokens.beforeClusterFrameStartsFirstInjectionToken); + const beforeFrameStartsSecond = runMany(tokens.beforeFrameStartsSecondInjectionToken); + const beforeMainFrameStartsSecond = runMany(tokens.beforeMainFrameStartsSecondInjectionToken); + const beforeClusterFrameStartsSecond = runMany(tokens.beforeClusterFrameStartsSecondInjectionToken); const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable); return async () => { await beforeFrameStartsFirst(); if (currentlyInClusterFrame) { - await beforeClusterFrameStarts(); + await beforeClusterFrameStartsFirst(); } else { - await beforeMainFrameStarts(); + await beforeMainFrameStartsFirst(); } - await beforeFrameStarts(); + await beforeFrameStartsSecond(); + + if (currentlyInClusterFrame) { + await beforeClusterFrameStartsSecond(); + } else { + await beforeMainFrameStartsSecond(); + } }; }, }); diff --git a/src/renderer/stores/init-user-store.injectable.ts b/src/renderer/stores/init-user-store.injectable.ts index a65181ca67..fba2287fe0 100644 --- a/src/renderer/stores/init-user-store.injectable.ts +++ b/src/renderer/stores/init-user-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable"; const initUserStoreInjectable = getInjectable({ @@ -19,7 +19,7 @@ const initUserStoreInjectable = getInjectable({ }, runAfter: di.inject(initDefaultUpdateChannelInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initUserStoreInjectable; diff --git a/src/renderer/themes/setup-apply-active-theme.injectable.ts b/src/renderer/themes/setup-apply-active-theme.injectable.ts index 17b2e655a5..960f5f8764 100644 --- a/src/renderer/themes/setup-apply-active-theme.injectable.ts +++ b/src/renderer/themes/setup-apply-active-theme.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; import initializeSystemThemeTypeInjectable from "../../features/theme/system-type/renderer/initialize.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; import initUserStoreInjectable from "../stores/init-user-store.injectable"; import activeThemeInjectable from "./active.injectable"; import applyLensThemeInjectable from "./apply-lens-theme.injectable"; @@ -31,7 +31,7 @@ const setupApplyActiveThemeInjectable = getInjectable({ di.inject(initUserStoreInjectable), ], }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupApplyActiveThemeInjectable; diff --git a/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts b/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts index cbcf63ea76..aea98b3360 100644 --- a/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts +++ b/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../before-frame-starts/tokens"; import listeningOnMessageChannelsInjectable from "../../../../common/utils/channel/listening-on-message-channels.injectable"; const startListeningOfChannelsInjectable = getInjectable({ @@ -18,7 +18,7 @@ const startListeningOfChannelsInjectable = getInjectable({ }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default startListeningOfChannelsInjectable; diff --git a/src/renderer/utils/rbac.ts b/src/renderer/utils/rbac.ts index 46219febf6..93c153435f 100644 --- a/src/renderer/utils/rbac.ts +++ b/src/renderer/utils/rbac.ts @@ -5,6 +5,7 @@ import type { KubeResource } from "../../common/rbac"; import { apiResourceRecord } from "../../common/rbac"; +import { object } from "../../common/utils"; export const ResourceNames: Record = { "namespaces": "Namespaces", @@ -42,7 +43,7 @@ export const ResourceNames: Record = { "serviceaccounts": "Service Accounts", }; -export const ResourceKindMap: Record = Object.fromEntries( - Object.entries(apiResourceRecord) - .map(([resource, { kind }]) => [kind, resource as KubeResource]), +export const ResourceKindMap = object.fromEntries( + object.entries(apiResourceRecord) + .map(([resource, { kind }]) => [kind, resource]), ); diff --git a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts index f813445855..3666bf0840 100644 --- a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts +++ b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import { syncBoxInitialValueChannel } from "../../../common/utils/sync-box/channels"; import createSyncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable"; import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token"; @@ -32,7 +32,7 @@ const provideInitialValuesForSyncBoxesInjectable = getInjectable({ }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default provideInitialValuesForSyncBoxesInjectable; diff --git a/src/renderer/vars/build-version/init.injectable.ts b/src/renderer/vars/build-version/init.injectable.ts index 7e7b7e9876..0734960289 100644 --- a/src/renderer/vars/build-version/init.injectable.ts +++ b/src/renderer/vars/build-version/init.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import buildVersionInjectable from "./build-version.injectable"; const initializeBuildVersionInjectable = getInjectable({ @@ -16,7 +16,7 @@ const initializeBuildVersionInjectable = getInjectable({ await buildVersion.init(); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initializeBuildVersionInjectable; diff --git a/src/renderer/vars/default-update-channel/init.injectable.ts b/src/renderer/vars/default-update-channel/init.injectable.ts index c7435230c8..a349769815 100644 --- a/src/renderer/vars/default-update-channel/init.injectable.ts +++ b/src/renderer/vars/default-update-channel/init.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initReleaseChannelInjectable from "../release-channel/init.injectable"; import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable"; @@ -18,7 +18,7 @@ const initDefaultUpdateChannelInjectable = getInjectable({ }, runAfter: di.inject(initReleaseChannelInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initDefaultUpdateChannelInjectable; diff --git a/src/renderer/vars/release-channel/init.injectable.ts b/src/renderer/vars/release-channel/init.injectable.ts index 016ec0826d..e0092abf55 100644 --- a/src/renderer/vars/release-channel/init.injectable.ts +++ b/src/renderer/vars/release-channel/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import releaseChannelInjectable from "../../../common/vars/release-channel.injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initSemanticBuildVersionInjectable from "../semantic-build-version/init.injectable"; const initReleaseChannelInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initReleaseChannelInjectable = getInjectable({ }, runAfter: di.inject(initSemanticBuildVersionInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initReleaseChannelInjectable; diff --git a/src/renderer/vars/semantic-build-version/init.injectable.ts b/src/renderer/vars/semantic-build-version/init.injectable.ts index bf9d3e02d4..62a9a3387f 100644 --- a/src/renderer/vars/semantic-build-version/init.injectable.ts +++ b/src/renderer/vars/semantic-build-version/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import buildSemanticVersionInjectable from "../../../common/vars/build-semantic-version.injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initializeBuildVersionInjectable from "../build-version/init.injectable"; const initSemanticBuildVersionInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initSemanticBuildVersionInjectable = getInjectable({ }, runAfter: di.inject(initializeBuildVersionInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initSemanticBuildVersionInjectable; diff --git a/src/test-utils/mock-responses.ts b/src/test-utils/mock-responses.ts new file mode 100644 index 0000000000..ce016cabc7 --- /dev/null +++ b/src/test-utils/mock-responses.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; +import { PassThrough } from "stream"; + +export const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { + const res: jest.Mocked = { + buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), + clone: jest.fn(() => res), + arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), + blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), + body: new PassThrough(), + bodyUsed: false, + headers: new Headers() as NodeFetchHeaders, + json: jest.fn(async () => JSON.parse(await res.text())), + ok: 200 <= statusCode && statusCode < 300, + redirected: 300 <= statusCode && statusCode < 400, + size: data.length, + status: statusCode, + statusText: "some-text", + text: jest.fn(async () => data), + type: "basic", + url, + formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), + }; + + return res; +}; + +export const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => { + const res: jest.Mocked = { + buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), + clone: jest.fn(() => res), + arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), + blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), + body: stream, + bodyUsed: false, + headers: new Headers() as NodeFetchHeaders, + json: jest.fn(async () => JSON.parse(await res.text())), + ok: 200 <= statusCode && statusCode < 300, + redirected: 300 <= statusCode && statusCode < 400, + size: 10, + status: statusCode, + statusText: "some-text", + text: jest.fn(() => { + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + stream.on("error", (err) => reject(err)); + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); + }), + type: "basic", + url, + formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), + }; + + return res; +};