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:
parent
832f29f666
commit
4161ee832c
@ -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", () => {
|
||||||
|
|||||||
@ -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", () => {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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([
|
||||||
|
|||||||
@ -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)}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user