diff --git a/.eslintrc.js b/.eslintrc.js index 33d28967ff64..148ba2c6014d 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", @@ -212,6 +220,15 @@ module.exports = { { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]}, ] }, - } + }, + { + files: [ + "extensions/**/*.ts", + "extensions/**/*.tsx", + ], + rules: { + "import/no-unresolved": "off", // warns on @k8slens/extensions + } + }, ] }; diff --git a/extensions/kube-object-event-status/src/resolver.tsx b/extensions/kube-object-event-status/src/resolver.tsx index 03e1f0cdd9b4..52a0c5c040b5 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/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 03ef6e23ae81..14e95d12646e 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -40,7 +40,7 @@ describe("Lens command palette", () => { it("opens command dialog from menu", async () => { await app.electron.ipcRenderer.send("test-menu-item-click", "View", "Command Palette..."); - await app.client.waitUntilTextExists(".Select__option", "Preferences: Open"); + await app.client.waitUntilTextExists(".Select__option", "Hotbar: Switch"); await app.client.keys("Escape"); }); }); diff --git a/package.json b/package.json index 05b64573578d..7d7368093e4f 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 e69de29bb2d1..000000000000 diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 7a82547e618c..249006c3e626 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/__tests__/search-store.test.ts b/src/common/__tests__/search-store.test.ts index b4c99b7e1955..49baa60ea913 100644 --- a/src/common/__tests__/search-store.test.ts +++ b/src/common/__tests__/search-store.test.ts @@ -22,6 +22,7 @@ import { SearchStore } from "../search-store"; import { Console } from "console"; import { stdout, stderr } from "process"; +import { DockStore } from "../../renderer/components/dock"; jest.mock("electron", () => ({ app: { @@ -31,7 +32,6 @@ jest.mock("electron", () => ({ console = new Console(stdout, stderr); -let searchStore: SearchStore = null; const logs = [ "1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost", "1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization", @@ -39,16 +39,26 @@ const logs = [ ]; describe("search store tests", () => { - beforeEach(async () => { - searchStore = new SearchStore(); + beforeEach(() => { + DockStore.createInstance(); + SearchStore.createInstance(); }); + afterEach(() => { + DockStore.resetInstance(); + SearchStore.resetInstance(); + }) + it("does nothing with empty search query", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch([], ""); expect(searchStore.occurrences).toEqual([]); }); it("doesn't break if no text provided", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(null, "replica"); expect(searchStore.occurrences).toEqual([]); @@ -57,33 +67,45 @@ describe("search store tests", () => { }); it("find 3 occurrences across 3 lines", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "172"); expect(searchStore.occurrences).toEqual([0, 1, 2]); }); it("find occurrences within 1 line (case-insensitive)", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "Starting"); expect(searchStore.occurrences).toEqual([2, 2]); }); it("sets overlay index equal to first occurrence", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "Replica"); expect(searchStore.activeOverlayIndex).toBe(0); }); it("set overlay index to next occurrence", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "172"); searchStore.setNextOverlayActive(); expect(searchStore.activeOverlayIndex).toBe(1); }); it("sets overlay to last occurrence", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "172"); searchStore.setPrevOverlayActive(); expect(searchStore.activeOverlayIndex).toBe(2); }); it("gets line index where overlay is located", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "synchronization"); expect(searchStore.activeOverlayLine).toBe(1); }); @@ -95,12 +117,16 @@ describe("search store tests", () => { }); it("gets active find number", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "172"); searchStore.setNextOverlayActive(); expect(searchStore.activeFind).toBe(2); }); it("gets total finds number", () => { + const searchStore = SearchStore.getInstance(); + searchStore.onSearch(logs, "Starting"); expect(searchStore.totalFinds).toBe(2); }); diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 608862c2af27..cb2b285b4d1c 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -22,12 +22,11 @@ import path from "path"; import Config from "conf"; import type { Options as ConfOptions } from "conf/dist/source/types"; -import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"; +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, subscribeToBroadcast, unsubscribeFromBroadcast } from "./ipc"; +import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc"; import isEqual from "lodash/isEqual"; export interface BaseStoreParams extends ConfOptions { @@ -118,23 +117,17 @@ export abstract class BaseStore extends Singleton { ); if (ipcMain) { - const callback = (event: IpcMainEvent, model: T) => { + this.syncDisposers.push(ipcMainOn(this.syncMainChannel, (event, model: T) => { logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model }); this.onSync(model); - }; - - subscribeToBroadcast(this.syncMainChannel, callback); - this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback)); + })); } if (ipcRenderer) { - const callback = (event: IpcRendererEvent, model: T) => { + this.syncDisposers.push(ipcRendererOn(this.syncRendererChannel, (event, model: T) => { logger.silly(`[STORE]: SYNC ${this.name} from main`, { model }); this.onSyncFromMain(model); - }; - - subscribeToBroadcast(this.syncRendererChannel, callback); - this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback)); + })); } } diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 43fee50eaf12..0e9a4bed67dc 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -20,15 +20,15 @@ */ 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 = { address?: { namespace: string; @@ -109,7 +109,7 @@ export class KubernetesCluster extends CatalogEntity { - ctx.navigate("/add-cluster"); + ctx.navigate(addClusterURL()); } }); }); diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index 718151c3ed6c..8f9686be7cbe 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -19,87 +19,9 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { handleRequest } from "./ipc"; -import { ClusterId, ClusterStore } from "./cluster-store"; -import { appEventBus } from "./event-bus"; -import { ResourceApplier } from "../main/resource-applier"; -import { ipcMain, IpcMainInvokeEvent } from "electron"; -import { clusterFrameMap } from "./cluster-frames"; - export const clusterActivateHandler = "cluster:activate"; export const clusterSetFrameIdHandler = "cluster:set-frame-id"; export const clusterRefreshHandler = "cluster:refresh"; export const clusterDisconnectHandler = "cluster:disconnect"; export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all"; export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all"; - -if (ipcMain) { - handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { - return ClusterStore.getInstance() - .getById(clusterId) - ?.activate(force); - }); - - handleRequest(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(); - } - }); - - handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => { - return ClusterStore.getInstance() - .getById(clusterId) - ?.refresh({ refreshMetadata: true }); - }); - - handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => { - appEventBus.emit({name: "cluster", action: "stop"}); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - cluster.disconnect(); - clusterFrameMap.delete(cluster.id); - } - }); - - handleRequest(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`; - } - }); - - handleRequest(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 89c71255c2ab..56432714d58c 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -19,117 +19,23 @@ * 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 { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; +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 { - 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; - } + private static StateChannel = "cluster:state"; @observable activeCluster: ClusterId; @observable removedClusters = observable.map(); @@ -170,7 +76,7 @@ export class ClusterStore extends BaseStore { } }); } else if (ipcMain) { - handleRequest(ClusterStore.stateRequestChannel, (): clusterStateSync[] => { + ipcMain.handle(ClusterStore.stateRequestChannel, (): clusterStateSync[] => { const states: clusterStateSync[] = []; this.clustersList.forEach((cluster) => { @@ -191,17 +97,25 @@ export class ClusterStore extends BaseStore { reaction(() => this.connectedClustersList, () => { this.pushState(); }), - () => unsubscribeAllFromBroadcast("cluster:state"), ); } } + handleStateChange = (event: any, clusterId: string, state: ClusterState) => { + logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state); + this.getById(clusterId)?.setState(state); + }; + registerIpcListener() { logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`); - subscribeToBroadcast("cluster:state", (event, clusterId: string, state: ClusterState) => { - logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state); - this.getById(clusterId)?.setState(state); - }); + + if (ipcMain) { + this.disposer.push(ipcMainOn(ClusterStore.StateChannel, this.handleStateChange)); + } + + if (ipcRenderer) { + this.disposer.push(ipcRendererOn(ClusterStore.StateChannel, this.handleStateChange)); + } } unregisterIpcListener() { @@ -301,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); } } @@ -351,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 000000000000..a31cdaf23326 --- /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 66e591e7653e..24750ac4bc31 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -27,13 +27,10 @@ import { ipcMain, ipcRenderer, webContents, remote } from "electron"; import { toJS } from "mobx"; import logger from "../../main/logger"; import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; +import type { Disposer } from "../utils"; const subFramesChannel = "ipc:get-sub-frames"; -export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) { - ipcMain.handle(channel, listener); -} - export async function requestMain(channel: string, ...args: any[]) { return ipcRenderer.invoke(channel, ...args); } @@ -71,34 +68,18 @@ export function broadcastMessage(channel: string, ...args: any[]) { }); } -export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) { - if (ipcRenderer) { - ipcRenderer.on(channel, listener); - } else if (ipcMain) { - ipcMain.on(channel, listener); - } +export function ipcMainOn(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): Disposer { + ipcMain.on(channel, listener); - return listener; + return () => ipcMain.off(channel, listener); } -export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) { - if (ipcRenderer) { - ipcRenderer.off(channel, listener); - } else if (ipcMain) { - ipcMain.off(channel, listener); - } -} +export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { + ipcRenderer.on(channel, listener); -export function unsubscribeAllFromBroadcast(channel: string) { - if (ipcRenderer) { - ipcRenderer.removeAllListeners(channel); - } else if (ipcMain) { - ipcMain.removeAllListeners(channel); - } + return () => ipcRenderer.off(channel, listener); } -export function bindBroadcastHandlers() { - handleRequest(subFramesChannel, () => { - return getSubFrames(); - }); +export function initGetSubFramesHandler() { + ipcMain.handle(subFramesChannel, getSubFrames); } diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index 59101b5aa70c..0fd264db60a8 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -29,7 +29,7 @@ import { RoutingError, RoutingErrorType } from "./error"; import { ExtensionsStore } from "../../extensions/extensions-store"; import { ExtensionLoader } from "../../extensions/extension-loader"; import type { LensExtension } from "../../extensions/lens-extension"; -import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler-registry"; +import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler"; // IPC channel for protocol actions. Main broadcasts the open-url events to this channel. export const ProtocolHandlerIpcPrefix = "protocol-handler"; diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 55b8ca414cad..b5260431d747 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/renderer/components/+add-cluster/add-cluster.route.ts b/src/common/routes/add-cluster.ts similarity index 95% rename from src/renderer/components/+add-cluster/add-cluster.route.ts rename to src/common/routes/add-cluster.ts index 88e4b2ab0155..93ac41f0bf9e 100644 --- a/src/renderer/components/+add-cluster/add-cluster.route.ts +++ b/src/common/routes/add-cluster.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const addClusterRoute: RouteProps = { path: "/add-cluster" diff --git a/src/renderer/components/+apps/apps.route.ts b/src/common/routes/apps.ts similarity index 95% rename from src/renderer/components/+apps/apps.route.ts rename to src/common/routes/apps.ts index 9babe0ca455b..72a92616dcfc 100644 --- a/src/renderer/components/+apps/apps.route.ts +++ b/src/common/routes/apps.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const appsRoute: RouteProps = { path: "/apps", diff --git a/src/renderer/components/+catalog/catalog.route.ts b/src/common/routes/catalog.ts similarity index 95% rename from src/renderer/components/+catalog/catalog.route.ts rename to src/common/routes/catalog.ts index b7bbe8c99323..2dee4b4e0773 100644 --- a/src/renderer/components/+catalog/catalog.route.ts +++ b/src/common/routes/catalog.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export interface ICatalogViewRouteParam { group?: string; diff --git a/src/renderer/components/cluster-manager/cluster-view.route.ts b/src/common/routes/cluster-view.ts similarity index 87% rename from src/renderer/components/cluster-manager/cluster-view.route.ts rename to src/common/routes/cluster-view.ts index 5f7fde56d7fd..66b9c63d60d3 100644 --- a/src/renderer/components/cluster-manager/cluster-view.route.ts +++ b/src/common/routes/cluster-view.ts @@ -20,9 +20,9 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; -export interface IClusterViewRouteParams { +export interface ClusterViewRouteParams { clusterId: string; } @@ -31,4 +31,4 @@ export const clusterViewRoute: RouteProps = { path: "/cluster/:clusterId", }; -export const clusterViewURL = buildURL(clusterViewRoute.path); +export const clusterViewURL = buildURL(clusterViewRoute.path); diff --git a/src/renderer/components/+cluster/cluster.route.ts b/src/common/routes/cluster.ts similarity index 95% rename from src/renderer/components/+cluster/cluster.route.ts rename to src/common/routes/cluster.ts index 01ce68a4956b..3a39ccd22ac8 100644 --- a/src/renderer/components/+cluster/cluster.route.ts +++ b/src/common/routes/cluster.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const clusterRoute: RouteProps = { path: "/cluster" diff --git a/src/renderer/components/+config-maps/config-maps.route.ts b/src/common/routes/config-maps.ts similarity index 86% rename from src/renderer/components/+config-maps/config-maps.route.ts rename to src/common/routes/config-maps.ts index fa9293d4b0b5..99aeaaf553c9 100644 --- a/src/renderer/components/+config-maps/config-maps.route.ts +++ b/src/common/routes/config-maps.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const configMapsRoute: RouteProps = { path: "/configmaps" }; -export interface IConfigMapsRouteParams { +export interface ConfigMapsRouteParams { } -export const configMapsURL = buildURL(configMapsRoute.path); +export const configMapsURL = buildURL(configMapsRoute.path); diff --git a/src/renderer/components/+config/config.route.ts b/src/common/routes/config.ts similarity index 72% rename from src/renderer/components/+config/config.route.ts rename to src/common/routes/config.ts index 6e3cd5696e4f..7dca2906729d 100644 --- a/src/renderer/components/+config/config.route.ts +++ b/src/common/routes/config.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import type { IURLParams } from "../../../common/utils/buildUrl"; -import { configMapsRoute, configMapsURL } from "../+config-maps/config-maps.route"; -import { hpaRoute } from "../+config-autoscalers"; -import { limitRangesRoute } from "../+config-limit-ranges"; -import { pdbRoute } from "../+config-pod-disruption-budgets"; -import { resourceQuotaRoute } from "../+config-resource-quotas"; -import { secretsRoute } from "../+config-secrets"; +import type { URLParams } from "../utils/buildUrl"; +import { configMapsRoute, configMapsURL } from "./config-maps"; +import { hpaRoute } from "./hpa"; +import { limitRangesRoute } from "./limit-ranges"; +import { pdbRoute } from "./pod-disruption-budgets"; +import { resourceQuotaRoute } from "./resource-quotas"; +import { secretsRoute } from "./secrets"; export const configRoute: RouteProps = { path: [ @@ -39,4 +39,4 @@ export const configRoute: RouteProps = { ].map(route => route.path.toString()) }; -export const configURL = (params?: IURLParams) => configMapsURL(params); +export const configURL = (params?: URLParams) => configMapsURL(params); diff --git a/src/renderer/components/+custom-resources/crd.route.ts b/src/common/routes/crd.ts similarity index 83% rename from src/renderer/components/+custom-resources/crd.route.ts rename to src/common/routes/crd.ts index 91042649e300..1a70fac8750d 100644 --- a/src/renderer/components/+custom-resources/crd.route.ts +++ b/src/common/routes/crd.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const crdRoute: RouteProps = { path: "/crd" @@ -34,14 +34,14 @@ export const crdResourcesRoute: RouteProps = { path: `${crdRoute.path}/:group/:name` }; -export interface ICRDListQuery { +export interface CRDListQuery { groups?: string; } -export interface ICRDRouteParams { +export interface CRDRouteParams { group: string; name: string; } -export const crdURL = buildURL<{}, ICRDListQuery>(crdDefinitionsRoute.path); -export const crdResourcesURL = buildURL(crdResourcesRoute.path); +export const crdURL = buildURL<{}, CRDListQuery>(crdDefinitionsRoute.path); +export const crdResourcesURL = buildURL(crdResourcesRoute.path); diff --git a/src/renderer/components/+network-endpoints/endpoints.route.ts b/src/common/routes/endpoints.ts similarity index 95% rename from src/renderer/components/+network-endpoints/endpoints.route.ts rename to src/common/routes/endpoints.ts index 18aaf10c74c5..db09de66a04f 100644 --- a/src/renderer/components/+network-endpoints/endpoints.route.ts +++ b/src/common/routes/endpoints.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const endpointRoute: RouteProps = { path: "/endpoints" diff --git a/src/renderer/components/+entity-settings/entity-settings.route.ts b/src/common/routes/entity-settings.ts similarity index 96% rename from src/renderer/components/+entity-settings/entity-settings.route.ts rename to src/common/routes/entity-settings.ts index 20091c85e061..8368d81b7ed2 100644 --- a/src/renderer/components/+entity-settings/entity-settings.route.ts +++ b/src/common/routes/entity-settings.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export interface EntitySettingsRouteParams { entityId: string; diff --git a/src/renderer/components/+events/events.route.ts b/src/common/routes/events.ts similarity index 95% rename from src/renderer/components/+events/events.route.ts rename to src/common/routes/events.ts index 2760b7ef0b94..73995ce6b031 100644 --- a/src/renderer/components/+events/events.route.ts +++ b/src/common/routes/events.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const eventRoute: RouteProps = { path: "/events" diff --git a/src/renderer/components/+extensions/extensions.route.ts b/src/common/routes/extensions.ts similarity index 95% rename from src/renderer/components/+extensions/extensions.route.ts rename to src/common/routes/extensions.ts index e8552eac105e..6ef25d11c687 100644 --- a/src/renderer/components/+extensions/extensions.route.ts +++ b/src/common/routes/extensions.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const extensionsRoute: RouteProps = { path: "/extensions" diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.route.ts b/src/common/routes/helm-charts.ts similarity index 93% rename from src/renderer/components/+apps-helm-charts/helm-charts.route.ts rename to src/common/routes/helm-charts.ts index 55a19eb826a2..7689a50a17b9 100644 --- a/src/renderer/components/+apps-helm-charts/helm-charts.route.ts +++ b/src/common/routes/helm-charts.ts @@ -20,8 +20,8 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; -import { appsRoute } from "../+apps/apps.route"; +import { buildURL } from "../utils/buildUrl"; +import { appsRoute } from "./apps"; export const helmChartsRoute: RouteProps = { path: `${appsRoute.path}/charts/:repo?/:chartName?` diff --git a/src/renderer/components/+config-autoscalers/hpa.route.ts b/src/common/routes/hpa.ts similarity index 95% rename from src/renderer/components/+config-autoscalers/hpa.route.ts rename to src/common/routes/hpa.ts index 59a6945f894e..977aebb657b5 100644 --- a/src/renderer/components/+config-autoscalers/hpa.route.ts +++ b/src/common/routes/hpa.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const hpaRoute: RouteProps = { path: "/hpa" diff --git a/src/common/routes/index.ts b/src/common/routes/index.ts new file mode 100644 index 000000000000..6cdd81e2b4c8 --- /dev/null +++ b/src/common/routes/index.ts @@ -0,0 +1,54 @@ +/** + * 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 * from "./add-cluster"; +export * from "./apps"; +export * from "./catalog"; +export * from "./cluster-view"; +export * from "./cluster"; +export * from "./config-maps"; +export * from "./config"; +export * from "./crd"; +export * from "./endpoints"; +export * from "./entity-settings"; +export * from "./events"; +export * from "./extensions"; +export * from "./helm-charts"; +export * from "./hpa"; +export * from "./ingresses"; +export * from "./limit-ranges"; +export * from "./namespaces"; +export * from "./network-policies"; +export * from "./network"; +export * from "./nodes"; +export * from "./pod-disruption-budgets"; +export * from "./preferences"; +export * from "./releases"; +export * from "./resource-quotas"; +export * from "./secrets"; +export * from "./services"; +export * from "./storage-classes"; +export * from "./storage"; +export * from "./user-management"; +export * from "./volume-claims"; +export * from "./volumes"; +export * from "./welcome"; +export * from "./workloads"; diff --git a/src/renderer/components/+network-ingresses/ingresses.route.ts b/src/common/routes/ingresses.ts similarity index 95% rename from src/renderer/components/+network-ingresses/ingresses.route.ts rename to src/common/routes/ingresses.ts index 29a8ed6ada5a..66c37774d5a1 100644 --- a/src/renderer/components/+network-ingresses/ingresses.route.ts +++ b/src/common/routes/ingresses.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const ingressRoute: RouteProps = { path: "/ingresses" diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.route.ts b/src/common/routes/limit-ranges.ts similarity index 95% rename from src/renderer/components/+config-limit-ranges/limit-ranges.route.ts rename to src/common/routes/limit-ranges.ts index af3bf2708971..02b9258c6780 100644 --- a/src/renderer/components/+config-limit-ranges/limit-ranges.route.ts +++ b/src/common/routes/limit-ranges.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const limitRangesRoute: RouteProps = { path: "/limitranges" diff --git a/src/renderer/components/+namespaces/namespaces.route.ts b/src/common/routes/namespaces.ts similarity index 86% rename from src/renderer/components/+namespaces/namespaces.route.ts rename to src/common/routes/namespaces.ts index 6419f2f72c22..4a2395064862 100644 --- a/src/renderer/components/+namespaces/namespaces.route.ts +++ b/src/common/routes/namespaces.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const namespacesRoute: RouteProps = { path: "/namespaces" }; -export interface INamespacesRouteParams { +export interface NamespacesRouteParams { } -export const namespacesURL = buildURL(namespacesRoute.path); +export const namespacesURL = buildURL(namespacesRoute.path); diff --git a/src/renderer/components/+network-policies/network-policies.route.ts b/src/common/routes/network-policies.ts similarity index 85% rename from src/renderer/components/+network-policies/network-policies.route.ts rename to src/common/routes/network-policies.ts index 3f905939badd..b49094a4184c 100644 --- a/src/renderer/components/+network-policies/network-policies.route.ts +++ b/src/common/routes/network-policies.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const networkPoliciesRoute: RouteProps = { path: "/network-policies" }; -export interface INetworkPoliciesRouteParams { +export interface NetworkPoliciesRouteParams { } -export const networkPoliciesURL = buildURL(networkPoliciesRoute.path); +export const networkPoliciesURL = buildURL(networkPoliciesRoute.path); diff --git a/src/renderer/components/+network/network.route.ts b/src/common/routes/network.ts similarity index 76% rename from src/renderer/components/+network/network.route.ts rename to src/common/routes/network.ts index a827461f8f64..17b51e200a81 100644 --- a/src/renderer/components/+network/network.route.ts +++ b/src/common/routes/network.ts @@ -20,19 +20,19 @@ */ import type { RouteProps } from "react-router"; -import { endpointRoute } from "../+network-endpoints"; -import { ingressRoute } from "../+network-ingresses"; -import { networkPoliciesRoute } from "../+network-policies"; -import { servicesRoute, servicesURL } from "../+network-services"; -import type { IURLParams } from "../../../common/utils/buildUrl"; +import type { URLParams } from "../utils/buildUrl"; +import { endpointRoute } from "./endpoints"; +import { ingressRoute } from "./ingresses"; +import { networkPoliciesRoute } from "./network-policies"; +import { servicesRoute, servicesURL } from "./services"; export const networkRoute: RouteProps = { path: [ servicesRoute, endpointRoute, ingressRoute, - networkPoliciesRoute + networkPoliciesRoute, ].map(route => route.path.toString()) }; -export const networkURL = (params?: IURLParams) => servicesURL(params); +export const networkURL = (params?: URLParams) => servicesURL(params); diff --git a/src/renderer/components/+nodes/nodes.route.ts b/src/common/routes/nodes.ts similarity index 88% rename from src/renderer/components/+nodes/nodes.route.ts rename to src/common/routes/nodes.ts index dd9fc5a1d4db..bfb6d8ea003f 100644 --- a/src/renderer/components/+nodes/nodes.route.ts +++ b/src/common/routes/nodes.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const nodesRoute: RouteProps = { path: "/nodes" }; -export interface INodesRouteParams { +export interface NodesRouteParams { } -export const nodesURL = buildURL(nodesRoute.path); +export const nodesURL = buildURL(nodesRoute.path); diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts b/src/common/routes/pod-disruption-budgets.ts similarity index 86% rename from src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts rename to src/common/routes/pod-disruption-budgets.ts index a8b8efb65fc6..985752746adc 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts +++ b/src/common/routes/pod-disruption-budgets.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const pdbRoute: RouteProps = { path: "/poddisruptionbudgets" }; -export interface IPodDisruptionBudgetsRouteParams { +export interface PodDisruptionBudgetsRouteParams { } -export const pdbURL = buildURL(pdbRoute.path); +export const pdbURL = buildURL(pdbRoute.path); diff --git a/src/common/routes/preferences.ts b/src/common/routes/preferences.ts new file mode 100644 index 000000000000..45fd3074c8a1 --- /dev/null +++ b/src/common/routes/preferences.ts @@ -0,0 +1,29 @@ +/** + * 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 type { RouteProps } from "react-router"; +import { buildURL } from "../utils/buildUrl"; + +export const preferencesRoute: RouteProps = { + path: "/preferences" +}; + +export const preferencesURL = buildURL(preferencesRoute.path); diff --git a/src/renderer/components/+apps-releases/release.route.ts b/src/common/routes/releases.ts similarity index 85% rename from src/renderer/components/+apps-releases/release.route.ts rename to src/common/routes/releases.ts index 6a2c5d38506b..2984bfd24587 100644 --- a/src/renderer/components/+apps-releases/release.route.ts +++ b/src/common/routes/releases.ts @@ -20,16 +20,16 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; -import { appsRoute } from "../+apps/apps.route"; +import { buildURL } from "../utils/buildUrl"; +import { appsRoute } from "./apps"; export const releaseRoute: RouteProps = { path: `${appsRoute.path}/releases/:namespace?/:name?` }; -export interface IReleaseRouteParams { +export interface ReleaseRouteParams { name?: string; namespace?: string; } -export const releaseURL = buildURL(releaseRoute.path); +export const releaseURL = buildURL(releaseRoute.path); diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas.route.ts b/src/common/routes/resource-quotas.ts similarity index 86% rename from src/renderer/components/+config-resource-quotas/resource-quotas.route.ts rename to src/common/routes/resource-quotas.ts index dfbdab001dd7..7d8a5163a2ba 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quotas.route.ts +++ b/src/common/routes/resource-quotas.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const resourceQuotaRoute: RouteProps = { path: "/resourcequotas" }; -export interface IResourceQuotaRouteParams { +export interface ResourceQuotaRouteParams { } -export const resourceQuotaURL = buildURL(resourceQuotaRoute.path); +export const resourceQuotaURL = buildURL(resourceQuotaRoute.path); diff --git a/src/renderer/components/+config-secrets/secrets.route.ts b/src/common/routes/secrets.ts similarity index 92% rename from src/renderer/components/+config-secrets/secrets.route.ts rename to src/common/routes/secrets.ts index 487b0198b040..10ee187f64b0 100644 --- a/src/renderer/components/+config-secrets/secrets.route.ts +++ b/src/common/routes/secrets.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const secretsRoute: RouteProps = { path: "/secrets" }; -export interface ISecretsRouteParams { +export interface SecretsRouteParams { } export const secretsURL = buildURL(secretsRoute.path); diff --git a/src/renderer/components/+network-services/services.route.ts b/src/common/routes/services.ts similarity index 87% rename from src/renderer/components/+network-services/services.route.ts rename to src/common/routes/services.ts index a145e4b93cd4..b4e3acdad9d0 100644 --- a/src/renderer/components/+network-services/services.route.ts +++ b/src/common/routes/services.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const servicesRoute: RouteProps = { path: "/services" }; -export interface IServicesRouteParams { +export interface ServicesRouteParams { } -export const servicesURL = buildURL(servicesRoute.path); +export const servicesURL = buildURL(servicesRoute.path); diff --git a/src/renderer/components/+storage-classes/storage-classes.route.ts b/src/common/routes/storage-classes.ts similarity index 86% rename from src/renderer/components/+storage-classes/storage-classes.route.ts rename to src/common/routes/storage-classes.ts index 75f25c2b5f3d..1a0128cbf8de 100644 --- a/src/renderer/components/+storage-classes/storage-classes.route.ts +++ b/src/common/routes/storage-classes.ts @@ -20,14 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const storageClassesRoute: RouteProps = { path: "/storage-classes" }; -export interface IStorageClassesRouteParams { +export interface StorageClassesRouteParams { } -export const storageClassesURL = buildURL(storageClassesRoute.path); - +export const storageClassesURL = buildURL(storageClassesRoute.path); diff --git a/src/renderer/components/+storage/storage.route.ts b/src/common/routes/storage.ts similarity index 79% rename from src/renderer/components/+storage/storage.route.ts rename to src/common/routes/storage.ts index 6903a7637580..5de524795d4f 100644 --- a/src/renderer/components/+storage/storage.route.ts +++ b/src/common/routes/storage.ts @@ -20,10 +20,10 @@ */ import type { RouteProps } from "react-router"; -import { storageClassesRoute } from "../+storage-classes"; -import { volumeClaimsRoute, volumeClaimsURL } from "../+storage-volume-claims"; -import { volumesRoute } from "../+storage-volumes"; -import type { IURLParams } from "../../../common/utils/buildUrl"; +import type { URLParams } from "../utils/buildUrl"; +import { storageClassesRoute } from "./storage-classes"; +import { volumeClaimsRoute, volumeClaimsURL } from "./volume-claims"; +import { volumesRoute } from "./volumes"; export const storageRoute: RouteProps = { path: [ @@ -33,4 +33,4 @@ export const storageRoute: RouteProps = { ].map(route => route.path.toString()) }; -export const storageURL = (params?: IURLParams) => volumeClaimsURL(params); +export const storageURL = (params?: URLParams) => volumeClaimsURL(params); diff --git a/src/renderer/components/+user-management/user-management.route.ts b/src/common/routes/user-management.ts similarity index 76% rename from src/renderer/components/+user-management/user-management.route.ts rename to src/common/routes/user-management.ts index f6e4def1e86c..28efff026c77 100644 --- a/src/renderer/components/+user-management/user-management.route.ts +++ b/src/common/routes/user-management.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL, IURLParams } from "../../../common/utils/buildUrl"; +import { buildURL, URLParams } from "../utils/buildUrl"; // Routes export const serviceAccountsRoute: RouteProps = { @@ -46,18 +46,18 @@ export const usersManagementRoute: RouteProps = { }; // Route params -export interface IServiceAccountsRouteParams { +export interface ServiceAccountsRouteParams { } -export interface IRoleBindingsRouteParams { +export interface RoleBindingsRouteParams { } -export interface IRolesRouteParams { +export interface RolesRouteParams { } // URL-builders -export const usersManagementURL = (params?: IURLParams) => serviceAccountsURL(params); -export const serviceAccountsURL = buildURL(serviceAccountsRoute.path); -export const roleBindingsURL = buildURL(roleBindingsRoute.path); -export const rolesURL = buildURL(rolesRoute.path); +export const usersManagementURL = (params?: URLParams) => serviceAccountsURL(params); +export const serviceAccountsURL = buildURL(serviceAccountsRoute.path); +export const roleBindingsURL = buildURL(roleBindingsRoute.path); +export const rolesURL = buildURL(rolesRoute.path); export const podSecurityPoliciesURL = buildURL(podSecurityPoliciesRoute.path); diff --git a/src/renderer/components/+storage-volume-claims/volume-claims.route.ts b/src/common/routes/volume-claims.ts similarity index 86% rename from src/renderer/components/+storage-volume-claims/volume-claims.route.ts rename to src/common/routes/volume-claims.ts index 2d22b6ed32eb..42171328df3e 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claims.route.ts +++ b/src/common/routes/volume-claims.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const volumeClaimsRoute: RouteProps = { path: "/persistent-volume-claims" }; -export interface IVolumeClaimsRouteParams { +export interface VolumeClaimsRouteParams { } -export const volumeClaimsURL = buildURL(volumeClaimsRoute.path); +export const volumeClaimsURL = buildURL(volumeClaimsRoute.path); diff --git a/src/renderer/components/+storage-volumes/volumes.route.ts b/src/common/routes/volumes.ts similarity index 87% rename from src/renderer/components/+storage-volumes/volumes.route.ts rename to src/common/routes/volumes.ts index 822e2b738f2b..c5a969e98804 100644 --- a/src/renderer/components/+storage-volumes/volumes.route.ts +++ b/src/common/routes/volumes.ts @@ -20,13 +20,13 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const volumesRoute: RouteProps = { path: "/persistent-volumes" }; -export interface IVolumesRouteParams { +export interface VolumesRouteParams { } -export const volumesURL = buildURL(volumesRoute.path); +export const volumesURL = buildURL(volumesRoute.path); diff --git a/src/renderer/components/+welcome/welcome.route.ts b/src/common/routes/welcome.ts similarity index 95% rename from src/renderer/components/+welcome/welcome.route.ts rename to src/common/routes/welcome.ts index b4a1fc24e225..dd854d11a1d2 100644 --- a/src/renderer/components/+welcome/welcome.route.ts +++ b/src/common/routes/welcome.ts @@ -20,7 +20,7 @@ */ import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; +import { buildURL } from "../utils/buildUrl"; export const welcomeRoute: RouteProps = { path: "/welcome" diff --git a/src/renderer/components/+workloads/workloads.route.ts b/src/common/routes/workloads.ts similarity index 66% rename from src/renderer/components/+workloads/workloads.route.ts rename to src/common/routes/workloads.ts index baef1928b7f3..94a9378d024f 100644 --- a/src/renderer/components/+workloads/workloads.route.ts +++ b/src/common/routes/workloads.ts @@ -20,8 +20,8 @@ */ import type { RouteProps } from "react-router"; -import { buildURL, IURLParams } from "../../../common/utils/buildUrl"; -import type { KubeResource } from "../../../common/rbac"; +import { buildURL, URLParams } from "../utils/buildUrl"; +import type { KubeResource } from "../rbac"; // Routes export const overviewRoute: RouteProps = { @@ -63,40 +63,40 @@ export const workloadsRoute: RouteProps = { }; // Route params -export interface IWorkloadsOverviewRouteParams { +export interface WorkloadsOverviewRouteParams { } -export interface IPodsRouteParams { +export interface PodsRouteParams { } -export interface IDeploymentsRouteParams { +export interface DeploymentsRouteParams { } -export interface IDaemonSetsRouteParams { +export interface DaemonSetsRouteParams { } -export interface IStatefulSetsRouteParams { +export interface StatefulSetsRouteParams { } -export interface IReplicaSetsRouteParams { +export interface ReplicaSetsRouteParams { } -export interface IJobsRouteParams { +export interface JobsRouteParams { } -export interface ICronJobsRouteParams { +export interface CronJobsRouteParams { } // URL-builders -export const workloadsURL = (params?: IURLParams) => overviewURL(params); -export const overviewURL = buildURL(overviewRoute.path); -export const podsURL = buildURL(podsRoute.path); -export const deploymentsURL = buildURL(deploymentsRoute.path); -export const daemonSetsURL = buildURL(daemonSetsRoute.path); -export const statefulSetsURL = buildURL(statefulSetsRoute.path); -export const replicaSetsURL = buildURL(replicaSetsRoute.path); -export const jobsURL = buildURL(jobsRoute.path); -export const cronJobsURL = buildURL(cronJobsRoute.path); +export const workloadsURL = (params?: URLParams) => overviewURL(params); +export const overviewURL = buildURL(overviewRoute.path); +export const podsURL = buildURL(podsRoute.path); +export const deploymentsURL = buildURL(deploymentsRoute.path); +export const daemonSetsURL = buildURL(daemonSetsRoute.path); +export const statefulSetsURL = buildURL(statefulSetsRoute.path); +export const replicaSetsURL = buildURL(replicaSetsRoute.path); +export const jobsURL = buildURL(jobsRoute.path); +export const cronJobsURL = buildURL(cronJobsRoute.path); export const workloadURL: Partial>> = { "pods": podsURL, diff --git a/src/common/search-store.ts b/src/common/search-store.ts index ae4ba5fa5c35..b81bc62df3fe 100644 --- a/src/common/search-store.ts +++ b/src/common/search-store.ts @@ -20,10 +20,10 @@ */ import { action, computed, observable,reaction } from "mobx"; -import { dockStore } from "../renderer/components/dock/dock.store"; -import { autobind } from "../renderer/utils"; +import { DockStore } from "../renderer/components/dock"; +import { autobind, Singleton } from "../renderer/utils"; -export class SearchStore { +export class SearchStore extends Singleton { /** * An utility methods escaping user string to safely pass it into new Regex(variable) * @param value Unescaped string @@ -54,8 +54,10 @@ export class SearchStore { @observable activeOverlayIndex = -1; constructor() { - reaction(() => dockStore.selectedTabId, () => { - searchStore.reset(); + super(); + + reaction(() => DockStore.getInstance().selectedTabId, () => { + this.reset(); }); } @@ -173,5 +175,3 @@ export class SearchStore { this.occurrences = []; } } - -export const searchStore = new SearchStore; diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 19ba33615cab..c07ee8c18862 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/buildUrl.ts b/src/common/utils/buildUrl.ts index d373aa634dad..965a0be1bafa 100644 --- a/src/common/utils/buildUrl.ts +++ b/src/common/utils/buildUrl.ts @@ -21,7 +21,7 @@ import { compile } from "path-to-regexp"; -export interface IURLParams

