Fix allowed resources checks on GKE (#6657)

* Add check for incomplete SelfSubjectRulesReview to fix GKE

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Adding namespaced for KubeApiResource

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Refactoring of AuthorizationNamespaceReview

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Removing dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Refactoring ListApiResources

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Extract ClusterContext into deps for KubeObjectStore to fix circular import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix remaining type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash in frame by consolidating setup into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type errors and remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix core resources not showing up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix namespaces not being shown

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Simplify ClusterContext to remove something only NamespaceStore needs

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make sure the public API doesn't change

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fixing namespace-select-filter tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix other tests requiring overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix kludge in cluster-frame tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix remaining test failures

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix integration test due to incorrect casting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix integration test and kube watches not working at all

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix secret details test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix non-ApplicationBuilder tests by adding overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash due to trying to read hostedCluster too soon

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash due to timing issues
- Make injectable phases more explicit for renderer

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-12-20 17:20:27 +02:00
1 parent e36f3d2d70
commit 443081493b
196 files changed
+2379 -2146

No files matched your search

@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+17 -7
View File
@@ -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<Runnable>({
id: "even-before-frame-starts",
id: "before-frame-starts-first",
});
// NOTE: these are only run when process.isMainFrame === true
export const beforeMainFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "even-before-main-frame-starts",
export const beforeMainFrameStartsFirstInjectionToken = getInjectionToken<Runnable>({
id: "before-main-frame-starts-first",
});
// NOTE: these are only run when process.isMainFrame === false
export const beforeClusterFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "even-before-cluster-frame-starts",
export const beforeClusterFrameStartsFirstInjectionToken = getInjectionToken<Runnable>({
id: "before-cluster-frame-starts-first",
});
export const beforeFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "before-frame-starts",
export const beforeFrameStartsSecondInjectionToken = getInjectionToken<Runnable>({
id: "before-frame-starts-second",
});
// NOTE: these are only run when process.isMainFrame === true
export const beforeMainFrameStartsSecondInjectionToken = getInjectionToken<Runnable>({
id: "before-main-frame-starts-second",
});
// NOTE: these are only run when process.isMainFrame === false
export const beforeClusterFrameStartsSecondInjectionToken = getInjectionToken<Runnable>({
id: "before-cluster-frame-starts-second",
});
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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."); },
};
@@ -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,
@@ -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<ClusterOverviewStorageState>;
readonly nodeStore: NodeStore;
requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames;
@@ -58,7 +59,7 @@ export class ClusterOverviewStore extends KubeObjectStore<Cluster, ClusterApi> i
}
constructor(protected readonly dependencies: ClusterOverviewStoreDependencies, api: ClusterApi) {
super(api);
super(dependencies, api);
makeObservable(this);
autoBind(this);
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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",
@@ -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,
});
@@ -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,
@@ -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<LegacyAutoRegistration>;
}
@@ -22,7 +22,7 @@ export class CustomResourceDefinitionStore extends KubeObjectStore<CustomResourc
api: CustomResourceDefinitionApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
makeObservable(this);
autoBind(this);
@@ -7,6 +7,7 @@ import assert from "assert";
import getPodByIdInjectable from "../+workloads-pods/get-pod-by-id.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import kubeEventApiInjectable from "../../../common/k8s-api/endpoints/events.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { EventStore } from "./store";
@@ -19,6 +20,7 @@ const eventStoreInjectable = getInjectable({
return new EventStore({
getPodById: di.inject(getPodByIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
+4 -4
View File
@@ -5,7 +5,7 @@
import groupBy from "lodash/groupBy";
import compact from "lodash/compact";
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 { KubeEvent, KubeEventApi } from "../../../common/k8s-api/endpoints/events.api";
@@ -13,17 +13,17 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { Pod } from "../../../common/k8s-api/endpoints/pod.api";
import type { GetPodById } from "../+workloads-pods/get-pod-by-id.injectable";
interface Dependencies {
export interface EventStoreDependencies extends KubeObjectStoreDependencies {
getPodById: GetPodById;
}
export class EventStore extends KubeObjectStore<KubeEvent, KubeEventApi> {
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);
}
@@ -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);
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NamespaceSelectFilter /> renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves renders 1`] = `
<body>
<div>
<div
@@ -86,7 +86,7 @@ exports[`<NamespaceSelectFilter /> renders 1`] = `
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked renders 1`] = `
<body>
<div>
<div
@@ -229,286 +229,6 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-2"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-2
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-2-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-3
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-3-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-4
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-4-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-5
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-5-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-6
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-6-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-7
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-7-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-8
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-8-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-9
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-9-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
@@ -542,7 +262,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-11"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
@@ -577,7 +297,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-12"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
@@ -612,7 +332,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-13"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
@@ -644,13 +364,293 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-2
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-2-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-3
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-3-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-4
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-4-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-5
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-5-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-6
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-6-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-7
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-7-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-8
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-8-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-9
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-9-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
</div>
</div>
</div>
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked renders 1`] = `
<body>
<div>
<div
@@ -736,7 +736,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked renders
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when clicked again renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked when clicked again renders 1`] = `
<body>
<div>
<div
@@ -918,7 +918,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-3
test-10
</span>
</div>
</div>
@@ -942,7 +942,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-4
test-11
</span>
</div>
</div>
@@ -966,7 +966,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-5
test-12
</span>
</div>
</div>
@@ -990,7 +990,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-6
test-13
</span>
</div>
</div>
@@ -1014,7 +1014,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-7
test-3
</span>
</div>
</div>
@@ -1038,7 +1038,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-8
test-4
</span>
</div>
</div>
@@ -1062,7 +1062,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-9
test-5
</span>
</div>
</div>
@@ -1086,7 +1086,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-10
test-6
</span>
</div>
</div>
@@ -1110,7 +1110,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-11
test-7
</span>
</div>
</div>
@@ -1134,7 +1134,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-12
test-8
</span>
</div>
</div>
@@ -1158,7 +1158,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-13
test-9
</span>
</div>
</div>
@@ -1168,7 +1168,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
<body>
<div>
<div
@@ -1254,7 +1254,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</body>
`;
exports[`<NamespaceSelectFilter /> 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[`<NamespaceSelectFilter /> 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`] = `
<body>
<div>
<div
@@ -1397,6 +1397,102 @@ exports[`<NamespaceSelectFilter /> 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"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-10
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-11
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-12
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-13
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
@@ -1419,7 +1515,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-3"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
@@ -1454,7 +1550,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-4"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
@@ -1478,7 +1574,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-5"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
@@ -1502,7 +1598,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-6"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
@@ -1526,7 +1622,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-7"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
@@ -1550,7 +1646,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-8"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
@@ -1574,7 +1670,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-9"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
@@ -1595,102 +1691,6 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-10
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-11
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-12
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-13
</span>
</div>
</div>
</div>
</div>
</div>
@@ -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("<NamespaceSelectFilter />", () => {
let di: DiContainer;
let namespaceStore: NamespaceStore;
let fetchMock: AsyncFnMock<Fetch>;
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((
<NamespaceSelectFilter id="namespace-select-filter" />
));
});
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("<NamespaceSelectFilter />", () => {
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("<NamespaceSelectFilter />", () => {
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");
});
});
});
});
});
@@ -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<string[] | undefined>("selected_namespaces", undefined),
clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
+27 -47
View File
@@ -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<string[] | undefined>;
interface Dependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer<string[] | undefined>;
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
}
export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
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<Namespace, NamespaceApi> {
}
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<Namespace, NamespaceApi> {
});
}
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<Namespace, NamespaceApi> {
}
@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<Namespace, NamespaceApi> {
}
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<Namespace, NamespaceApi> {
}
protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<Namespace[]> {
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[]) => {
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
+3 -3
View File
@@ -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<Node, NodeApi> {
constructor(api: NodeApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
constructor(dependencies: KubeObjectStoreDependencies, api: NodeApi, opts?: KubeObjectStoreOptions) {
super(dependencies, api, opts);
makeObservable(this);
autoBind(this);
@@ -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,
});
@@ -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,
@@ -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<StorageClass, StorageClas
api: StorageClassApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
}
getPersistentVolumes(storageClass: StorageClass) {
@@ -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 persistentVolumeClaimApiInjectable from "../../../common/k8s-api/endpoints/persistent-volume-claim.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PersistentVolumeClaimStore } from "./store";
@@ -16,7 +17,9 @@ const persistentVolumeClaimStoreInjectable = getInjectable({
const api = di.inject(persistentVolumeClaimApiInjectable);
return new PersistentVolumeClaimStore(api);
return new PersistentVolumeClaimStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -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 persistentVolumeApiInjectable from "../../../common/k8s-api/endpoints/persistent-volume.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PersistentVolumeStore } from "./store";
@@ -16,7 +17,9 @@ const persistentVolumeStoreInjectable = getInjectable({
const api = di.inject(persistentVolumeApiInjectable);
return new PersistentVolumeStore(api);
return new PersistentVolumeStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -13,6 +13,9 @@ import { renderFor } from "../../../test-utils/renderFor";
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
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 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("ClusterRoleBindingDialog tests", () => {
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);
@@ -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,
});
@@ -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,
});
@@ -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);
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
@@ -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<CronJob, CronJobApi> {
constructor(protected readonly dependencies: Dependencies, api: CronJobApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getStatuses(cronJobs?: CronJob[]) {
@@ -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,
@@ -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<DaemonSet, DaemonSetApi> {
constructor(protected readonly dependencies: DaemonSetStoreDependencies, api: DaemonSetApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(daemonSet: DaemonSet): Pod[] {
@@ -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,
@@ -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<Deployment, DeploymentApi> {
constructor(protected readonly dependencies: DeploymentStoreDependencies, api: DeploymentApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
protected sortItems(items: Deployment[]) {
@@ -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,
@@ -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<Job, JobApi> {
constructor(protected readonly dependencies: Dependencies, api: JobApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(job: Job): Pod[] {
@@ -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(
<div className="OverviewStatuses">
<div className="workloads">
{workloads.get().map((workload) => (
<div className="workload" key={workload.resourceName}>
<div className="workload" key={formatKubeApiResource(workload.resource)}>
<div className="title">
<a onClick={workload.open}>
{`${workload.title} (${workload.amountOfItems.get()})`}
@@ -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<React.ElementType<{}>[]>;
clusterFrameContext: ClusterFrameContext;
clusterFrameContext: ClusterContext;
subscribeStores: SubscribeStores;
podStore: PodStore;
daemonSetStore: DaemonSetStore;
@@ -123,7 +123,7 @@ class NonInjectedWorkloadsOverview extends React.Component<Dependencies> {
export const WorkloadsOverview = withInjectables<Dependencies>(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),
@@ -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,
};
@@ -19,7 +19,10 @@ const daemonsetsWorkloadInjectable = getInjectable({
const store = di.inject(daemonsetsStoreInjectable);
return {
resourceName: "daemonsets",
resource: {
apiName: "daemonsets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const deploymentsWorkloadInjectable = getInjectable({
const store = di.inject(deploymentsStoreInjectable);
return {
resourceName: "deployments",
resource: {
apiName: "deployments",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const jobsWorkloadInjectable = getInjectable({
const store = di.inject(jobStoreInjectable);
return {
resourceName: "jobs",
resource: {
apiName: "jobs",
group: "batch",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const podsWorkloadInjectable = getInjectable({
const store = di.inject(podStoreInjectable);
return {
resourceName: "pods",
resource: {
apiName: "pods",
group: "v1",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const replicasetsWorkloadInjectable = getInjectable({
const store = di.inject(replicasetsStoreInjectable);
return {
resourceName: "replicasets",
resource: {
apiName: "replicasets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const statefulsetsWorkloadInjectable = getInjectable({
const store = di.inject(statefulsetsStoreInjectable);
return {
resourceName: "statefulsets",
resource: {
apiName: "statefulsets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -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<number>;
status: IComputedValue<WorkloadStatus>;
@@ -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 extends object>(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)
));
},
});
@@ -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,
@@ -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<Pod, PodApi> {
api: PodApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
}
readonly kubeMetrics = observable.array<PodMetrics>([]);
@@ -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,
@@ -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<ReplicaSet, ReplicaSetApi> {
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) {
@@ -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,
@@ -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<StatefulSet, StatefulSetApi> {
constructor(protected readonly dependencies: Dependencies, api: StatefulSetApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(statefulSet: StatefulSet) {
@@ -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);
});
@@ -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);
@@ -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
@@ -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);
@@ -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);
});
@@ -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);
@@ -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);
@@ -32,13 +32,13 @@ export class ClusterAccessibleNamespaces extends React.Component<ClusterAccessib
placeholder="Add new namespace..."
add={(newNamespace) => {
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"
/>
@@ -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<GroupBase<{ label: string; value: string }>[]>;
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<CreateResourceProps & De
const resources = yaml.loadAll(this.data).filter(isObject);
if (resources.length === 0) {
return void logger.info("Nothing to create");
return this.props.logger.info("Nothing to create");
}
const creatingResources = resources.map(async (resource) => {
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(
<p>
{kind}
{" "}
<a onClick={prevDefault(showDetails)}>
{name}
</a>
{" successfully created."}
</p>,
);
} 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((
<p>
{kind}
{" "}
<a
onClick={prevDefault(() => {
const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace });
navigate(getDetailsUrl(resourceLink));
close();
})}
>
{name}
</a>
{" successfully created."}
</p>
));
});
await Promise.allSettled(creatingResources);
@@ -168,8 +179,11 @@ export const CreateResource = withInjectables<Dependencies, CreateResourceProps>
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),
}),
});
+17 -5
View File
@@ -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<SearchInputProps> = {
placeholder: "Search...",
};
interface Dependencies {
isMac: boolean;
}
@observer
export class SearchInput extends React.Component<SearchInputProps> {
class NonInjectedSearchInput extends React.Component<SearchInputProps & Dependencies> {
static defaultProps = defaultProps as object;
private inputRef = createRef<Input>();
constructor(props: SearchInputProps) {
constructor(props: SearchInputProps & Dependencies) {
super(props);
autoBind(this);
}
@@ -48,7 +53,7 @@ export class SearchInput extends React.Component<SearchInputProps> {
}
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<SearchInputProps> {
}
render() {
const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, ...inputProps } = this.props;
const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, isMac, ...inputProps } = this.props;
let rightIcon = <Icon small material="search"/>;
if (showClearIcon && value) {
@@ -97,3 +102,10 @@ export class SearchInput extends React.Component<SearchInputProps> {
);
}
}
export const SearchInput = withInjectables<Dependencies, SearchInputProps>(NonInjectedSearchInput, {
getProps: (di, props) => ({
...props,
isMac: di.inject(isMacInjectable),
}),
});
@@ -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<string>;
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),
@@ -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,
});
@@ -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"
}
}
@@ -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<KubeResource>();
const allowedResourcesState = observable.set<string>();
const windowHelpers = new Map<string, { di: DiContainer; getRendered: () => 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<Cluster> as Cluster;
windowDi.override(activeKubernetesClusterInjectable, () =>
computed(() => catalogEntityFromCluster(clusterStub)),
@@ -538,20 +530,8 @@ export const getApplicationBuilder = () => {
getTotalCount: () => namespaceItems.length,
} as Partial<NamespaceStore> 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;
@@ -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("<ClusterFrame />", () => {
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("<ClusterFrame />", () => {
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", () => {
@@ -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),
});
},
});
@@ -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);
};
@@ -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;
@@ -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<Dependencies, WorkloadEventsProps>(NonInjectedWorkloadEvents, {
getProps: (di, props) => ({
workloadEventsAreAllowed: di.inject(isAllowedResourceInjectable, "events"),
workloadEventsAreAllowed: di.inject(shouldShowResourceInjectionToken, {
apiName: "events",
group: "v1",
}),
...props,
}),
});
Loaded 100 of 196 files, more files were not shown because too many files have changed in this diff. Show more