diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index ca52f6f1d2..e03e0aa533 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -7,14 +7,13 @@ import fs from "fs"; import mockFs from "mock-fs"; import path from "path"; import fse from "fs-extra"; -import type { Cluster } from "../cluster/cluster"; import type { ClusterStore } from "../cluster-store/cluster-store"; import { Console } from "console"; import { stdout, stderr } from "process"; import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; -import type { ClusterModel } from "../cluster-types"; import type { DiContainer } from "@ogre-tools/injectable"; +import type { CreateCluster } from "../cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; @@ -25,17 +24,19 @@ import directoryForTempInjectable from "../app-paths/directory-for-temp/director import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable"; import normalizedPlatformInjectable from "../vars/normalized-platform.injectable"; +import fsInjectable from "../fs/fs.injectable"; console = new Console(stdout, stderr); const testDataIcon = fs.readFileSync( "test-data/cluster-store-migration-icon.png", ); +const clusterServerUrl = "https://localhost"; const kubeconfig = ` apiVersion: v1 clusters: - cluster: - server: https://localhost + server: ${clusterServerUrl} name: test contexts: - context: @@ -78,7 +79,7 @@ jest.mock("electron", () => ({ describe("cluster-store", () => { let mainDi: DiContainer; let clusterStore: ClusterStore; - let createCluster: (model: ClusterModel) => Cluster; + let createCluster: CreateCluster; beforeEach(async () => { mainDi = getDiForUnitTesting({ doGeneralOverrides: true }); @@ -94,6 +95,7 @@ describe("cluster-store", () => { mainDi.permitSideEffects(getConfigurationFileModelInjectable); mainDi.permitSideEffects(appVersionInjectable); mainDi.permitSideEffects(clusterStoreInjectable); + mainDi.permitSideEffects(fsInjectable); mainDi.unoverride(clusterStoreInjectable); }); @@ -143,6 +145,8 @@ describe("cluster-store", () => { getCustomKubeConfigDirectory("foo"), kubeconfig, ), + }, { + clusterServerUrl, }); clusterStore.addCluster(cluster); diff --git a/src/common/cluster-store/cluster-store.ts b/src/common/cluster-store/cluster-store.ts index 55ebf6aad1..0fba4d48c4 100644 --- a/src/common/cluster-store/cluster-store.ts +++ b/src/common/cluster-store/cluster-store.ts @@ -16,13 +16,17 @@ import { disposer, toJS } from "../utils"; import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types"; import { requestInitialClusterStates } from "../../renderer/ipc"; import { clusterStates } from "../ipc/cluster"; +import type { CreateCluster } from "../cluster/create-cluster-injection-token"; +import { loadConfigFromString, validateKubeConfig } from "../kube-helpers"; +import type { ReadFileSync } from "../fs/read-file-sync.injectable"; export interface ClusterStoreModel { clusters?: ClusterModel[]; } interface Dependencies { - createCluster: (model: ClusterModel) => Cluster; + createCluster: CreateCluster; + readFileSync: ReadFileSync; } export class ClusterStore extends BaseStore { @@ -111,12 +115,24 @@ export class ClusterStore extends BaseStore { return undefined; } + private createNewCluster(model: ClusterModel): Cluster { + const kubeConfigData = this.dependencies.readFileSync(model.kubeConfigPath); + const { config } = loadConfigFromString(kubeConfigData); + const result = validateKubeConfig(config, model.contextName); + + if (result.error) { + throw result.error; + } + + return this.dependencies.createCluster(model, { clusterServerUrl: result.cluster.server }); + } + addCluster(clusterOrModel: ClusterModel | Cluster): Cluster { appEventBus.emit({ name: "cluster", action: "add" }); const cluster = clusterOrModel instanceof Cluster ? clusterOrModel - : this.dependencies.createCluster(clusterOrModel); + : this.createNewCluster(clusterOrModel); this.clusters.set(cluster.id, cluster); @@ -136,7 +152,7 @@ export class ClusterStore extends BaseStore { if (cluster) { cluster.updateModel(clusterModel); } else { - cluster = this.dependencies.createCluster(clusterModel); + cluster = this.createNewCluster(clusterModel); } newClusters.set(clusterModel.id, cluster); } catch (error) { diff --git a/src/common/cluster-types.ts b/src/common/cluster-types.ts index 9bf88109a6..9c7e49b662 100644 --- a/src/common/cluster-types.ts +++ b/src/common/cluster-types.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ + /** * JSON serializable metadata type */ @@ -67,6 +68,15 @@ export interface ClusterModel { labels?: Record; } +/** + * This data is retreived from the kubeconfig file before calling the cluster constructor. + * + * That is done to remove the external dependency on the construction of Cluster instances. + */ +export interface ClusterConfigData { + clusterServerUrl: string; +} + /** * The complete set of cluster settings or preferences */ diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index 1652e18ef1..f3723e140b 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -10,13 +10,13 @@ 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 { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers"; +import { loadConfigFromFile } from "../kube-helpers"; import type { KubeApiResource, KubeResource } from "../rbac"; import { apiResourceRecord, apiResources } 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"; -import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types"; +import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types"; import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types"; import { disposer, isDefined, isRequestError, toJS } from "../utils"; import type { Response } from "request"; @@ -236,27 +236,11 @@ export class Cluster implements ClusterModel, ClusterState { return this.preferences.defaultNamespace; } - constructor(private readonly dependencies: ClusterDependencies, model: ClusterModel) { + constructor(private readonly dependencies: ClusterDependencies, model: ClusterModel, configData: ClusterConfigData) { makeObservable(this); this.id = model.id; this.updateModel(model); - - const { config } = loadConfigFromFileSync(this.kubeConfigPath); - const validationError = validateKubeConfig(config, this.contextName); - - if (validationError) { - throw validationError; - } - - const context = config.getContextObject(this.contextName); - - assert(context); - - const cluster = config.getCluster(context.cluster); - - assert(cluster); - - this.apiUrl = cluster.server; + this.apiUrl = configData.clusterServerUrl; // for the time being, until renderer gets its own cluster type this._contextHandler = this.dependencies.createContextHandler(this); diff --git a/src/common/cluster/create-cluster-injection-token.ts b/src/common/cluster/create-cluster-injection-token.ts index f1e8ef9757..a07ce4459f 100644 --- a/src/common/cluster/create-cluster-injection-token.ts +++ b/src/common/cluster/create-cluster-injection-token.ts @@ -3,10 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { ClusterModel } from "../cluster-types"; +import type { ClusterConfigData, ClusterModel } from "../cluster-types"; import type { Cluster } from "./cluster"; -export type CreateCluster = (model: ClusterModel) => Cluster; +export type CreateCluster = (model: ClusterModel, configData: ClusterConfigData) => Cluster; export const createClusterInjectionToken = getInjectionToken({ id: "create-cluster-token", diff --git a/src/common/fs/read-file-sync.injectable.ts b/src/common/fs/read-file-sync.injectable.ts new file mode 100644 index 0000000000..cdb2e4b4d2 --- /dev/null +++ b/src/common/fs/read-file-sync.injectable.ts @@ -0,0 +1,19 @@ +/** + * 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 fsInjectable from "./fs.injectable"; + +export type ReadFileSync = (filePath: string) => string; + +const readFileSyncInjectable = getInjectable({ + id: "read-file-sync", + instantiate: (di): ReadFileSync => { + const { readFileSync } = di.inject(fsInjectable); + + return (filePath) => readFileSync(filePath, "utf-8"); + }, +}); + +export default readFileSyncInjectable; diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts index 974b09ddf8..f24d4dbe2d 100644 --- a/src/common/kube-helpers.ts +++ b/src/common/kube-helpers.ts @@ -150,7 +150,7 @@ export function loadConfigFromString(content: string): ConfigResult { export interface SplitConfigEntry { config: KubeConfig; - error?: string; + validationResult: ValidateKubeConfigResult; } /** @@ -179,7 +179,7 @@ export function splitConfig(kubeConfig: KubeConfig): SplitConfigEntry[] { return { config, - error: validateKubeConfig(config, ctx.name)?.toString(), + validationResult: validateKubeConfig(config, ctx.name), }; }); } @@ -243,25 +243,44 @@ export function dumpConfigYaml(kubeConfig: PartialDeep): string { return yaml.dump(config, { skipInvalid: true }); } +export type ValidateKubeConfigResult = { + error: Error; +} | { + error?: undefined; + context: Context; + cluster: Cluster; + user: User; +}; + /** * Checks if `config` has valid `Context`, `User`, `Cluster`, and `exec` fields (if present when required) * * Note: This function returns an error instead of throwing it, returning `undefined` if the validation passes */ -export function validateKubeConfig(config: KubeConfig, contextName: string): Error | undefined { - const contextObject = config.getContextObject(contextName); +export function validateKubeConfig(config: KubeConfig, contextName: string): ValidateKubeConfigResult { + const context = config.getContextObject(contextName); - if (!contextObject) { - return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`); + if (!context) { + return { + error: new Error(`No valid context object provided in kubeconfig for context '${contextName}'`), + }; } - if (!config.getCluster(contextObject.cluster)) { - return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`); + const cluster = config.getCluster(context.cluster); + + if (!cluster) { + return { + error: new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`), + }; } - if (!config.getUser(contextObject.user)) { - return new Error(`No valid user object provided in kubeconfig for context '${contextName}'`); + const user = config.getUser(context.user); + + if (!user) { + return { + error: new Error(`No valid user object provided in kubeconfig for context '${contextName}'`), + }; } - return undefined; + return { cluster, user, context }; } diff --git a/src/jest-after-env.setup.ts b/src/jest-after-env.setup.ts index b9ee36c4cf..69b18161a8 100644 --- a/src/jest-after-env.setup.ts +++ b/src/jest-after-env.setup.ts @@ -2,4 +2,5 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import "@testing-library/jest-dom/extend-expect"; + +import "@testing-library/jest-dom"; diff --git a/src/main/catalog-sources/kubeconfig-sync/manager.ts b/src/main/catalog-sources/kubeconfig-sync/manager.ts index af5510c3bc..6b841389e2 100644 --- a/src/main/catalog-sources/kubeconfig-sync/manager.ts +++ b/src/main/catalog-sources/kubeconfig-sync/manager.ts @@ -25,9 +25,11 @@ import { createHash } from "crypto"; import { homedir } from "os"; import globToRegExp from "glob-to-regexp"; import { inspect } from "util"; -import type { ClusterModel, UpdateClusterModel } from "../../../common/cluster-types"; +import type { ClusterConfigData, UpdateClusterModel } from "../../../common/cluster-types"; import type { Cluster } from "../../../common/cluster/cluster"; import type { CatalogEntityRegistry } from "../../catalog/entity-registry"; +import type { CreateCluster } from "../../../common/cluster/create-cluster-injection-token"; +import assert from "assert"; const logPrefix = "[KUBECONFIG-SYNC]:"; @@ -56,7 +58,7 @@ interface KubeconfigSyncManagerDependencies { readonly directoryForKubeConfigs: string; readonly entityRegistry: CatalogEntityRegistry; readonly clusterManager: ClusterManager; - createCluster: (model: ClusterModel) => Cluster; + createCluster: CreateCluster; } const kubeConfigSyncName = "lens:kube-sync"; @@ -147,17 +149,26 @@ export class KubeconfigSyncManager { } // exported for testing -export function configToModels(rootConfig: KubeConfig, filePath: string): UpdateClusterModel[] { - const validConfigs = []; +export function configToModels(rootConfig: KubeConfig, filePath: string): [UpdateClusterModel, ClusterConfigData][] { + const validConfigs: ReturnType = []; for (const { config, error } of splitConfig(rootConfig)) { if (error) { logger.debug(`${logPrefix} context failed validation: ${error}`, { context: config.currentContext, filePath }); } else { - validConfigs.push({ - kubeConfigPath: filePath, - contextName: config.currentContext, - }); + const cluster = config.getCluster(config.currentContext); + + assert(cluster, "Config somehow passed validations but still doesn't have a cluster"); + + validConfigs.push([ + { + kubeConfigPath: filePath, + contextName: config.currentContext, + }, + { + clusterServerUrl: cluster.server, + }, + ]); } } @@ -169,7 +180,7 @@ type RootSource = ObservableMap; interface ComputeDiffDependencies { directoryForKubeConfigs: string; - createCluster: (model: ClusterModel) => Cluster; + createCluster: CreateCluster; clusterManager: ClusterManager; } @@ -184,15 +195,15 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterMan } const rawModels = configToModels(config, filePath); - const models = new Map(rawModels.map(m => [m.contextName, m])); + const models = new Map(rawModels.map(([model, configData]) => [model.contextName, [model, configData] as const])); logger.debug(`${logPrefix} File now has ${models.size} entries`, { filePath }); for (const [contextName, value] of source) { - const model = models.get(contextName); + const data = models.get(contextName); // remove and disconnect clusters that were removed from the config - if (!model) { + if (!data) { // remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting clusterManager.deleting.delete(value[0].id); @@ -207,17 +218,17 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterMan // diff against that // or update the model and mark it as not needed to be added - value[0].updateModel(model); + value[0].updateModel(data[0]); models.delete(contextName); logger.debug(`${logPrefix} Updated old cluster from sync`, { filePath, contextName }); } - for (const [contextName, model] of models) { + for (const [contextName, [model, configData]] of models) { // add new clusters to the source try { const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex"); - const cluster = ClusterStore.getInstance().getById(clusterId) || createCluster({ ...model, id: clusterId }); + const cluster = ClusterStore.getInstance().getById(clusterId) || createCluster({ ...model, id: clusterId }, configData); if (!cluster.apiUrl) { throw new Error("Cluster constructor failed, see above error"); diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index cd34979925..07c2ee3247 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -32,7 +32,7 @@ const createClusterInjectable = getInjectable({ createVersionDetector: di.inject(createVersionDetectorInjectable), }; - return (model) => new Cluster(dependencies, model); + return (model, configData) => new Cluster(dependencies, model, configData); }, injectionToken: createClusterInjectionToken, diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index c27f3f05a1..626dd83c5b 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -41,7 +41,7 @@ function getContexts(config: KubeConfig): Map { splitConfig(config) .map(({ config, error }) => [config.currentContext, { config, - error, + error: error?.toString(), }]), ); } diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index 1ba8df8401..6eac0e30c7 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -17,7 +17,6 @@ import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; import type { ClusterOverviewStore } from "./cluster-overview-store/cluster-overview-store"; import { ClusterPieCharts } from "./cluster-pie-charts"; -import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import type { EventStore } from "../+events/store"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -71,7 +70,7 @@ class NonInjectedClusterOverview extends React.Component { this.metricPoller.stop(); } - renderMetrics(isMetricsHidden?: boolean) { + renderMetrics(isMetricsHidden: boolean) { if (isMetricsHidden) { return null; } @@ -84,7 +83,7 @@ class NonInjectedClusterOverview extends React.Component { ); } - renderClusterOverview(isLoaded: boolean, isMetricsHidden?: boolean) { + renderClusterOverview(isLoaded: boolean, isMetricsHidden: boolean) { if (!isLoaded) { return ; } @@ -98,9 +97,9 @@ class NonInjectedClusterOverview extends React.Component { } render() { - const { eventStore, nodeStore } = this.props; + const { eventStore, nodeStore, hostedCluster } = this.props; const isLoaded = nodeStore.isLoaded && eventStore.isLoaded; - const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Cluster); + const isMetricHidden = hostedCluster.isMetricHidden(ClusterMetricsResourceType.Cluster); return ( diff --git a/src/renderer/components/command-palette/command-container.tsx b/src/renderer/components/command-palette/command-container.tsx index 759e41e312..f435bdeec0 100644 --- a/src/renderer/components/command-palette/command-container.tsx +++ b/src/renderer/components/command-palette/command-container.tsx @@ -12,51 +12,48 @@ import { CommandDialog } from "./command-dialog"; import type { ClusterId } from "../../../common/cluster-types"; import type { CommandOverlay } from "./command-overlay.injectable"; import commandOverlayInjectable from "./command-overlay.injectable"; -import { isMac } from "../../../common/vars"; -import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; -import { broadcastMessage, ipcRendererOn } from "../../../common/ipc"; -import type { Disposer } from "../../utils"; +import type { ipcRendererOn } from "../../../common/ipc"; +import { broadcastMessage } from "../../../common/ipc"; import { withInjectables } from "@ogre-tools/injectable-react"; +import type { AddWindowEventListener } from "../../window/event-listener.injectable"; import windowAddEventListenerInjectable from "../../window/event-listener.injectable"; import type { IComputedValue } from "mobx"; import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; -import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluster-id.injectable"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; +import legacyOnChannelListenInjectable from "../../ipc/legacy-channel-listen.injectable"; interface Dependencies { - addWindowEventListener: (type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => Disposer; + addWindowEventListener: AddWindowEventListener; commandOverlay: CommandOverlay; - clusterId?: ClusterId; - matchedClusterId: IComputedValue; - entityRegistry: CatalogEntityRegistry; + clusterId: ClusterId | undefined; + matchedClusterId: IComputedValue; + isMac: boolean; + legacyOnChannelListen: typeof ipcRendererOn; } @observer class NonInjectedCommandContainer extends React.Component { - private escHandler(event: KeyboardEvent) { - const { commandOverlay } = this.props; - + private escHandler = (event: KeyboardEvent) => { if (event.key === "Escape") { event.stopPropagation(); - commandOverlay.close(); + this.props.commandOverlay.close(); } - } + }; handleCommandPalette = () => { - const { commandOverlay, entityRegistry } = this.props; - const clusterIsActive = this.props.matchedClusterId.get() !== undefined; + const matchedClusterId = this.props.matchedClusterId.get(); - if (clusterIsActive) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - broadcastMessage(`command-palette:${entityRegistry.activeEntity!.getId()}:open`); + if (matchedClusterId !== undefined) { + broadcastMessage(`command-palette:${matchedClusterId}:open`); } else { - commandOverlay.open(); + this.props.commandOverlay.open(); } }; onKeyboardShortcut(action: () => void) { return ({ key, shiftKey, ctrlKey, altKey, metaKey }: KeyboardEvent) => { - const ctrlOrCmd = isMac ? metaKey && !ctrlKey : !metaKey && ctrlKey; + const ctrlOrCmd = this.props.isMac ? metaKey && !ctrlKey : !metaKey && ctrlKey; if (key === "p" && shiftKey && ctrlOrCmd && !altKey) { action(); @@ -75,9 +72,9 @@ class NonInjectedCommandContainer extends React.Component { : "command-palette:open"; disposeOnUnmount(this, [ - ipcRendererOn(ipcChannel, action), + this.props.legacyOnChannelListen(ipcChannel, action), addWindowEventListener("keydown", this.onKeyboardShortcut(action)), - addWindowEventListener("keyup", (e) => this.escHandler(e), true), + addWindowEventListener("keyup", this.escHandler, true), ]); } @@ -106,6 +103,7 @@ export const CommandContainer = withInjectables(NonInjectedCommand addWindowEventListener: di.inject(windowAddEventListenerInjectable), commandOverlay: di.inject(commandOverlayInjectable), matchedClusterId: di.inject(matchedClusterIdInjectable), - entityRegistry: di.inject(catalogEntityRegistryInjectable), + isMac: di.inject(isMacInjectable), + legacyOnChannelListen: di.inject(legacyOnChannelListenInjectable), }), }); diff --git a/src/renderer/components/layout/sidebar-item.tsx b/src/renderer/components/layout/sidebar-item.tsx index 605c08d10f..64a63be8c4 100644 --- a/src/renderer/components/layout/sidebar-item.tsx +++ b/src/renderer/components/layout/sidebar-item.tsx @@ -80,9 +80,7 @@ class NonInjectedSidebarItem extends React.Component< return (
diff --git a/src/renderer/create-cluster/create-cluster.injectable.ts b/src/renderer/create-cluster/create-cluster.injectable.ts index 688662a965..7e997477a6 100644 --- a/src/renderer/create-cluster/create-cluster.injectable.ts +++ b/src/renderer/create-cluster/create-cluster.injectable.ts @@ -28,7 +28,7 @@ const createClusterInjectable = getInjectable({ createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); }, }; - return (model) => new Cluster(dependencies, model); + return (model, configData) => new Cluster(dependencies, model, configData); }, injectionToken: createClusterInjectionToken, diff --git a/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap b/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap new file mode 100644 index 0000000000..f4ddc5a0b7 --- /dev/null +++ b/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap @@ -0,0 +1,1490 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` given cluster with list nodes and namespaces permissions given no matching component given current url is starting url renders 1`] = ` +
+
+