From 76048e5537cc0b8446e8e26f2112c06dedd1d886 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 19 May 2021 08:17:51 -0400 Subject: [PATCH] Ban circular dependencies - ApiManager now creates the instances of all stores - ReleaseStore is Singleton-like - Move most types and helper functions into seperate files Signed-off-by: Sebastian Malton --- .eslintrc.js | 8 + .../kube-object-event-status/src/resolver.tsx | 6 +- package.json | 1 + src/common/.gitkeep | 0 src/common/__tests__/cluster-store.test.ts | 12 +- src/common/base-store.ts | 3 +- .../catalog-entities/kubernetes-cluster.ts | 6 +- src/common/cluster-ipc.ts | 77 ----- src/common/cluster-store.ts | 131 +------- src/common/cluster-types.ts | 145 ++++++++ src/common/ipc/ipc.ts | 2 +- src/common/rbac.ts | 17 - src/common/user-store.ts | 3 +- src/common/utils/index.ts | 3 +- ...{saveToAppFiles.ts => kubeconfig-files.ts} | 28 +- src/extensions/registries/command-registry.ts | 4 + src/extensions/renderer-api/k8s-api.ts | 62 ++-- src/extensions/renderer-api/navigation.ts | 2 +- src/main/catalog-sources/kubeconfig-sync.ts | 7 +- .../cluster-detectors/cluster-id-detector.ts | 2 +- .../cluster-detectors/detector-registry.ts | 2 +- .../distribution-detector.ts | 6 +- .../cluster-detectors/last-seen-detector.ts | 2 +- .../cluster-detectors/nodes-count-detector.ts | 2 +- .../cluster-detectors/version-detector.ts | 2 +- src/main/cluster-manager.ts | 4 +- src/main/cluster.ts | 43 +-- src/main/context-handler.ts | 2 +- src/main/exit-app.ts | 12 +- src/main/helm/helm-release-manager.ts | 2 +- src/main/index.ts | 26 +- src/main/initializers/ipc-handlers.ts | 93 ++++++ src/main/proxy/lens-proxy.ts | 60 ++-- src/main/resource-applier.ts | 8 +- src/main/routes/metrics-route.ts | 4 +- src/main/window-manager.ts | 31 +- src/migrations/cluster-store/3.6.0-beta.1.ts | 7 +- src/migrations/cluster-store/snap.ts | 2 +- .../api/__tests__/api-manager.test.ts | 34 +- src/renderer/api/__tests__/kube-api.test.ts | 3 + src/renderer/api/api-manager.ts | 86 ++++- src/renderer/api/endpoints/cluster.api.ts | 6 +- .../endpoints/persistent-volume-claims.api.ts | 2 +- .../api/endpoints/poddisruptionbudget.api.ts | 2 +- .../api/endpoints/podsecuritypolicy.api.ts | 2 +- .../api/endpoints/resource-applier.api.ts | 20 +- src/renderer/api/kube-api-parse.ts | 54 --- src/renderer/api/kube-api.ts | 20 +- src/renderer/api/kube-object.ts | 6 +- src/renderer/api/kube-watch-api.ts | 37 ++- src/renderer/bootstrap.tsx | 4 +- .../components/+add-cluster/add-cluster.tsx | 3 +- .../+apps-releases/release-details.tsx | 20 +- .../+apps-releases/release-menu.tsx | 4 +- .../release-rollback-dialog.tsx | 4 +- .../+apps-releases/release.store.ts | 48 ++- .../components/+apps-releases/releases.tsx | 20 +- src/renderer/components/+apps/apps.tsx | 7 +- .../components/+cluster/cluster-issues.tsx | 23 +- .../+cluster/cluster-metric-switchers.tsx | 8 +- .../components/+cluster/cluster-metrics.tsx | 5 +- .../+cluster/cluster-overview.store.ts | 28 +- .../components/+cluster/cluster-overview.tsx | 26 +- .../+cluster/cluster-pie-charts.tsx | 9 +- .../+config-autoscalers/hpa-details.tsx | 6 +- .../+config-autoscalers/hpa.store.ts | 6 +- .../components/+config-autoscalers/hpa.tsx | 12 +- .../components/+config-autoscalers/index.ts | 1 + .../components/+config-limit-ranges/index.ts | 1 + .../limit-ranges.store.ts | 4 - .../+config-limit-ranges/limit-ranges.tsx | 11 +- .../+config-maps/config-map-details.tsx | 11 +- .../+config-maps/config-maps.store.ts | 4 - .../components/+config-maps/config-maps.tsx | 12 +- src/renderer/components/+config-maps/index.ts | 1 + .../+config-pod-disruption-budgets/index.ts | 1 + .../pod-disruption-budgets.store.ts | 8 +- .../pod-disruption-budgets.tsx | 11 +- .../+config-resource-quotas/index.ts | 1 + .../resource-quotas.store.ts | 4 - .../resource-quotas.tsx | 12 +- .../components/+config-secrets/index.ts | 1 + .../+config-secrets/secret-details.tsx | 11 +- .../+config-secrets/secrets.store.ts | 4 - .../components/+config-secrets/secrets.tsx | 12 +- src/renderer/components/+config/config.tsx | 20 +- .../components/+custom-resources/crd-list.tsx | 17 +- .../crd-resource-details.tsx | 7 +- .../+custom-resources/crd-resources.tsx | 13 +- .../components/+custom-resources/crd.store.ts | 23 +- .../+custom-resources/custom-resources.tsx | 5 +- .../components/+custom-resources/index.ts | 1 + .../components/+events/event-details.tsx | 4 +- .../components/+events/event.store.ts | 15 +- src/renderer/components/+events/events.tsx | 28 +- src/renderer/components/+events/index.ts | 1 + .../components/+events/kube-event-details.tsx | 12 +- .../components/+events/kube-event-icon.tsx | 11 +- .../+namespaces/add-namespace-dialog.tsx | 11 +- src/renderer/components/+namespaces/index.ts | 1 + .../+namespaces/namespace-details.tsx | 27 +- .../+namespaces/namespace-select-filter.tsx | 15 +- .../+namespaces/namespace-select.tsx | 21 +- .../components/+namespaces/namespace.store.ts | 19 +- .../components/+namespaces/namespaces.tsx | 12 +- .../endpoint-subset-list.tsx | 4 +- .../+network-endpoints/endpoints.store.ts | 4 - .../+network-endpoints/endpoints.tsx | 12 +- .../components/+network-endpoints/index.ts | 1 + .../components/+network-ingresses/index.ts | 1 + .../+network-ingresses/ingress-details.tsx | 17 +- .../+network-ingresses/ingress.store.ts | 4 - .../+network-ingresses/ingresses.tsx | 12 +- .../components/+network-policies/index.ts | 1 + .../+network-policies/network-policies.tsx | 12 +- .../+network-policies/network-policy.store.ts | 4 - .../components/+network-services/index.ts | 1 + .../service-details-endpoint.tsx | 10 +- .../+network-services/service-details.tsx | 20 +- .../+network-services/services.store.ts | 4 - .../components/+network-services/services.tsx | 12 +- src/renderer/components/+network/network.tsx | 16 +- src/renderer/components/+nodes/index.ts | 1 + .../components/+nodes/node-details.tsx | 29 +- src/renderer/components/+nodes/nodes.store.ts | 4 - src/renderer/components/+nodes/nodes.tsx | 36 +- .../+pod-security-policies/index.ts | 1 + .../pod-security-policies.store.ts | 8 +- .../pod-security-policies.tsx | 11 +- .../components/+storage-classes/index.ts | 1 + .../storage-class-details.tsx | 19 +- .../+storage-classes/storage-class.store.ts | 14 +- .../+storage-classes/storage-classes.tsx | 12 +- .../+storage-volume-claims/index.ts | 1 + .../volume-claim-details.tsx | 25 +- .../volume-claim.store.ts | 12 +- .../+storage-volume-claims/volume-claims.tsx | 21 +- .../components/+storage-volumes/index.ts | 1 + .../+storage-volumes/volume-details-list.tsx | 13 +- .../+storage-volumes/volume-details.tsx | 4 +- .../+storage-volumes/volumes.store.ts | 4 - .../components/+storage-volumes/volumes.tsx | 16 +- src/renderer/components/+storage/storage.tsx | 14 +- .../add-role-binding-dialog.tsx | 52 +-- .../+user-management-roles-bindings/index.ts | 1 + .../role-binding-details.tsx | 11 +- .../role-bindings.store.ts | 8 - .../role-bindings.tsx | 11 +- .../add-role-dialog.tsx | 10 +- .../+user-management-roles/index.ts | 1 + .../+user-management-roles/roles.store.ts | 8 - .../+user-management-roles/roles.tsx | 11 +- .../create-service-account-dialog.tsx | 10 +- .../index.ts | 1 + .../service-accounts-details.tsx | 15 +- .../service-accounts.store.ts | 4 - .../service-accounts.tsx | 12 +- .../+user-management/user-management.tsx | 16 +- .../+workloads-cronjobs/cronjob-details.tsx | 21 +- .../+workloads-cronjobs/cronjob.store.ts | 14 +- .../+workloads-cronjobs/cronjobs.tsx | 25 +- .../components/+workloads-cronjobs/index.ts | 1 + .../daemonset-details.tsx | 29 +- .../+workloads-daemonsets/daemonsets.store.ts | 13 +- .../+workloads-daemonsets/daemonsets.tsx | 34 +- .../components/+workloads-daemonsets/index.ts | 1 + .../deployment-details.tsx | 57 ++-- .../deployment-scale-dialog.test.tsx | 6 +- .../deployments.store.ts | 13 +- .../+workloads-deployments/deployments.tsx | 38 ++- .../+workloads-deployments/index.ts | 1 + .../components/+workloads-jobs/index.ts | 1 + .../+workloads-jobs/job-details.tsx | 22 +- .../components/+workloads-jobs/job.store.ts | 15 +- .../components/+workloads-jobs/jobs.tsx | 27 +- .../+workloads-overview/overview-statuses.tsx | 43 +-- .../+workloads-overview/overview.tsx | 38 +-- .../components/+workloads-pods/index.ts | 1 + .../+workloads-pods/pod-container-env.tsx | 10 +- .../+workloads-pods/pod-details-list.tsx | 19 +- .../+workloads-pods/pod-details.tsx | 21 +- .../components/+workloads-pods/pods.store.ts | 4 - .../components/+workloads-pods/pods.tsx | 29 +- .../+workloads-replicasets/index.ts | 1 + .../replicaset-details.tsx | 29 +- .../replicaset-scale-dialog.test.tsx | 2 +- .../replicasets.store.ts | 11 +- .../+workloads-replicasets/replicasets.tsx | 12 +- .../+workloads-statefulsets/index.ts | 1 + .../statefulset-details.tsx | 29 +- .../statefulset-scale-dialog.test.tsx | 2 +- .../statefulset.store.ts | 11 +- .../+workloads-statefulsets/statefulsets.tsx | 36 +- src/renderer/components/+workloads/index.ts | 1 - .../components/+workloads/workloads.stores.ts | 40 --- .../components/+workloads/workloads.tsx | 22 +- src/renderer/components/app.tsx | 60 ++-- .../cluster-manager/cluster-status.tsx | 3 +- .../components/cluster-manager/lens-views.ts | 4 +- .../command-palette/command-container.tsx | 61 +--- .../command-palette/command-dialog.tsx | 2 +- .../components/command-palette/index.ts | 1 + .../overlay.ts} | 23 +- src/renderer/components/context.ts | 67 ++-- .../dock/__test__/log-tab.store.test.ts | 8 +- .../components/dock/create-resource.tsx | 2 +- .../components/dock/edit-resource.store.ts | 8 +- .../components/dock/install-chart.tsx | 4 +- .../components/dock/log-resource-selector.tsx | 6 +- src/renderer/components/dock/log-tab.store.ts | 17 +- .../components/dock/upgrade-chart.store.ts | 16 +- .../components/dock/upgrade-chart.tsx | 6 +- .../item-object-list/item-list-layout.tsx | 9 +- .../item-object-list/page-filters-select.tsx | 10 +- src/renderer/components/kube-object/index.ts | 1 + .../kube-object/kube-object-details.tsx | 78 +---- .../kube-object/kube-object-list-layout.tsx | 25 +- .../kube-object/kube-object-menu.tsx | 6 +- .../kube-object/kube-object-meta.tsx | 6 +- src/renderer/components/kube-object/params.ts | 69 ++++ src/renderer/components/layout/sidebar.tsx | 203 +++++++----- src/renderer/components/menu/menu-actions.tsx | 2 +- .../components/virtual-list/virtual-list.tsx | 2 +- .../initializers/api-manager-stores.ts | 85 +++++ src/renderer/kube-object.store.ts | 62 ++-- src/renderer/utils/createStorage.ts | 12 +- src/renderer/utils/storageHelper.ts | 4 +- webpack.extensions.ts | 12 +- webpack.main.ts | 6 + webpack.renderer.ts | 13 +- yarn.lock | 313 +++++++++++++++++- 231 files changed, 2517 insertions(+), 1627 deletions(-) delete mode 100644 src/common/.gitkeep create mode 100644 src/common/cluster-types.ts rename src/common/utils/{saveToAppFiles.ts => kubeconfig-files.ts} (58%) create mode 100644 src/main/initializers/ipc-handlers.ts delete mode 100644 src/renderer/components/+workloads/workloads.stores.ts rename src/renderer/components/{+custom-resources/crd-resource.store.ts => command-palette/overlay.ts} (74%) create mode 100644 src/renderer/components/kube-object/params.ts create mode 100644 src/renderer/initializers/api-manager-stores.ts diff --git a/.eslintrc.js b/.eslintrc.js index 33d28967ff643..b8152dcdf1a4f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,7 @@ module.exports = { ], extends: [ "eslint:recommended", + "plugin:import/recommended", ], env: { node: true @@ -53,6 +54,7 @@ module.exports = { ], rules: { "header/header": [2, "./license-header"], + // "import/no-cycle": 2, "indent": ["error", 2, { "SwitchCase": 1, }], @@ -93,6 +95,8 @@ module.exports = { parser: "@typescript-eslint/parser", extends: [ "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", ], plugins: [ "header", @@ -104,6 +108,7 @@ module.exports = { }, rules: { "header/header": [2, "./license-header"], + // "import/no-cycle": 2, "no-invalid-this": "off", "@typescript-eslint/no-invalid-this": ["error"], "@typescript-eslint/explicit-function-return-type": "off", @@ -158,6 +163,8 @@ module.exports = { extends: [ "plugin:@typescript-eslint/recommended", "plugin:react/recommended", + "plugin:import/recommended", + "plugin:import/typescript", ], parserOptions: { ecmaVersion: 2018, @@ -166,6 +173,7 @@ module.exports = { }, rules: { "header/header": [2, "./license-header"], + // "import/no-cycle": 2, "no-invalid-this": "off", "@typescript-eslint/no-invalid-this": ["error"], "@typescript-eslint/explicit-function-return-type": "off", diff --git a/extensions/kube-object-event-status/src/resolver.tsx b/extensions/kube-object-event-status/src/resolver.tsx index 03e1f0cdd9b41..52a0c5c040b51 100644 --- a/extensions/kube-object-event-status/src/resolver.tsx +++ b/extensions/kube-object-event-status/src/resolver.tsx @@ -22,7 +22,7 @@ import { K8sApi } from "@k8slens/extensions"; export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus { - const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi); + const eventStore = K8sApi.ApiManager.getInstance().getStore(K8sApi.eventApi); const events = (eventStore as K8sApi.EventStore).getEventsByObject(object); const warnings = events.filter(evt => evt.isWarning()); @@ -42,7 +42,7 @@ export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus { if (!pod.hasIssues()) { return null; } - const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi); + const eventStore = K8sApi.ApiManager.getInstance().getStore(K8sApi.eventApi); const events = (eventStore as K8sApi.EventStore).getEventsByObject(pod); const warnings = events.filter(evt => evt.isWarning()); @@ -59,7 +59,7 @@ export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus { } export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeObjectStatus { - const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi); + const eventStore = K8sApi.ApiManager.getInstance().getStore(K8sApi.eventApi); let events = (eventStore as K8sApi.EventStore).getEventsByObject(cronJob); const warnings = events.filter(evt => evt.isWarning()); diff --git a/package.json b/package.json index 05b64573578d6..7d7368093e4f6 100644 --- a/package.json +++ b/package.json @@ -315,6 +315,7 @@ "electron-notarize": "^0.3.0", "eslint": "^7.7.0", "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.23.2", "eslint-plugin-react": "^7.21.5", "eslint-plugin-unused-imports": "^1.0.1", "file-loader": "^6.0.0", diff --git a/src/common/.gitkeep b/src/common/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 7a82547e618c8..249006c3e6260 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -23,9 +23,11 @@ import fs from "fs"; import mockFs from "mock-fs"; import yaml from "js-yaml"; import { Cluster } from "../../main/cluster"; -import { ClusterStore, getClusterIdFromHost } from "../cluster-store"; +import { ClusterStore } from "../cluster-store"; import { Console } from "console"; import { stdout, stderr } from "process"; +import { embedCustomKubeConfig } from "../utils"; +import { getClusterIdFromHost } from "../cluster-types"; console = new Console(stdout, stderr); @@ -102,7 +104,7 @@ describe("empty config", () => { icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5", clusterName: "minikube" }, - kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", kubeconfig) + kubeConfigPath: embedCustomKubeConfig("foo", kubeconfig) }) ); }); @@ -135,7 +137,7 @@ describe("empty config", () => { preferences: { clusterName: "prod" }, - kubeConfigPath: ClusterStore.embedCustomKubeConfig("prod", kubeconfig) + kubeConfigPath: embedCustomKubeConfig("prod", kubeconfig) }), new Cluster({ id: "dev", @@ -143,7 +145,7 @@ describe("empty config", () => { preferences: { clusterName: "dev" }, - kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", kubeconfig) + kubeConfigPath: embedCustomKubeConfig("dev", kubeconfig) }) ); }); @@ -154,7 +156,7 @@ describe("empty config", () => { }); it("check if cluster's kubeconfig file saved", () => { - const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig"); + const file = embedCustomKubeConfig("boo", "kubeconfig"); expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig"); }); diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 2655aab8fce34..cb2b285b4d1c0 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -24,8 +24,7 @@ import Config from "conf"; import type { Options as ConfOptions } from "conf/dist/source/types"; import { app, ipcMain, ipcRenderer, remote } from "electron"; import { IReactionOptions, observable, reaction, runInAction, when } from "mobx"; -import Singleton from "./utils/singleton"; -import { getAppVersion } from "./utils/app-version"; +import { Singleton, getAppVersion } from "./utils"; import logger from "../main/logger"; import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc"; import isEqual from "lodash/isEqual"; diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index ca78cba92c431..0e9a4bed67dc2 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -20,13 +20,13 @@ */ import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; -import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; +import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog"; import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; import { requestMain } from "../ipc"; import { productName } from "../vars"; -import { CatalogCategory, CatalogCategorySpec } from "../catalog"; import { addClusterURL } from "../routes"; +import { storedKubeConfigFolder } from "../utils"; import { app } from "electron"; export type KubernetesClusterPrometheusMetrics = { @@ -109,7 +109,7 @@ export class KubernetesCluster extends CatalogEntity { - return ClusterStore.getInstance() - .getById(clusterId) - ?.activate(force); - }); - - ipcMain.handle(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => { - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId }); - cluster.pushState(); - } - }); - - ipcMain.handle(clusterRefreshHandler, (event, clusterId: ClusterId) => { - return ClusterStore.getInstance() - .getById(clusterId) - ?.refresh({ refreshMetadata: true }); - }); - - ipcMain.handle(clusterDisconnectHandler, (event, clusterId: ClusterId) => { - appEventBus.emit({name: "cluster", action: "stop"}); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - cluster.disconnect(); - clusterFrameMap.delete(cluster.id); - } - }); - - ipcMain.handle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - appEventBus.emit({name: "cluster", action: "kubectl-apply-all"}); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - const applier = new ResourceApplier(cluster); - - try { - const stdout = await applier.kubectlApplyAll(resources, extraArgs); - - return { stdout }; - } catch (error: any) { - return { stderr: error }; - } - } else { - throw `${clusterId} is not a valid cluster id`; - } - }); - - ipcMain.handle(clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - appEventBus.emit({name: "cluster", action: "kubectl-delete-all"}); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - const applier = new ResourceApplier(cluster); - - try { - const stdout = await applier.kubectlDeleteAll(resources, extraArgs); - - return { stdout }; - } catch (error: any) { - return { stderr: error }; - } - } else { - throw `${clusterId} is not a valid cluster id`; - } - }); -} diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index c50968fd897cf..56432714d58c9 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -19,120 +19,24 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import path from "path"; import { app, ipcMain, ipcRenderer, remote, webFrame } from "electron"; -import { unlink } from "fs-extra"; +import * as fse from "fs-extra"; +import path from "path"; import { action, comparer, computed, observable, reaction, toJS } from "mobx"; import { BaseStore } from "./base-store"; -import { Cluster, ClusterState } from "../main/cluster"; +import { Cluster } from "../main/cluster"; import migrations from "../migrations/cluster-store"; import logger from "../main/logger"; import { appEventBus } from "./event-bus"; -import { dumpConfigYaml } from "./kube-helpers"; -import { saveToAppFiles } from "./utils/saveToAppFiles"; -import type { KubeConfig } from "@kubernetes/client-node"; import { ipcMainOn, ipcRendererOn, requestMain } from "./ipc"; import type { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting"; -import { disposer, noop } from "./utils"; - -export interface ClusterIconUpload { - clusterId: string; - name: string; - path: string; -} - -export interface ClusterMetadata { - [key: string]: string | number | boolean | object; -} - -export type ClusterPrometheusMetadata = { - success?: boolean; - provider?: string; - autoDetected?: boolean; -}; - -export interface ClusterStoreModel { - activeCluster?: ClusterId; // last opened cluster - clusters?: ClusterModel[]; -} - -export type ClusterId = string; - -export interface UpdateClusterModel extends Omit { - id?: ClusterId; -} - -export interface ClusterModel { - /** Unique id for a cluster */ - id: ClusterId; - - /** Path to cluster kubeconfig */ - kubeConfigPath: string; - - /** - * Workspace id - * - * @deprecated - */ - workspace?: string; - - /** User context in kubeconfig */ - contextName?: string; - - /** Preferences */ - preferences?: ClusterPreferences; - - /** Metadata */ - metadata?: ClusterMetadata; - - /** List of accessible namespaces */ - accessibleNamespaces?: string[]; - - /** @deprecated */ - kubeConfig?: string; // yaml -} - -export interface ClusterPreferences extends ClusterPrometheusPreferences { - terminalCWD?: string; - clusterName?: string; - iconOrder?: number; - icon?: string; - httpsProxy?: string; - hiddenMetrics?: string[]; -} - -export interface ClusterPrometheusPreferences { - prometheus?: { - namespace: string; - service: string; - port: number; - prefix: string; - }; - prometheusProvider?: { - type: string; - }; -} +import { disposer, getCustomKubeConfigPath, noop } from "./utils"; +import type { ClusterStoreModel, ClusterId, ClusterModel, ClusterState } from "./cluster-types"; +import { getHostedClusterId } from "./cluster-types"; export class ClusterStore extends BaseStore { private static StateChannel = "cluster:state"; - static get storedKubeConfigFolder(): string { - return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs"); - } - - static getCustomKubeConfigPath(clusterId: ClusterId): string { - return path.resolve(ClusterStore.storedKubeConfigFolder, clusterId); - } - - static embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string { - const filePath = ClusterStore.getCustomKubeConfigPath(clusterId); - const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig); - - saveToAppFiles(filePath, fileContents, { mode: 0o600 }); - - return filePath; - } - @observable activeCluster: ClusterId; @observable removedClusters = observable.map(); @observable clusters = observable.map(); @@ -311,9 +215,13 @@ export class ClusterStore extends BaseStore { } // remove only custom kubeconfigs (pasted as text) - if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) { - await unlink(cluster.kubeConfigPath).catch(noop); + if (cluster.kubeConfigPath == getCustomKubeConfigPath(clusterId)) { + await fse.unlink(cluster.kubeConfigPath).catch(noop); } + + const localStorage = path.resolve((app || remote.app).getPath("userData"), "lens-local-storage", `${cluster.id}.json`); + + await fse.unlink(localStorage).catch(noop); } } @@ -361,21 +269,6 @@ export class ClusterStore extends BaseStore { } } -export function getClusterIdFromHost(host: string): ClusterId | undefined { - // e.g host == "%clusterId.localhost:45345" - const subDomains = host.split(":")[0].split("."); - - return subDomains.slice(-2, -1)[0]; // ClusterId or undefined -} - -export function getClusterFrameUrl(clusterId: ClusterId) { - return `//${clusterId}.${location.host}`; -} - -export function getHostedClusterId() { - return getClusterIdFromHost(location.host); -} - export function getHostedCluster(): Cluster { return ClusterStore.getInstance().getById(getHostedClusterId()); } diff --git a/src/common/cluster-types.ts b/src/common/cluster-types.ts new file mode 100644 index 0000000000000..a31cdaf23326b --- /dev/null +++ b/src/common/cluster-types.ts @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +export enum ClusterStatus { + AccessGranted = 2, + AccessDenied = 1, + Offline = 0 +} + +export enum ClusterMetadataKey { + VERSION = "version", + CLUSTER_ID = "id", + DISTRIBUTION = "distribution", + NODES_COUNT = "nodes", + LAST_SEEN = "lastSeen", + PROMETHEUS = "prometheus" +} + +export type ClusterRefreshOptions = { + refreshMetadata?: boolean +}; + +export interface ClusterState { + apiUrl: string; + online: boolean; + disconnected: boolean; + accessible: boolean; + ready: boolean; + failureReason: string; + isAdmin: boolean; + allowedNamespaces: string[] + allowedResources: string[] + isGlobalWatchEnabled: boolean; +} + +export interface ClusterIconUpload { + clusterId: string; + name: string; + path: string; +} + +export interface ClusterMetadata { + [key: string]: string | number | boolean | object; +} + +export type ClusterPrometheusMetadata = { + success?: boolean; + provider?: string; + autoDetected?: boolean; +}; + +export interface ClusterStoreModel { + activeCluster?: ClusterId; // last opened cluster + clusters?: ClusterModel[]; +} + +export type ClusterId = string; + +export interface UpdateClusterModel extends Omit { + id?: ClusterId; +} + +export interface ClusterModel { + /** Unique id for a cluster */ + id: ClusterId; + + /** Path to cluster kubeconfig */ + kubeConfigPath: string; + + /** + * Workspace id + * + * @deprecated + */ + workspace?: string; + + /** User context in kubeconfig */ + contextName?: string; + + /** Preferences */ + preferences?: ClusterPreferences; + + /** Metadata */ + metadata?: ClusterMetadata; + + /** List of accessible namespaces */ + accessibleNamespaces?: string[]; + + /** @deprecated */ + kubeConfig?: string; // yaml +} + +export interface ClusterPreferences extends ClusterPrometheusPreferences { + terminalCWD?: string; + clusterName?: string; + iconOrder?: number; + icon?: string; + httpsProxy?: string; + hiddenMetrics?: string[]; +} + +export interface ClusterPrometheusPreferences { + prometheus?: { + namespace: string; + service: string; + port: number; + prefix: string; + }; + prometheusProvider?: { + type: string; + }; +} + +export function getClusterIdFromHost(host: string): ClusterId | undefined { + // e.g host == "%clusterId.localhost:45345" + const subDomains = host.split(":")[0].split("."); + + return subDomains.slice(-2, -1)[0]; // ClusterId or undefined +} + +export function getClusterFrameUrl(clusterId: ClusterId) { + return `//${clusterId}.${location.host}`; +} + +export function getHostedClusterId() { + return getClusterIdFromHost(location.host); +} diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index 917fe29571df3..24750ac4bc31c 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -80,6 +80,6 @@ export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRen return () => ipcRenderer.off(channel, listener); } -export function bindBroadcastHandlers() { +export function initGetSubFramesHandler() { ipcMain.handle(subFramesChannel, getSubFrames); } diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 55b8ca414cada..b5260431d7476 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -19,8 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { getHostedCluster } from "./cluster-store"; - export type KubeResource = "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" | @@ -73,18 +71,3 @@ export const apiResourceRecord: Record = { // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) export const apiResources: KubeApiResource[] = Object.entries(apiResourceRecord) .map(([apiName, data]) => ({ apiName: apiName as KubeResource, ...data })); - -export function isAllowedResource(resources: KubeResource | KubeResource[]) { - if (!Array.isArray(resources)) { - resources = [resources]; - } - const { allowedResources = [] } = getHostedCluster() || {}; - - for (const resource of resources) { - if (!allowedResources.includes(resource)) { - return false; - } - } - - return true; -} diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 19ba33615cab3..c07ee8c18862e 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -26,14 +26,13 @@ import { readFile } from "fs-extra"; import { action, computed, observable, reaction, toJS } from "mobx"; import moment from "moment-timezone"; import { BaseStore } from "./base-store"; -import migrations from "../migrations/user-store"; +import migrations, { fileNameMigration } from "../migrations/user-store"; import { getAppVersion } from "./utils/app-version"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { appEventBus } from "./event-bus"; import logger from "../main/logger"; import path from "path"; import os from "os"; -import { fileNameMigration } from "../migrations/user-store"; import { ObservableToggleSet } from "../renderer/utils"; export interface UserStoreModel { diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 5b1315954920e..8bdf9df343e8b 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -32,14 +32,13 @@ export * from "./debouncePromise"; export * from "./defineGlobal"; export * from "./delay"; export * from "./disposer"; -export * from "./disposer"; export * from "./downloadFile"; export * from "./escapeRegExp"; export * from "./extended-map"; export * from "./getRandId"; export * from "./openExternal"; export * from "./reject-promise"; -export * from "./saveToAppFiles"; +export * from "./kubeconfig-files"; export * from "./singleton"; export * from "./splitArray"; export * from "./tar"; diff --git a/src/common/utils/saveToAppFiles.ts b/src/common/utils/kubeconfig-files.ts similarity index 58% rename from src/common/utils/saveToAppFiles.ts rename to src/common/utils/kubeconfig-files.ts index b957cdb38ec71..a75fe8a324f9b 100644 --- a/src/common/utils/saveToAppFiles.ts +++ b/src/common/utils/kubeconfig-files.ts @@ -19,17 +19,27 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Save file to electron app directory (e.g. "/Users/$USER/Library/Application Support/Lens" for MacOS) -import path from "path"; +import type { KubeConfig } from "@kubernetes/client-node"; import { app, remote } from "electron"; -import { ensureDirSync, writeFileSync } from "fs-extra"; -import type { WriteFileOptions } from "fs"; +import path from "path"; +import type { ClusterId } from "../cluster-types"; +import { dumpConfigYaml } from "../kube-helpers"; +import * as fse from "fs-extra"; + +export function storedKubeConfigFolder(): string { + return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs"); +} + +export function getCustomKubeConfigPath(clusterId: ClusterId): string { + return path.resolve(storedKubeConfigFolder(), clusterId); +} -export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string { - const absPath = path.resolve((app || remote.app).getPath("userData"), filePath); +export function embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string { + const filePath = getCustomKubeConfigPath(clusterId); + const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig); - ensureDirSync(path.dirname(absPath)); - writeFileSync(absPath, contents, options); + fse.ensureDirSync(path.dirname(filePath)); + fse.writeFileSync(filePath, fileContents, { mode: 0o600 }); - return absPath; + return filePath; } diff --git a/src/extensions/registries/command-registry.ts b/src/extensions/registries/command-registry.ts index 967fbd12710d4..5ecd23dfc8d01 100644 --- a/src/extensions/registries/command-registry.ts +++ b/src/extensions/registries/command-registry.ts @@ -53,4 +53,8 @@ export class CommandRegistry extends BaseRegistry { return super.add(filteredItems, extension); } + + getById(commandId: string): CommandRegistration | undefined { + return this.getItems().find(({ id }) => id == commandId); + } } diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index d16dbe83f523b..ae58e13595e32 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -19,9 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export { isAllowedResource } from "../../common/rbac"; export { ResourceStack } from "../../common/k8s/resource-stack"; -export { apiManager } from "../../renderer/api/api-manager"; +export { ApiManager } from "../../renderer/api/api-manager"; export { KubeObjectStore } from "../../renderer/kube-object.store"; export { KubeApi, forCluster } from "../../renderer/api/kube-api"; export { KubeObject } from "../../renderer/api/kube-object"; @@ -38,13 +37,13 @@ export { ReplicaSet, replicaSetApi } from "../../renderer/api/endpoints"; export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints"; export { LimitRange, limitRangeApi } from "../../renderer/api/endpoints"; export { HorizontalPodAutoscaler, hpaApi } from "../../renderer/api/endpoints"; -export { PodDisruptionBudget, pdbApi } from "../../renderer/api/endpoints"; +export { PodDisruptionBudget, podDisruptionBudgetApi } from "../../renderer/api/endpoints"; export { Service, serviceApi } from "../../renderer/api/endpoints"; export { Endpoint, endpointApi } from "../../renderer/api/endpoints"; export { Ingress, ingressApi, IngressApi } from "../../renderer/api/endpoints"; export { NetworkPolicy, networkPolicyApi } from "../../renderer/api/endpoints"; export { PersistentVolume, persistentVolumeApi } from "../../renderer/api/endpoints"; -export { PersistentVolumeClaim, pvcApi, PersistentVolumeClaimsApi } from "../../renderer/api/endpoints"; +export { PersistentVolumeClaim, persistentVolumeClaimsApi, PersistentVolumeClaimsApi } from "../../renderer/api/endpoints"; export { StorageClass, storageClassApi } from "../../renderer/api/endpoints"; export { Namespace, namespacesApi } from "../../renderer/api/endpoints"; export { KubeEvent, eventApi } from "../../renderer/api/endpoints"; @@ -63,31 +62,30 @@ export type { ISecretRef } from "../../renderer/api/endpoints"; export type { KubeObjectStatus } from "./kube-object-status"; // stores -export type { EventStore } from "../../renderer/components/+events/event.store"; -export type { PodsStore } from "../../renderer/components/+workloads-pods/pods.store"; -export type { NodesStore } from "../../renderer/components/+nodes/nodes.store"; -export type { DeploymentStore } from "../../renderer/components/+workloads-deployments/deployments.store"; -export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets/daemonsets.store"; -export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets/statefulset.store"; -export type { JobStore } from "../../renderer/components/+workloads-jobs/job.store"; -export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs/cronjob.store"; -export type { ConfigMapsStore } from "../../renderer/components/+config-maps/config-maps.store"; -export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store"; -export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store"; -export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store"; -export type { LimitRangesStore } from "../../renderer/components/+config-limit-ranges/limit-ranges.store"; -export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store"; -export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store"; -export type { ServiceStore } from "../../renderer/components/+network-services/services.store"; -export type { EndpointStore } from "../../renderer/components/+network-endpoints/endpoints.store"; -export type { IngressStore } from "../../renderer/components/+network-ingresses/ingress.store"; -export type { NetworkPolicyStore } from "../../renderer/components/+network-policies/network-policy.store"; -export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store"; -export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store"; -export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store"; -export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store"; -export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts/service-accounts.store"; -export type { RolesStore } from "../../renderer/components/+user-management-roles/roles.store"; -export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings/role-bindings.store"; -export type { CRDStore } from "../../renderer/components/+custom-resources/crd.store"; -export type { CRDResourceStore } from "../../renderer/components/+custom-resources/crd-resource.store"; +export type { EventStore } from "../../renderer/components/+events"; +export type { PodsStore } from "../../renderer/components/+workloads-pods"; +export type { NodesStore } from "../../renderer/components/+nodes"; +export type { DeploymentStore } from "../../renderer/components/+workloads-deployments"; +export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets"; +export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets"; +export type { JobStore } from "../../renderer/components/+workloads-jobs"; +export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs"; +export type { ConfigMapsStore } from "../../renderer/components/+config-maps"; +export type { SecretsStore } from "../../renderer/components/+config-secrets"; +export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets"; +export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas"; +export type { LimitRangesStore } from "../../renderer/components/+config-limit-ranges"; +export type { HpaStore as HPAStore } from "../../renderer/components/+config-autoscalers"; +export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets"; +export type { ServiceStore } from "../../renderer/components/+network-services"; +export type { EndpointStore } from "../../renderer/components/+network-endpoints"; +export type { IngressStore } from "../../renderer/components/+network-ingresses"; +export type { NetworkPolicyStore } from "../../renderer/components/+network-policies"; +export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes"; +export type { PersistentVolumeClaimStore as VolumeClaimStore } from "../../renderer/components/+storage-volume-claims"; +export type { StorageClassStore } from "../../renderer/components/+storage-classes"; +export type { NamespaceStore } from "../../renderer/components/+namespaces"; +export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts"; +export type { RolesStore } from "../../renderer/components/+user-management-roles"; +export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings"; +export type { CrdStore as CRDStore } from "../../renderer/components/+custom-resources"; diff --git a/src/extensions/renderer-api/navigation.ts b/src/extensions/renderer-api/navigation.ts index 7fc321f620ca6..86b4fb9c8abe3 100644 --- a/src/extensions/renderer-api/navigation.ts +++ b/src/extensions/renderer-api/navigation.ts @@ -24,7 +24,7 @@ import { navigation } from "../../renderer/navigation"; export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param"; export { navigate, isActiveRoute } from "../../renderer/navigation/helpers"; -export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details"; +export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/params"; export type { URLParams } from "../../common/utils/buildUrl"; // exporting to extensions-api version of helper without `isSystem` flag diff --git a/src/main/catalog-sources/kubeconfig-sync.ts b/src/main/catalog-sources/kubeconfig-sync.ts index c4a8b0a514f4c..61a65e69a4f0b 100644 --- a/src/main/catalog-sources/kubeconfig-sync.ts +++ b/src/main/catalog-sources/kubeconfig-sync.ts @@ -25,14 +25,15 @@ import { watch } from "chokidar"; import fs from "fs"; import fse from "fs-extra"; import type stream from "stream"; -import { Disposer, ExtendedObservableMap, iter, Singleton } from "../../common/utils"; +import { Disposer, ExtendedObservableMap, iter, Singleton, storedKubeConfigFolder } from "../../common/utils"; import logger from "../logger"; import type { KubeConfig } from "@kubernetes/client-node"; import { loadConfigFromString, splitConfig, validateKubeConfig } from "../../common/kube-helpers"; import { Cluster } from "../cluster"; import { catalogEntityFromCluster } from "../cluster-manager"; import { UserStore } from "../../common/user-store"; -import { ClusterStore, UpdateClusterModel } from "../../common/cluster-store"; +import { ClusterStore } from "../../common/cluster-store"; +import type { UpdateClusterModel } from "../../common/cluster-types"; import { createHash } from "crypto"; const logPrefix = "[KUBECONFIG-SYNC]:"; @@ -62,7 +63,7 @@ export class KubeconfigSyncManager extends Singleton { ))); // This must be done so that c&p-ed clusters are visible - this.startNewSync(ClusterStore.storedKubeConfigFolder); + this.startNewSync(storedKubeConfigFolder()); for (const filePath of UserStore.getInstance().syncKubeconfigEntries.keys()) { this.startNewSync(filePath); diff --git a/src/main/cluster-detectors/cluster-id-detector.ts b/src/main/cluster-detectors/cluster-id-detector.ts index 9818b0d990b6d..21d7a005b30d7 100644 --- a/src/main/cluster-detectors/cluster-id-detector.ts +++ b/src/main/cluster-detectors/cluster-id-detector.ts @@ -21,7 +21,7 @@ import { BaseClusterDetector } from "./base-cluster-detector"; import { createHash } from "crypto"; -import { ClusterMetadataKey } from "../cluster"; +import { ClusterMetadataKey } from "../../common/cluster-types"; export class ClusterIdDetector extends BaseClusterDetector { key = ClusterMetadataKey.CLUSTER_ID; diff --git a/src/main/cluster-detectors/detector-registry.ts b/src/main/cluster-detectors/detector-registry.ts index 82a09059e35c8..40b4fa6949382 100644 --- a/src/main/cluster-detectors/detector-registry.ts +++ b/src/main/cluster-detectors/detector-registry.ts @@ -20,7 +20,7 @@ */ import { observable } from "mobx"; -import type { ClusterMetadata } from "../../common/cluster-store"; +import type { ClusterMetadata } from "../../common/cluster-types"; import type { Cluster } from "../cluster"; import type { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector"; import { ClusterIdDetector } from "./cluster-id-detector"; diff --git a/src/main/cluster-detectors/distribution-detector.ts b/src/main/cluster-detectors/distribution-detector.ts index c7f2791cee64f..0deb598b39d83 100644 --- a/src/main/cluster-detectors/distribution-detector.ts +++ b/src/main/cluster-detectors/distribution-detector.ts @@ -20,7 +20,7 @@ */ import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../cluster"; +import { ClusterMetadataKey } from "../../common/cluster-types"; export class DistributionDetector extends BaseClusterDetector { key = ClusterMetadataKey.DISTRIBUTION; @@ -60,7 +60,7 @@ export class DistributionDetector extends BaseClusterDetector { if (this.isK0s()) { return { value: "k0s", accuracy: 80}; } - + if (this.isVMWare()) { return { value: "vmware", accuracy: 90}; } @@ -179,7 +179,7 @@ export class DistributionDetector extends BaseClusterDetector { protected isK0s() { return this.version.includes("-k0s"); } - + protected isAlibaba() { return this.version.includes("-aliyun"); } diff --git a/src/main/cluster-detectors/last-seen-detector.ts b/src/main/cluster-detectors/last-seen-detector.ts index 537fef96cb169..2aed3d0640a50 100644 --- a/src/main/cluster-detectors/last-seen-detector.ts +++ b/src/main/cluster-detectors/last-seen-detector.ts @@ -20,7 +20,7 @@ */ import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../cluster"; +import { ClusterMetadataKey } from "../../common/cluster-types"; export class LastSeenDetector extends BaseClusterDetector { key = ClusterMetadataKey.LAST_SEEN; diff --git a/src/main/cluster-detectors/nodes-count-detector.ts b/src/main/cluster-detectors/nodes-count-detector.ts index f30f5e6c70a8a..2e136a7acdfc8 100644 --- a/src/main/cluster-detectors/nodes-count-detector.ts +++ b/src/main/cluster-detectors/nodes-count-detector.ts @@ -19,8 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { ClusterMetadataKey } from "../../common/cluster-types"; import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../cluster"; export class NodesCountDetector extends BaseClusterDetector { key = ClusterMetadataKey.NODES_COUNT; diff --git a/src/main/cluster-detectors/version-detector.ts b/src/main/cluster-detectors/version-detector.ts index f7240ab3ea3ca..2b7b76786f8f9 100644 --- a/src/main/cluster-detectors/version-detector.ts +++ b/src/main/cluster-detectors/version-detector.ts @@ -19,8 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { ClusterMetadataKey } from "../../common/cluster-types"; import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../cluster"; export class VersionDetector extends BaseClusterDetector { key = ClusterMetadataKey.VERSION; diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 211172a2a6184..33ad86fa2820a 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -19,11 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "../common/cluster-ipc"; import type http from "http"; import { ipcMain } from "electron"; import { action, autorun, reaction, toJS } from "mobx"; -import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store"; +import { ClusterStore } from "../common/cluster-store"; +import { getClusterIdFromHost } from "../common/cluster-types"; import type { Cluster } from "./cluster"; import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 6def60b08f862..a6fa888d304ef 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -20,7 +20,6 @@ */ import { ipcMain } from "electron"; -import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-store"; import { action, comparer, computed, observable, reaction, toJS, when } from "mobx"; import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc"; import { ContextHandler } from "./context-handler"; @@ -33,38 +32,8 @@ import logger from "./logger"; import { VersionDetector } from "./cluster-detectors/version-detector"; import { detectorRegistry } from "./cluster-detectors/detector-registry"; import plimit from "p-limit"; - -export enum ClusterStatus { - AccessGranted = 2, - AccessDenied = 1, - Offline = 0 -} - -export enum ClusterMetadataKey { - VERSION = "version", - CLUSTER_ID = "id", - DISTRIBUTION = "distribution", - NODES_COUNT = "nodes", - LAST_SEEN = "lastSeen", - PROMETHEUS = "prometheus" -} - -export type ClusterRefreshOptions = { - refreshMetadata?: boolean -}; - -export interface ClusterState { - apiUrl: string; - online: boolean; - disconnected: boolean; - accessible: boolean; - ready: boolean; - failureReason: string; - isAdmin: boolean; - allowedNamespaces: string[] - allowedResources: string[] - isGlobalWatchEnabled: boolean; -} +import type { ClusterModel, ClusterState, ClusterId, ClusterPreferences, ClusterMetadata, ClusterPrometheusPreferences, UpdateClusterModel, ClusterRefreshOptions } from "../common/cluster-types"; +import { ClusterStatus } from "../common/cluster-types"; /** * Cluster @@ -711,4 +680,12 @@ export class Cluster implements ClusterModel, ClusterState { return true; // allowed by default for other resources } + + isAllowedResources(...kinds: string[]): boolean { + return kinds.every(kind => this.isAllowedResource(kind)); + } + + isAnyAllowedResources(...kinds: string[]): boolean { + return kinds.length === 0 || kinds.some(kind => this.isAllowedResource(kind)); + } } diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index 6fa7ca1ef5ba7..446718f8d6f52 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -20,7 +20,7 @@ */ import type { PrometheusService } from "./prometheus/provider-registry"; -import type { ClusterPrometheusPreferences } from "../common/cluster-store"; +import type { ClusterPrometheusPreferences } from "../common/cluster-types"; import type { Cluster } from "./cluster"; import type httpProxy from "http-proxy"; import url, { UrlWithStringQuery } from "url"; diff --git a/src/main/exit-app.ts b/src/main/exit-app.ts index 2f9e3b00c7279..cd53b995ef439 100644 --- a/src/main/exit-app.ts +++ b/src/main/exit-app.ts @@ -26,17 +26,11 @@ import { ClusterManager } from "./cluster-manager"; import logger from "./logger"; export function exitApp() { - console.log("before windowManager"); - const windowManager = WindowManager.getInstance(false); - - console.log("before clusterManager"); - const clusterManager = ClusterManager.getInstance(false); + appEventBus.emit({ name: "service", action: "close" }); - console.log("after clusterManager"); + WindowManager.getInstance(false)?.hide(); + ClusterManager.getInstance(false)?.stop(); - appEventBus.emit({ name: "service", action: "close" }); - windowManager?.hide(); - clusterManager?.stop(); logger.info("SERVICE:QUIT"); setTimeout(() => { app.exit(); diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 852c649ede2c6..7853977e1d539 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import * as tempy from "tempy"; +import tempy from "tempy"; import fse from "fs-extra"; import * as yaml from "js-yaml"; import { promiseExec} from "../promise-exec"; diff --git a/src/main/index.ts b/src/main/index.ts index 25a87bf30d7d3..32534cc7b83d7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -45,8 +45,8 @@ import type { LensExtensionId } from "../extensions/lens-extension"; import { FilesystemProvisionerStore } from "./extension-filesystem"; import { installDeveloperTools } from "./developer-tools"; import { LensProtocolRouterMain } from "./protocol-handler"; -import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils"; -import { bindBroadcastHandlers } from "../common/ipc"; +import { disposer, getAppVersion, getAppVersionFromProxyServer } from "../common/utils"; +import { initGetSubFramesHandler } from "../common/ipc"; import { startUpdateChecking } from "./app-updater"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import { CatalogPusher } from "./catalog-pusher"; @@ -54,10 +54,14 @@ import { catalogEntityRegistry } from "../common/catalog"; import { HotbarStore } from "../common/hotbar-store"; import { HelmRepoManager } from "./helm/helm-repo-manager"; import { KubeconfigSyncManager } from "./catalog-sources"; -import { handleWsUpgrade } from "./proxy/ws-upgrade"; import { initRegistries } from "./initializers"; +import { initIpcMainHandlers } from "./initializers/ipc-handlers"; +import { Router } from "./router"; +import { initMenu } from "./menu"; +import { initTray } from "./tray"; const workingDir = path.join(app.getPath("appData"), appName); +const cleanup = disposer(); app.setName(appName); @@ -114,7 +118,8 @@ app.on("ready", async () => { logger.info("🐚 Syncing shell environment"); await shellSync(); - bindBroadcastHandlers(); + initGetSubFramesHandler(); + initIpcMainHandlers(); powerMonitor.on("shutdown", () => { app.exit(); @@ -140,14 +145,15 @@ app.on("ready", async () => { filesystemStore.load(), ]); - const lensProxy = LensProxy.createInstance(handleWsUpgrade); - ClusterManager.createInstance(); KubeconfigSyncManager.createInstance().startSync(); try { logger.info("🔌 Starting LensProxy"); - await lensProxy.listen(); + await LensProxy.createInstance( + new Router(), + req => ClusterManager.getInstance().getClusterForRequest(req), + ).listen(); } catch (error) { dialog.showErrorBox("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`); app.exit(); @@ -156,7 +162,7 @@ app.on("ready", async () => { // test proxy connection try { logger.info("🔎 Testing LensProxy connection ..."); - const versionFromProxy = await getAppVersionFromProxyServer(lensProxy.port); + const versionFromProxy = await getAppVersionFromProxyServer(LensProxy.getInstance().port); if (getAppVersion() !== versionFromProxy) { logger.error("Proxy server responded with invalid response"); @@ -182,6 +188,8 @@ app.on("ready", async () => { logger.info("🖥️ Starting WindowManager"); const windowManager = WindowManager.createInstance(); + cleanup.push(initMenu(windowManager), initTray(windowManager)); + installDeveloperTools(); if (!startHidden) { @@ -259,6 +267,8 @@ app.on("will-quit", (event) => { return; // skip exit to make tray work, to quit go to app's global menu or tray's menu } + + cleanup(); }); app.on("open-url", (event, rawUrl) => { diff --git a/src/main/initializers/ipc-handlers.ts b/src/main/initializers/ipc-handlers.ts new file mode 100644 index 0000000000000..9cf0eb4b76d9e --- /dev/null +++ b/src/main/initializers/ipc-handlers.ts @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { ipcMain, IpcMainInvokeEvent } from "electron"; +import { clusterFrameMap } from "../../common/cluster-frames"; +import * as channels from "../../common/cluster-ipc"; +import { ClusterStore } from "../../common/cluster-store"; +import type { ClusterId } from "../../common/cluster-types"; +import { appEventBus } from "../../common/event-bus"; +import { ResourceApplier } from "../resource-applier"; + +export function initIpcMainHandlers() { + ipcMain.handle(channels.clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { + return ClusterStore.getInstance() + .getById(clusterId) + ?.activate(force); + }); + + ipcMain.handle(channels.clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => { + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (cluster) { + clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId }); + cluster.pushState(); + } + }); + + ipcMain.handle(channels.clusterRefreshHandler, (event, clusterId: ClusterId) => { + return ClusterStore.getInstance() + .getById(clusterId) + ?.refresh({ refreshMetadata: true }); + }); + + ipcMain.handle(channels.clusterDisconnectHandler, (event, clusterId: ClusterId) => { + appEventBus.emit({ name: "cluster", action: "stop" }); + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (cluster) { + cluster.disconnect(); + clusterFrameMap.delete(cluster.id); + } + }); + + ipcMain.handle(channels.clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => { + appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" }); + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (cluster) { + const applier = new ResourceApplier(cluster); + + applier.kubectlApplyAll(resources); + } else { + throw `${clusterId} is not a valid cluster id`; + } + }); + + ipcMain.handle(channels.clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { + appEventBus.emit({ name: "cluster", action: "kubectl-delete-all" }); + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (cluster) { + const applier = new ResourceApplier(cluster); + + try { + const stdout = await applier.kubectlDeleteAll(resources, extraArgs); + + return { stdout }; + } catch (error: any) { + return { stderr: error }; + } + } else { + throw `${clusterId} is not a valid cluster id`; + } + }); +} diff --git a/src/main/proxy/lens-proxy.ts b/src/main/proxy/lens-proxy.ts index 199d48398b601..b1dfc27351566 100644 --- a/src/main/proxy/lens-proxy.ts +++ b/src/main/proxy/lens-proxy.ts @@ -25,41 +25,40 @@ import spdy from "spdy"; import httpProxy from "http-proxy"; import url from "url"; import { apiPrefix, apiKubePrefix } from "../../common/vars"; -import { Router } from "../router"; +import type { Router } from "../router"; import type { ContextHandler } from "../context-handler"; import logger from "../logger"; import { Singleton } from "../../common/utils"; -import { ClusterManager } from "../cluster-manager"; - -type WSUpgradeHandler = (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => void; +import type { Cluster } from "../cluster"; +import { LocalShellSession, NodeShellSession } from "../shell-session"; +import type * as WebSocket from "ws"; +import { Server } from "ws"; export class LensProxy extends Singleton { protected origin: string; protected proxyServer: http.Server; - protected router = new Router(); protected closed = false; protected retryCounters = new Map(); public port: number; - constructor(handleWsUpgrade: WSUpgradeHandler) { + constructor(protected router: Router, protected getClusterForRequest: (req: http.IncomingMessage) => Cluster) { super(); const proxy = this.createProxy(); - this.proxyServer = spdy.createServer({ - spdy: { - plain: true, - protocols: ["http/1.1", "spdy/3.1"] - } - }, (req: http.IncomingMessage, res: http.ServerResponse) => { - this.handleRequest(proxy, req, res); - }); - - this.proxyServer + this.proxyServer = spdy + .createServer({ + spdy: { + plain: true, + protocols: ["http/1.1", "spdy/3.1"] + } + }, (req: http.IncomingMessage, res: http.ServerResponse) => { + this.handleRequest(proxy, req, res); + }) .on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { if (req.url.startsWith(`${apiPrefix}?`)) { - handleWsUpgrade(req, socket, head); + this.handleWsUpgrade(req, socket, head); } else { this.handleProxyUpgrade(proxy, req, socket, head); } @@ -103,8 +102,27 @@ export class LensProxy extends Singleton { this.closed = true; } + protected async handleWsUpgrade(req: http.IncomingMessage, socket: net.Socket, head: Buffer) { + const wsServer = new Server({ noServer: true }); + + wsServer.on("connection", ((socket: WebSocket, req: http.IncomingMessage) => { + const cluster = this.getClusterForRequest(req); + const nodeParam = url.parse(req.url, true).query["node"]?.toString(); + const shell = nodeParam + ? new NodeShellSession(socket, cluster, nodeParam) + : new LocalShellSession(socket, cluster); + + shell.open() + .catch(error => logger.error(`[SHELL-SESSION]: failed to open: ${error}`, { error })); + })); + + wsServer.handleUpgrade(req, socket, head, (con) => { + wsServer.emit("connection", con, req); + }); + } + protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { - const cluster = ClusterManager.getInstance().getClusterForRequest(req); + const cluster = this.getClusterForRequest(req); if (cluster) { const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, ""); @@ -205,7 +223,7 @@ export class LensProxy extends Singleton { return proxy; } - protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise { + protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise { if (req.url.startsWith(apiKubePrefix)) { delete req.headers.authorization; req.url = req.url.replace(apiKubePrefix, ""); @@ -213,6 +231,8 @@ export class LensProxy extends Singleton { return contextHandler.getApiTarget(isWatchRequest); } + + return null; } protected getRequestId(req: http.IncomingMessage) { @@ -220,7 +240,7 @@ export class LensProxy extends Singleton { } protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) { - const cluster = ClusterManager.getInstance().getClusterForRequest(req); + const cluster = this.getClusterForRequest(req); if (cluster) { const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler); diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index 5dd02cc6179e7..8e6e3177e7e31 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -25,7 +25,7 @@ import { exec } from "child_process"; import fs from "fs"; import * as yaml from "js-yaml"; import path from "path"; -import * as tempy from "tempy"; +import tempy from "tempy"; import logger from "./logger"; import { appEventBus } from "../common/event-bus"; import { cloneJsonObject } from "../common/utils"; @@ -34,19 +34,19 @@ export class ResourceApplier { constructor(protected cluster: Cluster) { } - async apply(resource: KubernetesObject | any): Promise { + async apply(resource: KubernetesObject | any): Promise { resource = this.sanitizeObject(resource); appEventBus.emit({name: "resource", action: "apply"}); return await this.kubectlApply(yaml.safeDump(resource)); } - protected async kubectlApply(content: string): Promise { + protected async kubectlApply(content: string): Promise { const { kubeCtl } = this.cluster; const kubectlPath = await kubeCtl.getPath(); const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath(); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const fileName = tempy.file({ name: "resource.yaml" }); fs.writeFileSync(fileName, content); diff --git a/src/main/routes/metrics-route.ts b/src/main/routes/metrics-route.ts index 0599114219561..fea79ef851c19 100644 --- a/src/main/routes/metrics-route.ts +++ b/src/main/routes/metrics-route.ts @@ -22,10 +22,10 @@ import _ from "lodash"; import type { LensApiRequest } from "../router"; import { respondJson } from "../utils/http-responses"; -import { Cluster, ClusterMetadataKey } from "../cluster"; -import type { ClusterPrometheusMetadata } from "../../common/cluster-store"; +import type { Cluster } from "../cluster"; import logger from "../logger"; import { getMetrics } from "../k8s-request"; +import { ClusterPrometheusMetadata, ClusterMetadataKey } from "../../common/cluster-types"; export type IMetricsQuery = string | string[] | { [metricName: string]: string; diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 6158424a4f995..ded4d8faecf9a 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -19,14 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { ClusterId } from "../common/cluster-store"; +import type { ClusterId } from "../common/cluster-types"; import { observable } from "mobx"; import { app, BrowserWindow, dialog, shell, webContents } from "electron"; import windowStateKeeper from "electron-window-state"; import { appEventBus } from "../common/event-bus"; import { ipcMainOn } from "../common/ipc"; -import { initMenu } from "./menu"; -import { initTray } from "./tray"; import { Singleton } from "../common/utils"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; @@ -38,15 +36,15 @@ export class WindowManager extends Singleton { protected mainWindow: BrowserWindow; protected splashWindow: BrowserWindow; protected windowState: windowStateKeeper.State; - protected disposers: Record = {}; @observable activeClusterId: ClusterId; constructor() { super(); - this.bindEvents(); - this.initMenu(); - this.initTray(); + + ipcMainOn(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, (event, clusterId: ClusterId) => { + this.activeClusterId = clusterId; + }); } get mainUrl() { @@ -130,21 +128,6 @@ export class WindowManager extends Singleton { } } - protected async initMenu() { - this.disposers.menuAutoUpdater = initMenu(this); - } - - protected initTray() { - this.disposers.trayAutoUpdater = initTray(this); - } - - protected bindEvents() { - // track visible cluster from ui - ipcMainOn(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, (event, clusterId: ClusterId) => { - this.activeClusterId = clusterId; - }); - } - async ensureMainWindow(): Promise { if (!this.mainWindow) await this.initMainWindow(); this.mainWindow.show(); @@ -214,9 +197,5 @@ export class WindowManager extends Singleton { this.splashWindow.destroy(); this.mainWindow = null; this.splashWindow = null; - Object.entries(this.disposers).forEach(([name, dispose]) => { - dispose(); - delete this.disposers[name]; - }); } } diff --git a/src/migrations/cluster-store/3.6.0-beta.1.ts b/src/migrations/cluster-store/3.6.0-beta.1.ts index df51510f234e7..e47046ddd1437 100644 --- a/src/migrations/cluster-store/3.6.0-beta.1.ts +++ b/src/migrations/cluster-store/3.6.0-beta.1.ts @@ -26,14 +26,15 @@ import path from "path"; import { app, remote } from "electron"; import { migration } from "../migration-wrapper"; import fse from "fs-extra"; -import { ClusterModel, ClusterStore } from "../../common/cluster-store"; import { loadConfig } from "../../common/kube-helpers"; +import type { ClusterModel } from "../../common/cluster-types"; +import { getCustomKubeConfigPath, embedCustomKubeConfig } from "../../common/utils"; export default migration({ version: "3.6.0-beta.1", run(store, printLog) { const userDataPath = (app || remote.app).getPath("userData"); - const kubeConfigBase = ClusterStore.getCustomKubeConfigPath(""); + const kubeConfigBase = getCustomKubeConfigPath(""); const storedClusters: ClusterModel[] = store.get("clusters") || []; if (!storedClusters.length) return; @@ -47,7 +48,7 @@ export default migration({ */ try { // take the embedded kubeconfig and dump it into a file - cluster.kubeConfigPath = ClusterStore.embedCustomKubeConfig(cluster.id, cluster.kubeConfig); + cluster.kubeConfigPath = embedCustomKubeConfig(cluster.id, cluster.kubeConfig); cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext(); delete cluster.kubeConfig; diff --git a/src/migrations/cluster-store/snap.ts b/src/migrations/cluster-store/snap.ts index 3dcf998f1cd81..4ca354eb93790 100644 --- a/src/migrations/cluster-store/snap.ts +++ b/src/migrations/cluster-store/snap.ts @@ -22,9 +22,9 @@ // Fix embedded kubeconfig paths under snap config import { migration } from "../migration-wrapper"; -import type { ClusterModel } from "../../common/cluster-store"; import { getAppVersion } from "../../common/utils/app-version"; import fs from "fs"; +import type { ClusterModel } from "../../common/cluster-types"; export default migration({ version: getAppVersion(), // Run always after upgrade diff --git a/src/renderer/api/__tests__/api-manager.test.ts b/src/renderer/api/__tests__/api-manager.test.ts index 8c7beb12c95c8..25f8a308a3b67 100644 --- a/src/renderer/api/__tests__/api-manager.test.ts +++ b/src/renderer/api/__tests__/api-manager.test.ts @@ -19,43 +19,59 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { ingressStore } from "../../components/+network-ingresses/ingress.store"; -import { apiManager } from "../api-manager"; +import { Cluster } from "../../../main/cluster"; +import { KubeObjectStore } from "../../kube-object.store"; +import { ApiManager } from "../api-manager"; import { KubeApi } from "../kube-api"; +import { KubeObject } from "../kube-object"; class TestApi extends KubeApi { - protected async checkPreferredVersion() { return; } } describe("ApiManager", () => { + beforeEach(() => { + ApiManager.createInstance(new Cluster({ + id: "foobar", + kubeConfigPath: "/foobar", + })); + }); + + afterEach(() => { + ApiManager.resetInstance(); + }); + describe("registerApi", () => { it("re-register store if apiBase changed", async () => { const apiBase = "apis/v1/foo"; const fallbackApiBase = "/apis/extensions/v1beta1/foo"; const kubeApi = new TestApi({ + objectConstructor: KubeObject, apiBase, fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, }); - apiManager.registerApi(apiBase, kubeApi); + ApiManager.getInstance().registerApi(apiBase, kubeApi); + + class TestStore extends KubeObjectStore { + api = kubeApi; + } // Define to use test api for ingress store - Object.defineProperty(ingressStore, "api", { value: kubeApi }); - apiManager.registerStore(ingressStore, [kubeApi]); + ApiManager.getInstance().registerStore(TestStore); // Test that store is returned with original apiBase - expect(apiManager.getStore(kubeApi)).toBe(ingressStore); + expect(ApiManager.getInstance().getStore(kubeApi)).toBeInstanceOf(TestStore); // Change apiBase similar as checkPreferredVersion does Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase }); - apiManager.registerApi(fallbackApiBase, kubeApi); + ApiManager.getInstance().registerApi(fallbackApiBase, kubeApi); // Test that store is returned with new apiBase - expect(apiManager.getStore(kubeApi)).toBe(ingressStore); + expect(ApiManager.getInstance().getStore(kubeApi)).toBeInstanceOf(TestStore); }); }); }); diff --git a/src/renderer/api/__tests__/kube-api.test.ts b/src/renderer/api/__tests__/kube-api.test.ts index c7415cba7310b..99454c0133f8a 100644 --- a/src/renderer/api/__tests__/kube-api.test.ts +++ b/src/renderer/api/__tests__/kube-api.test.ts @@ -20,6 +20,7 @@ */ import { KubeApi } from "../kube-api"; +import { KubeObject } from "../kube-object"; describe("KubeApi", () => { it("uses url from apiBase if apiBase contains the resource", async () => { @@ -53,6 +54,7 @@ describe("KubeApi", () => { const apiBase = "/apis/networking.k8s.io/v1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; const kubeApi = new KubeApi({ + objectConstructor: KubeObject, apiBase, fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, @@ -91,6 +93,7 @@ describe("KubeApi", () => { const apiBase = "apis/networking.k8s.io/v1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; const kubeApi = new KubeApi({ + objectConstructor: KubeObject, apiBase, fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, diff --git a/src/renderer/api/api-manager.ts b/src/renderer/api/api-manager.ts index ada5424bef101..95e14ae9068b1 100644 --- a/src/renderer/api/api-manager.ts +++ b/src/renderer/api/api-manager.ts @@ -19,16 +19,37 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { KubeObjectStore } from "../kube-object.store"; +import type { KubeObjectStore, KubeObjectStoreConstructor } from "../kube-object.store"; import { action, observable } from "mobx"; -import { autobind } from "../utils"; -import { KubeApi, parseKubeApi } from "./kube-api"; +import { autobind, Singleton } from "../utils"; +import type { KubeApi } from "./kube-api"; +import { parseKubeApi } from "./kube-api-parse"; +import type { IKubeApiLinkRef, IKubeObjectRef } from "./kube-api-parse"; +import type { KubeObject } from "./kube-object"; +import type { Cluster } from "../../main/cluster"; + +export function createKubeApiURL(ref: IKubeApiLinkRef): string { + const { apiPrefix = "/apis", resource, apiVersion, name } = ref; + let { namespace } = ref; + + if (namespace) { + namespace = `namespaces/${namespace}`; + } + + return [apiPrefix, apiVersion, namespace, resource, name] + .filter(v => v) + .join("/"); +} @autobind() -export class ApiManager { +export class ApiManager extends Singleton { private apis = observable.map(); - private stores = observable.map(); + private stores = observable.map>(); + + constructor(protected cluster: Cluster) { + super(); + } getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) { if (typeof pathOrCallback === "string") { @@ -71,15 +92,56 @@ export class ApiManager { } @action - registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) { - apis.forEach(api => { + registerStore>(storeConstructor: KubeObjectStoreConstructor, apis?: KubeApi[]): this { + const store = new storeConstructor(this.cluster); + + for (const api of apis ?? [store.api]) { this.stores.set(api.apiBase, store); - }); + } + + return this; } - getStore(api: string | KubeApi): S { - return this.stores.get(this.resolveApi(api)?.apiBase) as S; + getStore>(api: string | KubeApi): Store | undefined { + return this.stores.get(this.resolveApi(api)?.apiBase) as Store; } -} -export const apiManager = new ApiManager(); + lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string { + const { + kind, apiVersion, name, + namespace = parentObject.getNs() + } = ref; + + if (!kind) return ""; + + // search in registered apis by 'kind' & 'apiVersion' + const api = this.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion); + + if (api) { + return api.getUrl({ namespace, name }); + } + + // lookup api by generated resource link + const apiPrefixes = ["/apis", "/api"]; + const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`; + + for (const apiPrefix of apiPrefixes) { + const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource }); + + if (this.getApi(apiLink)) { + return apiLink; + } + } + + // resolve by kind only (hpa's might use refs to older versions of resources for example) + const apiByKind = this.getApi(api => api.kind === kind); + + if (apiByKind) { + return apiByKind.getUrl({ name, namespace }); + } + + // otherwise generate link with default prefix + // resource still might exists in k8s, but api is not registered in the app + return createKubeApiURL({ apiVersion, name, namespace, resource }); + } +} diff --git a/src/renderer/api/endpoints/cluster.api.ts b/src/renderer/api/endpoints/cluster.api.ts index 309f936ac7372..299602a6c942c 100644 --- a/src/renderer/api/endpoints/cluster.api.ts +++ b/src/renderer/api/endpoints/cluster.api.ts @@ -23,7 +23,7 @@ import { IMetrics, IMetricsReqParams, metricsApi } from "./metrics.api"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -export class ClusterApi extends KubeApi { +export class ClusterApi extends KubeApi { static kind = "Cluster"; static namespaced = true; @@ -71,7 +71,7 @@ export interface IClusterMetrics { fsUsage: T; } -export class Cluster extends KubeObject { +export class KubeCluster extends KubeObject { static kind = "Cluster"; static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters"; @@ -117,5 +117,5 @@ export class Cluster extends KubeObject { } export const clusterApi = new ClusterApi({ - objectConstructor: Cluster, + objectConstructor: KubeCluster, }); diff --git a/src/renderer/api/endpoints/persistent-volume-claims.api.ts b/src/renderer/api/endpoints/persistent-volume-claims.api.ts index 92641945d7caa..028f605ee5ec4 100644 --- a/src/renderer/api/endpoints/persistent-volume-claims.api.ts +++ b/src/renderer/api/endpoints/persistent-volume-claims.api.ts @@ -108,6 +108,6 @@ export class PersistentVolumeClaim extends KubeObject { } } -export const pvcApi = new PersistentVolumeClaimsApi({ +export const persistentVolumeClaimsApi = new PersistentVolumeClaimsApi({ objectConstructor: PersistentVolumeClaim, }); diff --git a/src/renderer/api/endpoints/poddisruptionbudget.api.ts b/src/renderer/api/endpoints/poddisruptionbudget.api.ts index b9763b093cb36..7569484607b54 100644 --- a/src/renderer/api/endpoints/poddisruptionbudget.api.ts +++ b/src/renderer/api/endpoints/poddisruptionbudget.api.ts @@ -65,6 +65,6 @@ export class PodDisruptionBudget extends KubeObject { } -export const pdbApi = new KubeApi({ +export const podDisruptionBudgetApi = new KubeApi({ objectConstructor: PodDisruptionBudget, }); diff --git a/src/renderer/api/endpoints/podsecuritypolicy.api.ts b/src/renderer/api/endpoints/podsecuritypolicy.api.ts index 1244c3e13eac7..59c384950c1b0 100644 --- a/src/renderer/api/endpoints/podsecuritypolicy.api.ts +++ b/src/renderer/api/endpoints/podsecuritypolicy.api.ts @@ -110,6 +110,6 @@ export class PodSecurityPolicy extends KubeObject { } } -export const pspApi = new KubeApi({ +export const podSecurityPolicyApi = new KubeApi({ objectConstructor: PodSecurityPolicy, }); diff --git a/src/renderer/api/endpoints/resource-applier.api.ts b/src/renderer/api/endpoints/resource-applier.api.ts index 1756977c95ba7..f2a9cff5bf516 100644 --- a/src/renderer/api/endpoints/resource-applier.api.ts +++ b/src/renderer/api/endpoints/resource-applier.api.ts @@ -20,35 +20,21 @@ */ import jsYaml from "js-yaml"; -import { KubeObject } from "../kube-object"; import type { KubeJsonApiData } from "../kube-json-api"; import { apiBase } from "../index"; -import { apiManager } from "../api-manager"; export const resourceApplierApi = { annotations: [ "kubectl.kubernetes.io/last-applied-configuration" ], - async update(resource: object | string): Promise { + async update(resource: object | string): Promise { if (typeof resource === "string") { resource = jsYaml.safeLoad(resource); } - return apiBase - .post("/stack", { data: resource }) - .then(data => { - const items = data.map(obj => { - const api = apiManager.getApiByKind(obj.kind, obj.apiVersion); + const result = await apiBase.post("/stack", { data: resource }); - if (api) { - return new api.objectConstructor(obj); - } else { - return new KubeObject(obj); - } - }); - - return items.length === 1 ? items[0] : items; - }); + return result[0]; } }; diff --git a/src/renderer/api/kube-api-parse.ts b/src/renderer/api/kube-api-parse.ts index b02067fb63111..45e69e20b4deb 100644 --- a/src/renderer/api/kube-api-parse.ts +++ b/src/renderer/api/kube-api-parse.ts @@ -21,9 +21,7 @@ // Parse kube-api path and get api-version, group, etc. -import type { KubeObject } from "./kube-object"; import { splitArray } from "../../common/utils"; -import { apiManager } from "./api-manager"; export interface IKubeObjectRef { kind: string; @@ -123,55 +121,3 @@ export function parseKubeApi(path: string): IKubeApiParsed { namespace, resource, name, }; } - -export function createKubeApiURL(ref: IKubeApiLinkRef): string { - const { apiPrefix = "/apis", resource, apiVersion, name } = ref; - let { namespace } = ref; - - if (namespace) { - namespace = `namespaces/${namespace}`; - } - - return [apiPrefix, apiVersion, namespace, resource, name] - .filter(v => v) - .join("/"); -} - -export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string { - const { - kind, apiVersion, name, - namespace = parentObject.getNs() - } = ref; - - if (!kind) return ""; - - // search in registered apis by 'kind' & 'apiVersion' - const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion); - - if (api) { - return api.getUrl({ namespace, name }); - } - - // lookup api by generated resource link - const apiPrefixes = ["/apis", "/api"]; - const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`; - - for (const apiPrefix of apiPrefixes) { - const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource }); - - if (apiManager.getApi(apiLink)) { - return apiLink; - } - } - - // resolve by kind only (hpa's might use refs to older versions of resources for example) - const apiByKind = apiManager.getApi(api => api.kind === kind); - - if (apiByKind) { - return apiByKind.getUrl({ name, namespace }); - } - - // otherwise generate link with default prefix - // resource still might exists in k8s, but api is not registered in the app - return createKubeApiURL({ apiVersion, name, namespace, resource }); -} diff --git a/src/renderer/api/kube-api.ts b/src/renderer/api/kube-api.ts index 21e370a207d21..4cef93015930d 100644 --- a/src/renderer/api/kube-api.ts +++ b/src/renderer/api/kube-api.ts @@ -25,9 +25,9 @@ import merge from "lodash/merge"; import { stringify } from "querystring"; import { apiKubePrefix, isDevelopment, isTestEnv } from "../../common/vars"; import logger from "../../main/logger"; -import { apiManager } from "./api-manager"; +import { ApiManager, createKubeApiURL } from "./api-manager"; import { apiKube } from "./index"; -import { createKubeApiURL, parseKubeApi } from "./kube-api-parse"; +import { parseKubeApi } from "./kube-api-parse"; import { IKubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object"; import byline from "byline"; import type { IKubeWatchEvent } from "./kube-watch-api"; @@ -49,18 +49,13 @@ export interface IKubeApiOptions { */ fallbackApiBases?: string[]; - objectConstructor?: IKubeObjectConstructor; + objectConstructor: IKubeObjectConstructor; request?: KubeJsonApi; isNamespaced?: boolean; kind?: string; checkPreferredVersion?: boolean; } -export interface KubeApiListOptions { - namespace?: string; - reqInit?: RequestInit; -} - export interface IKubeApiQueryParams { watch?: boolean | number; resourceVersion?: string; @@ -152,7 +147,7 @@ export class KubeApi { constructor(protected options: IKubeApiOptions) { const { - objectConstructor = KubeObject as IKubeObjectConstructor, + objectConstructor, request = apiKube, kind = options.objectConstructor?.kind, isNamespaced = options.objectConstructor?.namespaced @@ -174,8 +169,7 @@ export class KubeApi { this.objectConstructor = objectConstructor; this.checkPreferredVersion(); - this.parseResponse = this.parseResponse.bind(this); - apiManager.registerApi(apiBase, this); + ApiManager.getInstance().registerApi(apiBase, this); } get apiVersionWithGroup() { @@ -264,7 +258,7 @@ export class KubeApi { if (this.apiVersionPreferred) { Object.defineProperty(this, "apiBase", { value: this.getUrl() }); - apiManager.registerApi(this.apiBase, this); + ApiManager.getInstance().registerApi(this.apiBase, this); } } } @@ -506,5 +500,3 @@ export class KubeApi { } } } - -export * from "./kube-api-parse"; diff --git a/src/renderer/api/kube-object.ts b/src/renderer/api/kube-object.ts index 06c431851cbfa..ecdfaeedcf1aa 100644 --- a/src/renderer/api/kube-object.ts +++ b/src/renderer/api/kube-object.ts @@ -265,11 +265,11 @@ export class KubeObject implements ItemObject { } // use unified resource-applier api for updating all k8s objects - async update(data: Partial) { - return resourceApplierApi.update({ + async updateReturnNew(data: Partial): Promise { + return new (this.constructor as any)(resourceApplierApi.update({ ...this.toPlainObject(), ...data, - }); + })); } delete(params?: JsonApiParams) { diff --git a/src/renderer/api/kube-watch-api.ts b/src/renderer/api/kube-watch-api.ts index 4c68e0fdfa4c4..317e9bdbafce1 100644 --- a/src/renderer/api/kube-watch-api.ts +++ b/src/renderer/api/kube-watch-api.ts @@ -23,14 +23,17 @@ // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams import type { KubeObjectStore } from "../kube-object.store"; -import type { ClusterContext } from "../components/context"; +import { allNamespaces, selectedNamespaces } from "../components/context"; import plimit from "p-limit"; -import { comparer, IReactionDisposer, observable, reaction, when } from "mobx"; -import { autobind, noop } from "../utils"; -import type { KubeApi } from "./kube-api"; +import { comparer, IReactionDisposer, reaction } from "mobx"; +import { autobind, Disposer, noop, Singleton } from "../utils"; import type { KubeJsonApiData } from "./kube-json-api"; import { isDebugging, isProduction } from "../../common/vars"; +import type { Cluster } from "../../main/cluster"; +import type { KubeObject } from "./kube-object"; +import type { KubeApi } from "./kube-api"; +import { ApiManager } from "./api-manager"; export interface IKubeWatchEvent { type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR"; @@ -51,16 +54,12 @@ export interface IKubeWatchLog { } @autobind() -export class KubeWatchApi { - @observable context: ClusterContext = null; - - contextReady = when(() => Boolean(this.context)); - - isAllowedApi(api: KubeApi): boolean { - return Boolean(this.context?.cluster.isAllowedResource(api.kind)); +export class KubeWatchApi extends Singleton { + constructor(protected cluster: Cluster) { + super(); } - preloadStores(stores: KubeObjectStore[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) { + private preloadStores(stores: KubeObjectStore[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) { const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages const preloading: Promise[] = []; @@ -78,9 +77,15 @@ export class KubeWatchApi { }; } - subscribeStores(stores: KubeObjectStore[], opts: IKubeWatchSubscribeStoreOptions = {}): () => void { + subscribeApis(apis: KubeApi[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer { + const manager = ApiManager.getInstance(); + + return this.subscribeStores(apis.map(api => manager.getStore(api)), opts); + } + + subscribeStores(stores: KubeObjectStore[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer { const { preload = true, waitUntilLoaded = true, loadOnce = false, } = opts; - const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? []; + const subscribingNamespaces = opts.namespaces ?? allNamespaces(this.cluster); const unsubscribeList: Function[] = []; let isUnsubscribed = false; @@ -109,7 +114,7 @@ export class KubeWatchApi { } // reload stores only for context namespaces change - cancelReloading = reaction(() => this.context?.contextNamespaces, namespaces => { + cancelReloading = reaction(() => selectedNamespaces(), namespaces => { preloading?.cancelLoading(); unsubscribeList.forEach(unsubscribe => unsubscribe()); unsubscribeList.length = 0; @@ -149,5 +154,3 @@ export class KubeWatchApi { } } } - -export const kubeWatchApi = new KubeWatchApi(); diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 0a59d069282ad..db1f39c284122 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -37,7 +37,7 @@ import { ExtensionDiscovery } from "../extensions/extension-discovery"; import { ExtensionLoader } from "../extensions/extension-loader"; import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "../main/extension-filesystem"; -import { App } from "./components/app"; +import { ClusterFrame } from "./components/app"; import { LensApp } from "./lens-app"; import { ThemeStore } from "./theme.store"; import { HelmRepoManager } from "../main/helm/helm-repo-manager"; @@ -127,4 +127,4 @@ export async function bootstrap(App: AppComponent) { } // run -bootstrap(process.isMainFrame ? LensApp : App); +bootstrap(process.isMainFrame ? LensApp : ClusterFrame); diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index e211c2d6f143a..2320076a37c0e 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -38,6 +38,7 @@ import { PageLayout } from "../layout/page-layout"; import { docsUrl } from "../../../common/vars"; import { Input } from "../input"; import { catalogURL, preferencesURL } from "../../../common/routes"; +import { embedCustomKubeConfig } from "../../utils"; @observer export class AddCluster extends React.Component { @@ -107,7 +108,7 @@ export class AddCluster extends React.Component { }).map(context => { const clusterId = uuid(); const kubeConfig = this.kubeContexts.get(context); - const kubeConfigPath = ClusterStore.embedCustomKubeConfig(clusterId, kubeConfig); // save in app-files folder + const kubeConfigPath = embedCustomKubeConfig(clusterId, kubeConfig); // save in app-files folder return { id: clusterId, diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx index 260611e420a76..fe225db7b5cb9 100644 --- a/src/renderer/components/+apps-releases/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details.tsx @@ -37,14 +37,14 @@ import { Spinner } from "../spinner"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { AceEditor } from "../ace-editor"; import { Button } from "../button"; -import { releaseStore } from "./release.store"; +import { ReleaseStore } from "./release.store"; import { Notifications } from "../notifications"; import { createUpgradeChartTab } from "../dock/upgrade-chart.store"; import { ThemeStore } from "../../theme.store"; -import { apiManager } from "../../api/api-manager"; +import { ApiManager } from "../../api/api-manager"; import { SubTitle } from "../layout/sub-title"; -import { secretsStore } from "../+config-secrets/secrets.store"; -import { Secret } from "../../api/endpoints"; +import type { SecretsStore } from "../+config-secrets/secrets.store"; +import { Secret, secretsApi } from "../../api/endpoints"; import { getDetailsUrl } from "../kube-object"; import { Checkbox } from "../checkbox"; @@ -62,6 +62,10 @@ export class ReleaseDetails extends Component { @observable saving = false; @observable releaseSecret: Secret; + private get secretsStore() { + return ApiManager.getInstance().getStore(secretsApi); + } + @disposeOnUnmount releaseSelector = reaction(() => this.props.release, release => { if (!release) return; @@ -72,9 +76,9 @@ export class ReleaseDetails extends Component { ); @disposeOnUnmount - secretWatcher = reaction(() => secretsStore.items.toJS(), () => { + secretWatcher = reaction(() => this.secretsStore.items.toJS(), () => { if (!this.props.release) return; - const { getReleaseSecret } = releaseStore; + const { getReleaseSecret } = ReleaseStore.getInstance(); const { release } = this.props; const secret = getReleaseSecret(release); @@ -115,7 +119,7 @@ export class ReleaseDetails extends Component { this.saving = true; try { - await releaseStore.update(name, namespace, data); + await ReleaseStore.getInstance().update(name, namespace, data); Notifications.ok(

Release {name} successfully updated!

); @@ -197,7 +201,7 @@ export class ReleaseDetails extends Component { {items.map(item => { const name = item.getName(); const namespace = item.getNs(); - const api = apiManager.getApi(item.metadata.selfLink); + const api = ApiManager.getInstance().getApi(item.metadata.selfLink); const detailsUrl = api ? getDetailsUrl(api.getUrl({ name, namespace })) : ""; return ( diff --git a/src/renderer/components/+apps-releases/release-menu.tsx b/src/renderer/components/+apps-releases/release-menu.tsx index 78e4c8e94ba0b..5b13ab77be0ef 100644 --- a/src/renderer/components/+apps-releases/release-menu.tsx +++ b/src/renderer/components/+apps-releases/release-menu.tsx @@ -22,12 +22,12 @@ import React from "react"; import type { HelmRelease } from "../../api/endpoints/helm-releases.api"; import { autobind, cssNames } from "../../utils"; -import { releaseStore } from "./release.store"; import { MenuActions, MenuActionsProps } from "../menu/menu-actions"; import { MenuItem } from "../menu"; import { Icon } from "../icon"; import { ReleaseRollbackDialog } from "./release-rollback-dialog"; import { createUpgradeChartTab } from "../dock/upgrade-chart.store"; +import { ReleaseStore } from "./release.store"; interface Props extends MenuActionsProps { release: HelmRelease; @@ -37,7 +37,7 @@ interface Props extends MenuActionsProps { export class HelmReleaseMenu extends React.Component { @autobind() remove() { - return releaseStore.remove(this.props.release); + return ReleaseStore.getInstance().remove(this.props.release); } @autobind() diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog.tsx b/src/renderer/components/+apps-releases/release-rollback-dialog.tsx index bb25f6f5d66b0..8d17f89d65b03 100644 --- a/src/renderer/components/+apps-releases/release-rollback-dialog.tsx +++ b/src/renderer/components/+apps-releases/release-rollback-dialog.tsx @@ -27,7 +27,7 @@ import { observer } from "mobx-react"; import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; import { getReleaseHistory, HelmRelease, IReleaseRevision } from "../../api/endpoints/helm-releases.api"; -import { releaseStore } from "./release.store"; +import { ReleaseStore } from "./release.store"; import { Select, SelectOption } from "../select"; import { Notifications } from "../notifications"; import orderBy from "lodash/orderBy"; @@ -73,7 +73,7 @@ export class ReleaseRollbackDialog extends React.Component { const revisionNumber = this.revision.revision; try { - await releaseStore.rollback(this.release.getName(), this.release.getNs(), revisionNumber); + await ReleaseStore.getInstance().rollback(this.release.getName(), this.release.getNs(), revisionNumber); this.close(); } catch (err) { Notifications.error(err); diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts index 7ed2ac42c73d1..55133b3c585d4 100644 --- a/src/renderer/components/+apps-releases/release.store.ts +++ b/src/renderer/components/+apps-releases/release.store.ts @@ -24,24 +24,44 @@ import { action, observable, reaction, when } from "mobx"; import { autobind } from "../../utils"; import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../api/endpoints/helm-releases.api"; import { ItemStore } from "../../item.store"; -import type { Secret } from "../../api/endpoints"; -import { secretsStore } from "../+config-secrets/secrets.store"; -import { namespaceStore } from "../+namespaces/namespace.store"; +import { Secret, secretsApi } from "../../api/endpoints"; +import type { SecretsStore } from "../+config-secrets/secrets.store"; import { Notifications } from "../notifications"; +import { ApiManager } from "../../api/api-manager"; +import { isLoadingFromAllNamespaces, selectedNamespaces } from "../context"; +import type { Cluster } from "../../../main/cluster"; @autobind() export class ReleaseStore extends ItemStore { + private static instance?: ReleaseStore; + + static getInstance() { + if (!this.instance) { + throw new TypeError("instance of ReleaseStore is not created"); + } + + return this.instance; + } + + static createInstance(cluster: Cluster) { + return this.instance ??= new this(cluster); + } + + private get secretsStore() { + return ApiManager.getInstance().getStore(secretsApi); + } + releaseSecrets = observable.map(); - constructor() { + private constructor(protected cluster: Cluster) { super(); - when(() => secretsStore.isLoaded, () => { + when(() => this.secretsStore.isLoaded, () => { this.releaseSecrets.replace(this.getReleaseSecrets()); }); } watchAssociatedSecrets(): (() => void) { - return reaction(() => secretsStore.items.toJS(), () => { + return reaction(() => this.secretsStore.items.toJS(), () => { if (this.isLoading) return; const newSecrets = this.getReleaseSecrets(); const amountChanged = newSecrets.length !== this.releaseSecrets.size; @@ -57,19 +77,19 @@ export class ReleaseStore extends ItemStore { } watchSelecteNamespaces(): (() => void) { - return reaction(() => namespaceStore.context.contextNamespaces, namespaces => { + return reaction(() => selectedNamespaces(), namespaces => { this.loadAll(namespaces); }); } private getReleaseSecrets() { - return secretsStore + return this.secretsStore .getByLabel({ owner: "helm" }) .map(s => [s.getId(), s] as const); } getReleaseSecret(release: HelmRelease) { - return secretsStore.getByLabel({ + return this.secretsStore.getByLabel({ owner: "helm", name: release.getName() }) @@ -100,15 +120,11 @@ export class ReleaseStore extends ItemStore { } async loadFromContextNamespaces(): Promise { - return this.loadAll(namespaceStore.context.contextNamespaces); + return this.loadAll(selectedNamespaces()); } async loadItems(namespaces: string[]) { - const isLoadingAll = namespaceStore.context.allNamespaces?.length > 1 - && namespaceStore.context.cluster.accessibleNamespaces.length === 0 - && namespaceStore.context.allNamespaces.every(ns => namespaces.includes(ns)); - - if (isLoadingAll) { + if (isLoadingFromAllNamespaces(this.cluster, namespaces)) { return listReleases(); } @@ -149,5 +165,3 @@ export class ReleaseStore extends ItemStore { return Promise.all(this.selectedItems.map(this.remove)); } } - -export const releaseStore = new ReleaseStore(); diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index 32fa7ada43429..b3ee7d1e7cf95 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -25,15 +25,17 @@ import React, { Component } from "react"; import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; -import { releaseStore } from "./release.store"; +import { ReleaseStore } from "./release.store"; import type { HelmRelease } from "../../api/endpoints/helm-releases.api"; import { ReleaseDetails } from "./release-details"; import { ReleaseRollbackDialog } from "./release-rollback-dialog"; import { navigation } from "../../navigation"; import { ItemListLayout } from "../item-object-list/item-list-layout"; import { HelmReleaseMenu } from "./release-menu"; -import { secretsStore } from "../+config-secrets/secrets.store"; +import type { SecretsStore } from "../+config-secrets/secrets.store"; import { ReleaseRouteParams, releaseURL } from "../../../common/routes"; +import { secretsApi } from "../../api/endpoints"; +import { ApiManager } from "../../api/api-manager"; enum columnId { name = "name", @@ -51,17 +53,21 @@ interface Props extends RouteComponentProps { @observer export class HelmReleases extends Component { + private get secretsStore() { + return ApiManager.getInstance().getStore(secretsApi); + } + componentDidMount() { disposeOnUnmount(this, [ - releaseStore.watchAssociatedSecrets(), - releaseStore.watchSelecteNamespaces(), + ReleaseStore.getInstance().watchAssociatedSecrets(), + ReleaseStore.getInstance().watchSelecteNamespaces(), ]); } get selectedRelease() { const { match: { params: { name, namespace } } } = this.props; - return releaseStore.items.find(release => { + return ReleaseStore.getInstance().items.find(release => { return release.getName() == name && release.getNs() == namespace; }); } @@ -99,8 +105,8 @@ export class HelmReleases extends Component { isConfigurable tableId="helm_releases" className="HelmReleases" - store={releaseStore} - dependentStores={[secretsStore]} + store={ReleaseStore.getInstance()} + dependentStores={[this.secretsStore]} sortingCallbacks={{ [columnId.name]: (release: HelmRelease) => release.getName(), [columnId.namespace]: (release: HelmRelease) => release.getNs(), diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx index 346dd255a4e31..550e61a6d9d69 100644 --- a/src/renderer/components/+apps/apps.tsx +++ b/src/renderer/components/+apps/apps.tsx @@ -26,10 +26,11 @@ import { HelmCharts } from "../+apps-helm-charts"; import { HelmReleases } from "../+apps-releases"; import { namespaceUrlParam } from "../+namespaces/namespace.store"; import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes"; +import type { Cluster } from "../../../main/cluster"; @observer -export class Apps extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { +export class Apps extends React.Component<{ cluster: Cluster }> { + static tabRoutes(): TabLayoutRoute[] { const query = namespaceUrlParam.toObjectParam(); return [ @@ -50,7 +51,7 @@ export class Apps extends React.Component { render() { return ( - + ); } } diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index ce6496f4905f4..771b6d0ebb9a8 100644 --- a/src/renderer/components/+cluster/cluster-issues.tsx +++ b/src/renderer/components/+cluster/cluster-issues.tsx @@ -27,14 +27,15 @@ import { computed } from "mobx"; import { Icon } from "../icon"; import { SubHeader } from "../layout/sub-header"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import { nodesStore } from "../+nodes/nodes.store"; -import { eventStore } from "../+events/event.store"; +import type { NodesStore } from "../+nodes"; +import type { EventStore } from "../+events/event.store"; import { autobind, cssNames, prevDefault } from "../../utils"; import type { ItemObject } from "../../item.store"; import { Spinner } from "../spinner"; import { ThemeStore } from "../../theme.store"; -import { lookupApiLink } from "../../api/kube-api"; import { kubeSelectedUrlParam, showDetails } from "../kube-object"; +import { ApiManager } from "../../api/api-manager"; +import { eventApi, nodesApi } from "../../api/endpoints"; interface Props { className?: string; @@ -62,11 +63,19 @@ export class ClusterIssues extends React.Component { [sortBy.age]: (warning: IWarning) => warning.timeDiffFromNow, }; + private get nodesStore() { + return ApiManager.getInstance().getStore(nodesApi); + } + + private get eventStore() { + return ApiManager.getInstance().getStore(eventApi); + } + @computed get warnings() { const warnings: IWarning[] = []; // Node bad conditions - nodesStore.items.forEach(node => { + this.nodesStore.items.forEach(node => { const { kind, selfLink, getId, getName, getAge, getTimeDiffFromNow } = node; node.getWarningConditions().forEach(({ message }) => { @@ -83,7 +92,7 @@ export class ClusterIssues extends React.Component { }); // Warning events for Workloads - const events = eventStore.getWarnings(); + const events = this.eventStore.getWarnings(); events.forEach(error => { const { message, involvedObject, getAge, getTimeDiffFromNow } = error; @@ -96,7 +105,7 @@ export class ClusterIssues extends React.Component { age: getAge(), message, kind, - selfLink: lookupApiLink(involvedObject, error), + selfLink: ApiManager.getInstance().lookupApiLink(involvedObject, error), }); }); @@ -135,7 +144,7 @@ export class ClusterIssues extends React.Component { renderContent() { const { warnings } = this; - if (!eventStore.isLoaded) { + if (!this.eventStore.isLoaded) { return ( ); diff --git a/src/renderer/components/+cluster/cluster-metric-switchers.tsx b/src/renderer/components/+cluster/cluster-metric-switchers.tsx index b80dd10527e1d..8b41c512b6050 100644 --- a/src/renderer/components/+cluster/cluster-metric-switchers.tsx +++ b/src/renderer/components/+cluster/cluster-metric-switchers.tsx @@ -23,12 +23,16 @@ import "./cluster-metric-switchers.scss"; import React from "react"; import { observer } from "mobx-react"; -import { nodesStore } from "../+nodes/nodes.store"; +import type { NodesStore } from "../+nodes"; import { cssNames } from "../../utils"; import { Radio, RadioGroup } from "../radio"; -import { clusterOverviewStore, MetricNodeRole, MetricType } from "./cluster-overview.store"; +import { ClusterObjectStore, MetricNodeRole, MetricType } from "./cluster-overview.store"; +import { ApiManager } from "../../api/api-manager"; +import { clusterApi, nodesApi } from "../../api/endpoints"; export const ClusterMetricSwitchers = observer(() => { + const nodesStore = ApiManager.getInstance().getStore(nodesApi); + const clusterOverviewStore = ApiManager.getInstance().getStore(clusterApi); const { metricType, metricNodeRole, getMetricsValues, metrics } = clusterOverviewStore; const { masterNodes, workerNodes } = nodesStore; const metricsValues = getMetricsValues(metrics); diff --git a/src/renderer/components/+cluster/cluster-metrics.tsx b/src/renderer/components/+cluster/cluster-metrics.tsx index 0c4910e7e5789..f6b8757546d4e 100644 --- a/src/renderer/components/+cluster/cluster-metrics.tsx +++ b/src/renderer/components/+cluster/cluster-metrics.tsx @@ -24,7 +24,7 @@ import "./cluster-metrics.scss"; import React from "react"; import { observer } from "mobx-react"; import type { ChartOptions, ChartPoint } from "chart.js"; -import { clusterOverviewStore, MetricType } from "./cluster-overview.store"; +import { ClusterObjectStore, MetricType } from "./cluster-overview.store"; import { BarChart } from "../chart"; import { bytesToUnits } from "../../utils"; import { Spinner } from "../spinner"; @@ -32,8 +32,11 @@ import { ZebraStripes } from "../chart/zebra-stripes.plugin"; import { ClusterNoMetrics } from "./cluster-no-metrics"; import { ClusterMetricSwitchers } from "./cluster-metric-switchers"; import { getMetricLastPoints } from "../../api/endpoints/metrics.api"; +import { ApiManager } from "../../api/api-manager"; +import { clusterApi } from "../../api/endpoints"; export const ClusterMetrics = observer(() => { + const clusterOverviewStore = ApiManager.getInstance().getStore(clusterApi); const { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics } = clusterOverviewStore; const { memoryCapacity, cpuCapacity } = getMetricLastPoints(clusterOverviewStore.metrics); const metricValues = getMetricsValues(metrics); diff --git a/src/renderer/components/+cluster/cluster-overview.store.ts b/src/renderer/components/+cluster/cluster-overview.store.ts index c9ffc4008c023..031a5dab2bc3c 100644 --- a/src/renderer/components/+cluster/cluster-overview.store.ts +++ b/src/renderer/components/+cluster/cluster-overview.store.ts @@ -21,11 +21,11 @@ import { action, observable, reaction, when } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; -import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints"; +import { KubeCluster, clusterApi, IClusterMetrics, nodesApi } from "../../api/endpoints"; import { autobind, createStorage } from "../../utils"; import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api"; -import { nodesStore } from "../+nodes/nodes.store"; -import { apiManager } from "../../api/api-manager"; +import type { NodesStore } from "../+nodes/nodes.store"; +import { ApiManager } from "../../api/api-manager"; export enum MetricType { MEMORY = "memory", @@ -43,7 +43,7 @@ export interface ClusterOverviewStorageState { } @autobind() -export class ClusterOverviewStore extends KubeObjectStore implements ClusterOverviewStorageState { +export class ClusterObjectStore extends KubeObjectStore implements ClusterOverviewStorageState { api = clusterApi; @observable metrics: Partial = {}; @@ -70,12 +70,11 @@ export class ClusterOverviewStore extends KubeObjectStore implements Cl this.storage.merge({ metricNodeRole: value }); } - constructor() { - super(); - this.init(); + private get nodesStore() { + return ApiManager.getInstance().getStore(nodesApi); } - private init() { + protected init = () => { // TODO: refactor, seems not a correct place to be // auto-refresh metrics on user-action reaction(() => this.metricNodeRole, () => { @@ -85,18 +84,18 @@ export class ClusterOverviewStore extends KubeObjectStore implements Cl }); // check which node type to select - reaction(() => nodesStore.items.length, () => { - const { masterNodes, workerNodes } = nodesStore; + reaction(() => this.nodesStore.items.length, () => { + const { masterNodes, workerNodes } = this.nodesStore; if (!masterNodes.length) this.metricNodeRole = MetricNodeRole.WORKER; if (!workerNodes.length) this.metricNodeRole = MetricNodeRole.MASTER; }); - } + }; @action async loadMetrics(params?: IMetricsReqParams) { - await when(() => nodesStore.isLoaded); - const { masterNodes, workerNodes } = nodesStore; + await when(() => this.nodesStore.isLoaded); + const { masterNodes, workerNodes } = this.nodesStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; this.metrics = await clusterApi.getMetrics(nodes.map(node => node.getName()), params); @@ -126,6 +125,3 @@ export class ClusterOverviewStore extends KubeObjectStore implements Cl this.storage?.reset(); } } - -export const clusterOverviewStore = new ClusterOverviewStore(); -apiManager.registerStore(clusterOverviewStore); diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index cc72c7cc6328b..4466d475ab695 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -24,24 +24,38 @@ import "./cluster-overview.scss"; import React from "react"; import { reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import { nodesStore } from "../+nodes/nodes.store"; -import { podsStore } from "../+workloads-pods/pods.store"; import { ClusterStore, getHostedCluster } from "../../../common/cluster-store"; import { interval } from "../../utils"; import { TabLayout } from "../layout/tab-layout"; import { Spinner } from "../spinner"; import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; -import { clusterOverviewStore } from "./cluster-overview.store"; import { ClusterPieCharts } from "./cluster-pie-charts"; import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; +import { clusterApi, nodesApi, podsApi } from "../../api/endpoints"; +import type { NodesStore } from "../+nodes"; +import type { PodsStore } from "../+workloads-pods"; +import { ApiManager } from "../../api/api-manager"; +import type { ClusterObjectStore } from "./cluster-overview.store"; @observer export class ClusterOverview extends React.Component { + private get nodesStore() { + return ApiManager.getInstance().getStore(nodesApi); + } + + private get podsStore() { + return ApiManager.getInstance().getStore(podsApi); + } + + private get clusterObjectStore() { + return ApiManager.getInstance().getStore(clusterApi); + } + private metricPoller = interval(60, () => this.loadMetrics()); loadMetrics() { - getHostedCluster().available && clusterOverviewStore.loadMetrics(); + getHostedCluster().available && this.clusterObjectStore.loadMetrics(); } componentDidMount() { @@ -49,7 +63,7 @@ export class ClusterOverview extends React.Component { disposeOnUnmount(this, [ reaction( - () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher + () => this.clusterObjectStore.metricNodeRole, // Toggle Master/Worker node switcher () => this.metricPoller.restart(true) ), ]); @@ -86,7 +100,7 @@ export class ClusterOverview extends React.Component { } render() { - const isLoaded = nodesStore.isLoaded && podsStore.isLoaded; + const isLoaded = this.nodesStore.isLoaded && this.podsStore.isLoaded; const isMetricsHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Cluster); return ( diff --git a/src/renderer/components/+cluster/cluster-pie-charts.tsx b/src/renderer/components/+cluster/cluster-pie-charts.tsx index b7b84c817dd07..a78c486062e0c 100644 --- a/src/renderer/components/+cluster/cluster-pie-charts.tsx +++ b/src/renderer/components/+cluster/cluster-pie-charts.tsx @@ -23,17 +23,22 @@ import "./cluster-pie-charts.scss"; import React from "react"; import { observer } from "mobx-react"; -import { clusterOverviewStore, MetricNodeRole } from "./cluster-overview.store"; +import { ClusterObjectStore, MetricNodeRole } from "./cluster-overview.store"; import { Spinner } from "../spinner"; import { Icon } from "../icon"; -import { nodesStore } from "../+nodes/nodes.store"; +import type { NodesStore } from "../+nodes"; import { ChartData, PieChart } from "../chart"; import { ClusterNoMetrics } from "./cluster-no-metrics"; import { bytesToUnits } from "../../utils"; import { ThemeStore } from "../../theme.store"; import { getMetricLastPoints } from "../../api/endpoints/metrics.api"; +import { ApiManager } from "../../api/api-manager"; +import { clusterApi, nodesApi } from "../../api/endpoints"; export const ClusterPieCharts = observer(() => { + const clusterOverviewStore = ApiManager.getInstance().getStore(clusterApi); + const nodesStore = ApiManager.getInstance().getStore(nodesApi); + const renderLimitWarning = () => { return (
diff --git a/src/renderer/components/+config-autoscalers/hpa-details.tsx b/src/renderer/components/+config-autoscalers/hpa-details.tsx index 1a6db54e78c65..31d83931e4d23 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -30,8 +30,8 @@ import { KubeObjectDetailsProps, getDetailsUrl } from "../kube-object"; import { cssNames } from "../../utils"; import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import { lookupApiLink } from "../../api/kube-api"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { ApiManager } from "../../api/api-manager"; interface Props extends KubeObjectDetailsProps { } @@ -54,7 +54,7 @@ export class HpaDetails extends React.Component { case HpaMetricType.Object: const { target } = metric.object; const { kind, name } = target; - const objectUrl = getDetailsUrl(lookupApiLink(target, hpa)); + const objectUrl = getDetailsUrl(ApiManager.getInstance().lookupApiLink(target, hpa)); return ( <> @@ -107,7 +107,7 @@ export class HpaDetails extends React.Component { {scaleTargetRef && ( - + {scaleTargetRef.kind}/{scaleTargetRef.name} )} diff --git a/src/renderer/components/+config-autoscalers/hpa.store.ts b/src/renderer/components/+config-autoscalers/hpa.store.ts index aa94a4a3cd1e3..3553ac7dcdbad 100644 --- a/src/renderer/components/+config-autoscalers/hpa.store.ts +++ b/src/renderer/components/+config-autoscalers/hpa.store.ts @@ -22,12 +22,8 @@ import { autobind } from "../../utils"; import { KubeObjectStore } from "../../kube-object.store"; import { HorizontalPodAutoscaler, hpaApi } from "../../api/endpoints/hpa.api"; -import { apiManager } from "../../api/api-manager"; @autobind() -export class HPAStore extends KubeObjectStore { +export class HpaStore extends KubeObjectStore { api = hpaApi; } - -export const hpaStore = new HPAStore(); -apiManager.registerStore(hpaStore); diff --git a/src/renderer/components/+config-autoscalers/hpa.tsx b/src/renderer/components/+config-autoscalers/hpa.tsx index d5c65216bed2d..2c50f3f9a39ff 100644 --- a/src/renderer/components/+config-autoscalers/hpa.tsx +++ b/src/renderer/components/+config-autoscalers/hpa.tsx @@ -25,12 +25,13 @@ import React from "react"; import { observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object"; -import type { HorizontalPodAutoscaler } from "../../api/endpoints/hpa.api"; -import { hpaStore } from "./hpa.store"; +import { HorizontalPodAutoscaler, hpaApi } from "../../api/endpoints/hpa.api"; import { Badge } from "../badge"; import { cssNames } from "../../utils"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { IHpaRouteParams } from "../../../common/routes"; +import { ApiManager } from "../../api/api-manager"; +import type { HpaStore } from "./hpa.store"; enum columnId { name = "name", @@ -48,6 +49,10 @@ interface Props extends RouteComponentProps { @observer export class HorizontalPodAutoscalers extends React.Component { + private get hpaStore() { + return ApiManager.getInstance().getStore(hpaApi); + } + getTargets(hpa: HorizontalPodAutoscaler) { const metrics = hpa.getMetrics(); const metricsRemainCount = metrics.length - 1; @@ -62,7 +67,8 @@ export class HorizontalPodAutoscalers extends React.Component { item.getName(), [columnId.namespace]: (item: HorizontalPodAutoscaler) => item.getNs(), diff --git a/src/renderer/components/+config-autoscalers/index.ts b/src/renderer/components/+config-autoscalers/index.ts index d7c91afc15679..d6a715f35e895 100644 --- a/src/renderer/components/+config-autoscalers/index.ts +++ b/src/renderer/components/+config-autoscalers/index.ts @@ -20,4 +20,5 @@ */ export * from "./hpa"; +export * from "./hpa.store"; export * from "./hpa-details"; diff --git a/src/renderer/components/+config-limit-ranges/index.ts b/src/renderer/components/+config-limit-ranges/index.ts index 5666fc77c4cdc..0c9e384eb33a9 100644 --- a/src/renderer/components/+config-limit-ranges/index.ts +++ b/src/renderer/components/+config-limit-ranges/index.ts @@ -20,4 +20,5 @@ */ export * from "./limit-ranges"; +export * from "./limit-ranges.store"; export * from "./limit-range-details"; diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts b/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts index a6c7cc770cb32..d9fe5524b0695 100644 --- a/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts @@ -21,13 +21,9 @@ import { autobind } from "../../../common/utils/autobind"; import { KubeObjectStore } from "../../kube-object.store"; -import { apiManager } from "../../api/api-manager"; import { LimitRange, limitRangeApi } from "../../api/endpoints/limit-range.api"; @autobind() export class LimitRangesStore extends KubeObjectStore { api = limitRangeApi; } - -export const limitRangeStore = new LimitRangesStore(); -apiManager.registerStore(limitRangeStore); diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.tsx b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx index 664654814c34e..a1af127729c84 100644 --- a/src/renderer/components/+config-limit-ranges/limit-ranges.tsx +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx @@ -24,11 +24,12 @@ import "./limit-ranges.scss"; import type { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; import { KubeObjectListLayout } from "../kube-object/kube-object-list-layout"; -import { limitRangeStore } from "./limit-ranges.store"; import React from "react"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { LimitRange } from "../../api/endpoints/limit-range.api"; +import { LimitRange, limitRangeApi } from "../../api/endpoints/limit-range.api"; import type { LimitRangeRouteParams } from "../../../common/routes"; +import { ApiManager } from "../../api/api-manager"; +import type { LimitRangesStore } from "./limit-ranges.store"; enum columnId { name = "name", @@ -41,13 +42,17 @@ interface Props extends RouteComponentProps { @observer export class LimitRanges extends React.Component { + private get limitRangeStore() { + return ApiManager.getInstance().getStore(limitRangeApi); + } + render() { return ( item.getName(), [columnId.namespace]: (item: LimitRange) => item.getNs(), diff --git a/src/renderer/components/+config-maps/config-map-details.tsx b/src/renderer/components/+config-maps/config-map-details.tsx index f1af70047d7bd..05f3454f7061f 100644 --- a/src/renderer/components/+config-maps/config-map-details.tsx +++ b/src/renderer/components/+config-maps/config-map-details.tsx @@ -28,16 +28,21 @@ import { DrawerTitle } from "../drawer"; import { Notifications } from "../notifications"; import { Input } from "../input"; import { Button } from "../button"; -import { configMapsStore } from "./config-maps.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { ConfigMap } from "../../api/endpoints"; +import { ConfigMap, configMapApi } from "../../api/endpoints"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import type { ConfigMapsStore } from "."; +import { ApiManager } from "../../api/api-manager"; interface Props extends KubeObjectDetailsProps { } @observer export class ConfigMapDetails extends React.Component { + private get configMapsStore() { + return ApiManager.getInstance().getStore(configMapApi); + } + @observable isSaving = false; @observable data = observable.map(); @@ -58,7 +63,7 @@ export class ConfigMapDetails extends React.Component { try { this.isSaving = true; - await configMapsStore.update(configMap, { ...configMap, data: this.data.toJSON() }); + await this.configMapsStore.update(configMap, { ...configMap, data: this.data.toJSON() }); Notifications.ok(

<>ConfigMap {configMap.getName()} successfully updated. diff --git a/src/renderer/components/+config-maps/config-maps.store.ts b/src/renderer/components/+config-maps/config-maps.store.ts index 51457c8c20c0d..f5ccdd234970b 100644 --- a/src/renderer/components/+config-maps/config-maps.store.ts +++ b/src/renderer/components/+config-maps/config-maps.store.ts @@ -22,12 +22,8 @@ import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; import { ConfigMap, configMapApi } from "../../api/endpoints/configmap.api"; -import { apiManager } from "../../api/api-manager"; @autobind() export class ConfigMapsStore extends KubeObjectStore { api = configMapApi; } - -export const configMapsStore = new ConfigMapsStore(); -apiManager.registerStore(configMapsStore); diff --git a/src/renderer/components/+config-maps/config-maps.tsx b/src/renderer/components/+config-maps/config-maps.tsx index fb757a69bf9c4..0029bcc3fdfff 100644 --- a/src/renderer/components/+config-maps/config-maps.tsx +++ b/src/renderer/components/+config-maps/config-maps.tsx @@ -24,11 +24,12 @@ import "./config-maps.scss"; import React from "react"; import { observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; -import { configMapsStore } from "./config-maps.store"; -import type { ConfigMap } from "../../api/endpoints/configmap.api"; +import { ConfigMap, configMapApi } from "../../api/endpoints/configmap.api"; import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { ConfigMapsRouteParams } from "../../../common/routes"; +import { ApiManager } from "../../api/api-manager"; +import type { ConfigMapsStore } from "./config-maps.store"; enum columnId { name = "name", @@ -42,12 +43,17 @@ interface Props extends RouteComponentProps { @observer export class ConfigMaps extends React.Component { + private get configMapsStore() { + return ApiManager.getInstance().getStore(configMapApi); + } + render() { return ( item.getName(), [columnId.namespace]: (item: ConfigMap) => item.getNs(), diff --git a/src/renderer/components/+config-maps/index.ts b/src/renderer/components/+config-maps/index.ts index b4ca786263c7a..36f41cdd66309 100644 --- a/src/renderer/components/+config-maps/index.ts +++ b/src/renderer/components/+config-maps/index.ts @@ -20,4 +20,5 @@ */ export * from "./config-maps"; +export * from "./config-maps.store"; export * from "./config-map-details"; diff --git a/src/renderer/components/+config-pod-disruption-budgets/index.ts b/src/renderer/components/+config-pod-disruption-budgets/index.ts index d494a1fa36cd3..2154ef04a626c 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/index.ts +++ b/src/renderer/components/+config-pod-disruption-budgets/index.ts @@ -20,4 +20,5 @@ */ export * from "./pod-disruption-budgets"; +export * from "./pod-disruption-budgets.store"; export * from "./pod-disruption-budgets-details"; diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts index d97593542d53c..c614624e7b507 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts @@ -21,13 +21,9 @@ import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; -import { PodDisruptionBudget, pdbApi } from "../../api/endpoints/poddisruptionbudget.api"; -import { apiManager } from "../../api/api-manager"; +import { PodDisruptionBudget, podDisruptionBudgetApi } from "../../api/endpoints/poddisruptionbudget.api"; @autobind() export class PodDisruptionBudgetsStore extends KubeObjectStore { - api = pdbApi; + api = podDisruptionBudgetApi; } - -export const podDisruptionBudgetsStore = new PodDisruptionBudgetsStore(); -apiManager.registerStore(podDisruptionBudgetsStore); diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx index abc87a6a4d060..eff9e02baf0cd 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx @@ -23,10 +23,11 @@ import "./pod-disruption-budgets.scss"; import * as React from "react"; import { observer } from "mobx-react"; -import { podDisruptionBudgetsStore } from "./pod-disruption-budgets.store"; -import type { PodDisruptionBudget } from "../../api/endpoints/poddisruptionbudget.api"; +import { PodDisruptionBudget, podDisruptionBudgetApi } from "../../api/endpoints/poddisruptionbudget.api"; import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; +import type { PodDisruptionBudgetsStore } from "./pod-disruption-budgets.store"; +import { ApiManager } from "../../api/api-manager"; enum columnId { name = "name", @@ -43,13 +44,17 @@ interface Props extends KubeObjectDetailsProps { @observer export class PodDisruptionBudgets extends React.Component { + private get podDisruptionBudgetsStore() { + return ApiManager.getInstance().getStore(podDisruptionBudgetApi); + } + render() { return ( pdb.getName(), [columnId.namespace]: (pdb: PodDisruptionBudget) => pdb.getNs(), diff --git a/src/renderer/components/+config-resource-quotas/index.ts b/src/renderer/components/+config-resource-quotas/index.ts index e6f87451e560b..acbd8645ffac8 100644 --- a/src/renderer/components/+config-resource-quotas/index.ts +++ b/src/renderer/components/+config-resource-quotas/index.ts @@ -20,4 +20,5 @@ */ export * from "./resource-quotas"; +export * from "./resource-quotas.store"; export * from "./resource-quota-details"; diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas.store.ts b/src/renderer/components/+config-resource-quotas/resource-quotas.store.ts index 5d8907a0b061b..6cfd12dca8510 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quotas.store.ts +++ b/src/renderer/components/+config-resource-quotas/resource-quotas.store.ts @@ -22,12 +22,8 @@ import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api"; -import { apiManager } from "../../api/api-manager"; @autobind() export class ResourceQuotasStore extends KubeObjectStore { api = resourceQuotaApi; } - -export const resourceQuotaStore = new ResourceQuotasStore(); -apiManager.registerStore(resourceQuotaStore); diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas.tsx b/src/renderer/components/+config-resource-quotas/resource-quotas.tsx index d1ce05c0985a6..4fd78eefd371c 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quotas.tsx +++ b/src/renderer/components/+config-resource-quotas/resource-quotas.tsx @@ -25,11 +25,12 @@ import React from "react"; import { observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object"; -import type { ResourceQuota } from "../../api/endpoints/resource-quota.api"; +import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api"; import { AddQuotaDialog } from "./add-quota-dialog"; -import { resourceQuotaStore } from "./resource-quotas.store"; +import type { ResourceQuotasStore } from "./resource-quotas.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { ResourceQuotaRouteParams } from "../../../common/routes"; +import { ApiManager } from "../../api/api-manager"; enum columnId { name = "name", @@ -42,13 +43,18 @@ interface Props extends RouteComponentProps { @observer export class ResourceQuotas extends React.Component { + private get resourceQuotaStore() { + return ApiManager.getInstance().getStore(resourceQuotaApi); + } + render() { return ( <> item.getName(), [columnId.namespace]: (item: ResourceQuota) => item.getNs(), diff --git a/src/renderer/components/+config-secrets/index.ts b/src/renderer/components/+config-secrets/index.ts index 783c83455dda3..7373eaed9211d 100644 --- a/src/renderer/components/+config-secrets/index.ts +++ b/src/renderer/components/+config-secrets/index.ts @@ -20,4 +20,5 @@ */ export * from "./secrets"; +export * from "./secrets.store"; export * from "./secret-details"; diff --git a/src/renderer/components/+config-secrets/secret-details.tsx b/src/renderer/components/+config-secrets/secret-details.tsx index 18ea67d5f7e91..2cfe754e21be8 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -31,16 +31,21 @@ import { Button } from "../button"; import { Notifications } from "../notifications"; import { base64 } from "../../utils"; import { Icon } from "../icon"; -import { secretsStore } from "./secrets.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { Secret } from "../../api/endpoints"; +import { Secret, secretsApi } from "../../api/endpoints"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import type { SecretsStore } from "."; +import { ApiManager } from "../../api/api-manager"; interface Props extends KubeObjectDetailsProps { } @observer export class SecretDetails extends React.Component { + private get secretsStore() { + return ApiManager.getInstance().getStore(secretsApi); + } + @observable isSaving = false; @observable data: { [name: string]: string } = {}; @observable revealSecret: { [name: string]: boolean } = {}; @@ -64,7 +69,7 @@ export class SecretDetails extends React.Component { this.isSaving = true; try { - await secretsStore.update(secret, { ...secret, data: this.data }); + await this.secretsStore.update(secret, { ...secret, data: this.data }); Notifications.ok("Secret successfully updated."); } catch (err) { Notifications.error(err); diff --git a/src/renderer/components/+config-secrets/secrets.store.ts b/src/renderer/components/+config-secrets/secrets.store.ts index c680c5fb9a4b9..079d252e89bdb 100644 --- a/src/renderer/components/+config-secrets/secrets.store.ts +++ b/src/renderer/components/+config-secrets/secrets.store.ts @@ -22,12 +22,8 @@ import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; import { Secret, secretsApi } from "../../api/endpoints"; -import { apiManager } from "../../api/api-manager"; @autobind() export class SecretsStore extends KubeObjectStore { api = secretsApi; } - -export const secretsStore = new SecretsStore(); -apiManager.registerStore(secretsStore); diff --git a/src/renderer/components/+config-secrets/secrets.tsx b/src/renderer/components/+config-secrets/secrets.tsx index d74305a499dd0..1e43002861fc6 100644 --- a/src/renderer/components/+config-secrets/secrets.tsx +++ b/src/renderer/components/+config-secrets/secrets.tsx @@ -24,13 +24,14 @@ import "./secrets.scss"; import React from "react"; import { observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; -import type { Secret } from "../../api/endpoints"; +import { Secret, secretsApi } from "../../api/endpoints"; import { AddSecretDialog } from "./add-secret-dialog"; import { KubeObjectListLayout } from "../kube-object"; import { Badge } from "../badge"; -import { secretsStore } from "./secrets.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { SecretsRouteParams } from "../../../common/routes"; +import type { SecretsStore } from "./secrets.store"; +import { ApiManager } from "../../api/api-manager"; enum columnId { name = "name", @@ -46,13 +47,18 @@ interface Props extends RouteComponentProps { @observer export class Secrets extends React.Component { + private get secretsStore() { + return ApiManager.getInstance().getStore(secretsApi); + } + render() { return ( <> item.getName(), [columnId.namespace]: (item: Secret) => item.getNs(), diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index c5948ed37fdb4..13c0f38541fbe 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -28,17 +28,17 @@ import { namespaceUrlParam } from "../+namespaces/namespace.store"; import { ResourceQuotas } from "../+config-resource-quotas"; import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; import { HorizontalPodAutoscalers } from "../+config-autoscalers"; -import { isAllowedResource } from "../../../common/rbac"; import { LimitRanges } from "../+config-limit-ranges"; import * as routes from "../../../common/routes"; +import type { Cluster } from "../../../main/cluster"; @observer -export class Config extends React.Component { - static get tabRoutes(): TabLayoutRoute[] { +export class Config extends React.Component<{ cluster: Cluster }> { + static tabRoutes(cluster: Cluster): TabLayoutRoute[] { const query = namespaceUrlParam.toObjectParam(); const tabs: TabLayoutRoute[] = []; - if (isAllowedResource("configmaps")) { + if (cluster.isAllowedResource("configmaps")) { tabs.push({ title: "ConfigMaps", component: ConfigMaps, @@ -47,7 +47,7 @@ export class Config extends React.Component { }); } - if (isAllowedResource("secrets")) { + if (cluster.isAllowedResource("secrets")) { tabs.push({ title: "Secrets", component: Secrets, @@ -56,7 +56,7 @@ export class Config extends React.Component { }); } - if (isAllowedResource("resourcequotas")) { + if (cluster.isAllowedResource("resourcequotas")) { tabs.push({ title: "Resource Quotas", component: ResourceQuotas, @@ -65,7 +65,7 @@ export class Config extends React.Component { }); } - if (isAllowedResource("limitranges")) { + if (cluster.isAllowedResource("limitranges")) { tabs.push({ title: "Limit Ranges", component: LimitRanges, @@ -74,7 +74,7 @@ export class Config extends React.Component { }); } - if (isAllowedResource("horizontalpodautoscalers")) { + if (cluster.isAllowedResource("horizontalpodautoscalers")) { tabs.push({ title: "HPA", component: HorizontalPodAutoscalers, @@ -83,7 +83,7 @@ export class Config extends React.Component { }); } - if (isAllowedResource("poddisruptionbudgets")) { + if (cluster.isAllowedResource("poddisruptionbudgets")) { tabs.push({ title: "Pod Disruption Budgets", component: PodDisruptionBudgets, @@ -97,7 +97,7 @@ export class Config extends React.Component { render() { return ( - + ); } } diff --git a/src/renderer/components/+custom-resources/crd-list.tsx b/src/renderer/components/+custom-resources/crd-list.tsx index bb6753dce23e8..67cb66b355193 100644 --- a/src/renderer/components/+custom-resources/crd-list.tsx +++ b/src/renderer/components/+custom-resources/crd-list.tsx @@ -27,11 +27,12 @@ import { observer } from "mobx-react"; import { Link } from "react-router-dom"; import { stopPropagation } from "../../utils"; import { KubeObjectListLayout } from "../kube-object"; -import { crdStore } from "./crd.store"; -import type { CustomResourceDefinition } from "../../api/endpoints/crd.api"; +import type { CrdStore } from "./crd.store"; +import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api"; import { Select, SelectOption } from "../select"; import { createPageParam } from "../../navigation"; import { Icon } from "../icon"; +import { ApiManager } from "../../api/api-manager"; export const crdGroupsUrlParam = createPageParam({ name: "groups", @@ -54,12 +55,16 @@ export class CrdList extends React.Component { return crdGroupsUrlParam.get(); } + private get crdStore() { + return ApiManager.getInstance().getStore(crdApi); + } + @computed get items() { if (this.selectedGroups.length) { - return crdStore.items.filter(item => this.selectedGroups.includes(item.getGroup())); + return this.crdStore.items.filter(item => this.selectedGroups.includes(item.getGroup())); } - return crdStore.items; // show all by default + return this.crdStore.items; // show all by default } toggleSelection(group: string) { @@ -88,7 +93,7 @@ export class CrdList extends React.Component { tableId="crd" className="CrdList" isClusterScoped={true} - store={crdStore} + store={this.crdStore} items={items} sortingCallbacks={sortingCallbacks} searchFilters={Object.values(sortingCallbacks)} @@ -105,7 +110,7 @@ export class CrdList extends React.Component {