1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Move ownership and enabling tracking into cluster store

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-03-22 08:06:56 -04:00
parent b066fb3527
commit 29dadd478a
16 changed files with 182 additions and 112 deletions

View File

@ -28,11 +28,7 @@ class KubectlDownloader {
resolveWithFullResponse: true resolveWithFullResponse: true
}).catch((error) => { console.log(error); }); }).catch((error) => { console.log(error); });
if (response.headers["etag"]) { return response.headers?.["etag"]?.replace(/"/g, "") ?? "";
return response.headers["etag"].replace(/"/g, "");
}
return "";
} }
public async checkBinary() { public async checkBinary() {
@ -87,7 +83,7 @@ class KubectlDownloader {
throw(error); throw(error);
}); });
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
file.on("close", () => { file.on("close", () => {
console.log("kubectl binary download closed"); console.log("kubectl binary download closed");
fs.chmod(this.path, 0o755, (err) => { fs.chmod(this.path, 0o755, (err) => {
@ -116,4 +112,3 @@ downloads.forEach((dlOpts) => {
console.log(`Downloading: ${JSON.stringify(dlOpts)}`); console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete"))); downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
}); });

View File

@ -362,12 +362,12 @@
"terser-webpack-plugin": "^3.0.3", "terser-webpack-plugin": "^3.0.3",
"ts-jest": "^26.1.0", "ts-jest": "^26.1.0",
"ts-loader": "^7.0.5", "ts-loader": "^7.0.5",
"ts-node": "^8.10.2", "ts-node": "^9.1.1",
"type-fest": "^0.18.0", "type-fest": "^0.18.0",
"typedoc": "0.17.0-3", "typedoc": "0.17.0-3",
"typedoc-plugin-markdown": "^2.4.0", "typedoc-plugin-markdown": "^2.4.0",
"typeface-roboto": "^0.0.75", "typeface-roboto": "^0.0.75",
"typescript": "4.0.2", "typescript": "^4.2.3",
"url-loader": "^4.1.0", "url-loader": "^4.1.0",
"webpack": "^4.44.2", "webpack": "^4.44.2",
"webpack-cli": "^3.3.11", "webpack-cli": "^3.3.11",

View File

@ -88,7 +88,7 @@ describe("empty config", () => {
expect(storedCluster.id).toBe("foo"); expect(storedCluster.id).toBe("foo");
expect(storedCluster.preferences.terminalCWD).toBe("/tmp"); expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5"); expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
expect(storedCluster.enabled).toBe(true); expect(clusterStore.isClusterEnabled(storedCluster)).toBe(true);
}); });
it("adds cluster to default workspace", () => { it("adds cluster to default workspace", () => {
@ -265,8 +265,8 @@ describe("config with existing clusters", () => {
it("marks owned cluster disabled by default", () => { it("marks owned cluster disabled by default", () => {
const storedClusters = clusterStore.clustersList; const storedClusters = clusterStore.clustersList;
expect(storedClusters[0].enabled).toBe(true); expect(clusterStore.isClusterEnabled(storedClusters[0])).toBe(true);
expect(storedClusters[2].enabled).toBe(false); expect(clusterStore.isClusterEnabled(storedClusters[2])).toBe(false);
}); });
}); });
@ -336,9 +336,9 @@ users:
const storedClusters = clusterStore.clustersList; const storedClusters = clusterStore.clustersList;
expect(storedClusters.length).toBe(2); expect(storedClusters.length).toBe(2);
expect(storedClusters[0].enabled).toBeFalsy; expect(clusterStore.isClusterEnabled(storedClusters[0])).toBeFalsy;
expect(storedClusters[1].id).toBe("cluster2"); expect(storedClusters[1].id).toBe("cluster2");
expect(storedClusters[1].enabled).toBeTruthy; expect(clusterStore.isClusterEnabled(storedClusters[1])).toBeTruthy;
}); });
}); });

View File

