Catalog & Hotbar - initial groundwork (#2418)

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2021-04-09 09:11:58 +03:00
1 parent e18a041b13
commit 99a464c61d
109 files changed
+1604 -2612

No files matched your search

+2 -5
View File
@@ -31,7 +31,6 @@ jest.mock("request-promise-native");
import { Console } from "console";
import mockFs from "mock-fs";
import { workspaceStore } from "../../common/workspace-store";
import { Cluster } from "../cluster";
import { ContextHandler } from "../context-handler";
import { getFreePort } from "../port";
@@ -81,8 +80,7 @@ describe("create clusters", () => {
c = new Cluster({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
kubeConfigPath: "minikube-config.yml"
});
});
@@ -162,8 +160,7 @@ describe("create clusters", () => {
}({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
kubeConfigPath: "minikube-config.yml"
});
await c.init(port);
+2 -5
View File
@@ -26,7 +26,6 @@ jest.mock("winston", () => ({
import { KubeconfigManager } from "../kubeconfig-manager";
import mockFs from "mock-fs";
import { Cluster } from "../cluster";
import { workspaceStore } from "../../common/workspace-store";
import { ContextHandler } from "../context-handler";
import { getFreePort } from "../port";
import fse from "fs-extra";
@@ -77,8 +76,7 @@ describe("kubeconfig manager tests", () => {
const cluster = new Cluster({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
kubeConfigPath: "minikube-config.yml"
});
const contextHandler = new ContextHandler(cluster);
const port = await getFreePort();
@@ -98,8 +96,7 @@ describe("kubeconfig manager tests", () => {
const cluster = new Cluster({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
kubeConfigPath: "minikube-config.yml"
});
const contextHandler = new ContextHandler(cluster);
const port = await getFreePort();
+32
View File
@@ -0,0 +1,32 @@
import { autorun, toJS } from "mobx";
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "../common/ipc";
import { CatalogEntityRegistry} from "../common/catalog-entity-registry";
import "../common/catalog-entities/kubernetes-cluster";
export class CatalogPusher {
static init(catalog: CatalogEntityRegistry) {
new CatalogPusher(catalog).init();
}
private constructor(private catalog: CatalogEntityRegistry) {}
init() {
const disposers: { (): void; }[] = [];
disposers.push(autorun(() => {
this.broadcast();
}));
const listener = subscribeToBroadcast("catalog:broadcast", () => {
this.broadcast();
});
disposers.push(() => unsubscribeFromBroadcast("catalog:broadcast", listener));
return disposers;
}
broadcast() {
broadcastMessage("catalog:items", toJS(this.catalog.items, { recurseEverything: true }));
}
}
+104 -1
View File
@@ -1,16 +1,25 @@
import "../common/cluster-ipc";
import type http from "http";
import { ipcMain } from "electron";
import { autorun, reaction } from "mobx";
import { action, autorun, observable, reaction, toJS } from "mobx";
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store";
import { Cluster } from "./cluster";
import logger from "./logger";
import { apiKubePrefix } from "../common/vars";
import { Singleton } from "../common/utils";
import { CatalogEntity } from "../common/catalog-entity";
import { KubernetesCluster } from "../common/catalog-entities/kubernetes-cluster";
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
const clusterOwnerRef = "ClusterManager";
export class ClusterManager extends Singleton {
@observable.deep catalogSource: CatalogEntity[] = [];
constructor(public readonly port: number) {
super();
catalogEntityRegistry.addSource("lens:kubernetes-clusters", this.catalogSource);
// auto-init clusters
reaction(() => clusterStore.enabledClustersList, (clusters) => {
clusters.forEach((cluster) => {
@@ -19,8 +28,18 @@ export class ClusterManager extends Singleton {
cluster.init(port);
}
});
}, { fireImmediately: true });
reaction(() => toJS(clusterStore.enabledClustersList, { recurseEverything: true }), () => {
this.updateCatalogSource(clusterStore.enabledClustersList);
}, { fireImmediately: true });
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
this.syncClustersFromCatalog(entities);
});
// auto-stop removed clusters
autorun(() => {
const removedClusters = Array.from(clusterStore.removedClusters.values());
@@ -40,6 +59,90 @@ export class ClusterManager extends Singleton {
ipcMain.on("network:online", () => { this.onNetworkOnline(); });
}
@action protected updateCatalogSource(clusters: Cluster[]) {
this.catalogSource.forEach((entity, index) => {
const clusterIndex = clusters.findIndex((cluster) => entity.metadata.uid === cluster.id);
if (clusterIndex === -1) {
this.catalogSource.splice(index, 1);
}
});
clusters.filter((c) => !c.ownerRef).forEach((cluster) => {
const entityIndex = this.catalogSource.findIndex((entity) => entity.metadata.uid === cluster.id);
const newEntity = this.catalogEntityFromCluster(cluster);
if (entityIndex === -1) {
this.catalogSource.push(newEntity);
} else {
const oldEntity = this.catalogSource[entityIndex];
newEntity.status.phase = cluster.disconnected ? "disconnected" : "connected";
newEntity.status.active = !cluster.disconnected;
newEntity.metadata.labels = {
...newEntity.metadata.labels,
...oldEntity.metadata.labels
};
this.catalogSource.splice(entityIndex, 1, newEntity);
}
});
}
@action syncClustersFromCatalog(entities: KubernetesCluster[]) {
entities.filter((entity) => entity.metadata.source !== "local").forEach((entity: KubernetesCluster) => {
const cluster = clusterStore.getById(entity.metadata.uid);
if (!cluster) {
clusterStore.addCluster({
id: entity.metadata.uid,
enabled: true,
ownerRef: clusterOwnerRef,
preferences: {
clusterName: entity.metadata.name
},
kubeConfigPath: entity.spec.kubeconfigPath,
contextName: entity.spec.kubeconfigContext
});
} else {
cluster.enabled = true;
if (!cluster.ownerRef) cluster.ownerRef = clusterOwnerRef;
cluster.preferences.clusterName = entity.metadata.name;
cluster.kubeConfigPath = entity.spec.kubeconfigPath;
cluster.contextName = entity.spec.kubeconfigContext;
entity.status = {
phase: cluster.disconnected ? "disconnected" : "connected",
active: !cluster.disconnected
};
}
});
}
protected catalogEntityFromCluster(cluster: Cluster) {
return new KubernetesCluster(toJS({
apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster",
metadata: {
uid: cluster.id,
name: cluster.name,
source: "local",
labels: {
"distro": (cluster.metadata["distribution"] || "unknown").toString()
}
},
spec: {
kubeconfigPath: cluster.kubeConfigPath,
kubeconfigContext: cluster.contextName
},
status: {
phase: cluster.disconnected ? "disconnected" : "connected",
reason: "",
message: "",
active: !cluster.disconnected
}
}));
}
protected onNetworkOffline() {
logger.info("[CLUSTER-MANAGER]: network is offline");
clusterStore.enabledClustersList.forEach((cluster) => {
+4 -7
View File
@@ -1,7 +1,6 @@
import { ipcMain } from "electron";
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../common/cluster-store";
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store";
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars";
import { broadcastMessage, InvalidKubeconfigChannel, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
@@ -104,18 +103,16 @@ export class Cluster implements ClusterModel, ClusterState {
* @observable
*/
@observable contextName: string;
/**
* Workspace id
*
* @observable
*/
@observable workspace: WorkspaceId;
/**
* Path to kubeconfig
*
* @observable
*/
@observable kubeConfigPath: string;
/**
* @deprecated
*/
@observable workspace: string;
/**
* Kubernetes API server URL
*
+5 -2
View File
@@ -17,7 +17,6 @@ import { registerFileProtocol } from "../common/register-protocol";
import logger from "./logger";
import { clusterStore } from "../common/cluster-store";
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { appEventBus } from "../common/event-bus";
import { extensionLoader } from "../extensions/extension-loader";
import { extensionsStore } from "../extensions/extensions-store";
@@ -30,6 +29,9 @@ import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
import { bindBroadcastHandlers } from "../common/ipc";
import { startUpdateChecking } from "./app-updater";
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
import { CatalogPusher } from "./catalog-pusher";
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
import { hotbarStore } from "../common/hotbar-store";
const workingDir = path.join(app.getPath("appData"), appName);
let proxyPort: number;
@@ -107,7 +109,7 @@ app.on("ready", async () => {
await Promise.all([
userStore.load(),
clusterStore.load(),
workspaceStore.load(),
hotbarStore.load(),
extensionsStore.load(),
filesystemProvisionerStore.load(),
]);
@@ -164,6 +166,7 @@ app.on("ready", async () => {
}
ipcMain.on(IpcRendererNavigationEvents.LOADED, () => {
CatalogPusher.init(catalogEntityRegistry);
startUpdateChecking();
LensProtocolRouterMain
.getInstance<LensProtocolRouterMain>()
+8
View File
@@ -7,6 +7,7 @@ import { preferencesURL } from "../renderer/components/+preferences/preferences.
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.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 logger from "./logger";
import { exitApp } from "./exit-app";
@@ -175,6 +176,13 @@ export function buildMenu(windowManager: WindowManager) {
const viewMenu: MenuItemConstructorOptions = {
label: "View",
submenu: [
{
label: "Catalog",
accelerator: "Shift+CmdOrCtrl+C",
click() {
navigate(catalogURL());
}
},
{
label: "Command Palette...",
accelerator: "Shift+CmdOrCtrl+P",
-25
View File
@@ -5,10 +5,7 @@ import { autorun } from "mobx";
import { showAbout } from "./menu";
import { checkForUpdates } from "./app-updater";
import { WindowManager } from "./window-manager";
import { clusterStore } from "../common/cluster-store";
import { workspaceStore } from "../common/workspace-store";
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route";
import logger from "./logger";
import { isDevelopment, isWindows } from "../common/vars";
import { exitApp } from "./exit-app";
@@ -78,28 +75,6 @@ function createTrayMenu(windowManager: WindowManager): Menu {
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to Preferences`, { error }));
},
},
{
label: "Clusters",
submenu: workspaceStore.enabledWorkspacesList
.map(workspace => [workspace, clusterStore.getByWorkspaceId(workspace.id)] as const)
.map(([workspace, clusters]) => ({
label: workspace.name,
toolTip: workspace.description,
enabled: clusters.length > 0,
submenu: clusters.map(({ id: clusterId, name: label, online, workspace }) => ({
checked: online,
type: "checkbox",
label,
toolTip: clusterId,
click() {
workspaceStore.setActive(workspace);
windowManager
.navigate(clusterViewURL({ params: { clusterId } }))
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to cluster`, { clusterId, error }));
}
}))
})),
},
{
label: "Check for updates",
click() {