mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix flakiness and improve tests for DeleteClusterDialog
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
7e47c633bf
commit
aec299feb4
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
||||||
|
|
||||||
|
const filteredCategoriesInjectable = getInjectable({
|
||||||
|
id: "filtered-categories",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const registry = di.inject(catalogCategoryRegistryInjectable);
|
||||||
|
|
||||||
|
return computed(() => [...registry.filteredItems]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default filteredCategoriesInjectable;
|
||||||
15
src/common/fs/delete-file.injectable.ts
Normal file
15
src/common/fs/delete-file.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export type DeleteFile = (filePath: string) => Promise<void>;
|
||||||
|
|
||||||
|
const deleteFileInjectable = getInjectable({
|
||||||
|
id: "delete-file",
|
||||||
|
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default deleteFileInjectable;
|
||||||
@ -8,9 +8,6 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
|||||||
export const clusterVisibilityHandler = "cluster:visibility";
|
export const clusterVisibilityHandler = "cluster:visibility";
|
||||||
export const clusterRefreshHandler = "cluster:refresh";
|
export const clusterRefreshHandler = "cluster:refresh";
|
||||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||||
export const clusterDeleteHandler = "cluster:delete";
|
|
||||||
export const clusterSetDeletingHandler = "cluster:deleting:set";
|
|
||||||
export const clusterClearDeletingHandler = "cluster:deleting:clear";
|
|
||||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||||
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||||
export const clusterStates = "cluster:states";
|
export const clusterStates = "cluster:states";
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export type RequestFromChannel = <
|
|||||||
channel: TChannel,
|
channel: TChannel,
|
||||||
...request: TChannel["_requestSignature"] extends void
|
...request: TChannel["_requestSignature"] extends void
|
||||||
? []
|
? []
|
||||||
: [TChannel["_requestSignature"]]
|
: [SetRequired<TChannel, "_requestSignature">["_requestSignature"]]
|
||||||
) => Promise<SetRequired<TChannel, "_responseSignature">["_responseSignature"]>;
|
) => Promise<SetRequired<TChannel, "_responseSignature">["_responseSignature"]>;
|
||||||
|
|
||||||
export const requestFromChannelInjectionToken =
|
export const requestFromChannelInjectionToken =
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
|
||||||
|
export type ClearClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
||||||
|
|
||||||
|
const clearClusterAsDeletingChannelInjectable = getInjectable({
|
||||||
|
id: "clear-cluster-as-deleting-channel",
|
||||||
|
instantiate: (): ClearClusterAsDeletingChannel => ({
|
||||||
|
id: "clear-cluster-as-deleting",
|
||||||
|
}),
|
||||||
|
injectionToken: requestChannelInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearClusterAsDeletingChannelInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
|
||||||
|
export type DeleteClusterChannel = RequestChannel<ClusterId, void>;
|
||||||
|
|
||||||
|
const deleteClusterChannelInjectable = getInjectable({
|
||||||
|
id: "delete-cluster-channel",
|
||||||
|
instantiate: (): DeleteClusterChannel => ({
|
||||||
|
id: "delete-cluster",
|
||||||
|
}),
|
||||||
|
injectionToken: requestChannelInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default deleteClusterChannelInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||||
|
|
||||||
|
export type SetClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
||||||
|
|
||||||
|
const setClusterAsDeletingChannelInjectable = getInjectable({
|
||||||
|
id: "set-cluster-as-deleting-channel",
|
||||||
|
instantiate: (): SetClusterAsDeletingChannel => ({
|
||||||
|
id: "set-cluster-as-deleting",
|
||||||
|
}),
|
||||||
|
injectionToken: requestChannelInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setClusterAsDeletingChannelInjectable;
|
||||||
@ -0,0 +1,283 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import type { CreateCluster } from "../../../common/cluster/create-cluster-injection-token";
|
||||||
|
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||||
|
import createContextHandlerInjectable from "../../../main/context-handler/create-context-handler.injectable";
|
||||||
|
import createKubeconfigManagerInjectable from "../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||||
|
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
|
||||||
|
import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injectable";
|
||||||
|
import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable";
|
||||||
|
import openDeleteClusterDialogInjectable, { type OpenDeleteClusterDialog } from "../../../renderer/components/delete-cluster-dialog/open.injectable";
|
||||||
|
import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
|
||||||
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
|
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
|
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import path from "path";
|
||||||
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
|
|
||||||
|
const currentClusterServerUrl = "https://localhost";
|
||||||
|
const nonCurrentClusterServerUrl = "http://localhost";
|
||||||
|
const multiClusterConfig = `
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: ${currentClusterServerUrl}
|
||||||
|
name: some-current-context-cluster
|
||||||
|
- cluster:
|
||||||
|
server: ${nonCurrentClusterServerUrl}
|
||||||
|
name: some-non-current-context-cluster
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: some-current-context-cluster
|
||||||
|
user: some-user
|
||||||
|
name: some-current-context
|
||||||
|
- context:
|
||||||
|
cluster: some-non-current-context-cluster
|
||||||
|
user: some-user
|
||||||
|
name: some-non-current-context
|
||||||
|
current-context: some-current-context
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: some-user
|
||||||
|
user:
|
||||||
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
|
`;
|
||||||
|
|
||||||
|
const singleClusterServerUrl = "http://localhost";
|
||||||
|
const singleClusterConfig = `
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: ${singleClusterServerUrl}
|
||||||
|
name: some-cluster
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: some-cluster
|
||||||
|
user: some-user
|
||||||
|
name: some-context
|
||||||
|
current-context: some-context
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: some-user
|
||||||
|
user:
|
||||||
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe("Deleting a cluster", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||||
|
let createCluster: CreateCluster;
|
||||||
|
let rendered: RenderResult;
|
||||||
|
let config: KubeConfig;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
config = new KubeConfig();
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.beforeApplicationStart((mainDi) => {
|
||||||
|
mainDi.override(createContextHandlerInjectable, () => () => undefined as never);
|
||||||
|
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never);
|
||||||
|
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||||
|
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||||
|
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.beforeWindowStart((windowDi) => {
|
||||||
|
windowDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||||
|
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||||
|
|
||||||
|
// TODO: remove this line when all global uses of appEventBus are removed
|
||||||
|
windowDi.permitSideEffects(appEventBusInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.afterWindowStart(windowDi => {
|
||||||
|
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
|
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
|
||||||
|
|
||||||
|
navigateToCatalog();
|
||||||
|
});
|
||||||
|
|
||||||
|
rendered = await builder.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the kubeconfig has multiple clusters", () => {
|
||||||
|
let currentCluster: Cluster;
|
||||||
|
let nonCurrentCluster: Cluster;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config.loadFromString(multiClusterConfig);
|
||||||
|
|
||||||
|
currentCluster = createCluster({
|
||||||
|
id: "some-current-context-cluster",
|
||||||
|
contextName: "some-current-context",
|
||||||
|
preferences: {
|
||||||
|
clusterName: "some-current-context-cluster",
|
||||||
|
},
|
||||||
|
kubeConfigPath: "./temp-kube-config",
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: currentClusterServerUrl,
|
||||||
|
});
|
||||||
|
nonCurrentCluster = createCluster({
|
||||||
|
id: "some-non-current-context-cluster",
|
||||||
|
contextName: "some-non-current-context",
|
||||||
|
preferences: {
|
||||||
|
clusterName: "some-non-current-context-cluster",
|
||||||
|
},
|
||||||
|
kubeConfigPath: "./temp-kube-config",
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: currentClusterServerUrl,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the dialog is opened for the current cluster", () => {
|
||||||
|
// TODO: replace with actual behaviour instead of technical use
|
||||||
|
beforeEach(async () => {
|
||||||
|
openDeleteClusterDialog({
|
||||||
|
cluster: currentCluster,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
await rendered.findByTestId("delete-cluster-dialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows context switcher", () => {
|
||||||
|
expect(rendered.queryByText("Select new context...")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows warning", () => {
|
||||||
|
expect(rendered.queryByTestId("current-context-warning")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the dialog is opened for not the current cluster", () => {
|
||||||
|
// TODO: replace with actual behaviour instead of technical use
|
||||||
|
beforeEach(async () => {
|
||||||
|
openDeleteClusterDialog({
|
||||||
|
cluster: nonCurrentCluster,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
await rendered.findByTestId("delete-cluster-dialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows warning", () => {
|
||||||
|
expect(rendered.queryByTestId("kubeconfig-change-warning")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show context switcher", () => {
|
||||||
|
expect(rendered.queryByText("Select new context...")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when context switching checkbox is clicked", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
rendered.getByTestId("delete-cluster-dialog-context-switch").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows context switcher", () => {
|
||||||
|
expect(rendered.queryByText("Select new context...")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when an internal kubeconfig cluster is used", () => {
|
||||||
|
let currentCluster: Cluster;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config.loadFromString(singleClusterConfig);
|
||||||
|
|
||||||
|
const directoryForKubeConfigs = builder.applicationWindow.only.di.inject(directoryForKubeConfigsInjectable);
|
||||||
|
|
||||||
|
currentCluster = createCluster({
|
||||||
|
id: "some-cluster",
|
||||||
|
contextName: "some-context",
|
||||||
|
preferences: {
|
||||||
|
clusterName: "some-cluster",
|
||||||
|
},
|
||||||
|
kubeConfigPath: path.join(directoryForKubeConfigs, "some-cluster.json"),
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: singleClusterServerUrl,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the dialog is opened", () => {
|
||||||
|
// TODO: replace with actual behaviour instead of technical use
|
||||||
|
beforeEach(async () => {
|
||||||
|
openDeleteClusterDialog({
|
||||||
|
cluster: currentCluster,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
await rendered.findByTestId("delete-cluster-dialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows warning", () => {
|
||||||
|
expect(rendered.queryByTestId("internal-kubeconfig-warning")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the kubeconfig has only one cluster", () => {
|
||||||
|
let currentCluster: Cluster;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config.loadFromString(singleClusterConfig);
|
||||||
|
|
||||||
|
currentCluster = createCluster({
|
||||||
|
id: "some-cluster",
|
||||||
|
contextName: "some-context",
|
||||||
|
preferences: {
|
||||||
|
clusterName: "some-cluster",
|
||||||
|
},
|
||||||
|
kubeConfigPath: "./temp-kube-config",
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: singleClusterServerUrl,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the dialog is opened", () => {
|
||||||
|
// TODO: replace with actual behaviour instead of technical use
|
||||||
|
beforeEach(async () => {
|
||||||
|
openDeleteClusterDialog({
|
||||||
|
cluster: currentCluster,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
await rendered.findByTestId("delete-cluster-dialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows warning", () => {
|
||||||
|
expect(rendered.queryByTestId("no-more-contexts-warning")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||||
|
import clustersThatAreBeingDeletedInjectable from "../../../../main/clusters-that-are-being-deleted.injectable";
|
||||||
|
import clearClusterAsDeletingChannelInjectable from "../common/clear-as-deleting-channel.injectable";
|
||||||
|
|
||||||
|
const clearClusterAsDeletingChannelHandlerInjectable = getInjectable({
|
||||||
|
id: "clear-cluster-as-deleting-channel-handler",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
channel: di.inject(clearClusterAsDeletingChannelInjectable),
|
||||||
|
handler: (clusterId) => clustersThatAreBeingDeleted.delete(clusterId),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: requestChannelListenerInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearClusterAsDeletingChannelHandlerInjectable;
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
|
||||||
|
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||||
|
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||||
|
import deleteFileInjectable from "../../../../common/fs/delete-file.injectable";
|
||||||
|
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||||
|
import { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||||
|
import deleteClusterChannelInjectable from "../common/delete-channel.injectable";
|
||||||
|
|
||||||
|
const deleteClusterChannelHandlerInjectable = getInjectable({
|
||||||
|
id: "delete-cluster-channel-handler",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const appEventBus = di.inject(appEventBusInjectable);
|
||||||
|
const clusterStore = di.inject(clusterStoreInjectable);
|
||||||
|
const clusterFrames = di.inject(clusterFramesInjectable);
|
||||||
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
const directoryForLensLocalStorage = di.inject(directoryForLensLocalStorageInjectable);
|
||||||
|
const deleteFile = di.inject(deleteFileInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
channel: di.inject(deleteClusterChannelInjectable),
|
||||||
|
handler: async (clusterId) =>{
|
||||||
|
appEventBus.emit({ name: "cluster", action: "remove" });
|
||||||
|
|
||||||
|
const cluster = clusterStore.getById(clusterId);
|
||||||
|
|
||||||
|
if (!cluster) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.disconnect();
|
||||||
|
clusterFrames.delete(cluster.id);
|
||||||
|
|
||||||
|
// Remove from the cluster store as well, this should clear any old settings
|
||||||
|
clusterStore.clusters.delete(cluster.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// remove the local storage file
|
||||||
|
const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||||
|
|
||||||
|
await deleteFile(localStorageFilePath);
|
||||||
|
} catch {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: requestChannelListenerInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default deleteClusterChannelHandlerInjectable;
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||||
|
import clustersThatAreBeingDeletedInjectable from "../../../../main/clusters-that-are-being-deleted.injectable";
|
||||||
|
import setClusterAsDeletingChannelInjectable from "../common/set-as-deleting-channel.injectable";
|
||||||
|
|
||||||
|
const setClusterAsDeletingChannelHandlerInjectable = getInjectable({
|
||||||
|
id: "set-cluster-as-deleting-channel-handler",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
channel: di.inject(setClusterAsDeletingChannelInjectable),
|
||||||
|
handler: (clusterId) => clustersThatAreBeingDeleted.add(clusterId),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: requestChannelListenerInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setClusterAsDeletingChannelHandlerInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||||
|
import clearClusterAsDeletingChannelInjectable from "../common/clear-as-deleting-channel.injectable";
|
||||||
|
|
||||||
|
export type RequestClearClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||||
|
|
||||||
|
const requestClearClusterAsDeletingInjectable = getInjectable({
|
||||||
|
id: "request-clear-cluster-as-deleting",
|
||||||
|
instantiate: (di): RequestClearClusterAsDeleting => {
|
||||||
|
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||||
|
const clearClusterAsDeletingChannel = di.inject(clearClusterAsDeletingChannelInjectable);
|
||||||
|
|
||||||
|
return (clusterId) => requestChannel(clearClusterAsDeletingChannel, clusterId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestClearClusterAsDeletingInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||||
|
import deleteClusterChannelInjectable from "../common/delete-channel.injectable";
|
||||||
|
|
||||||
|
export type RequestDeleteCluster = (clusterId: ClusterId) => Promise<void>;
|
||||||
|
|
||||||
|
const requestDeleteClusterInjectable = getInjectable({
|
||||||
|
id: "request-delete-cluster",
|
||||||
|
instantiate: (di): RequestDeleteCluster => {
|
||||||
|
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||||
|
const deleteClusterChannel = di.inject(deleteClusterChannelInjectable);
|
||||||
|
|
||||||
|
return (clusterId) => requestChannel(deleteClusterChannel, clusterId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestDeleteClusterInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||||
|
import setClusterAsDeletingChannelInjectable from "../common/set-as-deleting-channel.injectable";
|
||||||
|
|
||||||
|
export type RequestSetClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||||
|
|
||||||
|
const requestSetClusterAsDeletingInjectable = getInjectable({
|
||||||
|
id: "request-set-cluster-as-deleting",
|
||||||
|
instantiate: (di): RequestSetClusterAsDeleting => {
|
||||||
|
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||||
|
const setClusterAsDeletingChannel = di.inject(setClusterAsDeletingChannelInjectable);
|
||||||
|
|
||||||
|
return (clusterId) => requestChannel(setClusterAsDeletingChannel, clusterId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestSetClusterAsDeletingInjectable;
|
||||||
@ -6916,7 +6916,7 @@ exports[`add custom helm repository in preferences when navigating to preference
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter leave"
|
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -7502,7 +7502,7 @@ exports[`add custom helm repository in preferences when navigating to preference
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter leave"
|
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import isPathInjectable from "../../renderer/components/input/validators/is-path
|
|||||||
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
||||||
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
||||||
import type { AsyncResult } from "../../common/utils/async-result";
|
import type { AsyncResult } from "../../common/utils/async-result";
|
||||||
|
import { useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
|
||||||
describe("add custom helm repository in preferences", () => {
|
describe("add custom helm repository in preferences", () => {
|
||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
@ -33,6 +34,8 @@ describe("add custom helm repository in preferences", () => {
|
|||||||
|
|
||||||
builder = getApplicationBuilder();
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
useFakeTime("2021-01-01 12:00:00");
|
||||||
|
|
||||||
execFileMock = asyncFn();
|
execFileMock = asyncFn();
|
||||||
getActiveHelmRepositoriesMock = asyncFn();
|
getActiveHelmRepositoriesMock = asyncFn();
|
||||||
showSuccessNotificationMock = jest.fn();
|
showSuccessNotificationMock = jest.fn();
|
||||||
|
|||||||
14
src/main/clusters-that-are-being-deleted.injectable.ts
Normal file
14
src/main/clusters-that-are-being-deleted.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import type { ClusterId } from "../common/cluster-types";
|
||||||
|
|
||||||
|
const clustersThatAreBeingDeletedInjectable = getInjectable({
|
||||||
|
id: "clusters-that-are-being-deleted",
|
||||||
|
instantiate: () => observable.set<ClusterId>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clustersThatAreBeingDeletedInjectable;
|
||||||
@ -3,12 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
|
||||||
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
import clusterManagerInjectable from "../../../cluster/manager.injectable";
|
import clusterManagerInjectable from "../../../cluster/manager.injectable";
|
||||||
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
|
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
|
||||||
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
|
|
||||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||||
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||||
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
|
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
|
||||||
@ -21,14 +19,8 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
const directoryForLensLocalStorage = di.inject(
|
|
||||||
directoryForLensLocalStorageInjectable,
|
|
||||||
);
|
|
||||||
|
|
||||||
const clusterManager = di.inject(clusterManagerInjectable);
|
const clusterManager = di.inject(clusterManagerInjectable);
|
||||||
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
|
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
|
||||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
const clusterStore = di.inject(clusterStoreInjectable);
|
const clusterStore = di.inject(clusterStoreInjectable);
|
||||||
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
||||||
@ -42,8 +34,6 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
|||||||
|
|
||||||
setupIpcMainHandlers({
|
setupIpcMainHandlers({
|
||||||
applicationMenuItems,
|
applicationMenuItems,
|
||||||
getAbsolutePath,
|
|
||||||
directoryForLensLocalStorage,
|
|
||||||
clusterManager,
|
clusterManager,
|
||||||
catalogEntityRegistry,
|
catalogEntityRegistry,
|
||||||
clusterStore,
|
clusterStore,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import type { IpcMainInvokeEvent } from "electron";
|
import type { IpcMainInvokeEvent } from "electron";
|
||||||
import { BrowserWindow, Menu } from "electron";
|
import { BrowserWindow, Menu } from "electron";
|
||||||
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
||||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler, clusterSetDeletingHandler, clusterClearDeletingHandler } from "../../../../common/ipc/cluster";
|
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
||||||
import { appEventBus } from "../../../../common/app-event-bus/event-bus";
|
import { appEventBus } from "../../../../common/app-event-bus/event-bus";
|
||||||
@ -14,9 +14,7 @@ import type { CatalogEntityRegistry } from "../../../catalog";
|
|||||||
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
||||||
import type { ClusterManager } from "../../../cluster/manager";
|
import type { ClusterManager } from "../../../cluster/manager";
|
||||||
import { ResourceApplier } from "../../../resource-applier";
|
import { ResourceApplier } from "../../../resource-applier";
|
||||||
import { remove } from "fs-extra";
|
import type { IComputedValue } from "mobx";
|
||||||
import type { IComputedValue, ObservableSet } from "mobx";
|
|
||||||
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
|
|
||||||
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
||||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
||||||
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
|
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
|
||||||
@ -26,27 +24,21 @@ import type { Theme } from "../../../theme/operating-system-theme-state.injectab
|
|||||||
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
|
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
directoryForLensLocalStorage: string;
|
|
||||||
getAbsolutePath: GetAbsolutePath;
|
|
||||||
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
|
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
|
||||||
clusterManager: ClusterManager;
|
clusterManager: ClusterManager;
|
||||||
catalogEntityRegistry: CatalogEntityRegistry;
|
catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
clusterStore: ClusterStore;
|
clusterStore: ClusterStore;
|
||||||
operatingSystemTheme: IComputedValue<Theme>;
|
operatingSystemTheme: IComputedValue<Theme>;
|
||||||
askUserForFilePaths: AskUserForFilePaths;
|
askUserForFilePaths: AskUserForFilePaths;
|
||||||
clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setupIpcMainHandlers = ({
|
export const setupIpcMainHandlers = ({
|
||||||
applicationMenuItems,
|
applicationMenuItems,
|
||||||
directoryForLensLocalStorage,
|
|
||||||
getAbsolutePath,
|
|
||||||
clusterManager,
|
clusterManager,
|
||||||
catalogEntityRegistry,
|
catalogEntityRegistry,
|
||||||
clusterStore,
|
clusterStore,
|
||||||
operatingSystemTheme,
|
operatingSystemTheme,
|
||||||
askUserForFilePaths,
|
askUserForFilePaths,
|
||||||
clustersThatAreBeingDeleted,
|
|
||||||
}: Dependencies) => {
|
}: Dependencies) => {
|
||||||
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||||
return ClusterStore.getInstance()
|
return ClusterStore.getInstance()
|
||||||
@ -85,40 +77,6 @@ export const setupIpcMainHandlers = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMainHandle(clusterDeleteHandler, async (event, clusterId: ClusterId) => {
|
|
||||||
appEventBus.emit({ name: "cluster", action: "remove" });
|
|
||||||
|
|
||||||
const clusterStore = ClusterStore.getInstance();
|
|
||||||
const cluster = clusterStore.getById(clusterId);
|
|
||||||
|
|
||||||
if (!cluster) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster.disconnect();
|
|
||||||
clusterFrameMap.delete(cluster.id);
|
|
||||||
|
|
||||||
// Remove from the cluster store as well, this should clear any old settings
|
|
||||||
clusterStore.clusters.delete(cluster.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// remove the local storage file
|
|
||||||
const localStorageFilePath = getAbsolutePath(directoryForLensLocalStorage, `${cluster.id}.json`);
|
|
||||||
|
|
||||||
await remove(localStorageFilePath);
|
|
||||||
} catch {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => {
|
|
||||||
clustersThatAreBeingDeleted.add(clusterId);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMainHandle(clusterClearDeletingHandler, (event, clusterId: string) => {
|
|
||||||
clustersThatAreBeingDeleted.delete(clusterId);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||||
appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" });
|
appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" });
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { HelmChartManager } from "../helm-chart-manager";
|
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||||
|
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||||
|
|
||||||
const getHelmChartVersionsInjectable = getInjectable({
|
const getHelmChartVersionsInjectable = getInjectable({
|
||||||
@ -11,6 +12,7 @@ const getHelmChartVersionsInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||||
|
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||||
|
|
||||||
return async (repoName: string, chartName: string) => {
|
return async (repoName: string, chartName: string) => {
|
||||||
const repo = await getActiveHelmRepository(repoName);
|
const repo = await getActiveHelmRepository(repoName);
|
||||||
@ -19,7 +21,7 @@ const getHelmChartVersionsInjectable = getInjectable({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HelmChartManager.forRepo(repo).chartVersions(chartName);
|
return getChartManager(repo).chartVersions(chartName);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -9,23 +9,21 @@ import styles from "./catalog-menu.module.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import type { TreeItemProps } from "@material-ui/lab";
|
import type { TreeItemProps } from "@material-ui/lab";
|
||||||
import { TreeItem, TreeView } from "@material-ui/lab";
|
import { TreeItem, TreeView } from "@material-ui/lab";
|
||||||
import { catalogCategoryRegistry } from "../../api/catalog-category-registry";
|
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { StylesProvider } from "@material-ui/core";
|
import { StylesProvider } from "@material-ui/core";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import type { CatalogCategory } from "../../api/catalog-entity";
|
import type { CatalogCategory } from "../../api/catalog-entity";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { CatalogCategoryLabel } from "./catalog-category-label";
|
import { CatalogCategoryLabel } from "./catalog-category-label";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import filteredCategoriesInjectable from "../../../common/catalog/filtered-categories.injectable";
|
||||||
|
|
||||||
export interface CatalogMenuProps {
|
export interface CatalogMenuProps {
|
||||||
activeTab: string | undefined;
|
activeTab: string | undefined;
|
||||||
onItemClick: (id: string) => void;
|
onItemClick: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCategories() {
|
|
||||||
return catalogCategoryRegistry.filteredItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCategoryIcon(category: CatalogCategory) {
|
function getCategoryIcon(category: CatalogCategory) {
|
||||||
const { icon } = category.metadata ?? {};
|
const { icon } = category.metadata ?? {};
|
||||||
|
|
||||||
@ -44,44 +42,56 @@ function Item(props: TreeItemProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CatalogMenu = observer((props: CatalogMenuProps) => {
|
interface Dependencies {
|
||||||
return (
|
filteredCategories: IComputedValue<CatalogCategory[]>;
|
||||||
// Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4
|
}
|
||||||
<StylesProvider injectFirst>
|
|
||||||
<div className="flex flex-col w-full">
|
const NonInjectedCatalogMenu = observer(({
|
||||||
<div className={styles.catalog}>Catalog</div>
|
activeTab,
|
||||||
<TreeView
|
filteredCategories,
|
||||||
defaultExpanded={["catalog"]}
|
onItemClick,
|
||||||
defaultCollapseIcon={<Icon material="expand_more"/>}
|
}: CatalogMenuProps & Dependencies) => (
|
||||||
defaultExpandIcon={<Icon material="chevron_right" />}
|
// Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4
|
||||||
selected={props.activeTab || "browse"}
|
<StylesProvider injectFirst>
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
<div className={styles.catalog}>Catalog</div>
|
||||||
|
<TreeView
|
||||||
|
defaultExpanded={["catalog"]}
|
||||||
|
defaultCollapseIcon={<Icon material="expand_more" />}
|
||||||
|
defaultExpandIcon={<Icon material="chevron_right" />}
|
||||||
|
selected={activeTab || "browse"}
|
||||||
|
>
|
||||||
|
<Item
|
||||||
|
nodeId="browse"
|
||||||
|
label="Browse"
|
||||||
|
data-testid="*-tab"
|
||||||
|
onClick={() => onItemClick("*")} />
|
||||||
|
<Item
|
||||||
|
nodeId="catalog"
|
||||||
|
label={<div className={styles.parent}>Categories</div>}
|
||||||
|
className={cssNames(styles.bordered)}
|
||||||
>
|
>
|
||||||
<Item
|
{
|
||||||
nodeId="browse"
|
filteredCategories.get()
|
||||||
label="Browse"
|
.map(category => (
|
||||||
data-testid="*-tab"
|
|
||||||
onClick={() => props.onItemClick("*")}
|
|
||||||
/>
|
|
||||||
<Item
|
|
||||||
nodeId="catalog"
|
|
||||||
label={<div className={styles.parent}>Categories</div>}
|
|
||||||
className={cssNames(styles.bordered)}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
getCategories().map(category => (
|
|
||||||
<Item
|
<Item
|
||||||
icon={getCategoryIcon(category)}
|
icon={getCategoryIcon(category)}
|
||||||
key={category.getId()}
|
key={category.getId()}
|
||||||
nodeId={category.getId()}
|
nodeId={category.getId()}
|
||||||
label={<CatalogCategoryLabel category={category}/>}
|
label={<CatalogCategoryLabel category={category} />}
|
||||||
data-testid={`${category.getId()}-tab`}
|
data-testid={`${category.getId()}-tab`}
|
||||||
onClick={() => props.onItemClick(category.getId())}
|
onClick={() => onItemClick(category.getId())} />
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</Item>
|
</Item>
|
||||||
</TreeView>
|
</TreeView>
|
||||||
</div>
|
</div>
|
||||||
</StylesProvider>
|
</StylesProvider>
|
||||||
);
|
));
|
||||||
|
|
||||||
|
export const CatalogMenu = withInjectables<Dependencies, CatalogMenuProps>(NonInjectedCatalogMenu, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
filteredCategories: di.inject(filteredCategoriesInjectable),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import "./animate.scss";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { cssNames, noop } from "../../utils";
|
import { cssNames, noop } from "../../utils";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { RequestAnimationFrame } from "./request-animation-frame.injectable";
|
||||||
import requestAnimationFrameInjectable from "./request-animation-frame.injectable";
|
import requestAnimationFrameInjectable from "./request-animation-frame.injectable";
|
||||||
import defaultEnterDurationForAnimatedInjectable from "./default-enter-duration.injectable";
|
import defaultEnterDurationForAnimatedInjectable from "./default-enter-duration.injectable";
|
||||||
import defaultLeaveDurationForAnimatedInjectable from "./default-leave-duration.injectable";
|
import defaultLeaveDurationForAnimatedInjectable from "./default-leave-duration.injectable";
|
||||||
@ -24,7 +25,7 @@ export interface AnimateProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
requestAnimationFrame: (callback: () => void) => void;
|
requestAnimationFrame: RequestAnimationFrame;
|
||||||
defaultEnterDuration: number;
|
defaultEnterDuration: number;
|
||||||
defaultLeaveDuration: number;
|
defaultLeaveDuration: number;
|
||||||
}
|
}
|
||||||
@ -46,21 +47,25 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
|||||||
onLeave: onLeaveHandler = noop<[]>,
|
onLeave: onLeaveHandler = noop<[]>,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState(enter);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [showClassNameEnter, setShowClassNameEnter] = useState(false);
|
const [showClassNameEnter, setShowClassNameEnter] = useState(false);
|
||||||
const [showClassNameLeave, setShowClassNameLeave] = useState(false);
|
const [showClassNameLeave, setShowClassNameLeave] = useState(false);
|
||||||
|
|
||||||
const contentElem = React.Children.only(children) as React.ReactElement<React.HTMLAttributes<any>>;
|
const contentElem = React.Children.only(children) as React.ReactElement<React.HTMLAttributes<any>>;
|
||||||
const onEnter = () => {
|
const classNames = cssNames("Animate", name, contentElem.props.className, {
|
||||||
setIsVisible(true);
|
enter: showClassNameEnter,
|
||||||
|
leave: showClassNameLeave,
|
||||||
|
});
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
useEffect(() => {
|
||||||
setShowClassNameEnter(true);
|
if (enter) {
|
||||||
onEnterHandler();
|
setIsVisible(true);
|
||||||
});
|
|
||||||
};
|
requestAnimationFrame(() => {
|
||||||
const onLeave = () => {
|
setShowClassNameEnter(true);
|
||||||
if (isVisible) {
|
onEnterHandler();
|
||||||
|
});
|
||||||
|
} else if (isVisible) {
|
||||||
setShowClassNameLeave(true);
|
setShowClassNameLeave(true);
|
||||||
onLeaveHandler();
|
onLeaveHandler();
|
||||||
|
|
||||||
@ -71,16 +76,7 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
|||||||
setShowClassNameLeave(false);
|
setShowClassNameLeave(false);
|
||||||
}, leaveDuration);
|
}, leaveDuration);
|
||||||
}
|
}
|
||||||
};
|
}, [enter]);
|
||||||
const toggle = (entering: boolean) => {
|
|
||||||
if (entering) {
|
|
||||||
onEnter();
|
|
||||||
} else {
|
|
||||||
onLeave();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => toggle(enter), [enter]);
|
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
return null;
|
return null;
|
||||||
@ -92,10 +88,7 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
|||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
|
|
||||||
return React.cloneElement(contentElem, {
|
return React.cloneElement(contentElem, {
|
||||||
className: cssNames("Animate", name, contentElem.props.className, {
|
className: classNames,
|
||||||
enter: showClassNameEnter,
|
|
||||||
leave: showClassNameLeave,
|
|
||||||
}),
|
|
||||||
children: contentElem.props.children,
|
children: contentElem.props.children,
|
||||||
style: {
|
style: {
|
||||||
...contentElem.props.style,
|
...contentElem.props.style,
|
||||||
@ -112,5 +105,3 @@ export const Animate = withInjectables<Dependencies, AnimateProps>(NonInjectedAn
|
|||||||
defaultLeaveDuration: di.inject(defaultLeaveDurationForAnimatedInjectable),
|
defaultLeaveDuration: di.inject(defaultLeaveDurationForAnimatedInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,275 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
|
||||||
import { fireEvent } from "@testing-library/react";
|
|
||||||
import type { RenderResult } from "@testing-library/react";
|
|
||||||
import * as selectEvent from "react-select-event";
|
|
||||||
import type { CreateCluster } from "../../../../common/cluster/create-cluster-injection-token";
|
|
||||||
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
|
|
||||||
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
|
|
||||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
|
||||||
import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
|
|
||||||
import type { ApplicationBuilder } from "../../test-utils/get-application-builder";
|
|
||||||
import { getApplicationBuilder } from "../../test-utils/get-application-builder";
|
|
||||||
import normalizedPlatformInjectable from "../../../../common/vars/normalized-platform.injectable";
|
|
||||||
import kubectlBinaryNameInjectable from "../../../../main/kubectl/binary-name.injectable";
|
|
||||||
import kubectlDownloadingNormalizedArchInjectable from "../../../../main/kubectl/normalized-arch.injectable";
|
|
||||||
import type { OpenDeleteClusterDialog } from "../open.injectable";
|
|
||||||
import openDeleteClusterDialogInjectable from "../open.injectable";
|
|
||||||
|
|
||||||
const currentClusterServerUrl = "https://localhost";
|
|
||||||
const nonCurrentClusterServerUrl = "http://localhost";
|
|
||||||
const multiClusterConfig = `
|
|
||||||
apiVersion: v1
|
|
||||||
clusters:
|
|
||||||
- cluster:
|
|
||||||
server: ${currentClusterServerUrl}
|
|
||||||
name: some-current-context-cluster
|
|
||||||
- cluster:
|
|
||||||
server: ${nonCurrentClusterServerUrl}
|
|
||||||
name: some-non-current-context-cluster
|
|
||||||
contexts:
|
|
||||||
- context:
|
|
||||||
cluster: some-current-context-cluster
|
|
||||||
user: some-user
|
|
||||||
name: some-current-context
|
|
||||||
- context:
|
|
||||||
cluster: some-non-current-context-cluster
|
|
||||||
user: some-user
|
|
||||||
name: some-non-current-context
|
|
||||||
current-context: some-current-context
|
|
||||||
kind: Config
|
|
||||||
preferences: {}
|
|
||||||
users:
|
|
||||||
- name: some-user
|
|
||||||
user:
|
|
||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
|
||||||
`;
|
|
||||||
|
|
||||||
const singleClusterServerUrl = "http://localhost";
|
|
||||||
const singleClusterConfig = `
|
|
||||||
apiVersion: v1
|
|
||||||
clusters:
|
|
||||||
- cluster:
|
|
||||||
server: ${singleClusterServerUrl}
|
|
||||||
name: some-cluster
|
|
||||||
contexts:
|
|
||||||
- context:
|
|
||||||
cluster: some-cluster
|
|
||||||
user: some-user
|
|
||||||
name: some-context
|
|
||||||
current-context: some-context
|
|
||||||
kind: Config
|
|
||||||
preferences: {}
|
|
||||||
users:
|
|
||||||
- name: some-user
|
|
||||||
user:
|
|
||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
|
||||||
`;
|
|
||||||
|
|
||||||
describe("<DeleteClusterDialog />", () => {
|
|
||||||
let builder: ApplicationBuilder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
builder = getApplicationBuilder();
|
|
||||||
|
|
||||||
builder.beforeApplicationStart((mainDi) => {
|
|
||||||
mainDi.override(createContextHandlerInjectable, () => () => undefined as never);
|
|
||||||
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never);
|
|
||||||
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
|
||||||
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
|
||||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.beforeWindowStart((windowDi) => {
|
|
||||||
windowDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows context switcher when deleting current cluster", async () => {
|
|
||||||
const config = new KubeConfig();
|
|
||||||
|
|
||||||
config.loadFromString(multiClusterConfig);
|
|
||||||
|
|
||||||
const rendered = await builder.render();
|
|
||||||
|
|
||||||
const windowDi = builder.applicationWindow.only.di;
|
|
||||||
|
|
||||||
const createCluster = windowDi.inject(createClusterInjectionToken);
|
|
||||||
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-current-context-cluster",
|
|
||||||
contextName: "some-current-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "some-current-context-cluster",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: currentClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
const { getByText } = rendered;
|
|
||||||
|
|
||||||
const menu = getByText("Select new context...");
|
|
||||||
|
|
||||||
expect(menu).toBeInTheDocument();
|
|
||||||
selectEvent.openMenu(menu);
|
|
||||||
|
|
||||||
expect(getByText("some-current-context")).toBeInTheDocument();
|
|
||||||
expect(getByText("some-non-current-context")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Kubeconfig with different clusters", () => {
|
|
||||||
let rendered: RenderResult;
|
|
||||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
|
||||||
let createCluster: CreateCluster;
|
|
||||||
let config: KubeConfig;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
config = new KubeConfig();
|
|
||||||
|
|
||||||
config.loadFromString(multiClusterConfig);
|
|
||||||
|
|
||||||
rendered = await builder.render();
|
|
||||||
|
|
||||||
const windowDi = builder.applicationWindow.only.di;
|
|
||||||
|
|
||||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
|
||||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
|
||||||
expect(rendered.container).toBeInstanceOf(HTMLElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows warning when deleting non-current-context cluster", () => {
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-non-current-context-cluster",
|
|
||||||
contextName: "some-non-current-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "minikube",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: nonCurrentClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
const message = "The contents of kubeconfig file will be changed!";
|
|
||||||
|
|
||||||
expect(rendered.getByText(message)).toBeInstanceOf(HTMLElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows warning when deleting current-context cluster", () => {
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-current-context-cluster",
|
|
||||||
contextName: "some-current-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "some-current-context-cluster",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: currentClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
expect(rendered.getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows context switcher after checkbox click", () => {
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-current-context-cluster",
|
|
||||||
contextName: "some-current-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "some-current-context-cluster",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: currentClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
const { getByText, getByTestId } = rendered;
|
|
||||||
const link = getByTestId("context-switch");
|
|
||||||
|
|
||||||
expect(link).toBeInstanceOf(HTMLElement);
|
|
||||||
fireEvent.click(link);
|
|
||||||
|
|
||||||
const menu = getByText("Select new context...");
|
|
||||||
|
|
||||||
expect(menu).toBeInTheDocument();
|
|
||||||
selectEvent.openMenu(menu);
|
|
||||||
|
|
||||||
expect(getByText("some-current-context")).toBeInTheDocument();
|
|
||||||
expect(getByText("some-non-current-context")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("given cluster in internal kubeconfig, when deleting cluster outside of current context, shows warning for internal kubeconfig cluster", () => {
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-non-current-context-cluster",
|
|
||||||
contextName: "some-non-current-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "some-non-current-context-cluster",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: nonCurrentClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
expect(rendered.getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement);
|
|
||||||
|
|
||||||
spy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Kubeconfig with single cluster", () => {
|
|
||||||
let rendered: RenderResult;
|
|
||||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
|
||||||
let createCluster: CreateCluster;
|
|
||||||
let config: KubeConfig;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
config = new KubeConfig();
|
|
||||||
|
|
||||||
config.loadFromString(singleClusterConfig);
|
|
||||||
|
|
||||||
rendered = await builder.render();
|
|
||||||
|
|
||||||
const windowDi = builder.applicationWindow.only.di;
|
|
||||||
|
|
||||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
|
||||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows warning if no other contexts left", () => {
|
|
||||||
const cluster = createCluster({
|
|
||||||
id: "some-cluster",
|
|
||||||
contextName: "some-context",
|
|
||||||
preferences: {
|
|
||||||
clusterName: "some-cluster",
|
|
||||||
},
|
|
||||||
kubeConfigPath: "./temp-kube-config",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: singleClusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
openDeleteClusterDialog({ cluster, config });
|
|
||||||
|
|
||||||
expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -17,8 +17,6 @@ const deleteClusterDialogClusterFrameChildComponentInjectable = getInjectable({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
injectionToken: clusterFrameChildComponentInjectionToken,
|
injectionToken: clusterFrameChildComponentInjectionToken,
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default deleteClusterDialogClusterFrameChildComponentInjectable;
|
export default deleteClusterDialogClusterFrameChildComponentInjectable;
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
|
||||||
import { dumpYaml } from "@kubernetes/client-node";
|
|
||||||
import fs from "fs";
|
|
||||||
import * as lockFile from "proper-lockfile";
|
|
||||||
|
|
||||||
export async function saveKubeconfig(config: KubeConfig, path: string) {
|
|
||||||
try {
|
|
||||||
const release = await lockFile.lock(path);
|
|
||||||
const contents = dumpYaml(JSON.parse(config.exportConfig()));
|
|
||||||
|
|
||||||
await fs.promises.writeFile(path, contents);
|
|
||||||
await release();
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Failed to acquire lock file.\n${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||||
|
import saveKubeconfigInjectable from "./save-kubeconfig.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(saveKubeconfigInjectable, () => async () => {
|
||||||
|
throw new Error("tried to save a mondified kubeconfig without override");
|
||||||
|
});
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { dumpYaml } from "@kubernetes/client-node";
|
||||||
|
import * as lockFile from "proper-lockfile";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import writeFileInjectable from "../../../common/fs/write-file.injectable";
|
||||||
|
|
||||||
|
export type SaveKubeconfig = (config: KubeConfig, path: string) => Promise<void>;
|
||||||
|
|
||||||
|
const saveKubeconfigInjectable = getInjectable({
|
||||||
|
id: "save-kubeconfig",
|
||||||
|
instantiate: (di): SaveKubeconfig => {
|
||||||
|
const writeFile = di.inject(writeFileInjectable);
|
||||||
|
|
||||||
|
return async (config, filePath) => {
|
||||||
|
const release = await lockFile.lock(filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contents = dumpYaml(JSON.parse(config.exportConfig()));
|
||||||
|
|
||||||
|
await writeFile(filePath, contents);
|
||||||
|
} finally {
|
||||||
|
await release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default saveKubeconfigInjectable;
|
||||||
|
|
||||||
@ -10,22 +10,34 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { saveKubeconfig } from "./save-config";
|
import type { ShowNotification } from "../notifications";
|
||||||
import { Notifications } from "../notifications";
|
|
||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc";
|
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
import type { HotbarStore } from "../../../common/hotbars/store";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||||
import type { DeleteClusterDialogState } from "./state.injectable";
|
import type { DeleteClusterDialogState } from "./state.injectable";
|
||||||
import deleteClusterDialogStateInjectable from "./state.injectable";
|
import deleteClusterDialogStateInjectable from "./state.injectable";
|
||||||
|
import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
||||||
|
import requestSetClusterAsDeletingInjectable from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
||||||
|
import type { RequestClearClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-clear-as-deleting.injectable";
|
||||||
|
import requestClearClusterAsDeletingInjectable from "../../../features/cluster/delete-dialog/renderer/request-clear-as-deleting.injectable";
|
||||||
|
import type { RequestDeleteCluster } from "../../../features/cluster/delete-dialog/renderer/request-delete.injectable";
|
||||||
|
import requestDeleteClusterInjectable from "../../../features/cluster/delete-dialog/renderer/request-delete.injectable";
|
||||||
|
import type { SaveKubeconfig } from "./save-kubeconfig.injectable";
|
||||||
|
import saveKubeconfigInjectable from "./save-kubeconfig.injectable";
|
||||||
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
state: IObservableValue<DeleteClusterDialogState | undefined>;
|
state: IObservableValue<DeleteClusterDialogState | undefined>;
|
||||||
hotbarStore: HotbarStore;
|
hotbarStore: HotbarStore;
|
||||||
|
requestSetClusterAsDeleting: RequestSetClusterAsDeleting;
|
||||||
|
requestDeleteCluster: RequestDeleteCluster;
|
||||||
|
requestClearClusterAsDeleting: RequestClearClusterAsDeleting;
|
||||||
|
showErrorNotification: ShowNotification;
|
||||||
|
saveKubeconfig: SaveKubeconfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -61,18 +73,18 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
async onDelete(state: DeleteClusterDialogState) {
|
async onDelete(state: DeleteClusterDialogState) {
|
||||||
const { cluster, config } = state;
|
const { cluster, config } = state;
|
||||||
|
|
||||||
await requestSetClusterAsDeleting(cluster.id);
|
await this.props.requestSetClusterAsDeleting(cluster.id);
|
||||||
this.removeContext(state);
|
this.removeContext(state);
|
||||||
this.changeCurrentContext(state);
|
this.changeCurrentContext(state);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveKubeconfig(config, cluster.kubeConfigPath);
|
await this.props.saveKubeconfig(config, cluster.kubeConfigPath);
|
||||||
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
|
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
|
||||||
await requestDeleteCluster(cluster.id);
|
await this.props.requestDeleteCluster(cluster.id);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(`Cannot remove cluster, failed to process config file. ${error}`);
|
this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`);
|
||||||
} finally {
|
} finally {
|
||||||
await requestClearClusterAsDeleting(cluster.id);
|
await this.props.requestClearClusterAsDeleting(cluster.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onClose();
|
this.onClose();
|
||||||
@ -155,6 +167,14 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getWarningMessage({ cluster, config }: DeleteClusterDialogState) {
|
getWarningMessage({ cluster, config }: DeleteClusterDialogState) {
|
||||||
|
if (cluster.isInLocalKubeconfig()) {
|
||||||
|
return (
|
||||||
|
<p data-testid="internal-kubeconfig-warning">
|
||||||
|
Are you sure you want to delete it? It can be re-added through the copy/paste mechanism.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
||||||
|
|
||||||
if (!contexts.length) {
|
if (!contexts.length) {
|
||||||
@ -173,14 +193,6 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.isInLocalKubeconfig()) {
|
|
||||||
return (
|
|
||||||
<p data-testid="internal-kubeconfig-warning">
|
|
||||||
Are you sure you want to delete it? It can be re-added through the copy/paste mechanism.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p data-testid="kubeconfig-change-warning">The contents of kubeconfig file will be changed!</p>
|
<p data-testid="kubeconfig-change-warning">The contents of kubeconfig file will be changed!</p>
|
||||||
);
|
);
|
||||||
@ -209,7 +221,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
<hr className={styles.hr} />
|
<hr className={styles.hr} />
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
data-testid="context-switch"
|
data-testid="delete-cluster-dialog-context-switch"
|
||||||
label={(
|
label={(
|
||||||
<>
|
<>
|
||||||
<span className="font-semibold">Select current-context</span>
|
<span className="font-semibold">Select current-context</span>
|
||||||
@ -255,6 +267,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
close={this.close}
|
close={this.close}
|
||||||
onClose={this.onClose}
|
onClose={this.onClose}
|
||||||
onOpen={state && (() => this.onOpen(state))}
|
onOpen={state && (() => this.onOpen(state))}
|
||||||
|
data-testid={state ? "delete-cluster-dialog" : undefined}
|
||||||
>
|
>
|
||||||
{state && this.renderContents(state)}
|
{state && this.renderContents(state)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -266,5 +279,10 @@ export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDele
|
|||||||
getProps: (di) => ({
|
getProps: (di) => ({
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
state: di.inject(deleteClusterDialogStateInjectable),
|
state: di.inject(deleteClusterDialogStateInjectable),
|
||||||
|
requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable),
|
||||||
|
requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable),
|
||||||
|
requestDeleteCluster: di.inject(requestDeleteClusterInjectable),
|
||||||
|
saveKubeconfig: di.inject(saveKubeconfigInjectable),
|
||||||
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -146,13 +146,11 @@ class NonInjectedDialog extends React.PureComponent<DialogProps & Dependencies &
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { modal, animated, pinned, "data-testid": testId } = this.props;
|
const { modal, animated, pinned, "data-testid": testId, className } = this.props;
|
||||||
let { className } = this.props;
|
|
||||||
|
|
||||||
className = cssNames("Dialog flex center", className, { modal, pinned });
|
|
||||||
let dialog = (
|
let dialog = (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={cssNames("Dialog flex center", className, { modal, pinned })}
|
||||||
onClick={stopPropagation}
|
onClick={stopPropagation}
|
||||||
ref={this.ref}
|
ref={this.ref}
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
|
|||||||
@ -68,6 +68,7 @@ import type { FakeExtensionOptions } from "./get-extension-fake";
|
|||||||
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
|
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
|
||||||
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
||||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { overrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
|
||||||
|
|
||||||
type Callback = (di: DiContainer) => void | Promise<void>;
|
type Callback = (di: DiContainer) => void | Promise<void>;
|
||||||
|
|
||||||
@ -114,6 +115,7 @@ export interface ApplicationBuilder {
|
|||||||
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
|
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
|
||||||
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
|
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
|
||||||
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
|
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
|
||||||
|
afterWindowStart: (callback: Callback) => ApplicationBuilder;
|
||||||
|
|
||||||
startHidden: () => Promise<void>;
|
startHidden: () => Promise<void>;
|
||||||
render: () => Promise<RenderResult>;
|
render: () => Promise<RenderResult>;
|
||||||
@ -168,6 +170,11 @@ export const getApplicationBuilder = () => {
|
|||||||
|
|
||||||
const beforeApplicationStartCallbacks: Callback[] = [];
|
const beforeApplicationStartCallbacks: Callback[] = [];
|
||||||
const beforeWindowStartCallbacks: Callback[] = [];
|
const beforeWindowStartCallbacks: Callback[] = [];
|
||||||
|
const afterWindowStartCallbacks: Callback[] = [];
|
||||||
|
|
||||||
|
const fsState = new Map();
|
||||||
|
|
||||||
|
overrideFsWithFakes(mainDi, fsState);
|
||||||
|
|
||||||
let environment = environments.application;
|
let environment = environments.application;
|
||||||
|
|
||||||
@ -202,6 +209,7 @@ export const getApplicationBuilder = () => {
|
|||||||
const windowDi = getRendererDi({ doGeneralOverrides: true });
|
const windowDi = getRendererDi({ doGeneralOverrides: true });
|
||||||
|
|
||||||
overrideChannelsForWindow(windowDi, windowId);
|
overrideChannelsForWindow(windowDi, windowId);
|
||||||
|
overrideFsWithFakes(windowDi, fsState);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
windowDi.register(rendererExtensionsStateInjectable);
|
windowDi.register(rendererExtensionsStateInjectable);
|
||||||
@ -235,6 +243,10 @@ export const getApplicationBuilder = () => {
|
|||||||
|
|
||||||
await startFrame();
|
await startFrame();
|
||||||
|
|
||||||
|
for (const callback of afterWindowStartCallbacks) {
|
||||||
|
await callback(windowDi);
|
||||||
|
}
|
||||||
|
|
||||||
const history = windowDi.inject(historyInjectable);
|
const history = windowDi.inject(historyInjectable);
|
||||||
|
|
||||||
const render = renderFor(windowDi);
|
const render = renderFor(windowDi);
|
||||||
@ -646,6 +658,18 @@ export const getApplicationBuilder = () => {
|
|||||||
return builder;
|
return builder;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
afterWindowStart(callback) {
|
||||||
|
const alreadyRenderedWindows = builder.applicationWindow.getAll();
|
||||||
|
|
||||||
|
alreadyRenderedWindows.forEach((window) => {
|
||||||
|
callback(window.di);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterWindowStartCallbacks.push(callback);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
},
|
||||||
|
|
||||||
startHidden: async () => {
|
startHidden: async () => {
|
||||||
mainDi.inject(lensProxyPortInjectable).set(42);
|
mainDi.inject(lensProxyPortInjectable).set(42);
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,6 @@ import cronJobTriggerDialogClusterFrameChildComponentInjectable from "./componen
|
|||||||
import deploymentScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-deployments/scale/deployment-scale-dialog-cluster-frame-child-component.injectable";
|
import deploymentScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-deployments/scale/deployment-scale-dialog-cluster-frame-child-component.injectable";
|
||||||
import replicasetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-replicasets/scale-dialog/replicaset-scale-dialog-cluster-frame-child-component.injectable";
|
import replicasetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-replicasets/scale-dialog/replicaset-scale-dialog-cluster-frame-child-component.injectable";
|
||||||
import statefulsetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-statefulsets/scale/statefulset-scale-dialog-cluster-frame-child-component.injectable";
|
import statefulsetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-statefulsets/scale/statefulset-scale-dialog-cluster-frame-child-component.injectable";
|
||||||
import deleteClusterDialogClusterFrameChildComponentInjectable from "./components/delete-cluster-dialog/delete-cluster-dialog-cluster-frame-child-component.injectable";
|
|
||||||
import kubeObjectDetailsClusterFrameChildComponentInjectable from "./components/kube-object-details/kube-object-details-cluster-frame-child-component.injectable";
|
import kubeObjectDetailsClusterFrameChildComponentInjectable from "./components/kube-object-details/kube-object-details-cluster-frame-child-component.injectable";
|
||||||
import kubeconfigDialogClusterFrameChildComponentInjectable from "./components/kubeconfig-dialog/kubeconfig-dialog-cluster-frame-child-component.injectable";
|
import kubeconfigDialogClusterFrameChildComponentInjectable from "./components/kubeconfig-dialog/kubeconfig-dialog-cluster-frame-child-component.injectable";
|
||||||
import portForwardDialogClusterFrameChildComponentInjectable from "./port-forward/port-forward-dialog-cluster-frame-child-component.injectable";
|
import portForwardDialogClusterFrameChildComponentInjectable from "./port-forward/port-forward-dialog-cluster-frame-child-component.injectable";
|
||||||
@ -149,7 +148,6 @@ export const getDiForUnitTesting = (
|
|||||||
deploymentScaleDialogClusterFrameChildComponentInjectable,
|
deploymentScaleDialogClusterFrameChildComponentInjectable,
|
||||||
replicasetScaleDialogClusterFrameChildComponentInjectable,
|
replicasetScaleDialogClusterFrameChildComponentInjectable,
|
||||||
statefulsetScaleDialogClusterFrameChildComponentInjectable,
|
statefulsetScaleDialogClusterFrameChildComponentInjectable,
|
||||||
deleteClusterDialogClusterFrameChildComponentInjectable,
|
|
||||||
kubeObjectDetailsClusterFrameChildComponentInjectable,
|
kubeObjectDetailsClusterFrameChildComponentInjectable,
|
||||||
kubeconfigDialogClusterFrameChildComponentInjectable,
|
kubeconfigDialogClusterFrameChildComponentInjectable,
|
||||||
portForwardDialogClusterFrameChildComponentInjectable,
|
portForwardDialogClusterFrameChildComponentInjectable,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { OpenDialogOptions } from "electron";
|
import type { OpenDialogOptions } from "electron";
|
||||||
import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
import { clusterActivateHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
||||||
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
||||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
||||||
import { openFilePickingDialogChannel } from "../../common/ipc/dialog";
|
import { openFilePickingDialogChannel } from "../../common/ipc/dialog";
|
||||||
@ -60,18 +60,6 @@ export function requestClusterDisconnection(clusterId: ClusterId, force?: boolea
|
|||||||
return requestMain(clusterDisconnectHandler, clusterId, force);
|
return requestMain(clusterDisconnectHandler, clusterId, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestSetClusterAsDeleting(clusterId: ClusterId): Promise<void> {
|
|
||||||
return requestMain(clusterSetDeletingHandler, clusterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestClearClusterAsDeleting(clusterId: ClusterId): Promise<void> {
|
|
||||||
return requestMain(clusterClearDeletingHandler, clusterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestDeleteCluster(clusterId: ClusterId): Promise<void> {
|
|
||||||
return requestMain(clusterDeleteHandler, clusterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestInitialClusterStates(): Promise<{ id: string; state: ClusterState }[]> {
|
export function requestInitialClusterStates(): Promise<{ id: string; state: ClusterState }[]> {
|
||||||
return requestMain(clusterStates);
|
return requestMain(clusterStates);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,9 @@ import readFileInjectable from "../common/fs/read-file.injectable";
|
|||||||
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
||||||
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
||||||
import pathExistsInjectable from "../common/fs/path-exists.injectable";
|
import pathExistsInjectable from "../common/fs/path-exists.injectable";
|
||||||
|
import deleteFileInjectable from "../common/fs/delete-file.injectable";
|
||||||
|
|
||||||
export const overrideFsWithFakes = (di: DiContainer) => {
|
export const overrideFsWithFakes = (di: DiContainer, state = new Map()) => {
|
||||||
const state = new Map();
|
|
||||||
|
|
||||||
const readFile = readFileFor(state);
|
const readFile = readFileFor(state);
|
||||||
|
|
||||||
di.override(readFileInjectable, () => readFile);
|
di.override(readFileInjectable, () => readFile);
|
||||||
@ -25,6 +24,9 @@ export const overrideFsWithFakes = (di: DiContainer) => {
|
|||||||
di.override(pathExistsInjectable, () => (
|
di.override(pathExistsInjectable, () => (
|
||||||
(filePath: string) => Promise.resolve(state.has(filePath))
|
(filePath: string) => Promise.resolve(state.has(filePath))
|
||||||
));
|
));
|
||||||
|
di.override(deleteFileInjectable, () => async (filePath: string) => {
|
||||||
|
state.delete(filePath);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const readFileFor = (state: Map<string, string>) => (filePath: string) => {
|
const readFileFor = (state: Map<string, string>) => (filePath: string) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user