@ -11,11 +11,12 @@ import { appEventBus } from "./event-bus";
import { dumpConfigYaml } from "./kube-helpers"; import { dumpConfigYaml } from "./kube-helpers";
import { saveToAppFiles } from "./utils/saveToAppFiles"; import { saveToAppFiles } from "./utils/saveToAppFiles";
import { KubeConfig } from "@kubernetes/client-node"; import { KubeConfig } from "@kubernetes/client-node";
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; import { broadcastMessage, handleRequest, InvalidKubeconfigChannel, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
import _ from "lodash"; import _ from "lodash";
import move from "array-move"; import move from "array-move";
import type { WorkspaceId } from "./workspace-store"; import type { WorkspaceId } from "./workspace-store";
import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting"; import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting";
import { LensExtensionId } from "../extensions/lens-extension";
export interface ClusterIconUpload { export interface ClusterIconUpload {
clusterId: string; clusterId: string;
@ -34,8 +35,9 @@ export type ClusterPrometheusMetadata = {
}; };
export interface ClusterStoreModel { export interface ClusterStoreModel {
activeCluster?: ClusterId; // last opened cluster activeClusterId?: ClusterId; // last opened cluster
clusters?: ClusterModel[]; clusters?: ClusterModel[];
clusterOwners?: [ClusterId, LensExtensionId][];
} }
export type ClusterId = string; export type ClusterId = string;
@ -59,11 +61,6 @@ export interface ClusterModel {
/** Metadata */ /** Metadata */
metadata?: ClusterMetadata; metadata?: ClusterMetadata;
/**
* If extension sets ownerRef it has to explicitly mark a cluster as enabled during onActive (or when cluster is saved)
*/
ownerRef?: string;
/** List of accessible namespaces */ /** List of accessible namespaces */
accessibleNamespaces?: string[]; accessibleNamespaces?: string[];
@ -71,6 +68,16 @@ export interface ClusterModel {
kubeConfig?: string; // yaml kubeConfig?: string; // yaml
} }
export interface ClusterManagementRecord {
ownerId: LensExtensionId;
enabled: boolean;
}
export interface GetByWorkspaceIdOptions {
includeDisabled?: boolean; // default false
sortByIconOrder?: boolean; // default true
}
export interface ClusterPreferences extends ClusterPrometheusPreferences { export interface ClusterPreferences extends ClusterPrometheusPreferences {
terminalCWD?: string; terminalCWD?: string;
clusterName?: string; clusterName?: string;
@ -92,6 +99,22 @@ export interface ClusterPrometheusPreferences {
}; };
} }
function splitAddClusterArgs(args: ClusterModel[] | [...ClusterModel[], string]): [ClusterModel[]] | [ClusterModel[], string] {
const lastArg = args.pop();
if (lastArg) {
return [[]];
}
if (typeof lastArg === "string") {
return [args as ClusterModel[], lastArg];
}
args.push(lastArg);
return [args as ClusterModel[]];
}
export class ClusterStore extends BaseStore<ClusterStoreModel> { export class ClusterStore extends BaseStore<ClusterStoreModel> {
static getCustomKubeConfigPath(clusterId: ClusterId): string { static getCustomKubeConfigPath(clusterId: ClusterId): string {
return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs", clusterId); return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs", clusterId);
@ -106,9 +129,11 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return filePath; return filePath;
} }
@observable activeCluster: ClusterId; @observable activeClusterId: ClusterId;
@observable removedClusters = observable.map<ClusterId, Cluster>(); @observable removedClusters = observable.map<ClusterId, Cluster>();
@observable clusters = observable.map<ClusterId, Cluster>(); @observable clusters = observable.map<ClusterId, Cluster>();
@observable clusterManagingInfo = observable.map<ClusterId, ClusterManagementRecord>();
@observable erroredClusterModels = observable.array<ClusterModel>();
private static stateRequestChannel = "cluster:states"; private static stateRequestChannel = "cluster:states";
@ -189,28 +214,50 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
}); });
} }
get activeClusterId() {
return this.activeCluster;
}
@computed get clustersList(): Cluster[] { @computed get clustersList(): Cluster[] {
return Array.from(this.clusters.values()); return Array.from(this.clusters.values());
} }
@computed get enabledClustersList(): Cluster[] { @computed get enabledClustersList(): Cluster[] {
return this.clustersList.filter((c) => c.enabled); return this.clustersList.filter(c => this.isClusterEnabled(c));
} }
@computed get active(): Cluster | null { @computed get active(): Cluster | null {
return this.getById(this.activeCluster); return this.getById(this.activeClusterId);
} }
@computed get connectedClustersList(): Cluster[] { @computed get connectedClustersList(): Cluster[] {
return this.clustersList.filter((c) => !c.disconnected); return this.clustersList.filter((c) => !c.disconnected);
} }
/**
*
* @param clusterOrId The cluster or its ID to check if it is owned
* @returns true if an extension has claimed to be managing a cluster
*/
isClusterManaged(clusterOrId: Cluster | ClusterId): boolean {
const clusterId = typeof clusterOrId === "string"
? clusterOrId
: clusterOrId.id;
return this.clusterManagingInfo.has(clusterId);
}
/**
* Get the enabled status of a cluster
* @param clusterOrId The cluster or its ID to check if it is owned
* @returns true if not managed, else true if owner has marked as enabled
*/
isClusterEnabled(clusterOrId: Cluster | ClusterId): boolean {
const clusterId = typeof clusterOrId === "string"
? clusterOrId
: clusterOrId.id;
return this.clusterManagingInfo.get(clusterId)?.enabled ?? true;
}
isActive(id: ClusterId) { isActive(id: ClusterId) {
return this.activeCluster === id; return this.activeClusterId === id;
} }
isMetricHidden(resource: ResourceType) { isMetricHidden(resource: ResourceType) {
@ -221,7 +268,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
setActive(id: ClusterId) { setActive(id: ClusterId) {
const clusterId = this.clusters.has(id) ? id : null; const clusterId = this.clusters.has(id) ? id : null;
this.activeCluster = clusterId; this.activeClusterId = clusterId;
workspaceStore.setLastActiveClusterId(clusterId); workspaceStore.setLastActiveClusterId(clusterId);
} }
@ -249,37 +296,50 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return this.clusters.get(id); return this.clusters.get(id);
} }
getByWorkspaceId(workspaceId: string): Cluster[] { getByWorkspaceId(workspaceId: string, options?: GetByWorkspaceIdOptions): Cluster[] {
const includeDisabled = options?.includeDisabled ?? false;
const sortByIconOrder = options?.sortByIconOrder ?? true;
const clusters = Array.from(this.clusters.values()) const clusters = Array.from(this.clusters.values())
.filter(cluster => cluster.workspace === workspaceId); .filter(cluster => (
cluster.workspace === workspaceId
&& (
includeDisabled
|| this.isClusterEnabled(cluster)
)
));
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder); if (sortByIconOrder) {
} return _.sortBy(clusters, cluster => cluster.preferences.iconOrder);
}
@action
addClusters(...models: ClusterModel[]): Cluster[] {
const clusters: Cluster[] = [];
models.forEach(model => {
clusters.push(this.addCluster(model));
});
return clusters; return clusters;
} }
@action @action
addCluster(model: ClusterModel | Cluster): Cluster { addClusters<M extends ClusterModel, MM extends M[]>(...args: ([...MM] | [...MM, LensExtensionId])): Cluster[] {
const [models, ownerId] = splitAddClusterArgs(args);
return models.map(model => this.addCluster(model, ownerId));
}
@action
addCluster(clusterOrModel: ClusterModel | Cluster, ownerId?: LensExtensionId): Cluster {
appEventBus.emit({ name: "cluster", action: "add" }); appEventBus.emit({ name: "cluster", action: "add" });
let cluster = model as Cluster;
if (!(model instanceof Cluster)) { const cluster = clusterOrModel instanceof Cluster
cluster = new Cluster(model); ? clusterOrModel
: new Cluster(clusterOrModel);
if (ownerId) {
if (this.isClusterManaged(cluster)) {
throw new Error("Extension tried to claim an already managed cluster");
}
this.clusterManagingInfo.set(cluster.id, { ownerId, enabled: false });
} }
if (!cluster.isManaged) { this.clusters.set(clusterOrModel.id, cluster);
cluster.enabled = true;
}
this.clusters.set(model.id, cluster);
return cluster; return cluster;
} }
@ -296,7 +356,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
if (cluster) { if (cluster) {
this.clusters.delete(clusterId); this.clusters.delete(clusterId);
if (this.activeCluster === clusterId) { if (this.activeClusterId === clusterId) {
this.setActive(null); this.setActive(null);
} }
@ -309,31 +369,43 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action @action
removeByWorkspaceId(workspaceId: string) { removeByWorkspaceId(workspaceId: string) {
this.getByWorkspaceId(workspaceId).forEach(cluster => { const workspaceClusters = this.getByWorkspaceId(workspaceId, {
this.removeById(cluster.id); includeDisabled: true,
sortByIconOrder: false,
}); });
for (const cluster of workspaceClusters) {
this.removeById(cluster.id);
}
} }
@action @action
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) { protected fromStore({ activeClusterId, clusters = [], clusterOwners = [] }: ClusterStoreModel = {}) {
const currentClusters = this.clusters.toJS(); const currentClusters = this.clusters.toJS();
const newClusters = new Map<ClusterId, Cluster>(); const newClusters = new Map<ClusterId, Cluster>();
const removedClusters = new Map<ClusterId, Cluster>(); const removedClusters = new Map<ClusterId, Cluster>();
const erroredClusterModels: ClusterModel[] = [];
const clusterOwnersMap = new Map<ClusterId, LensExtensionId>(clusterOwners);
// update new clusters // update new clusters
for (const clusterModel of clusters) { for (const clusterModel of clusters) {
let cluster = currentClusters.get(clusterModel.id); if (currentClusters.has(clusterModel.id)) {
const cluster = currentClusters.get(clusterModel.id);
if (cluster) {
cluster.updateModel(clusterModel); cluster.updateModel(clusterModel);
newClusters.set(clusterModel.id, cluster);
} else { } else {
cluster = new Cluster(clusterModel); try {
newClusters.set(clusterModel.id, new Cluster(clusterModel));
} catch (error) {
const { preferences, contextName: context, kubeConfigPath: kubeconfig } = clusterModel;
const clusterName = preferences?.clusterName || context;
if (!cluster.isManaged && cluster.apiUrl) { logger.error(`[CLUSTER-STORE]: Failed to load kubeconfig for the cluster '${clusterName}'.`, { error, context, kubeconfig });
cluster.enabled = true; broadcastMessage(InvalidKubeconfigChannel, clusterModel.id);
erroredClusterModels.push(clusterModel);
} }
} }
newClusters.set(clusterModel.id, cluster);
} }
// update removed clusters // update removed clusters
@ -343,15 +415,17 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
} }
}); });
this.activeCluster = newClusters.get(activeCluster)?.enabled ? activeCluster : null; this.activeClusterId = clusterOwnersMap.get(activeClusterId) ? null : activeClusterId;
this.clusters.replace(newClusters); this.clusters.replace(newClusters);
this.removedClusters.replace(removedClusters); this.removedClusters.replace(removedClusters);
this.erroredClusterModels.replace(erroredClusterModels);
this.clusterManagingInfo.replace(clusterOwnersMap);
} }
toJSON(): ClusterStoreModel { toJSON(): ClusterStoreModel {
return toJS({ return toJS({
activeCluster: this.activeCluster, activeClusterId: this.activeClusterId,
clusters: this.clustersList.map(cluster => cluster.toJSON()), clusters: this.clustersList.map(cluster => cluster.toJSON()).concat(this.erroredClusterModels),
}, { }, {
recurseEverything: true recurseEverything: true
}); });

View File

View File

@ -44,7 +44,7 @@ export abstract class ClusterFeature {
* *
* @param cluster the cluster that the feature is to be installed on * @param cluster the cluster that the feature is to be installed on
*/ */
abstract async install(cluster: Cluster): Promise<void>; abstract install(cluster: Cluster): Promise<void>;
/** /**
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation * to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
@ -52,7 +52,7 @@ export abstract class ClusterFeature {
* *
* @param cluster the cluster that the feature is to be upgraded on * @param cluster the cluster that the feature is to be upgraded on
*/ */
abstract async upgrade(cluster: Cluster): Promise<void>; abstract upgrade(cluster: Cluster): Promise<void>;
/** /**
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation * to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
@ -60,7 +60,7 @@ export abstract class ClusterFeature {
* *
* @param cluster the cluster that the feature is to be uninstalled from * @param cluster the cluster that the feature is to be uninstalled from
*/ */
abstract async uninstall(cluster: Cluster): Promise<void>; abstract uninstall(cluster: Cluster): Promise<void>;
/** /**
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation * to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
@ -72,7 +72,7 @@ export abstract class ClusterFeature {
* *
* @return a promise, resolved with the updated ClusterFeatureStatus * @return a promise, resolved with the updated ClusterFeatureStatus
*/ */
abstract async updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>; abstract updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
/** /**
* this is a helper method that conveniently applies kubernetes resources to the cluster. * this is a helper method that conveniently applies kubernetes resources to the cluster.

View File

@ -18,14 +18,14 @@ export class ClusterStore extends Singleton {
* Active cluster id * Active cluster id
*/ */
get activeClusterId(): string { get activeClusterId(): string {
return internalClusterStore.activeCluster; return internalClusterStore.activeClusterId;
} }
/** /**
* Set active cluster id * Set active cluster id
*/ */
set activeClusterId(id : ClusterId) { set activeClusterId(id : ClusterId) {
internalClusterStore.activeCluster = id; internalClusterStore.activeClusterId = id;
} }
/** /**

View File

@ -4,7 +4,7 @@ import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store"; import type { WorkspaceId } from "../common/workspace-store";
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx"; import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { broadcastMessage, InvalidKubeconfigChannel } from "../common/ipc"; import { broadcastMessage } from "../common/ipc";
import { ContextHandler } from "./context-handler"; import { ContextHandler } from "./context-handler";
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"; import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
import { Kubectl } from "./kubectl"; import { Kubectl } from "./kubectl";
@ -38,7 +38,6 @@ export type ClusterRefreshOptions = {
export interface ClusterState { export interface ClusterState {
initialized: boolean; initialized: boolean;
enabled: boolean;
apiUrl: string; apiUrl: string;
online: boolean; online: boolean;
disconnected: boolean; disconnected: boolean;
@ -71,12 +70,6 @@ export class Cluster implements ClusterModel, ClusterState {
* @internal * @internal
*/ */
public contextHandler: ContextHandler; public contextHandler: ContextHandler;
/**
* Owner reference
*
* If extension sets this it needs to also mark cluster as enabled on activate (or when added to a store)
*/
public ownerRef: string;
protected kubeconfigManager: KubeconfigManager; protected kubeconfigManager: KubeconfigManager;
protected eventDisposers: Function[] = []; protected eventDisposers: Function[] = [];
protected activated = false; protected activated = false;
@ -86,7 +79,7 @@ export class Cluster implements ClusterModel, ClusterState {
whenReady = when(() => this.ready); whenReady = when(() => this.ready);
/** /**
* Is cluster object initializinng on-going * Is cluster object initializing on-going
* *
* @observable * @observable
*/ */
@ -129,12 +122,6 @@ export class Cluster implements ClusterModel, ClusterState {
* @internal * @internal
*/ */
@observable kubeProxyUrl: string; // lens-proxy to kube-api url @observable kubeProxyUrl: string; // lens-proxy to kube-api url
/**
* Is cluster instance enabled (disabled clusters are currently hidden)
*
* @observable
*/
@observable enabled = false; // only enabled clusters are visible to users
/** /**
* Is cluster online * Is cluster online
* *
@ -258,23 +245,20 @@ export class Cluster implements ClusterModel, ClusterState {
constructor(model: ClusterModel) { constructor(model: ClusterModel) {
this.updateModel(model); this.updateModel(model);
try { const kubeconfig = this.getKubeconfig();
const kubeconfig = this.getKubeconfig();
validateKubeConfig(kubeconfig, this.contextName, { validateCluster: true, validateUser: false, validateExec: false}); validateKubeConfig(kubeconfig, this.contextName, { validateCluster: true, validateUser: false, validateExec: false});
this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server; this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server;
} catch(err) {
logger.error(err);
logger.error(`[CLUSTER] Failed to load kubeconfig for the cluster '${this.name || this.contextName}' (context: ${this.contextName}, kubeconfig: ${this.kubeConfigPath}).`);
broadcastMessage(InvalidKubeconfigChannel, model.id);
}
} }
kubeConfig?: string;
/** /**
* Is cluster managed by an extension * Is cluster managed by an extension
*
* @deprecated use `clusterStore.isClusterManaged(clusterId)`
*/ */
get isManaged(): boolean { get isManaged(): boolean {
return !!this.ownerRef; return false;
} }
/** /**
@ -612,7 +596,6 @@ export class Cluster implements ClusterModel, ClusterState {
workspace: this.workspace, workspace: this.workspace,
preferences: this.preferences, preferences: this.preferences,
metadata: this.metadata, metadata: this.metadata,
ownerRef: this.ownerRef,
accessibleNamespaces: this.accessibleNamespaces, accessibleNamespaces: this.accessibleNamespaces,
}; };
@ -627,7 +610,6 @@ export class Cluster implements ClusterModel, ClusterState {
getState(): ClusterState { getState(): ClusterState {
const state: ClusterState = { const state: ClusterState = {
initialized: this.initialized, initialized: this.initialized,
enabled: this.enabled,
apiUrl: this.apiUrl, apiUrl: this.apiUrl,
online: this.online, online: this.online,
ready: this.ready, ready: this.ready,

View File

@ -273,7 +273,7 @@ export class Kubectl {
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`); logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const stream = customRequest({ const stream = customRequest({
url: this.url, url: this.url,
gzip: true, gzip: true,

View File

@ -183,7 +183,7 @@ export class LensBinary {
throw(error); throw(error);
}); });
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
file.on("close", () => { file.on("close", () => {
this.logger.debug(`${this.originalBinaryName} binary download closed`); this.logger.debug(`${this.originalBinaryName} binary download closed`);
if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => { if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => {

View File

@ -0,0 +1,12 @@
import { migration } from "../migration-wrapper";
export default migration({
version: "4.2.0",
run(store) {
const activeCluster = store.get("activeCluster");
if (activeCluster) {
store.set("activeClusterId", activeCluster);
}
}
});

View File

@ -7,6 +7,7 @@ import version260Beta3 from "./2.6.0-beta.3";
import version270Beta0 from "./2.7.0-beta.0"; import version270Beta0 from "./2.7.0-beta.0";
import version270Beta1 from "./2.7.0-beta.1"; import version270Beta1 from "./2.7.0-beta.1";
import version360Beta1 from "./3.6.0-beta.1"; import version360Beta1 from "./3.6.0-beta.1";
import version420 from "./4.2.0";
import snap from "./snap"; import snap from "./snap";
export default { export default {
@ -17,5 +18,6 @@ export default {
...version270Beta0, ...version270Beta0,
...version270Beta1, ...version270Beta1,
...version360Beta1, ...version360Beta1,
...version420,
...snap ...snap
}; };

View File

@ -56,7 +56,6 @@ export class WorkspaceClusterStore extends ItemStore<ClusterItem> {
() => ( () => (
clusterStore clusterStore
.getByWorkspaceId(this.workspaceId) .getByWorkspaceId(this.workspaceId)
.filter(cluster => cluster.enabled)
.map(cluster => new ClusterItem(cluster)) .map(cluster => new ClusterItem(cluster))
) )
); );

View File

@ -108,8 +108,8 @@ export class ClustersMenu extends React.Component<Props> {
render() { render() {
const { className } = this.props; const { className } = this.props;
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId); const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId);
const clusters = clusterStore.getByWorkspaceId(workspace.id).filter(cluster => cluster.enabled); const clusters = clusterStore.getByWorkspaceId(workspace.id);
const activeClusterId = clusterStore.activeCluster; const activeClusterId = clusterStore.activeClusterId;
return ( return (
<div className={cssNames("ClustersMenu flex column", className)}> <div className={cssNames("ClustersMenu flex column", className)}>
@ -192,7 +192,7 @@ export class ClustersMenu extends React.Component<Props> {
@observer @observer
export class ChooseCluster extends React.Component { export class ChooseCluster extends React.Component {
@computed get options() { @computed get options() {
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId).filter(cluster => cluster.enabled); const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
const options = clusters.map((cluster) => { const options = clusters.map((cluster) => {
return { value: cluster.id, label: cluster.name }; return { value: cluster.id, label: cluster.name };
}); });

View File

@ -17,7 +17,7 @@ export class ReadableWebToNodeStream extends Readable {
* Default web API stream reader * Default web API stream reader
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader * https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
*/ */
private reader: ReadableStreamReader; private reader: ReadableStreamReader<any>;
private pendingRead: Promise<any>; private pendingRead: Promise<any>;
/** /**

View File

@ -4052,6 +4052,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-fetch@^3.0.4: cross-fetch@^3.0.4:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
@ -13460,12 +13465,13 @@ ts-loader@^7.0.5:
micromatch "^4.0.0" micromatch "^4.0.0"
semver "^6.0.0" semver "^6.0.0"
ts-node@^8.10.2: ts-node@^9.1.1:
version "8.10.2" version "9.1.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
dependencies: dependencies:
arg "^4.1.0" arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1" diff "^4.0.1"
make-error "^1.1.1" make-error "^1.1.1"
source-map-support "^0.5.17" source-map-support "^0.5.17"
@ -13618,16 +13624,16 @@ typeface-roboto@^0.0.75:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b" resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg== integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
typescript@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==
typescript@^4.0.3: typescript@^4.0.3:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9"
integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.9.4" version "3.9.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.4.tgz#867402377e043c1fc7b102253a22b64e5862401b" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.4.tgz#867402377e043c1fc7b102253a22b64e5862401b"