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

Hide disabled workspaces/clusters (#1573)

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2020-12-01 15:27:54 +02:00 committed by GitHub
parent 832f29f666
commit 4161ee832c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 180 additions and 32 deletions

View File

@ -7,6 +7,20 @@ import { workspaceStore } from "../workspace-store";
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png"); const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
jest.mock("electron", () => {
return {
app: {
getVersion: () => "99.99.99",
getPath: () => "tmp",
getLocale: () => "en"
},
ipcMain: {
handle: jest.fn(),
on: jest.fn()
}
};
});
let clusterStore: ClusterStore; let clusterStore: ClusterStore;
describe("empty config", () => { describe("empty config", () => {
@ -48,6 +62,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);
}); });
it("adds cluster to default workspace", () => { it("adds cluster to default workspace", () => {
@ -170,7 +185,8 @@ describe("config with existing clusters", () => {
kubeConfig: "foo", kubeConfig: "foo",
contextName: "foo", contextName: "foo",
preferences: { terminalCWD: "/foo" }, preferences: { terminalCWD: "/foo" },
workspace: "foo" workspace: "foo",
ownerRef: "foo"
}, },
] ]
}) })
@ -208,6 +224,12 @@ describe("config with existing clusters", () => {
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2"); expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
expect(storedClusters[2].id).toBe("cluster3"); expect(storedClusters[2].id).toBe("cluster3");
}); });
it("marks owned cluster disabled by default", () => {
const storedClusters = clusterStore.clustersList;
expect(storedClusters[0].enabled).toBe(true);
expect(storedClusters[2].enabled).toBe(false);
});
}); });
describe("pre 2.0 config with an existing cluster", () => { describe("pre 2.0 config with an existing cluster", () => {

View File

@ -6,6 +6,10 @@ jest.mock("electron", () => {
getVersion: () => "99.99.99", getVersion: () => "99.99.99",
getPath: () => "tmp", getPath: () => "tmp",
getLocale: () => "en" getLocale: () => "en"
},
ipcMain: {
handle: jest.fn(),
on: jest.fn()
} }
}; };
}); });
@ -60,7 +64,9 @@ describe("workspace store tests", () => {
name: "foobar", name: "foobar",
})); }));
expect(ws.getById("123").name).toBe("foobar"); const workspace = ws.getById("123");
expect(workspace.name).toBe("foobar");
expect(workspace.enabled).toBe(true);
}); });
it("cannot set a non-existent workspace to be active", () => { it("cannot set a non-existent workspace to be active", () => {

View File

@ -11,7 +11,7 @@ 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 { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; import { handleRequest, 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";
@ -40,13 +40,30 @@ export interface ClusterStoreModel {
export type ClusterId = string; export type ClusterId = string;
export interface ClusterModel { export interface ClusterModel {
/** Unique id for a cluster */
id: ClusterId; id: ClusterId;
/** Path to cluster kubeconfig */
kubeConfigPath: string; kubeConfigPath: string;
/** Workspace id */
workspace?: WorkspaceId; workspace?: WorkspaceId;
/** User context in kubeconfig */
contextName?: string; contextName?: string;
/** Preferences */
preferences?: ClusterPreferences; preferences?: ClusterPreferences;
/** 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; ownerRef?: string;
/** List of accessible namespaces */
accessibleNamespaces?: string[]; accessibleNamespaces?: string[];
/** @deprecated */ /** @deprecated */
@ -89,6 +106,8 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@observable removedClusters = observable.map<ClusterId, Cluster>(); @observable removedClusters = observable.map<ClusterId, Cluster>();
@observable clusters = observable.map<ClusterId, Cluster>(); @observable clusters = observable.map<ClusterId, Cluster>();
private static stateRequestChannel = "cluster:states";
private constructor() { private constructor() {
super({ super({
configName: "lens-cluster-store", configName: "lens-cluster-store",
@ -102,8 +121,40 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
this.pushStateToViewsAutomatically(); this.pushStateToViewsAutomatically();
} }
async load() {
await super.load();
type clusterStateSync = {
id: string;
state: ClusterState;
};
if (ipcRenderer) {
logger.info("[CLUSTER-STORE] requesting initial state sync");
const clusterStates: clusterStateSync[] = await requestMain(ClusterStore.stateRequestChannel);
clusterStates.forEach((clusterState) => {
const cluster = this.getById(clusterState.id);
if (cluster) {
cluster.setState(clusterState.state);
}
});
} else {
handleRequest(ClusterStore.stateRequestChannel, (): clusterStateSync[] => {
const states: clusterStateSync[] = [];
this.clustersList.forEach((cluster) => {
states.push({
state: cluster.getState(),
id: cluster.id
});
});
return states;
});
}
}
protected pushStateToViewsAutomatically() { protected pushStateToViewsAutomatically() {
if (!ipcRenderer) { if (!ipcRenderer) {
reaction(() => this.enabledClustersList, () => {
this.pushState();
});
reaction(() => this.connectedClustersList, () => { reaction(() => this.connectedClustersList, () => {
this.pushState(); this.pushState();
}); });
@ -205,6 +256,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
if (!(model instanceof Cluster)) { if (!(model instanceof Cluster)) {
cluster = new Cluster(model); cluster = new Cluster(model);
} }
if (!cluster.isManaged) {
cluster.enabled = true;
}
this.clusters.set(model.id, cluster); this.clusters.set(model.id, cluster);
return cluster; return cluster;
} }

View File

@ -3,7 +3,7 @@ import { action, computed, observable, toJS, reaction } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store"; import { clusterStore } from "./cluster-store";
import { appEventBus } from "./event-bus"; import { appEventBus } from "./event-bus";
import { broadcastMessage } from "../common/ipc"; import { broadcastMessage, handleRequest, requestMain } from "../common/ipc";
import logger from "../main/logger"; import logger from "../main/logger";
import type { ClusterId } from "./cluster-store"; import type { ClusterId } from "./cluster-store";
@ -27,11 +27,45 @@ export interface WorkspaceState {
} }
export class Workspace implements WorkspaceModel, WorkspaceState { export class Workspace implements WorkspaceModel, WorkspaceState {
/**
* Unique id for workspace
*
* @observable
*/
@observable id: WorkspaceId; @observable id: WorkspaceId;
/**
* Workspace name
*
* @observable
*/
@observable name: string; @observable name: string;
/**
* Workspace description
*
* @observable
*/
@observable description?: string; @observable description?: string;
/**
* Workspace owner reference
*
* If extension sets ownerRef then it needs to explicitly mark workspace as enabled onActivate (or when workspace is saved)
*
* @observable
*/
@observable ownerRef?: string; @observable ownerRef?: string;
/**
* Is workspace enabled
*
* Workspaces that don't have ownerRef will be enabled by default. Workspaces with ownerRef need to explicitly enable a workspace.
*
* @observable
*/
@observable enabled: boolean; @observable enabled: boolean;
/**
* Last active cluster id
*
* @observable
*/
@observable lastActiveClusterId?: ClusterId; @observable lastActiveClusterId?: ClusterId;
constructor(data: WorkspaceModel) { constructor(data: WorkspaceModel) {
@ -49,9 +83,9 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
} }
getState(): WorkspaceState { getState(): WorkspaceState {
return { return toJS({
enabled: this.enabled enabled: this.enabled
}; });
} }
pushState(state = this.getState()) { pushState(state = this.getState()) {
@ -77,16 +111,40 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> { export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
static readonly defaultId: WorkspaceId = "default"; static readonly defaultId: WorkspaceId = "default";
private static stateRequestChannel = "workspace:states";
private constructor() { private constructor() {
super({ super({
configName: "lens-workspace-store", configName: "lens-workspace-store",
}); });
}
if (!ipcRenderer) { async load() {
setInterval(() => { await super.load();
this.pushState(); type workspaceStateSync = {
}, 5000); id: string;
state: WorkspaceState;
};
if (ipcRenderer) {
logger.info("[WORKSPACE-STORE] requesting initial state sync");
const workspaceStates: workspaceStateSync[] = await requestMain(WorkspaceStore.stateRequestChannel);
workspaceStates.forEach((workspaceState) => {
const workspace = this.getById(workspaceState.id);
if (workspace) {
workspace.setState(workspaceState.state);
}
});
} else {
handleRequest(WorkspaceStore.stateRequestChannel, (): workspaceStateSync[] => {
const states: workspaceStateSync[] = [];
this.workspacesList.forEach((workspace) => {
states.push({
state: workspace.getState(),
id: workspace.id
});
});
return states;
});
} }
} }
@ -157,6 +215,10 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
return; return;
} }
this.workspaces.set(id, workspace); this.workspaces.set(id, workspace);
if (!workspace.isManaged) {
workspace.enabled = true;
}
appEventBus.emit({name: "workspace", action: "add"}); appEventBus.emit({name: "workspace", action: "add"});
return workspace; return workspace;
} }

View File

@ -37,6 +37,7 @@ 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;
@ -351,6 +352,7 @@ 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

@ -21,7 +21,7 @@ export class Workspaces extends React.Component {
@computed get workspaces(): Workspace[] { @computed get workspaces(): Workspace[] {
const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map(); const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map();
workspaceStore.workspacesList.forEach((w) => { workspaceStore.enabledWorkspacesList.forEach((w) => {
currentWorkspaces.set(w.id, w); currentWorkspaces.set(w.id, w);
}); });
const allWorkspaces = new Map([ const allWorkspaces = new Map([

View File

@ -65,26 +65,28 @@ export class ClustersMenu extends React.Component<Props> {
} }
})); }));
} }
menu.append(new MenuItem({ if (!cluster.isManaged) {
label: _i18n._(t`Remove`), menu.append(new MenuItem({
click: () => { label: _i18n._(t`Remove`),
ConfirmDialog.open({ click: () => {
okButtonProps: { ConfirmDialog.open({
primary: false, okButtonProps: {
accent: true, primary: false,
label: _i18n._(t`Remove`), accent: true,
}, label: _i18n._(t`Remove`),
ok: () => { },
if (clusterStore.activeClusterId === cluster.id) { ok: () => {
navigate(landingURL()); if (clusterStore.activeClusterId === cluster.id) {
clusterStore.setActive(null); navigate(landingURL());
} clusterStore.setActive(null);
clusterStore.removeById(cluster.id); }
}, clusterStore.removeById(cluster.id);
message: <p>Are you sure want to remove cluster <b title={cluster.id}>{cluster.contextName}</b>?</p>, },
}); message: <p>Are you sure want to remove cluster <b title={cluster.id}>{cluster.contextName}</b>?</p>,
} });
})); }
}));
}
menu.popup({ menu.popup({
window: remote.getCurrentWindow() window: remote.getCurrentWindow()
}); });
@ -106,7 +108,7 @@ export class ClustersMenu extends React.Component<Props> {
const { className } = this.props; const { className } = this.props;
const { newContexts } = userStore; const { newContexts } = userStore;
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId); const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId);
const clusters = clusterStore.getByWorkspaceId(workspace.id); const clusters = clusterStore.getByWorkspaceId(workspace.id).filter(cluster => cluster.enabled);
const activeClusterId = clusterStore.activeCluster; const activeClusterId = clusterStore.activeCluster;
return ( return (
<div className={cssNames("ClustersMenu flex column", className)}> <div className={cssNames("ClustersMenu flex column", className)}>