{ +export interface URLParams

{ params?: P; query?: Q; fragment?: string; @@ -30,7 +30,7 @@ export interface IURLParams

{ export function buildURL

(path: string | any) { const pathBuilder = compile(String(path)); - return function ({ params, query, fragment }: IURLParams = {}): string { + return function ({ params, query, fragment }: URLParams = {}): string { const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; const parts = [ pathBuilder(params), diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 5b1315954920..8bdf9df343e8 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/kubeconfig-files.ts b/src/common/utils/kubeconfig-files.ts new file mode 100644 index 000000000000..a75fe8a324f9 --- /dev/null +++ b/src/common/utils/kubeconfig-files.ts @@ -0,0 +1,45 @@ +/** + * 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 type { KubeConfig } from "@kubernetes/client-node"; +import { app, remote } from "electron"; +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 embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string { + const filePath = getCustomKubeConfigPath(clusterId); + const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig); + + fse.ensureDirSync(path.dirname(filePath)); + fse.writeFileSync(filePath, fileContents, { mode: 0o600 }); + + return filePath; +} diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index bc1d5e53319e..cc396985beb7 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -20,13 +20,13 @@ */ import { watch } from "chokidar"; -import { ipcRenderer } from "electron"; +import { ipcMain, ipcRenderer } from "electron"; import { EventEmitter } from "events"; import fse from "fs-extra"; import { observable, reaction, toJS, when } from "mobx"; import os from "os"; import path from "path"; -import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc"; +import { broadcastMessage, ipcRendererOn, requestMain } from "../common/ipc"; import { Singleton } from "../common/utils"; import logger from "../main/logger"; import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store"; @@ -130,13 +130,13 @@ export class ExtensionDiscovery extends Singleton { }; requestMain(ExtensionDiscovery.extensionDiscoveryChannel).then(onMessage); - subscribeToBroadcast(ExtensionDiscovery.extensionDiscoveryChannel, (_event, message: ExtensionDiscoveryChannelMessage) => { + ipcRendererOn(ExtensionDiscovery.extensionDiscoveryChannel, (_event, message: ExtensionDiscoveryChannelMessage) => { onMessage(message); }); } async initMain() { - handleRequest(ExtensionDiscovery.extensionDiscoveryChannel, () => this.toJSON()); + ipcMain.handle(ExtensionDiscovery.extensionDiscoveryChannel, () => this.toJSON()); reaction(() => this.toJSON(), () => { this.broadcast(); diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index b024ddd761dc..5328bebdfa6c 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -19,13 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, ipcRenderer, remote } from "electron"; +import { app, ipcMain, ipcRenderer, remote } from "electron"; import { EventEmitter } from "events"; import { isEqual } from "lodash"; import { action, computed, observable, reaction, toJS, when } from "mobx"; import path from "path"; import { getHostedCluster } from "../common/cluster-store"; -import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc"; +import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain } from "../common/ipc"; import { Singleton } from "../common/utils"; import logger from "../main/logger"; import type { InstalledExtension } from "./extension-discovery"; @@ -164,11 +164,11 @@ export class ExtensionLoader extends Singleton { this.broadcastExtensions(); }); - handleRequest(ExtensionLoader.extensionsMainChannel, () => { + ipcMain.handle(ExtensionLoader.extensionsMainChannel, () => { return Array.from(this.toJSON()); }); - subscribeToBroadcast(ExtensionLoader.extensionsRendererChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => { + ipcMainOn(ExtensionLoader.extensionsRendererChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => { this.syncExtensions(extensions); }); } @@ -193,7 +193,7 @@ export class ExtensionLoader extends Singleton { }); requestMain(ExtensionLoader.extensionsMainChannel).then(extensionListHandler); - subscribeToBroadcast(ExtensionLoader.extensionsMainChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => { + ipcRendererOn(ExtensionLoader.extensionsMainChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => { extensionListHandler(extensions); }); } @@ -207,11 +207,13 @@ export class ExtensionLoader extends Singleton { } loadOnMain() { + registries.MenuRegistry.createInstance(); + logger.debug(`${logModule}: load on main`); this.autoInitExtensions(async (extension: LensMainExtension) => { // Each .add returns a function to remove the item const removeItems = [ - registries.menuRegistry.add(extension.appMenus) + registries.MenuRegistry.getInstance().add(extension.appMenus) ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { @@ -230,12 +232,12 @@ export class ExtensionLoader extends Singleton { logger.debug(`${logModule}: load on main renderer (cluster manager)`); this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ - registries.globalPageRegistry.add(extension.globalPages, extension), - registries.appPreferenceRegistry.add(extension.appPreferences), - registries.entitySettingRegistry.add(extension.entitySettings), - registries.statusBarRegistry.add(extension.statusBarItems), - registries.commandRegistry.add(extension.commands), - registries.welcomeMenuRegistry.add(extension.welcomeMenus), + registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension), + registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences), + registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), + registries.StatusBarRegistry.getInstance().add(extension.statusBarItems), + registries.CommandRegistry.getInstance().add(extension.commands), + registries.WelcomeMenuRegistry.getInstance().add(extension.welcomeMenus), ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { @@ -260,12 +262,12 @@ export class ExtensionLoader extends Singleton { } const removeItems = [ - registries.clusterPageRegistry.add(extension.clusterPages, extension), - registries.clusterPageMenuRegistry.add(extension.clusterPageMenus, extension), - registries.kubeObjectMenuRegistry.add(extension.kubeObjectMenuItems), - registries.kubeObjectDetailRegistry.add(extension.kubeObjectDetailItems), - registries.kubeObjectStatusRegistry.add(extension.kubeObjectStatusTexts), - registries.commandRegistry.add(extension.commands), + registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension), + registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension), + registries.KubeObjectMenuRegistry.getInstance().add(extension.kubeObjectMenuItems), + registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems), + registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts), + registries.CommandRegistry.getInstance().add(extension.commands), ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { diff --git a/src/extensions/interfaces/registrations.ts b/src/extensions/interfaces/registrations.ts index e2e8d1ed1170..dadde70ceb20 100644 --- a/src/extensions/interfaces/registrations.ts +++ b/src/extensions/interfaces/registrations.ts @@ -26,4 +26,4 @@ export type { KubeObjectStatusRegistration } from "../registries/kube-object-sta export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry"; export type { PageMenuRegistration, ClusterPageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"; export type { StatusBarRegistration } from "../registries/status-bar-registry"; -export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler-registry"; +export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler"; diff --git a/src/extensions/npm/extensions/package.json b/src/extensions/npm/extensions/package.json index 6857b12ddae3..3833ec905e9c 100644 --- a/src/extensions/npm/extensions/package.json +++ b/src/extensions/npm/extensions/package.json @@ -2,7 +2,7 @@ "name": "@k8slens/extensions", "productName": "OpenLens extensions", "description": "OpenLens - Open Source Kubernetes IDE: extensions", - "version": "0.0.0", + "version": "5.0.0-beta.5", "copyright": "© 2021 OpenLens Authors", "license": "MIT", "main": "dist/src/extensions/extension-api.js", diff --git a/src/extensions/registries/__tests__/page-registry.test.ts b/src/extensions/registries/__tests__/page-registry.test.ts index fc40b1bacaa5..9b73c3c1daa1 100644 --- a/src/extensions/registries/__tests__/page-registry.test.ts +++ b/src/extensions/registries/__tests__/page-registry.test.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { getExtensionPageUrl, globalPageRegistry, PageParams } from "../page-registry"; +import { ClusterPageRegistry, getExtensionPageUrl, GlobalPageRegistry, PageParams } from "../page-registry"; import { LensExtension } from "../../lens-extension"; import React from "react"; import { Console } from "console"; @@ -42,7 +42,8 @@ describe("getPageUrl", () => { isBundled: false, isEnabled: true }); - globalPageRegistry.add({ + ClusterPageRegistry.createInstance(); + GlobalPageRegistry.createInstance().add({ id: "page-with-params", components: { Page: () => React.createElement("Page with params") @@ -54,6 +55,11 @@ describe("getPageUrl", () => { }, ext); }); + afterEach(() => { + GlobalPageRegistry.resetInstance(); + ClusterPageRegistry.resetInstance(); + }); + it("returns a page url for extension", () => { expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar"); }); @@ -102,7 +108,8 @@ describe("globalPageRegistry", () => { isBundled: false, isEnabled: true }); - globalPageRegistry.add([ + ClusterPageRegistry.createInstance(); + GlobalPageRegistry.createInstance().add([ { id: "test-page", components: { @@ -123,9 +130,14 @@ describe("globalPageRegistry", () => { ], ext); }); + afterEach(() => { + GlobalPageRegistry.resetInstance(); + ClusterPageRegistry.resetInstance(); + }); + describe("getByPageTarget", () => { it("matching to first registered page without id", () => { - const page = globalPageRegistry.getByPageTarget({ extensionId: ext.name }); + const page = GlobalPageRegistry.getInstance().getByPageTarget({ extensionId: ext.name }); expect(page.id).toEqual(undefined); expect(page.extensionId).toEqual(ext.name); @@ -133,7 +145,7 @@ describe("globalPageRegistry", () => { }); it("returns matching page", () => { - const page = globalPageRegistry.getByPageTarget({ + const page = GlobalPageRegistry.getInstance().getByPageTarget({ pageId: "test-page", extensionId: ext.name }); @@ -142,7 +154,7 @@ describe("globalPageRegistry", () => { }); it("returns null if target not found", () => { - const page = globalPageRegistry.getByPageTarget({ + const page = GlobalPageRegistry.getInstance().getByPageTarget({ pageId: "wrong-page", extensionId: ext.name }); diff --git a/src/extensions/registries/app-preference-registry.ts b/src/extensions/registries/app-preference-registry.ts index 4df13edc6369..fb20034d5220 100644 --- a/src/extensions/registries/app-preference-registry.ts +++ b/src/extensions/registries/app-preference-registry.ts @@ -46,5 +46,3 @@ export class AppPreferenceRegistry extends BaseRegistry { +export class BaseRegistry extends Singleton { private items = observable.map(); getItems(): I[] { diff --git a/src/extensions/registries/command-registry.ts b/src/extensions/registries/command-registry.ts index ba88f623dd31..5ecd23dfc8d0 100644 --- a/src/extensions/registries/command-registry.ts +++ b/src/extensions/registries/command-registry.ts @@ -53,6 +53,8 @@ export class CommandRegistry extends BaseRegistry { return super.add(filteredItems, extension); } -} -export const commandRegistry = new CommandRegistry(); + getById(commandId: string): CommandRegistration | undefined { + return this.getItems().find(({ id }) => id == commandId); + } +} diff --git a/src/extensions/registries/entity-setting-registry.ts b/src/extensions/registries/entity-setting-registry.ts index 80b20196c321..54d85ef57f4c 100644 --- a/src/extensions/registries/entity-setting-registry.ts +++ b/src/extensions/registries/entity-setting-registry.ts @@ -68,5 +68,3 @@ export class EntitySettingRegistry extends BaseRegistry (b.priority ?? 50) - (a.priority ?? 50)); } } - -export const entitySettingRegistry = new EntitySettingRegistry(); diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 7056206dafef..5f9bd64aa4a4 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -32,4 +32,4 @@ export * from "./kube-object-status-registry"; export * from "./command-registry"; export * from "./entity-setting-registry"; export * from "./welcome-menu-registry"; -export * from "./protocol-handler-registry"; +export * from "./protocol-handler"; diff --git a/src/extensions/registries/kube-object-detail-registry.ts b/src/extensions/registries/kube-object-detail-registry.ts index 7cc17bbc7ec9..844f5fc0eeec 100644 --- a/src/extensions/registries/kube-object-detail-registry.ts +++ b/src/extensions/registries/kube-object-detail-registry.ts @@ -20,10 +20,12 @@ */ import type React from "react"; +import type { KubeObjectDetailsProps } from "../renderer-api/components"; +import type { KubeObject } from "../renderer-api/k8s-api"; import { BaseRegistry } from "./base-registry"; export interface KubeObjectDetailComponents { - Details: React.ComponentType; + Details: React.ComponentType>; } export interface KubeObjectDetailRegistration { @@ -42,5 +44,3 @@ export class KubeObjectDetailRegistry extends BaseRegistry (b.priority ?? 50) - (a.priority ?? 50)); } } - -export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry(); diff --git a/src/extensions/registries/kube-object-menu-registry.ts b/src/extensions/registries/kube-object-menu-registry.ts index 414d55db5994..fbbc872d2baa 100644 --- a/src/extensions/registries/kube-object-menu-registry.ts +++ b/src/extensions/registries/kube-object-menu-registry.ts @@ -39,5 +39,3 @@ export class KubeObjectMenuRegistry extends BaseRegistry { } - -export const menuRegistry = new MenuRegistry(); diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 08df7b1c7946..53dc7431719a 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -77,5 +77,3 @@ export class ClusterPageMenuRegistry extends PageMenuRegistry { @@ -96,7 +96,7 @@ export function getExtensionPageUrl(target: PageTarget): string { return pageUrl.href.replace(pageUrl.origin, ""); } -export class PageRegistry extends BaseRegistry { +class PageRegistry extends BaseRegistry { protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage { const { id: pageId } = page; const extensionId = ext.name; @@ -139,5 +139,5 @@ export class PageRegistry extends BaseRegistry } } -export const globalPageRegistry = new PageRegistry(); -export const clusterPageRegistry = new PageRegistry(); +export class ClusterPageRegistry extends PageRegistry {} +export class GlobalPageRegistry extends PageRegistry {} diff --git a/src/extensions/registries/protocol-handler-registry.ts b/src/extensions/registries/protocol-handler.ts similarity index 100% rename from src/extensions/registries/protocol-handler-registry.ts rename to src/extensions/registries/protocol-handler.ts diff --git a/src/extensions/registries/status-bar-registry.ts b/src/extensions/registries/status-bar-registry.ts index 0814f9cc7afd..2afdee76a61b 100644 --- a/src/extensions/registries/status-bar-registry.ts +++ b/src/extensions/registries/status-bar-registry.ts @@ -41,5 +41,3 @@ export interface StatusBarRegistration extends StatusBarRegistrationV2 { export class StatusBarRegistry extends BaseRegistry { } - -export const statusBarRegistry = new StatusBarRegistry(); diff --git a/src/extensions/registries/welcome-menu-registry.ts b/src/extensions/registries/welcome-menu-registry.ts index 077f1d3aea80..709202845975 100644 --- a/src/extensions/registries/welcome-menu-registry.ts +++ b/src/extensions/registries/welcome-menu-registry.ts @@ -28,5 +28,3 @@ export interface WelcomeMenuRegistration { } export class WelcomeMenuRegistry extends BaseRegistry {} - -export const welcomeMenuRegistry = new WelcomeMenuRegistry(); diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index d16dbe83f523..ae58e13595e3 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 f8a338ac3fdf..86b4fb9c8abe 100644 --- a/src/extensions/renderer-api/navigation.ts +++ b/src/extensions/renderer-api/navigation.ts @@ -24,8 +24,8 @@ 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 type { IURLParams } from "../../common/utils/buildUrl"; +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 export function createPageParam(init: PageParamInit) { diff --git a/src/main/catalog-sources/kubeconfig-sync.ts b/src/main/catalog-sources/kubeconfig-sync.ts index c72b6081f2b5..1b36518df330 100644 --- a/src/main/catalog-sources/kubeconfig-sync.ts +++ b/src/main/catalog-sources/kubeconfig-sync.ts @@ -26,14 +26,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]:"; @@ -63,7 +64,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 9818b0d990b6..21d7a005b30d 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 82a09059e35c..40b4fa694938 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 c7f2791cee64..0deb598b39d8 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 537fef96cb16..2aed3d0640a5 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 f30f5e6c70a8..2e136a7acdfc 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 f7240ab3ea3c..2b7b76786f8f 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 9ef28c8f9f50..8fc9a4d3ca2a 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 6def60b08f86..a6fa888d304e 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 6fa7ca1ef5ba..446718f8d6f5 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 2f9e3b00c727..cd53b995ef43 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 852c649ede2c..7853977e1d53 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 2585b0740ece..f78dcc393032 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -46,7 +46,7 @@ import { FilesystemProvisionerStore } from "./extension-filesystem"; import { installDeveloperTools } from "./developer-tools"; import { LensProtocolRouterMain } from "./protocol-handler"; import { disposer, getAppVersion, getAppVersionFromProxyServer } from "../common/utils"; -import { bindBroadcastHandlers } from "../common/ipc"; +import { initGetSubFramesHandler } from "../common/ipc"; import { startUpdateChecking } from "./app-updater"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import { pushCatalogToRenderer } from "./catalog-pusher"; @@ -54,7 +54,11 @@ import { catalogEntityRegistry } from "./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(); @@ -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(); 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"); @@ -169,6 +175,7 @@ app.on("ready", async () => { app.exit(); } + initRegistries(); const extensionDiscovery = ExtensionDiscovery.createInstance(); ExtensionLoader.createInstance().init(); @@ -181,6 +188,8 @@ app.on("ready", async () => { logger.info("🖥️ Starting WindowManager"); const windowManager = WindowManager.createInstance(); + cleanup.push(initMenu(windowManager), initTray(windowManager)); + installDeveloperTools(); if (!startHidden) { @@ -260,6 +269,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/renderer/components/+cluster/index.ts b/src/main/initializers/index.ts similarity index 97% rename from src/renderer/components/+cluster/index.ts rename to src/main/initializers/index.ts index 34cc79e0f6a0..9cf15b3771d7 100644 --- a/src/renderer/components/+cluster/index.ts +++ b/src/main/initializers/index.ts @@ -19,5 +19,4 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./cluster.route"; - +export * from "./registries"; diff --git a/src/main/initializers/ipc-handlers.ts b/src/main/initializers/ipc-handlers.ts new file mode 100644 index 000000000000..9cf0eb4b76d9 --- /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/initializers/registries.ts b/src/main/initializers/registries.ts new file mode 100644 index 000000000000..28fcdff1a1ba --- /dev/null +++ b/src/main/initializers/registries.ts @@ -0,0 +1,26 @@ +/** + * 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 * as registries from "../../extensions/registries"; + +export function initRegistries() { + registries.MenuRegistry.createInstance(); +} diff --git a/src/main/menu.ts b/src/main/menu.ts index 707047067965..5b60541baa2f 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -23,16 +23,12 @@ import { app, BrowserWindow, dialog, ipcMain, IpcMainEvent, Menu, MenuItem, Menu import { autorun } from "mobx"; import type { WindowManager } from "./window-manager"; import { appName, isMac, isWindows, isTestEnv, docsUrl, supportUrl, productName } from "../common/vars"; -import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route"; -import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; -import { welcomeURL } from "../renderer/components/+welcome/welcome.route"; -import { extensionsURL } from "../renderer/components/+extensions/extensions.route"; -import { catalogURL } from "../renderer/components/+catalog/catalog.route"; -import { menuRegistry } from "../extensions/registries/menu-registry"; +import { MenuRegistry } from "../extensions/registries/menu-registry"; import logger from "./logger"; import { exitApp } from "./exit-app"; import { broadcastMessage } from "../common/ipc"; import * as packageJson from "../../package.json"; +import { preferencesURL, extensionsURL, addClusterURL, catalogURL, welcomeURL } from "../common/routes"; export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"; @@ -259,7 +255,7 @@ export function buildMenu(windowManager: WindowManager) { }; // Modify menu from extensions-api - menuRegistry.getItems().forEach(({ parentId, ...menuItem }) => { + MenuRegistry.getInstance().getItems().forEach(({ parentId, ...menuItem }) => { try { const topMenu = appMenu[parentId as MenuTopId].submenu as MenuItemConstructorOptions[]; diff --git a/src/main/proxy/lens-proxy.ts b/src/main/proxy/lens-proxy.ts index 199d48398b60..b1dfc2735156 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 5dd02cc6179e..8e6e3177e7e3 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 059911421956..fea79ef851c1 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/tray.ts b/src/main/tray.ts index cca309082863..24b00d122a40 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -26,10 +26,10 @@ import { autorun } from "mobx"; import { showAbout } from "./menu"; import { checkForUpdates, isAutoUpdateEnabled } from "./app-updater"; import type { WindowManager } from "./window-manager"; -import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; import logger from "./logger"; import { isDevelopment, isWindows, productName } from "../common/vars"; import { exitApp } from "./exit-app"; +import { preferencesURL } from "../common/routes"; const TRAY_LOG_PREFIX = "[TRAY]"; diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index f1d4073edbd9..ded4d8faecf9 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 { subscribeToBroadcast } from "../common/ipc"; -import { initMenu } from "./menu"; -import { initTray } from "./tray"; +import { ipcMainOn } from "../common/ipc"; 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 - subscribeToBroadcast(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 df51510f234e..e47046ddd143 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 3dcf998f1cd8..4ca354eb9379 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 8c7beb12c95c..25f8a308a3b6 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__/crd.test.ts b/src/renderer/api/__tests__/crd.test.ts index af7db4280c45..61fa4ae83fbc 100644 --- a/src/renderer/api/__tests__/crd.test.ts +++ b/src/renderer/api/__tests__/crd.test.ts @@ -19,10 +19,23 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { Cluster } from "../../../main/cluster"; +import { ApiManager } from "../api-manager"; import { CustomResourceDefinition } from "../endpoints"; import type { IKubeObjectMetadata } from "../kube-object"; describe("Crds", () => { + beforeEach(() => { + ApiManager.createInstance(new Cluster({ + id: "foo", + kubeConfigPath: "/bar", + })); + }); + + afterEach(() => { + ApiManager.resetInstance(); + }); + describe("getVersion", () => { it("should get the first version name from the list of versions", () => { const crd = new CustomResourceDefinition({ diff --git a/src/renderer/api/__tests__/kube-api.test.ts b/src/renderer/api/__tests__/kube-api.test.ts index c7415cba7310..f692af2706ce 100644 --- a/src/renderer/api/__tests__/kube-api.test.ts +++ b/src/renderer/api/__tests__/kube-api.test.ts @@ -19,9 +19,23 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { Cluster } from "../../../main/cluster"; +import { ApiManager } from "../api-manager"; import { KubeApi } from "../kube-api"; +import { KubeObject } from "../kube-object"; describe("KubeApi", () => { + beforeEach(() => { + ApiManager.createInstance(new Cluster({ + id: "foo", + kubeConfigPath: "/bar", + })); + }); + + afterEach(() => { + ApiManager.resetInstance(); + }); + it("uses url from apiBase if apiBase contains the resource", async () => { (fetch as any).mockResponse(async (request: any) => { if (request.url === "/api-kube/apis/networking.k8s.io/v1") { @@ -53,6 +67,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 +106,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/__tests__/pods.test.ts b/src/renderer/api/__tests__/pods.test.ts index 3315581d0469..f8567b69e737 100644 --- a/src/renderer/api/__tests__/pods.test.ts +++ b/src/renderer/api/__tests__/pods.test.ts @@ -19,6 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { Cluster } from "../../../main/cluster"; +import { ApiManager } from "../api-manager"; import { Pod } from "../endpoints"; interface GetDummyPodOptions { @@ -164,6 +166,17 @@ function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Po } describe("Pods", () => { + beforeEach(() => { + ApiManager.createInstance(new Cluster({ + id: "foo", + kubeConfigPath: "/bar", + })); + }); + + afterEach(() => { + ApiManager.resetInstance(); + }); + const podTests = []; for (let r = 0; r < 3; r += 1) { diff --git a/src/renderer/api/api-manager.ts b/src/renderer/api/api-manager.ts index ada5424bef10..54acc5988c65 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[]): Store { + const store = new storeConstructor(this.cluster); + + for (const api of apis ?? [store.api]) { this.stores.set(api.apiBase, store); - }); + } + + return store; } - 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/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index d6e2059edf22..70124b11462a 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -20,7 +20,7 @@ */ import { computed, observable } from "mobx"; -import { subscribeToBroadcast } from "../../common/ipc"; +import { ipcRendererOn } from "../../common/ipc"; import { CatalogCategory, CatalogEntity, CatalogEntityData, catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntityKindData } from "../../common/catalog"; import "../../common/catalog-entities"; import { iter } from "../utils"; @@ -32,7 +32,7 @@ export class CatalogEntityRegistry { constructor(private categoryRegistry: CatalogCategoryRegistry) {} init() { - subscribeToBroadcast("catalog:items", (ev, items: (CatalogEntityData & CatalogEntityKindData)[]) => { + ipcRendererOn("catalog:items", (event, items: (CatalogEntityData & CatalogEntityKindData)[]) => { this.rawItems.replace(items); }); } diff --git a/src/renderer/api/catalog-entity.ts b/src/renderer/api/catalog-entity.ts index 63a5ce62eb7c..1a0154defe03 100644 --- a/src/renderer/api/catalog-entity.ts +++ b/src/renderer/api/catalog-entity.ts @@ -20,7 +20,7 @@ */ import { navigate } from "../navigation"; -import { commandRegistry } from "../../extensions/registries"; +import { CommandRegistry } from "../../extensions/registries"; import type { CatalogEntity } from "../../common/catalog"; export { CatalogCategory, CatalogEntity } from "../../common/catalog"; @@ -37,6 +37,6 @@ export type { export const catalogEntityRunContext = { navigate: (url: string) => navigate(url), setCommandPaletteContext: (entity?: CatalogEntity) => { - commandRegistry.activeEntity = entity; + CommandRegistry.getInstance().activeEntity = entity; } }; diff --git a/src/renderer/api/endpoints/cluster.api.ts b/src/renderer/api/endpoints/cluster.api.ts index 309f936ac737..299602a6c942 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/crd.api.ts b/src/renderer/api/endpoints/crd.api.ts index 5d2b657ce491..c50e83020540 100644 --- a/src/renderer/api/endpoints/crd.api.ts +++ b/src/renderer/api/endpoints/crd.api.ts @@ -20,8 +20,8 @@ */ import { KubeObject } from "../kube-object"; -import { crdResourcesURL } from "../../components/+custom-resources/crd.route"; import { KubeApi } from "../kube-api"; +import { crdResourcesURL } from "../../../common/routes"; type AdditionalPrinterColumnsCommon = { name: string; diff --git a/src/renderer/api/endpoints/persistent-volume-claims.api.ts b/src/renderer/api/endpoints/persistent-volume-claims.api.ts index 92641945d7ca..028f605ee5ec 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 b9763b093cb3..7569484607b5 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 1244c3e13eac..59c384950c1b 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 1756977c95ba..f2a9cff5bf51 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 b02067fb6311..45e69e20b4de 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 21e370a207d2..4cef93015930 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-detail-registry.ts b/src/renderer/api/kube-object-detail-registry.ts index 0afc7907da78..1693fd56f17d 100644 --- a/src/renderer/api/kube-object-detail-registry.ts +++ b/src/renderer/api/kube-object-detail-registry.ts @@ -19,4 +19,4 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export { kubeObjectDetailRegistry } from "../../extensions/registries/kube-object-detail-registry"; +export { KubeObjectDetailRegistry } from "../../extensions/registries/kube-object-detail-registry"; diff --git a/src/renderer/api/kube-object.ts b/src/renderer/api/kube-object.ts index 06c431851cbf..ecdfaeedcf1a 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 4c68e0fdfa4c..317e9bdbafce 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 62430ac788c0..db1f39c28412 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -37,12 +37,13 @@ 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"; import { ExtensionInstallationStateStore } from "./components/+extensions/extension-install.store"; import { DefaultProps } from "./mui-base-theme"; +import { initCommandRegistry, initEntitySettingsRegistry, initKubeObjectMenuRegistry, initRegistries, initWelcomeMenuRegistry, intiKubeObjectDetailRegistry } from "./initializers"; /** * If this is a development buid, wait a second to attach @@ -74,6 +75,13 @@ export async function bootstrap(App: AppComponent) { await attachChromeDebugger(); rootElem.classList.toggle("is-mac", isMac); + initRegistries(); + initCommandRegistry(); + initEntitySettingsRegistry(); + initKubeObjectMenuRegistry(); + intiKubeObjectDetailRegistry(); + initWelcomeMenuRegistry(); + ExtensionLoader.createInstance().init(); ExtensionDiscovery.createInstance().init(); @@ -119,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 7f706e91982a..2320076a37c0 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -36,9 +36,10 @@ import { ExecValidationNotFoundError } from "../../../common/custom-errors"; import { appEventBus } from "../../../common/event-bus"; import { PageLayout } from "../layout/page-layout"; import { docsUrl } from "../../../common/vars"; -import { catalogURL } from "../+catalog"; -import { preferencesURL } from "../+preferences"; import { Input } from "../input"; +import { catalogURL, preferencesURL } from "../../../common/routes"; +import { embedCustomKubeConfig } from "../../utils"; + @observer export class AddCluster extends React.Component { @observable.ref kubeConfigLocal: KubeConfig; @@ -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/+add-cluster/index.ts b/src/renderer/components/+add-cluster/index.ts index f04c6a4bdcee..19498760603a 100644 --- a/src/renderer/components/+add-cluster/index.ts +++ b/src/renderer/components/+add-cluster/index.ts @@ -20,4 +20,3 @@ */ export * from "./add-cluster"; -export * from "./add-cluster.route"; diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.tsx b/src/renderer/components/+apps-helm-charts/helm-charts.tsx index 5e451e33e59f..e3055162b72b 100644 --- a/src/renderer/components/+apps-helm-charts/helm-charts.tsx +++ b/src/renderer/components/+apps-helm-charts/helm-charts.tsx @@ -24,13 +24,13 @@ import "./helm-charts.scss"; import React, { Component } from "react"; import type { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; -import { helmChartsURL, IHelmChartsRouteParams } from "./helm-charts.route"; import { helmChartStore } from "./helm-chart.store"; import type { HelmChart } from "../../api/endpoints/helm-charts.api"; import { HelmChartDetails } from "./helm-chart-details"; import { navigation } from "../../navigation"; import { ItemListLayout } from "../item-object-list/item-list-layout"; import { SearchInputUrl } from "../input"; +import { helmChartsURL, IHelmChartsRouteParams } from "../../../common/routes"; enum columnId { name = "name", diff --git a/src/renderer/components/+apps-helm-charts/index.ts b/src/renderer/components/+apps-helm-charts/index.ts index 400313ea3210..74f1f4d66a70 100644 --- a/src/renderer/components/+apps-helm-charts/index.ts +++ b/src/renderer/components/+apps-helm-charts/index.ts @@ -20,4 +20,3 @@ */ export * from "./helm-charts"; -export * from "./helm-charts.route"; diff --git a/src/renderer/components/+apps-releases/index.ts b/src/renderer/components/+apps-releases/index.ts index 01eeefb05fc8..100df69e95ed 100644 --- a/src/renderer/components/+apps-releases/index.ts +++ b/src/renderer/components/+apps-releases/index.ts @@ -20,4 +20,3 @@ */ export * from "./releases"; -export * from "./release.route"; diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx index 260611e420a7..fe225db7b5cb 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 78e4c8e94ba0..5b13ab77be0e 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 bb25f6f5d66b..8d17f89d65b0 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 7ed2ac42c73d..55133b3c585d 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 bbf394455471..b3ee7d1e7cf9 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 { IReleaseRouteParams, releaseURL } from "./release.route"; +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", @@ -46,22 +48,26 @@ enum columnId { updated = "update" } -interface Props extends RouteComponentProps { +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.command.ts b/src/renderer/components/+apps/apps.command.ts deleted file mode 100644 index 830fad12d4c2..000000000000 --- a/src/renderer/components/+apps/apps.command.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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 { navigate } from "../../navigation"; -import { commandRegistry } from "../../../extensions/registries/command-registry"; -import { helmChartsURL } from "../+apps-helm-charts"; -import { releaseURL } from "../+apps-releases"; - -commandRegistry.add({ - id: "cluster.viewHelmCharts", - title: "Cluster: View Helm Charts", - scope: "entity", - action: () => navigate(helmChartsURL()) -}); - -commandRegistry.add({ - id: "cluster.viewHelmReleases", - title: "Cluster: View Helm Releases", - scope: "entity", - action: () => navigate(releaseURL()) -}); diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx index 1e9ee3623af3..550e61a6d9d6 100644 --- a/src/renderer/components/+apps/apps.tsx +++ b/src/renderer/components/+apps/apps.tsx @@ -22,13 +22,15 @@ import React from "react"; import { observer } from "mobx-react"; import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { HelmCharts, helmChartsRoute, helmChartsURL } from "../+apps-helm-charts"; -import { HelmReleases, releaseRoute, releaseURL } from "../+apps-releases"; +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 [ @@ -49,7 +51,7 @@ export class Apps extends React.Component { render() { return ( - + ); } } diff --git a/src/renderer/components/+apps/index.ts b/src/renderer/components/+apps/index.ts index 9cec263b5b81..4c8d6dfea053 100644 --- a/src/renderer/components/+apps/index.ts +++ b/src/renderer/components/+apps/index.ts @@ -20,5 +20,3 @@ */ export * from "./apps"; -export * from "./apps.route"; -export * from "./apps.command"; diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 5211e034c27f..6cbda649df54 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -38,8 +38,8 @@ import { Tab, Tabs } from "../tabs"; import { catalogCategoryRegistry } from "../../../common/catalog"; import { CatalogAddButton } from "./catalog-add-button"; import type { RouteComponentProps } from "react-router"; -import type { ICatalogViewRouteParam } from "./catalog.route"; import { Notifications } from "../notifications"; +import type { ICatalogViewRouteParam } from "../../../common/routes"; enum sortBy { name = "name", diff --git a/src/renderer/components/+catalog/index.tsx b/src/renderer/components/+catalog/index.tsx index 0efe388e11b4..b658c981b262 100644 --- a/src/renderer/components/+catalog/index.tsx +++ b/src/renderer/components/+catalog/index.tsx @@ -19,5 +19,4 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./catalog.route"; export * from "./catalog"; diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index ce6496f4905f..771b6d0ebb9a 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 b80dd10527e1..8b41c512b605 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 0c4910e7e578..f6b8757546d4 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 c9ffc4008c02..031a5dab2bc3 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 cc72c7cc6328..4466d475ab69 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 b7b84c817dd0..a78c486062e0 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 5b9cf76f97ba..31d83931e4d2 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -29,11 +29,9 @@ import { Badge } from "../badge"; import { KubeObjectDetailsProps, getDetailsUrl } from "../kube-object"; import { cssNames } from "../../utils"; import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api"; -import { KubeEventDetails } from "../+events/kube-event-details"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import { lookupApiLink } from "../../api/kube-api"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; -import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +import { ApiManager } from "../../api/api-manager"; interface Props extends KubeObjectDetailsProps { } @@ -56,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 ( <> @@ -109,7 +107,7 @@ export class HpaDetails extends React.Component { {scaleTargetRef && ( - + {scaleTargetRef.kind}/{scaleTargetRef.name} )} @@ -150,20 +148,3 @@ export class HpaDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "HorizontalPodAutoscaler", - apiVersions: ["autoscaling/v2beta1"], - components: { - Details: (props) => - } -}); - -kubeObjectDetailRegistry.add({ - kind: "HorizontalPodAutoscaler", - apiVersions: ["autoscaling/v2beta1"], - priority: 5, - components: { - Details: (props) => - } -}); diff --git a/src/renderer/components/+config-autoscalers/hpa.store.ts b/src/renderer/components/+config-autoscalers/hpa.store.ts index aa94a4a3cd1e..3553ac7dcdba 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 54eb0b67a85e..2c50f3f9a39f 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 { IHpaRouteParams } from "./hpa.route"; -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 ae6379ef6d0e..d6a715f35e89 100644 --- a/src/renderer/components/+config-autoscalers/index.ts +++ b/src/renderer/components/+config-autoscalers/index.ts @@ -20,5 +20,5 @@ */ export * from "./hpa"; +export * from "./hpa.store"; export * from "./hpa-details"; -export * from "./hpa.route"; diff --git a/src/renderer/components/+config-limit-ranges/index.ts b/src/renderer/components/+config-limit-ranges/index.ts index bb96c6a3d392..0c9e384eb33a 100644 --- a/src/renderer/components/+config-limit-ranges/index.ts +++ b/src/renderer/components/+config-limit-ranges/index.ts @@ -20,5 +20,5 @@ */ export * from "./limit-ranges"; -export * from "./limit-ranges.route"; +export * from "./limit-ranges.store"; export * from "./limit-range-details"; diff --git a/src/renderer/components/+config-limit-ranges/limit-range-details.tsx b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx index d4c9d18f28d5..853102931369 100644 --- a/src/renderer/components/+config-limit-ranges/limit-range-details.tsx +++ b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx @@ -25,7 +25,6 @@ import React from "react"; import { observer } from "mobx-react"; import type { KubeObjectDetailsProps } from "../kube-object"; import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../api/endpoints/limit-range.api"; -import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { DrawerItem } from "../drawer/drawer-item"; import { Badge } from "../badge"; @@ -108,11 +107,3 @@ export class LimitRangeDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "LimitRange", - apiVersions: ["v1"], - components: { - Details: (props) => - } -}); 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 a6c7cc770cb3..d9fe5524b069 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 6784e6f7d6cd..a1af127729c8 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 type { LimitRangeRouteParams } from "./limit-ranges.route"; 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 b9dd05cfaaa3..05f3454f7061 100644 --- a/src/renderer/components/+config-maps/config-map-details.tsx +++ b/src/renderer/components/+config-maps/config-map-details.tsx @@ -28,18 +28,21 @@ import { DrawerTitle } from "../drawer"; import { Notifications } from "../notifications"; import { Input } from "../input"; import { Button } from "../button"; -import { KubeEventDetails } from "../+events/kube-event-details"; -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 { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +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(); @@ -60,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. @@ -115,20 +118,3 @@ export class ConfigMapDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "ConfigMap", - apiVersions: ["v1"], - components: { - Details: (props) => - } -}); - -kubeObjectDetailRegistry.add({ - kind: "ConfigMap", - apiVersions: ["v1"], - priority: 5, - components: { - Details: (props) => - } -}); diff --git a/src/renderer/components/+config-maps/config-maps.store.ts b/src/renderer/components/+config-maps/config-maps.store.ts index 51457c8c20c0..f5ccdd234970 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 b1997fa792cc..0029bcc3fdff 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 type { IConfigMapsRouteParams } from "./config-maps.route"; 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", @@ -37,17 +38,22 @@ enum columnId { age = "age", } -interface Props extends RouteComponentProps { +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 537b8478f0f8..36f41cdd6630 100644 --- a/src/renderer/components/+config-maps/index.ts +++ b/src/renderer/components/+config-maps/index.ts @@ -19,6 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./config-maps.route"; 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 af54d9ca0463..2154ef04a626 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/index.ts +++ b/src/renderer/components/+config-pod-disruption-budgets/index.ts @@ -19,6 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./pod-disruption-budgets.route"; 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-details.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx index ddb95aa62890..ad4bc41dc1e5 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx @@ -28,7 +28,6 @@ import { Badge } from "../badge"; import type { KubeObjectDetailsProps } from "../kube-object"; import type { PodDisruptionBudget } from "../../api/endpoints"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; -import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; interface Props extends KubeObjectDetailsProps { } @@ -74,11 +73,3 @@ export class PodDisruptionBudgetDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "PodDisruptionBudget", - apiVersions: ["policy/v1beta1"], - components: { - Details: (props) => - } -}); 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 d97593542d53..c614624e7b50 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 abc87a6a4d06..eff9e02baf0c 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 f4c10c854d9e..acbd8645ffac 100644 --- a/src/renderer/components/+config-resource-quotas/index.ts +++ b/src/renderer/components/+config-resource-quotas/index.ts @@ -19,6 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./resource-quotas.route"; export * from "./resource-quotas"; +export * from "./resource-quotas.store"; export * from "./resource-quota-details"; diff --git a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx index 662a8e26ec20..cf7c505dcfb1 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx +++ b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx @@ -30,7 +30,6 @@ import type { ResourceQuota } from "../../api/endpoints/resource-quota.api"; import { LineProgress } from "../line-progress"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; -import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; interface Props extends KubeObjectDetailsProps { } @@ -117,11 +116,3 @@ export class ResourceQuotaDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "ResourceQuota", - apiVersions: ["v1"], - components: { - Details: (props) => - } -}); 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 5d8907a0b061..6cfd12dca851 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 59be708d6f64..4fd78eefd371 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 { IResourceQuotaRouteParams } from "./resource-quotas.route"; +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", @@ -37,18 +38,23 @@ enum columnId { age = "age" } -interface Props extends RouteComponentProps { +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 1cfc5d40628a..7373eaed9211 100644 --- a/src/renderer/components/+config-secrets/index.ts +++ b/src/renderer/components/+config-secrets/index.ts @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./secrets.route"; 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 5a4aaca64ef3..2cfe754e21be 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -31,17 +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 { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +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 } = {}; @@ -65,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); @@ -138,11 +142,3 @@ export class SecretDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "Secret", - apiVersions: ["v1"], - components: { - Details: (props) => - } -}); diff --git a/src/renderer/components/+config-secrets/secrets.store.ts b/src/renderer/components/+config-secrets/secrets.store.ts index c680c5fb9a4b..079d252e89bd 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 b87c6a51ed1a..1e43002861fc 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 type { ISecretsRouteParams } from "./secrets.route"; 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", @@ -41,18 +42,23 @@ enum columnId { age = "age", } -interface Props extends RouteComponentProps { +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.command.ts b/src/renderer/components/+config/config.command.ts deleted file mode 100644 index 21aeb82bd2c6..000000000000 --- a/src/renderer/components/+config/config.command.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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 { navigate } from "../../navigation"; -import { commandRegistry } from "../../../extensions/registries/command-registry"; -import { configMapsURL } from "../+config-maps"; -import { secretsURL } from "../+config-secrets"; -import { resourceQuotaURL } from "../+config-resource-quotas"; -import { limitRangeURL } from "../+config-limit-ranges"; -import { hpaURL } from "../+config-autoscalers"; -import { pdbURL } from "../+config-pod-disruption-budgets"; - -commandRegistry.add({ - id: "cluster.viewConfigMaps", - title: "Cluster: View ConfigMaps", - scope: "entity", - action: () => navigate(configMapsURL()) -}); - -commandRegistry.add({ - id: "cluster.viewSecrets", - title: "Cluster: View Secrets", - scope: "entity", - action: () => navigate(secretsURL()) -}); - -commandRegistry.add({ - id: "cluster.viewResourceQuotas", - title: "Cluster: View ResourceQuotas", - scope: "entity", - action: () => navigate(resourceQuotaURL()) -}); - -commandRegistry.add({ - id: "cluster.viewLimitRanges", - title: "Cluster: View LimitRanges", - scope: "entity", - action: () => navigate(limitRangeURL()) -}); - -commandRegistry.add({ - id: "cluster.viewHorizontalPodAutoscalers", - title: "Cluster: View HorizontalPodAutoscalers (HPA)", - scope: "entity", - action: () => navigate(hpaURL()) -}); - -commandRegistry.add({ - id: "cluster.viewPodDisruptionBudget", - title: "Cluster: View PodDisruptionBudgets", - scope: "entity", - action: () => navigate(pdbURL()) -}); diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index 95c97cb21722..13c0f38541fb 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -22,81 +22,82 @@ import React from "react"; import { observer } from "mobx-react"; import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps"; -import { Secrets, secretsRoute, secretsURL } from "../+config-secrets"; +import { ConfigMaps } from "../+config-maps"; +import { Secrets } from "../+config-secrets"; import { namespaceUrlParam } from "../+namespaces/namespace.store"; -import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config-resource-quotas"; -import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; -import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers"; -import { isAllowedResource } from "../../../common/rbac"; -import { LimitRanges, limitRangesRoute, limitRangeURL } from "../+config-limit-ranges"; +import { ResourceQuotas } from "../+config-resource-quotas"; +import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; +import { HorizontalPodAutoscalers } from "../+config-autoscalers"; +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 routes: TabLayoutRoute[] = []; + const tabs: TabLayoutRoute[] = []; - if (isAllowedResource("configmaps")) { - routes.push({ + if (cluster.isAllowedResource("configmaps")) { + tabs.push({ title: "ConfigMaps", component: ConfigMaps, - url: configMapsURL({ query }), - routePath: configMapsRoute.path.toString(), + url: routes.configMapsURL({ query }), + routePath: routes.configMapsRoute.path.toString(), }); } - if (isAllowedResource("secrets")) { - routes.push({ + if (cluster.isAllowedResource("secrets")) { + tabs.push({ title: "Secrets", component: Secrets, - url: secretsURL({ query }), - routePath: secretsRoute.path.toString(), + url: routes.secretsURL({ query }), + routePath: routes.secretsRoute.path.toString(), }); } - if (isAllowedResource("resourcequotas")) { - routes.push({ + if (cluster.isAllowedResource("resourcequotas")) { + tabs.push({ title: "Resource Quotas", component: ResourceQuotas, - url: resourceQuotaURL({ query }), - routePath: resourceQuotaRoute.path.toString(), + url: routes.resourceQuotaURL({ query }), + routePath: routes.resourceQuotaRoute.path.toString(), }); } - if (isAllowedResource("limitranges")) { - routes.push({ + if (cluster.isAllowedResource("limitranges")) { + tabs.push({ title: "Limit Ranges", component: LimitRanges, - url: limitRangeURL({ query }), - routePath: limitRangesRoute.path.toString(), + url: routes.limitRangeURL({ query }), + routePath: routes.limitRangesRoute.path.toString(), }); } - if (isAllowedResource("horizontalpodautoscalers")) { - routes.push({ + if (cluster.isAllowedResource("horizontalpodautoscalers")) { + tabs.push({ title: "HPA", component: HorizontalPodAutoscalers, - url: hpaURL({ query }), - routePath: hpaRoute.path.toString(), + url: routes.hpaURL({ query }), + routePath: routes.hpaRoute.path.toString(), }); } - if (isAllowedResource("poddisruptionbudgets")) { - routes.push({ + if (cluster.isAllowedResource("poddisruptionbudgets")) { + tabs.push({ title: "Pod Disruption Budgets", component: PodDisruptionBudgets, - url: pdbURL({ query }), - routePath: pdbRoute.path.toString(), + url: routes.pdbURL({ query }), + routePath: routes.pdbRoute.path.toString(), }); } - return routes; + return tabs; } render() { return ( - + ); } } diff --git a/src/renderer/components/+config/index.ts b/src/renderer/components/+config/index.ts index e8be23f1bc94..521bf1d3a13e 100644 --- a/src/renderer/components/+config/index.ts +++ b/src/renderer/components/+config/index.ts @@ -19,6 +19,4 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "./config.route"; export * from "./config"; -export * from "./config.command"; diff --git a/src/renderer/components/+custom-resources/crd-details.tsx b/src/renderer/components/+custom-resources/crd-details.tsx index 757f38970210..74fa17b4f56e 100644 --- a/src/renderer/components/+custom-resources/crd-details.tsx +++ b/src/renderer/components/+custom-resources/crd-details.tsx @@ -33,7 +33,6 @@ import type { KubeObjectDetailsProps } from "../kube-object"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { Input } from "../input"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; -import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; interface Props extends KubeObjectDetailsProps { } @@ -156,11 +155,3 @@ export class CRDDetails extends React.Component { ); } } - -kubeObjectDetailRegistry.add({ - kind: "CustomResourceDefinition", - apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"], - components: { - Details: (props) => - } -}); diff --git a/src/renderer/components/+custom-resources/crd-list.tsx b/src/renderer/components/+custom-resources/crd-list.tsx index bb6753dce23e..67cb66b35519 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 {