mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
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:
1 parent
e36f3d2d70
commit
443081493b
196 files changed
+2379
-2146
No files matched your search
+2
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
+403
-403
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
+15
-1
@@ -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),
|
||||
|
||||
+4
-4
@@ -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,
|
||||
};
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ const daemonsetsWorkloadInjectable = getInjectable({
|
||||
const store = di.inject(daemonsetsStoreInjectable);
|
||||
|
||||
return {
|
||||
resourceName: "daemonsets",
|
||||
resource: {
|
||||
apiName: "daemonsets",
|
||||
group: "apps",
|
||||
},
|
||||
open: navigate,
|
||||
|
||||
amountOfItems: computed(
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ const deploymentsWorkloadInjectable = getInjectable({
|
||||
const store = di.inject(deploymentsStoreInjectable);
|
||||
|
||||
return {
|
||||
resourceName: "deployments",
|
||||
resource: {
|
||||
apiName: "deployments",
|
||||
group: "apps",
|
||||
},
|
||||
open: navigate,
|
||||
|
||||
amountOfItems: computed(
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ const jobsWorkloadInjectable = getInjectable({
|
||||
const store = di.inject(jobStoreInjectable);
|
||||
|
||||
return {
|
||||
resourceName: "jobs",
|
||||
resource: {
|
||||
apiName: "jobs",
|
||||
group: "batch",
|
||||
},
|
||||
open: navigate,
|
||||
|
||||
amountOfItems: computed(
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ const podsWorkloadInjectable = getInjectable({
|
||||
const store = di.inject(podStoreInjectable);
|
||||
|
||||
return {
|
||||
resourceName: "pods",
|
||||
resource: {
|
||||
apiName: "pods",
|
||||
group: "v1",
|
||||
},
|
||||
open: navigate,
|
||||
|
||||
amountOfItems: computed(
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ const replicasetsWorkloadInjectable = getInjectable({
|
||||
const store = di.inject(replicasetsStoreInjectable);
|
||||
|
||||
return {
|
||||
resourceName: "replicasets",
|
||||
resource: {
|
||||
apiName: "replicasets",
|
||||
group: "apps",
|
||||
},
|
||||
open: navigate,
|
||||
|
||||
amountOfItems: computed(
|
||||
|
||||
+4
-1
@@ -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),
|
||||
}),
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user