mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Improve how extensions can manage cluster/workspace stores (#1176)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
e4c56512e7
commit
4a6553967d
15 files changed
+256
-95
No files matched your search
@@ -64,13 +64,13 @@ describe("empty config", () => {
|
||||
|
||||
it("sets active cluster", () => {
|
||||
clusterStore.setActive("foo");
|
||||
expect(clusterStore.activeCluster.id).toBe("foo");
|
||||
expect(clusterStore.active.id).toBe("foo");
|
||||
})
|
||||
})
|
||||
|
||||
describe("with prod and dev clusters added", () => {
|
||||
beforeEach(() => {
|
||||
clusterStore.addCluster(
|
||||
clusterStore.addClusters(
|
||||
new Cluster({
|
||||
id: "prod",
|
||||
contextName: "prod",
|
||||
|
||||
@@ -10,7 +10,7 @@ jest.mock("electron", () => {
|
||||
}
|
||||
})
|
||||
|
||||
import { WorkspaceStore } from "../workspace-store"
|
||||
import { Workspace, WorkspaceStore } from "../workspace-store"
|
||||
|
||||
describe("workspace store tests", () => {
|
||||
describe("for an empty config", () => {
|
||||
@@ -35,16 +35,16 @@ describe("workspace store tests", () => {
|
||||
it("cannot remove the default workspace", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
expect(() => ws.removeWorkspace(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||
})
|
||||
|
||||
it("can update default workspace name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: WorkspaceStore.defaultId,
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.currentWorkspace.name).toBe("foobar");
|
||||
})
|
||||
@@ -52,10 +52,10 @@ describe("workspace store tests", () => {
|
||||
it("can add workspaces", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "123",
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.getById("123").name).toBe("foobar");
|
||||
})
|
||||
@@ -69,10 +69,10 @@ describe("workspace store tests", () => {
|
||||
it("can set a existent workspace to be active", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "abc",
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(() => ws.setActive("abc")).not.toThrowError();
|
||||
})
|
||||
@@ -80,15 +80,15 @@ describe("workspace store tests", () => {
|
||||
it("can remove a workspace", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "123",
|
||||
name: "foobar",
|
||||
});
|
||||
ws.saveWorkspace({
|
||||
}));
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "1234",
|
||||
name: "foobar 1",
|
||||
});
|
||||
ws.removeWorkspace("123");
|
||||
}));
|
||||
ws.removeWorkspaceById("123");
|
||||
|
||||
expect(ws.workspaces.size).toBe(2);
|
||||
})
|
||||
@@ -96,10 +96,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with existent name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "someid",
|
||||
name: "default",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@@ -107,10 +107,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with empty name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: "",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@@ -118,10 +118,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with ' ' name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: " ",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@@ -129,10 +129,10 @@ describe("workspace store tests", () => {
|
||||
it("trim workspace name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: "default ",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@@ -169,4 +169,4 @@ describe("workspace store tests", () => {
|
||||
expect(ws.currentWorkspaceId).toBe("abc");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
+63
-20
@@ -33,11 +33,12 @@ export type ClusterId = string;
|
||||
|
||||
export interface ClusterModel {
|
||||
id: ClusterId;
|
||||
kubeConfigPath: string;
|
||||
workspace?: WorkspaceId;
|
||||
contextName?: string;
|
||||
preferences?: ClusterPreferences;
|
||||
metadata?: ClusterMetadata;
|
||||
kubeConfigPath: string;
|
||||
ownerRef?: string;
|
||||
|
||||
/** @deprecated */
|
||||
kubeConfig?: string; // yaml
|
||||
@@ -72,25 +73,34 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@observable activeCluster: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
migrations: migrations,
|
||||
});
|
||||
|
||||
this.pushStateToViewsPeriodically()
|
||||
}
|
||||
|
||||
@observable activeClusterId: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
protected pushStateToViewsPeriodically() {
|
||||
if (!ipcRenderer) {
|
||||
// This is a bit of a hack, we need to do this because we might loose messages that are sent before a view is ready
|
||||
setInterval(() => {
|
||||
this.pushState()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcListener() {
|
||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
||||
ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
|
||||
this.applyWithoutSync(() => {
|
||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, model);
|
||||
this.getById(model.id)?.updateModel(model);
|
||||
})
|
||||
ipcRenderer.on("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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,21 +109,35 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
ipcRenderer.removeAllListeners("cluster:state")
|
||||
}
|
||||
|
||||
@computed get activeCluster(): Cluster | null {
|
||||
return this.getById(this.activeClusterId);
|
||||
pushState() {
|
||||
this.clusters.forEach((c) => {
|
||||
c.pushState()
|
||||
})
|
||||
}
|
||||
|
||||
get activeClusterId() {
|
||||
return this.activeCluster
|
||||
}
|
||||
|
||||
@computed get clustersList(): Cluster[] {
|
||||
return Array.from(this.clusters.values());
|
||||
}
|
||||
|
||||
@computed get enabledClustersList(): Cluster[] {
|
||||
return this.clustersList.filter((c) => c.enabled)
|
||||
}
|
||||
|
||||
@computed get active(): Cluster | null {
|
||||
return this.getById(this.activeCluster);
|
||||
}
|
||||
|
||||
isActive(id: ClusterId) {
|
||||
return this.activeClusterId === id;
|
||||
return this.activeCluster === id;
|
||||
}
|
||||
|
||||
@action
|
||||
setActive(id: ClusterId) {
|
||||
this.activeClusterId = this.clusters.has(id) ? id : null;
|
||||
this.activeCluster = this.clusters.has(id) ? id : null;
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -145,12 +169,28 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
addCluster(...models: ClusterModel[]) {
|
||||
addClusters(...models: ClusterModel[]): Cluster[] {
|
||||
const clusters: Cluster[] = []
|
||||
models.forEach(model => {
|
||||
appEventBus.emit({name: "cluster", action: "add"})
|
||||
const cluster = new Cluster(model);
|
||||
this.clusters.set(model.id, cluster);
|
||||
clusters.push(this.addCluster(model))
|
||||
})
|
||||
|
||||
return clusters
|
||||
}
|
||||
|
||||
@action
|
||||
addCluster(model: ClusterModel | Cluster ): Cluster {
|
||||
appEventBus.emit({name: "cluster", action: "add"})
|
||||
let cluster = model as Cluster;
|
||||
if (!(model instanceof Cluster)) {
|
||||
cluster = new Cluster(model)
|
||||
}
|
||||
this.clusters.set(model.id, cluster);
|
||||
return cluster
|
||||
}
|
||||
|
||||
async removeCluster(model: ClusterModel) {
|
||||
await this.removeById(model.id)
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -159,7 +199,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
const cluster = this.getById(clusterId);
|
||||
if (cluster) {
|
||||
this.clusters.delete(clusterId);
|
||||
if (this.activeClusterId === clusterId) {
|
||||
if (this.activeCluster === clusterId) {
|
||||
this.setActive(null);
|
||||
}
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
@@ -189,6 +229,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
cluster.updateModel(clusterModel);
|
||||
} else {
|
||||
cluster = new Cluster(clusterModel);
|
||||
if (!cluster.isManaged) {
|
||||
cluster.enabled = true
|
||||
}
|
||||
}
|
||||
newClusters.set(clusterModel.id, cluster);
|
||||
}
|
||||
@@ -200,14 +243,14 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
});
|
||||
|
||||
this.activeClusterId = newClusters.has(activeCluster) ? activeCluster : null;
|
||||
this.activeCluster = newClusters.has(activeCluster) ? activeCluster : null;
|
||||
this.clusters.replace(newClusters);
|
||||
this.removedClusters.replace(removedClusters);
|
||||
}
|
||||
|
||||
toJSON(): ClusterStoreModel {
|
||||
return toJS({
|
||||
activeCluster: this.activeClusterId,
|
||||
activeCluster: this.activeCluster,
|
||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
||||
}, {
|
||||
recurseEverything: true
|
||||
|
||||
+105
-10
@@ -1,19 +1,77 @@
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { action, computed, observable, toJS, reaction } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { clusterStore } from "./cluster-store"
|
||||
import { appEventBus } from "./event-bus";
|
||||
import { broadcastIpc } from "../common/ipc";
|
||||
import logger from "../main/logger";
|
||||
|
||||
export type WorkspaceId = string;
|
||||
|
||||
export interface WorkspaceStoreModel {
|
||||
currentWorkspace?: WorkspaceId;
|
||||
workspaces: Workspace[]
|
||||
workspaces: WorkspaceModel[]
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
export interface WorkspaceModel {
|
||||
id: WorkspaceId;
|
||||
name: string;
|
||||
description?: string;
|
||||
ownerRef?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceState {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
@observable id: WorkspaceId
|
||||
@observable name: string
|
||||
@observable description?: string
|
||||
@observable ownerRef?: string
|
||||
@observable enabled: boolean
|
||||
|
||||
constructor(data: WorkspaceModel) {
|
||||
Object.assign(this, data)
|
||||
|
||||
if (!ipcRenderer) {
|
||||
reaction(() => this.getState(), () => {
|
||||
this.pushState()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get isManaged(): boolean {
|
||||
return !!this.ownerRef
|
||||
}
|
||||
|
||||
getState(): WorkspaceState {
|
||||
return {
|
||||
enabled: this.enabled
|
||||
}
|
||||
}
|
||||
|
||||
pushState(state = this.getState()) {
|
||||
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id})
|
||||
broadcastIpc({
|
||||
channel: "workspace:state",
|
||||
args: [this.id, toJS(state)],
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
setState(state: WorkspaceState) {
|
||||
Object.assign(this, state)
|
||||
}
|
||||
|
||||
toJSON(): WorkspaceModel {
|
||||
return toJS({
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
ownerRef: this.ownerRef
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
@@ -23,15 +81,33 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
super({
|
||||
configName: "lens-workspace-store",
|
||||
});
|
||||
|
||||
if (!ipcRenderer) {
|
||||
setInterval(() => {
|
||||
this.pushState()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcListener() {
|
||||
logger.info("[WORKSPACE-STORE] starting to listen state events")
|
||||
ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
|
||||
this.getById(workspaceId)?.setState(state)
|
||||
})
|
||||
}
|
||||
|
||||
unregisterIpcListener() {
|
||||
super.unregisterIpcListener()
|
||||
ipcRenderer.removeAllListeners("workspace:state")
|
||||
}
|
||||
|
||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||
|
||||
@observable workspaces = observable.map<WorkspaceId, Workspace>({
|
||||
[WorkspaceStore.defaultId]: {
|
||||
[WorkspaceStore.defaultId]: new Workspace({
|
||||
id: WorkspaceStore.defaultId,
|
||||
name: "default"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@computed get currentWorkspace(): Workspace {
|
||||
@@ -42,6 +118,16 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
return Array.from(this.workspaces.values());
|
||||
}
|
||||
|
||||
@computed get enabledWorkspacesList() {
|
||||
return this.workspacesList.filter((w) => w.enabled);
|
||||
}
|
||||
|
||||
pushState() {
|
||||
this.workspaces.forEach((w) => {
|
||||
w.pushState()
|
||||
})
|
||||
}
|
||||
|
||||
isDefault(id: WorkspaceId) {
|
||||
return id === WorkspaceStore.defaultId;
|
||||
}
|
||||
@@ -61,11 +147,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
throw new Error(`workspace ${id} doesn't exist`);
|
||||
}
|
||||
this.currentWorkspaceId = id;
|
||||
clusterStore.activeClusterId = null; // fixme: handle previously selected cluster from current workspace
|
||||
clusterStore.activeCluster = null; // fixme: handle previously selected cluster from current workspace
|
||||
}
|
||||
|
||||
@action
|
||||
saveWorkspace(workspace: Workspace) {
|
||||
addWorkspace(workspace: Workspace) {
|
||||
const { id, name } = workspace;
|
||||
const existingWorkspace = this.getById(id);
|
||||
if (!name.trim() || this.getByName(name.trim())) {
|
||||
@@ -82,7 +168,12 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
removeWorkspace(id: WorkspaceId) {
|
||||
removeWorkspace(workspace: Workspace) {
|
||||
this.removeWorkspaceById(workspace.id)
|
||||
}
|
||||
|
||||
@action
|
||||
removeWorkspaceById(id: WorkspaceId) {
|
||||
const workspace = this.getById(id);
|
||||
if (!workspace) return;
|
||||
if (this.isDefault(id)) {
|
||||
@@ -103,7 +194,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
if (workspaces.length) {
|
||||
this.workspaces.clear();
|
||||
workspaces.forEach(workspace => {
|
||||
workspaces.forEach(ws => {
|
||||
const workspace = new Workspace(ws)
|
||||
if (!workspace.isManaged) {
|
||||
workspace.enabled = true
|
||||
}
|
||||
this.workspaces.set(workspace.id, workspace)
|
||||
})
|
||||
}
|
||||
@@ -112,7 +207,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
toJSON(): WorkspaceStoreModel {
|
||||
return toJS({
|
||||
currentWorkspace: this.currentWorkspaceId,
|
||||
workspaces: this.workspacesList,
|
||||
workspaces: this.workspacesList.map((w) => w.toJSON()),
|
||||
}, {
|
||||
recurseEverything: true
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user