mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix being able to view clusters outside the current workspace (#2355)
This commit is contained in:
parent
da6b5d6bde
commit
b6c3ce7d28
@ -102,12 +102,6 @@ describe("empty config", () => {
|
||||
await clusterStore.removeById("foo");
|
||||
expect(clusterStore.getById("foo")).toBeNull();
|
||||
});
|
||||
|
||||
it("sets active cluster", () => {
|
||||
clusterStore.setActive("foo");
|
||||
expect(clusterStore.active.id).toBe("foo");
|
||||
expect(workspaceStore.currentWorkspace.lastActiveClusterId).toBe("foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with prod and dev clusters added", () => {
|
||||
|
||||
@ -50,6 +50,12 @@ describe("workspace store tests", () => {
|
||||
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||
});
|
||||
|
||||
it("has the default workspace as active", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
expect(ws.isActive(WorkspaceStore.defaultId)).toBe(true);
|
||||
});
|
||||
|
||||
it("can update workspace description", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
const workspace = ws.addWorkspace(new Workspace({
|
||||
|
||||
182
src/common/__tests__/workspace.test.ts
Normal file
182
src/common/__tests__/workspace.test.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { Workspace } from "../workspace-store";
|
||||
import { clusterStore } from "../cluster-store";
|
||||
import { Cluster } from "../../main/cluster";
|
||||
|
||||
jest.mock("../cluster-store");
|
||||
|
||||
const mockedClusterStore = clusterStore as jest.Mocked<typeof clusterStore>;
|
||||
|
||||
describe("Workspace tests", () => {
|
||||
it("should be enabled if not managed", () => {
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f"
|
||||
});
|
||||
|
||||
expect(w.enabled).toBe(true);
|
||||
expect(w.isManaged).toBe(false);
|
||||
});
|
||||
|
||||
it("should not be enabled initially if managed", () => {
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
ownerRef: "f"
|
||||
});
|
||||
|
||||
expect(w.enabled).toBe(false);
|
||||
expect(w.isManaged).toBe(true);
|
||||
});
|
||||
|
||||
it("should be able to be enabled when managed", () => {
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
ownerRef: "f"
|
||||
});
|
||||
|
||||
expect(w.enabled).toBe(false);
|
||||
expect(w.isManaged).toBe(true);
|
||||
|
||||
w.enabled = true;
|
||||
expect(w.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("should allow valid clusterId to be set to activeClusterId", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f"
|
||||
});
|
||||
|
||||
w.setActiveCluster("foobar");
|
||||
expect(w.activeClusterId).toBe("foobar");
|
||||
});
|
||||
|
||||
it("should clear activeClusterId", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f"
|
||||
});
|
||||
|
||||
w.setActiveCluster("foobar");
|
||||
expect(w.activeClusterId).toBe("foobar");
|
||||
|
||||
w.clearActiveCluster();
|
||||
expect(w.activeClusterId).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should disallow valid clusterId to be set to activeClusterId", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f"
|
||||
});
|
||||
|
||||
w.setActiveCluster("foobar");
|
||||
expect(w.activeClusterId).toBe(undefined);
|
||||
});
|
||||
|
||||
describe("Workspace.tryClearAsCurrentActiveCluster", () => {
|
||||
it("should return false for non-matching ID", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
activeClusterId: "foobar"
|
||||
});
|
||||
|
||||
expect(w.tryClearAsActiveCluster("fa")).toBe(false);
|
||||
expect(w.activeClusterId).toBe("foobar");
|
||||
});
|
||||
it("should return false for non-matching cluster", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
activeClusterId: "foobar"
|
||||
});
|
||||
|
||||
expect(w.tryClearAsActiveCluster({ id: "fa" } as Cluster)).toBe(false);
|
||||
expect(w.activeClusterId).toBe("foobar");
|
||||
});
|
||||
|
||||
it("should return true for matching ID", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
activeClusterId: "foobar"
|
||||
});
|
||||
|
||||
expect(w.tryClearAsActiveCluster("foobar")).toBe(true);
|
||||
expect(w.activeClusterId).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return true for matching cluster", () => {
|
||||
mockedClusterStore.getById.mockImplementationOnce(id => {
|
||||
expect(id).toBe("foobar");
|
||||
|
||||
return {
|
||||
workspace: "f",
|
||||
id
|
||||
} as Cluster;
|
||||
});
|
||||
|
||||
const w = new Workspace({
|
||||
id: "f",
|
||||
name: "f",
|
||||
activeClusterId: "foobar"
|
||||
});
|
||||
|
||||
expect(w.tryClearAsActiveCluster({ id: "foobar"} as Cluster)).toBe(true);
|
||||
expect(w.activeClusterId).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -15,7 +15,6 @@ import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBro
|
||||
import _ from "lodash";
|
||||
import move from "array-move";
|
||||
import type { WorkspaceId } from "./workspace-store";
|
||||
import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting";
|
||||
|
||||
export interface ClusterIconUpload {
|
||||
clusterId: string;
|
||||
@ -34,7 +33,6 @@ export type ClusterPrometheusMetadata = {
|
||||
};
|
||||
|
||||
export interface ClusterStoreModel {
|
||||
activeCluster?: ClusterId; // last opened cluster
|
||||
clusters?: ClusterModel[];
|
||||
}
|
||||
|
||||
@ -106,7 +104,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@observable activeCluster: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
@ -189,10 +186,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
});
|
||||
}
|
||||
|
||||
get activeClusterId() {
|
||||
return this.activeCluster;
|
||||
}
|
||||
|
||||
@computed get clustersList(): Cluster[] {
|
||||
return Array.from(this.clusters.values());
|
||||
}
|
||||
@ -201,40 +194,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return this.clustersList.filter((c) => c.enabled);
|
||||
}
|
||||
|
||||
@computed get active(): Cluster | null {
|
||||
return this.getById(this.activeCluster);
|
||||
}
|
||||
|
||||
@computed get connectedClustersList(): Cluster[] {
|
||||
return this.clustersList.filter((c) => !c.disconnected);
|
||||
}
|
||||
|
||||
isActive(id: ClusterId) {
|
||||
return this.activeCluster === id;
|
||||
}
|
||||
|
||||
isMetricHidden(resource: ResourceType) {
|
||||
return Boolean(this.active?.preferences.hiddenMetrics?.includes(resource));
|
||||
}
|
||||
|
||||
@action
|
||||
setActive(clusterId: ClusterId) {
|
||||
const cluster = this.clusters.get(clusterId);
|
||||
|
||||
if (!cluster?.enabled) {
|
||||
clusterId = null;
|
||||
}
|
||||
|
||||
this.activeCluster = clusterId;
|
||||
workspaceStore.setLastActiveClusterId(clusterId);
|
||||
}
|
||||
|
||||
deactivate(id: ClusterId) {
|
||||
if (this.isActive(id)) {
|
||||
this.setActive(null);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
swapIconOrders(workspace: WorkspaceId, from: number, to: number) {
|
||||
const clusters = this.getByWorkspaceId(workspace);
|
||||
@ -268,28 +231,22 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
@action
|
||||
addClusters(...models: ClusterModel[]): Cluster[] {
|
||||
const clusters: Cluster[] = [];
|
||||
|
||||
models.forEach(model => {
|
||||
clusters.push(this.addCluster(model));
|
||||
});
|
||||
|
||||
return clusters;
|
||||
return models.map(model => this.addCluster(model));
|
||||
}
|
||||
|
||||
@action
|
||||
addCluster(model: ClusterModel | Cluster): Cluster {
|
||||
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
||||
appEventBus.emit({ name: "cluster", action: "add" });
|
||||
let cluster = model as Cluster;
|
||||
|
||||
if (!(model instanceof Cluster)) {
|
||||
cluster = new Cluster(model);
|
||||
}
|
||||
const cluster = clusterOrModel instanceof Cluster
|
||||
? clusterOrModel
|
||||
: new Cluster(clusterOrModel);
|
||||
|
||||
if (!cluster.isManaged) {
|
||||
cluster.enabled = true;
|
||||
}
|
||||
this.clusters.set(model.id, cluster);
|
||||
|
||||
this.clusters.set(cluster.id, cluster);
|
||||
|
||||
return cluster;
|
||||
}
|
||||
@ -304,12 +261,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
const cluster = this.getById(clusterId);
|
||||
|
||||
if (cluster) {
|
||||
workspaceStore.getById(cluster.workspace)?.tryClearAsActiveCluster(cluster);
|
||||
this.clusters.delete(clusterId);
|
||||
|
||||
if (this.activeCluster === clusterId) {
|
||||
this.setActive(null);
|
||||
}
|
||||
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) {
|
||||
unlink(cluster.kubeConfigPath).catch(() => null);
|
||||
@ -325,7 +279,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) {
|
||||
protected fromStore({ clusters = [] }: ClusterStoreModel = {}) {
|
||||
const currentClusters = this.clusters.toJS();
|
||||
const newClusters = new Map<ClusterId, Cluster>();
|
||||
const removedClusters = new Map<ClusterId, Cluster>();
|
||||
@ -353,14 +307,12 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
});
|
||||
|
||||
this.activeCluster = newClusters.get(activeCluster)?.enabled ? activeCluster : null;
|
||||
this.clusters.replace(newClusters);
|
||||
this.removedClusters.replace(removedClusters);
|
||||
}
|
||||
|
||||
toJSON(): ClusterStoreModel {
|
||||
return toJS({
|
||||
activeCluster: this.activeCluster,
|
||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
||||
}, {
|
||||
recurseEverything: true
|
||||
|
||||
@ -6,9 +6,14 @@ import { appEventBus } from "./event-bus";
|
||||
import { broadcastMessage, handleRequest, requestMain } from "../common/ipc";
|
||||
import logger from "../main/logger";
|
||||
import type { ClusterId } from "./cluster-store";
|
||||
import { Cluster } from "../main/cluster";
|
||||
import migrations from "../migrations/workspace-store";
|
||||
import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route";
|
||||
|
||||
export type WorkspaceId = string;
|
||||
|
||||
export class InvariantError extends Error {}
|
||||
|
||||
export interface WorkspaceStoreModel {
|
||||
workspaces: WorkspaceModel[];
|
||||
currentWorkspace?: WorkspaceId;
|
||||
@ -19,7 +24,7 @@ export interface WorkspaceModel {
|
||||
name: string;
|
||||
description?: string;
|
||||
ownerRef?: string;
|
||||
lastActiveClusterId?: ClusterId;
|
||||
activeClusterId?: ClusterId;
|
||||
}
|
||||
|
||||
export interface WorkspaceState {
|
||||
@ -61,18 +66,19 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
*/
|
||||
@observable ownerRef?: string;
|
||||
|
||||
@observable private _enabled = false;
|
||||
|
||||
/**
|
||||
* Last active cluster id
|
||||
*
|
||||
* @observable
|
||||
* The active cluster within this workspace
|
||||
*/
|
||||
@observable lastActiveClusterId?: ClusterId;
|
||||
#activeClusterId = observable.box<ClusterId | undefined>();
|
||||
|
||||
get activeClusterId() {
|
||||
return this.#activeClusterId.get();
|
||||
}
|
||||
|
||||
@observable private _enabled: boolean;
|
||||
|
||||
constructor(data: WorkspaceModel) {
|
||||
Object.assign(this, data);
|
||||
constructor(model: WorkspaceModel) {
|
||||
this[updateFromModel](model);
|
||||
|
||||
if (!ipcRenderer) {
|
||||
reaction(() => this.getState(), () => {
|
||||
@ -86,9 +92,9 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
*
|
||||
* Workspaces that don't have ownerRef will be enabled by default. Workspaces with ownerRef need to explicitly enable a workspace.
|
||||
*
|
||||
* @observable
|
||||
* @computed
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
@computed get enabled(): boolean {
|
||||
return !this.isManaged || this._enabled;
|
||||
}
|
||||
|
||||
@ -98,9 +104,82 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
|
||||
/**
|
||||
* Is workspace managed by an extension
|
||||
*
|
||||
* @computed
|
||||
*/
|
||||
get isManaged(): boolean {
|
||||
return !!this.ownerRef;
|
||||
@computed get isManaged(): boolean {
|
||||
return Boolean(this.ownerRef);
|
||||
}
|
||||
|
||||
@computed get activeCluster(): Cluster | undefined {
|
||||
return clusterStore.getById(this.activeClusterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the clusterId or cluster, checking some invariants
|
||||
* @param clusterOrId The ID or cluster object to resolve
|
||||
* @returns A Cluster instance of the specified cluster if it is in this workspace
|
||||
* @throws if provided a falsey value or if it is an unknown ClusterId or if
|
||||
* the cluster is not in this workspace.
|
||||
*/
|
||||
private resolveClusterOrId(clusterOrId: ClusterId | Cluster): Cluster {
|
||||
if (!clusterOrId) {
|
||||
throw new InvariantError("Must provide a Cluster or a ClusterId");
|
||||
}
|
||||
|
||||
const cluster = typeof clusterOrId === "string"
|
||||
? clusterStore.getById(clusterOrId)
|
||||
: clusterOrId;
|
||||
|
||||
if (!cluster) {
|
||||
throw new InvariantError(`ClusterId ${clusterOrId} is invalid`);
|
||||
}
|
||||
|
||||
if (cluster.workspace !== this.id) {
|
||||
throw new InvariantError(`Cluster ${cluster.name} is not in Workspace ${this.name}`);
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets workspace's active cluster to resolved `clusterOrId`. As long as it
|
||||
* is valid
|
||||
* @param clusterOrId the cluster instance or its ID
|
||||
*/
|
||||
@action setActiveCluster(clusterOrId?: ClusterId | Cluster) {
|
||||
try {
|
||||
if (clusterOrId === undefined) {
|
||||
this.#activeClusterId.set(undefined);
|
||||
} else {
|
||||
this.#activeClusterId.set(this.resolveClusterOrId(clusterOrId).id);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("[WORKSPACE]: activeClusterId was attempted to be set to an invalid value", { error, workspaceName: this.name });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to clear the cluster as this workspace's activeCluster.
|
||||
* @param clusterOrId the cluster instance or its ID
|
||||
* @returns true if it matches the `activeClusterId` (and is thus cleared) else false
|
||||
*/
|
||||
@action tryClearAsActiveCluster(clusterOrId: ClusterId | Cluster): boolean {
|
||||
const clusterId = typeof clusterOrId === "string"
|
||||
? clusterOrId
|
||||
: clusterOrId.id;
|
||||
|
||||
const clearActive = this.activeClusterId === clusterId;
|
||||
|
||||
if (clearActive) {
|
||||
this.clearActiveCluster();
|
||||
}
|
||||
|
||||
return clearActive;
|
||||
}
|
||||
|
||||
@action clearActiveCluster() {
|
||||
this.#activeClusterId.set(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,11 +208,15 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
* @param state workspace state
|
||||
*/
|
||||
@action setState(state: WorkspaceState) {
|
||||
Object.assign(this, state);
|
||||
this.enabled = state.enabled;
|
||||
}
|
||||
|
||||
[updateFromModel] = action((model: WorkspaceModel) => {
|
||||
Object.assign(this, model);
|
||||
this.id = model.id;
|
||||
this.name = model.name;
|
||||
this.description = model.description;
|
||||
this.ownerRef = model.ownerRef;
|
||||
this.setActiveCluster(model.activeClusterId);
|
||||
});
|
||||
|
||||
toJSON(): WorkspaceModel {
|
||||
@ -142,7 +225,7 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
ownerRef: this.ownerRef,
|
||||
lastActiveClusterId: this.lastActiveClusterId
|
||||
activeClusterId: this.activeClusterId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -152,16 +235,18 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
private static stateRequestChannel = "workspace:states";
|
||||
|
||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||
|
||||
@observable workspaces = observable.map<WorkspaceId, Workspace>();
|
||||
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-workspace-store",
|
||||
migrations
|
||||
});
|
||||
|
||||
this.workspaces.set(WorkspaceStore.defaultId, new Workspace({
|
||||
id: WorkspaceStore.defaultId,
|
||||
name: "default"
|
||||
name: "default",
|
||||
}));
|
||||
}
|
||||
|
||||
@ -233,6 +318,19 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
return id === WorkspaceStore.defaultId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `workspaceOrId` represents `WorkspaceStore.currentWorkspaceId`
|
||||
* @param workspaceOrId The workspace or its ID
|
||||
* @returns true if the given workspace is the currently active on
|
||||
*/
|
||||
isActive(workspaceOrId: Workspace | WorkspaceId): boolean {
|
||||
const workspaceId = typeof workspaceOrId === "string"
|
||||
? workspaceOrId
|
||||
: workspaceOrId.id;
|
||||
|
||||
return this.currentWorkspaceId === workspaceId;
|
||||
}
|
||||
|
||||
getById(id: WorkspaceId): Workspace {
|
||||
return this.workspaces.get(id);
|
||||
}
|
||||
@ -248,9 +346,34 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
if (!this.getById(id)) {
|
||||
throw new Error(`workspace ${id} doesn't exist`);
|
||||
}
|
||||
|
||||
this.currentWorkspaceId = id;
|
||||
}
|
||||
|
||||
@action
|
||||
async setActiveCluster(clusterOrId: ClusterId | Cluster): Promise<void> {
|
||||
const cluster = typeof clusterOrId === "string"
|
||||
? clusterStore.getById(clusterOrId)
|
||||
: clusterOrId;
|
||||
|
||||
if (!cluster?.enabled) {
|
||||
throw new Error(`cluster ${(clusterOrId as Cluster)?.id ?? clusterOrId} doesn't exist`);
|
||||
}
|
||||
|
||||
this.setActive(this.getById(cluster.workspace).id);
|
||||
|
||||
if (ipcRenderer) {
|
||||
const { navigate } = await import("../renderer/navigation");
|
||||
|
||||
navigate(clusterViewURL({ params: { clusterId: cluster.id } }));
|
||||
} else {
|
||||
const { WindowManager } = await import("../main/window-manager");
|
||||
const windowManager = WindowManager.getInstance() as any;
|
||||
|
||||
await windowManager.navigate(clusterViewURL({ params: { clusterId: cluster.id } }));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addWorkspace(workspace: Workspace) {
|
||||
const { id, name } = workspace;
|
||||
@ -293,14 +416,20 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
if (this.currentWorkspaceId === id) {
|
||||
this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
|
||||
}
|
||||
|
||||
this.workspaces.delete(id);
|
||||
|
||||
appEventBus.emit({name: "workspace", action: "remove"});
|
||||
clusterStore.removeByWorkspaceId(id);
|
||||
}
|
||||
|
||||
@action
|
||||
setLastActiveClusterId(clusterId?: ClusterId, workspaceId = this.currentWorkspaceId) {
|
||||
this.getById(workspaceId).lastActiveClusterId = clusterId;
|
||||
/**
|
||||
* Attempts to clear `cluster` as the `activeCluster` from its own workspace
|
||||
* @returns true if the cluster was previously the active one for its workspace
|
||||
*/
|
||||
tryClearAsActiveCluster(cluster: Cluster): boolean {
|
||||
return this.getById(cluster.workspace).tryClearAsActiveCluster(cluster);
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { clusterStore as internalClusterStore, ClusterId } from "../../common/cluster-store";
|
||||
import { workspaceStore as internalWorkspaceStore } from "../../common/workspace-store";
|
||||
import type { ClusterModel } from "../../common/cluster-store";
|
||||
import { Cluster } from "../../main/cluster";
|
||||
import { Singleton } from "../core-api/utils";
|
||||
@ -16,16 +17,22 @@ export class ClusterStore extends Singleton {
|
||||
|
||||
/**
|
||||
* Active cluster id
|
||||
*
|
||||
* @deprecated use `workspaceStore.currentWorkspace.activeClusterId`
|
||||
*/
|
||||
get activeClusterId(): string {
|
||||
return internalClusterStore.activeCluster;
|
||||
console.warn("get Store.ClusterStore.activeClusterId is deprecated. Use workspace.currentWorkspace.activeClusterId");
|
||||
|
||||
return internalWorkspaceStore.currentWorkspace.activeClusterId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active cluster id
|
||||
* @deprecated use `LensExtension.navigate()`
|
||||
*/
|
||||
set activeClusterId(id : ClusterId) {
|
||||
internalClusterStore.setActive(id);
|
||||
console.warn("Store.ClusterStore.activeClusterId is deprecated. Use LensExtension.navigate()");
|
||||
internalWorkspaceStore.currentWorkspace.setActiveCluster(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,9 +44,11 @@ export class ClusterStore extends Singleton {
|
||||
|
||||
/**
|
||||
* Get active cluster (a cluster which is currently visible)
|
||||
*
|
||||
* @deprecated use `clusterStore.getById(workspaceStore.currentWorkspace.activeClusterId)`
|
||||
*/
|
||||
get activeCluster(): Cluster | null {
|
||||
return internalClusterStore.active;
|
||||
get activeCluster(): Cluster {
|
||||
return clusterStore.getById(internalWorkspaceStore.currentWorkspace.activeClusterId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Singleton } from "../core-api/utils";
|
||||
import { workspaceStore as internalWorkspaceStore, WorkspaceStore as InternalWorkspaceStore, Workspace, WorkspaceId } from "../../common/workspace-store";
|
||||
import { ObservableMap } from "mobx";
|
||||
import { Cluster, ClusterId } from "../core-api/stores";
|
||||
|
||||
export { Workspace } from "../../common/workspace-store";
|
||||
export type { WorkspaceId, WorkspaceModel } from "../../common/workspace-store";
|
||||
@ -113,6 +114,14 @@ export class WorkspaceStore extends Singleton {
|
||||
removeWorkspaceById(id: WorkspaceId) {
|
||||
return internalWorkspaceStore.removeWorkspaceById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cluster and its workspace as active
|
||||
* @param clusterOrId the cluster's ID or instance to set as the active cluster
|
||||
*/
|
||||
setActiveCluster(clusterOrId: ClusterId | Cluster) {
|
||||
return internalWorkspaceStore.setActiveCluster(clusterOrId);
|
||||
}
|
||||
}
|
||||
|
||||
export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
@ -16,6 +16,7 @@ import logger from "./logger";
|
||||
import { VersionDetector } from "./cluster-detectors/version-detector";
|
||||
import { detectorRegistry } from "./cluster-detectors/detector-registry";
|
||||
import plimit from "p-limit";
|
||||
import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting";
|
||||
|
||||
export enum ClusterStatus {
|
||||
AccessGranted = 2,
|
||||
@ -315,6 +316,10 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
}
|
||||
}
|
||||
|
||||
public isMetricHidden(resource: ResourceType) {
|
||||
return Boolean(this.preferences.hiddenMetrics?.includes(resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@ -106,8 +106,7 @@ app.on("ready", async () => {
|
||||
// preload
|
||||
await Promise.all([
|
||||
userStore.load(),
|
||||
clusterStore.load(),
|
||||
workspaceStore.load(),
|
||||
clusterStore.load().then(() => workspaceStore.load()),
|
||||
extensionsStore.load(),
|
||||
filesystemProvisionerStore.load(),
|
||||
]);
|
||||
|
||||
28
src/migrations/workspace-store/4.2.0-beta.1.ts
Normal file
28
src/migrations/workspace-store/4.2.0-beta.1.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { migration } from "../migration-wrapper";
|
||||
|
||||
interface Pre420Beta1WorkspaceModel {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
ownerRef?: string;
|
||||
lastActiveClusterId?: string;
|
||||
}
|
||||
|
||||
export default migration({
|
||||
version: "4.2.0-beta.1",
|
||||
run(store) {
|
||||
const oldWorkspaces: Pre420Beta1WorkspaceModel[] = store.get("workspaces") ?? [];
|
||||
const workspaces = oldWorkspaces.map(({ lastActiveClusterId, ...rest }) => {
|
||||
if (lastActiveClusterId) {
|
||||
return {
|
||||
activeClusterId: lastActiveClusterId,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
return rest;
|
||||
});
|
||||
|
||||
store.set("workspaces", workspaces);
|
||||
}
|
||||
});
|
||||
5
src/migrations/workspace-store/index.ts
Normal file
5
src/migrations/workspace-store/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import version420Beta1 from "./4.2.0-beta.1";
|
||||
|
||||
export default {
|
||||
...version420Beta1
|
||||
};
|
||||
@ -56,8 +56,7 @@ export async function bootstrap(App: AppComponent) {
|
||||
// preload common stores
|
||||
await Promise.all([
|
||||
userStore.load(),
|
||||
workspaceStore.load(),
|
||||
clusterStore.load(),
|
||||
clusterStore.load().then(() => workspaceStore.load()),
|
||||
extensionsStore.load(),
|
||||
filesystemProvisionerStore.load(),
|
||||
themeStore.init(),
|
||||
|
||||
@ -45,7 +45,7 @@ export class AddCluster extends React.Component {
|
||||
@observable showSettings = false;
|
||||
|
||||
componentDidMount() {
|
||||
clusterStore.setActive(null);
|
||||
workspaceStore.currentWorkspace.clearActiveCluster();
|
||||
this.setKubeConfig(userStore.kubeConfigPath);
|
||||
appEventBus.emit({ name: "cluster-add", action: "start" });
|
||||
}
|
||||
@ -181,13 +181,11 @@ export class AddCluster extends React.Component {
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
clusterStore.addClusters(...newClusters);
|
||||
const [cluster, ...rest] = clusterStore.addClusters(...newClusters);
|
||||
|
||||
if (newClusters.length === 1) {
|
||||
const clusterId = newClusters[0].id;
|
||||
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
if (rest.length === 0) {
|
||||
workspaceStore.getById(cluster.workspace).setActiveCluster(cluster);
|
||||
navigate(clusterViewURL({ params: { clusterId: cluster.id } }));
|
||||
} else {
|
||||
if (newClusters.length > 1) {
|
||||
Notifications.ok(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { navigate } from "../../navigation";
|
||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterSettingsURL } from "./cluster-settings.route";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedClusterId } from "../../../common/cluster-store";
|
||||
|
||||
commandRegistry.add({
|
||||
id: "cluster.viewCurrentClusterSettings",
|
||||
@ -9,7 +9,7 @@ commandRegistry.add({
|
||||
scope: "global",
|
||||
action: () => navigate(clusterSettingsURL({
|
||||
params: {
|
||||
clusterId: clusterStore.active.id
|
||||
clusterId: getHostedClusterId(),
|
||||
}
|
||||
})),
|
||||
isActive: (context) => !!context.cluster
|
||||
|
||||
@ -16,6 +16,7 @@ import { PageLayout } from "../layout/page-layout";
|
||||
import { requestMain } from "../../../common/ipc";
|
||||
import { clusterActivateHandler, clusterRefreshHandler } from "../../../common/cluster-ipc";
|
||||
import { navigation } from "../../navigation";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
|
||||
}
|
||||
@ -39,7 +40,9 @@ export class ClusterSettings extends React.Component<Props> {
|
||||
reaction(() => this.cluster, this.refreshCluster, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
||||
reaction(() => this.cluster, cluster => {
|
||||
workspaceStore.getById(cluster.workspace).setActiveCluster(cluster);
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
})
|
||||
]);
|
||||
|
||||
@ -5,7 +5,7 @@ import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { nodesStore } from "../+nodes/nodes.store";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { clusterStore, getHostedCluster } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
import { interval } from "../../utils";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { Spinner } from "../spinner";
|
||||
@ -66,7 +66,7 @@ export class ClusterOverview extends React.Component {
|
||||
|
||||
render() {
|
||||
const isLoaded = nodesStore.isLoaded && podsStore.isLoaded;
|
||||
const isMetricsHidden = clusterStore.isMetricHidden(ResourceType.Cluster);
|
||||
const isMetricsHidden = getHostedCluster().isMetricHidden(ResourceType.Cluster);
|
||||
|
||||
return (
|
||||
<TabLayout>
|
||||
|
||||
@ -1,38 +1,58 @@
|
||||
import "./landing-page.scss";
|
||||
import React from "react";
|
||||
import { computed, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { WorkspaceId, workspaceStore } from "../../../common/workspace-store";
|
||||
import { WorkspaceOverview } from "./workspace-overview";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Icon } from "../icon";
|
||||
import { createStorage } from "../../utils";
|
||||
|
||||
@observer
|
||||
export class LandingPage extends React.Component {
|
||||
@observable showHint = true;
|
||||
private static storage = createStorage<WorkspaceId[]>("seen_workspaces", []);
|
||||
|
||||
@computed
|
||||
get clusters() {
|
||||
return clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
||||
@computed get workspace() {
|
||||
return workspaceStore.currentWorkspace;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const noClustersInScope = !this.clusters.length;
|
||||
const showStartupHint = this.showHint;
|
||||
// ignore workspaces that don't exist
|
||||
const seenWorkspaces = new Set(
|
||||
LandingPage
|
||||
.storage
|
||||
.get()
|
||||
.filter(id => workspaceStore.getById(id))
|
||||
);
|
||||
|
||||
if (showStartupHint && noClustersInScope) {
|
||||
Notifications.info(<><b>Welcome!</b><p>Get started by associating one or more clusters to Lens</p></>, {
|
||||
timeout: 30_000,
|
||||
id: "landing-welcome"
|
||||
});
|
||||
}
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.workspace, workspace => {
|
||||
const showWelcomeNotification = !(
|
||||
seenWorkspaces.has(workspace.id)
|
||||
|| workspace.isManaged
|
||||
|| clusterStore.getByWorkspaceId(workspace.id).length
|
||||
);
|
||||
|
||||
if (showWelcomeNotification) {
|
||||
Notifications.info(<><b>Welcome!</b><p>Get started by associating one or more clusters to Lens</p></>, {
|
||||
timeout: 30_000,
|
||||
id: "landing-welcome"
|
||||
});
|
||||
}
|
||||
|
||||
seenWorkspaces.add(workspace.id);
|
||||
LandingPage.storage.set(Array.from(seenWorkspaces));
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const showBackButton = this.clusters.length > 0;
|
||||
const header = <><Icon svg="logo-lens" big /> <h2>{workspaceStore.currentWorkspace.name}</h2></>;
|
||||
const showBackButton = Boolean(this.workspace.activeClusterId);
|
||||
const header = <><Icon svg="logo-lens" big /> <h2>{this.workspace.name}</h2></>;
|
||||
|
||||
return (
|
||||
<PageLayout className="LandingOverview flex" header={header} provideBackButtonNavigation={showBackButton} showOnTop={true}>
|
||||
|
||||
@ -15,7 +15,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||
}
|
||||
@ -101,6 +101,7 @@ export class IngressDetails extends React.Component<Props> {
|
||||
if (!ingress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { spec, status } = ingress;
|
||||
const ingressPoints = status?.loadBalancer?.ingress;
|
||||
const { metrics } = ingressStore;
|
||||
@ -108,8 +109,7 @@ export class IngressDetails extends React.Component<Props> {
|
||||
"Network",
|
||||
"Duration",
|
||||
];
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Ingress);
|
||||
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.Ingress);
|
||||
const { serviceName, servicePort } = ingress.getServiceNamePort();
|
||||
|
||||
return (
|
||||
|
||||
@ -18,7 +18,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Node> {
|
||||
}
|
||||
@ -54,7 +54,7 @@ export class NodeDetails extends React.Component<Props> {
|
||||
"Disk",
|
||||
"Pods",
|
||||
];
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Node);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.Node);
|
||||
|
||||
return (
|
||||
<div className="NodeDetails">
|
||||
|
||||
@ -15,7 +15,7 @@ import { getDetailsUrl, KubeObjectDetailsProps, KubeObjectMeta } from "../kube-o
|
||||
import { PersistentVolumeClaim } from "../../api/endpoints";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
||||
}
|
||||
@ -43,7 +43,7 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
||||
const metricTabs = [
|
||||
"Disk"
|
||||
];
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.VolumeClaim);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.VolumeClaim);
|
||||
|
||||
return (
|
||||
<div className="PersistentVolumeClaimDetails">
|
||||
|
||||
@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||
}
|
||||
@ -49,7 +49,7 @@ export class DaemonSetDetails extends React.Component<Props> {
|
||||
const nodeSelector = daemonSet.getNodeSelectors();
|
||||
const childPods = daemonSetStore.getChildPods(daemonSet);
|
||||
const metrics = daemonSetStore.metrics;
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.DaemonSet);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.DaemonSet);
|
||||
|
||||
return (
|
||||
<div className="DaemonSetDetails">
|
||||
|
||||
@ -20,7 +20,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||
}
|
||||
@ -49,7 +49,7 @@ export class DeploymentDetails extends React.Component<Props> {
|
||||
const selectors = deployment.getSelectors();
|
||||
const childPods = deploymentStore.getChildPods(deployment);
|
||||
const metrics = deploymentStore.metrics;
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Deployment);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.Deployment);
|
||||
|
||||
return (
|
||||
<div className="DeploymentDetails">
|
||||
|
||||
@ -12,7 +12,7 @@ import { ResourceMetrics } from "../resource-metrics";
|
||||
import { IMetrics } from "../../api/endpoints/metrics.api";
|
||||
import { ContainerCharts } from "./container-charts";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props {
|
||||
pod: Pod;
|
||||
@ -65,7 +65,7 @@ export class PodDetailsContainer extends React.Component<Props> {
|
||||
"Memory",
|
||||
"Filesystem",
|
||||
];
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Container);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.Container);
|
||||
|
||||
return (
|
||||
<div className="PodDetailsContainer">
|
||||
|
||||
@ -23,7 +23,7 @@ import { PodCharts, podMetricTabs } from "./pod-charts";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Pod> {
|
||||
}
|
||||
@ -68,7 +68,7 @@ export class PodDetails extends React.Component<Props> {
|
||||
const nodeSelector = pod.getNodeSelectors();
|
||||
const volumes = pod.getVolumes();
|
||||
const metrics = podsStore.metrics;
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Pod);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.Pod);
|
||||
|
||||
return (
|
||||
<div className="PodDetails">
|
||||
|
||||
@ -18,7 +18,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
||||
}
|
||||
@ -49,7 +49,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
||||
const nodeSelector = replicaSet.getNodeSelectors();
|
||||
const images = replicaSet.getImages();
|
||||
const childPods = replicaSetStore.getChildPods(replicaSet);
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.ReplicaSet);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.ReplicaSet);
|
||||
|
||||
return (
|
||||
<div className="ReplicaSetDetails">
|
||||
|
||||
@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { getHostedCluster } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
||||
}
|
||||
@ -48,7 +48,7 @@ export class StatefulSetDetails extends React.Component<Props> {
|
||||
const nodeSelector = statefulSet.getNodeSelectors();
|
||||
const childPods = statefulSetStore.getChildPods(statefulSet);
|
||||
const metrics = statefulSetStore.metrics;
|
||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.StatefulSet);
|
||||
const isMetricHidden = getHostedCluster().isMetricHidden(ResourceType.StatefulSet);
|
||||
|
||||
return (
|
||||
<div className="StatefulSetDetails">
|
||||
|
||||
@ -7,7 +7,6 @@ import { Input, InputValidator } from "../input";
|
||||
import { navigate } from "../../navigation";
|
||||
import { CommandOverlay } from "../command-palette/command-container";
|
||||
import { landingURL } from "../+landing-page";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
|
||||
const uniqueWorkspaceName: InputValidator = {
|
||||
condition: ({ required }) => required,
|
||||
@ -31,7 +30,6 @@ export class AddWorkspace extends React.Component {
|
||||
}
|
||||
|
||||
workspaceStore.setActive(workspace.id);
|
||||
clusterStore.setActive(null);
|
||||
navigate(landingURL());
|
||||
CommandOverlay.close();
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||
import { computed} from "mobx";
|
||||
import { WorkspaceStore, workspaceStore } from "../../../common/workspace-store";
|
||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { Select } from "../select";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { navigate } from "../../navigation";
|
||||
import { CommandOverlay } from "../command-palette/command-container";
|
||||
import { AddWorkspace } from "./add-workspace";
|
||||
@ -20,8 +20,8 @@ export class ChooseWorkspace extends React.Component {
|
||||
private static editActionId = "__edit__";
|
||||
|
||||
@computed get options() {
|
||||
const options = workspaceStore.enabledWorkspacesList.map((workspace) => {
|
||||
return { value: workspace.id, label: workspace.name };
|
||||
const options: SelectOption<string | symbol>[] = workspaceStore.enabledWorkspacesList.map((workspace) => {
|
||||
return { value: workspace.id, label: workspace.name, isDisabled: workspaceStore.isActive(workspace) };
|
||||
});
|
||||
|
||||
options.push({ value: ChooseWorkspace.overviewActionId, label: "Show current workspace overview ..." });
|
||||
@ -39,42 +39,30 @@ export class ChooseWorkspace extends React.Component {
|
||||
return options;
|
||||
}
|
||||
|
||||
onChange(id: string) {
|
||||
if (id === ChooseWorkspace.overviewActionId) {
|
||||
navigate(landingURL()); // overview of active workspace. TODO: change name from landing
|
||||
CommandOverlay.close();
|
||||
onChange(idOrAction: string): void {
|
||||
switch (idOrAction) {
|
||||
case ChooseWorkspace.overviewActionId:
|
||||
navigate(landingURL()); // overview of active workspace. TODO: change name from landing
|
||||
|
||||
return;
|
||||
return CommandOverlay.close();
|
||||
case ChooseWorkspace.addActionId:
|
||||
return CommandOverlay.open(<AddWorkspace />);
|
||||
case ChooseWorkspace.removeActionId:
|
||||
return CommandOverlay.open(<RemoveWorkspace />);
|
||||
case ChooseWorkspace.editActionId:
|
||||
return CommandOverlay.open(<EditWorkspace />);
|
||||
default: // assume id
|
||||
workspaceStore.setActive(idOrAction);
|
||||
const clusterId = workspaceStore.getById(idOrAction).activeClusterId;
|
||||
|
||||
if (clusterId) {
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
navigate(landingURL());
|
||||
}
|
||||
|
||||
CommandOverlay.close();
|
||||
}
|
||||
|
||||
if (id === ChooseWorkspace.addActionId) {
|
||||
CommandOverlay.open(<AddWorkspace />);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (id === ChooseWorkspace.removeActionId) {
|
||||
CommandOverlay.open(<RemoveWorkspace />);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (id === ChooseWorkspace.editActionId) {
|
||||
CommandOverlay.open(<EditWorkspace />);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
workspaceStore.setActive(id);
|
||||
const clusterId = workspaceStore.getById(id).lastActiveClusterId;
|
||||
|
||||
if (clusterId) {
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
navigate(landingURL());
|
||||
}
|
||||
|
||||
CommandOverlay.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -9,7 +9,8 @@ import { cssNames, IClassName } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { subscribeToBroadcast } from "../../../common/ipc";
|
||||
import { observable } from "mobx";
|
||||
import { computed, observable } from "mobx";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
cluster: Cluster;
|
||||
@ -18,7 +19,6 @@ interface Props extends DOMAttributes<HTMLElement> {
|
||||
showErrors?: boolean;
|
||||
showTooltip?: boolean;
|
||||
interactive?: boolean;
|
||||
isActive?: boolean;
|
||||
options?: HashiconParams;
|
||||
}
|
||||
|
||||
@ -33,8 +33,16 @@ export class ClusterIcon extends React.Component<Props> {
|
||||
|
||||
@observable eventCount = 0;
|
||||
|
||||
get eventCountBroadcast() {
|
||||
return `cluster-warning-event-count:${this.props.cluster.id}`;
|
||||
@computed get eventCountBroadcast() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
return `cluster-warning-event-count:${cluster.id}`;
|
||||
}
|
||||
|
||||
@computed get isActive() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
return workspaceStore.getById(cluster.workspace).activeClusterId === cluster.id;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -48,8 +56,9 @@ export class ClusterIcon extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isActive } = this;
|
||||
const {
|
||||
cluster, showErrors, showTooltip, errorClass, options, interactive, isActive,
|
||||
cluster, showErrors, showTooltip, errorClass, options, interactive,
|
||||
children, ...elemProps
|
||||
} = this.props;
|
||||
const { name, preferences, id: clusterId, online } = cluster;
|
||||
|
||||
@ -10,6 +10,7 @@ import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { Tooltip } from "../../components//tooltip";
|
||||
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
const navigate = (route: string) =>
|
||||
broadcastMessage(IpcRendererNavigationEvents.NAVIGATE_IN_APP, route);
|
||||
@ -24,8 +25,10 @@ export const ClusterActions = (cluster: Cluster) => ({
|
||||
params: { clusterId: cluster.id }
|
||||
})),
|
||||
disconnect: async () => {
|
||||
clusterStore.deactivate(cluster.id);
|
||||
navigate(landingURL());
|
||||
if (workspaceStore.tryClearAsActiveCluster(cluster)) {
|
||||
navigate(landingURL());
|
||||
}
|
||||
|
||||
await requestMain(clusterDisconnectHandler, cluster.id);
|
||||
},
|
||||
remove: () => {
|
||||
@ -38,7 +41,6 @@ export const ClusterActions = (cluster: Cluster) => ({
|
||||
label: "Remove"
|
||||
},
|
||||
ok: () => {
|
||||
clusterStore.deactivate(cluster.id);
|
||||
clusterStore.removeById(cluster.id);
|
||||
navigate(landingURL());
|
||||
},
|
||||
|
||||
@ -17,6 +17,7 @@ import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
||||
import { Extensions, extensionsRoute } from "../+extensions";
|
||||
import { getMatchedClusterId } from "../../navigation";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
@observer
|
||||
export class ClusterManager extends React.Component {
|
||||
@ -44,12 +45,12 @@ export class ClusterManager extends React.Component {
|
||||
}
|
||||
|
||||
get startUrl() {
|
||||
const { activeClusterId } = clusterStore;
|
||||
const { currentWorkspace } = workspaceStore;
|
||||
|
||||
if (activeClusterId) {
|
||||
if (currentWorkspace.activeClusterId) {
|
||||
return clusterViewURL({
|
||||
params: {
|
||||
clusterId: activeClusterId
|
||||
clusterId: currentWorkspace.activeClusterId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { ClusterStatus } from "./cluster-status";
|
||||
import { hasLoadedView } from "./lens-views";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
||||
}
|
||||
@ -24,7 +25,9 @@ export class ClusterView extends React.Component<Props> {
|
||||
|
||||
async componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
||||
reaction(() => this.cluster, cluster => {
|
||||
workspaceStore.getById(cluster.workspace).setActiveCluster(cluster);
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
})
|
||||
]);
|
||||
|
||||
@ -30,6 +30,10 @@ interface Props {
|
||||
export class ClustersMenu extends React.Component<Props> {
|
||||
@observable workspaceMenuVisible = false;
|
||||
|
||||
get workspace() {
|
||||
return workspaceStore.currentWorkspace;
|
||||
}
|
||||
|
||||
showCluster = (clusterId: ClusterId) => {
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
};
|
||||
@ -77,9 +81,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId);
|
||||
const clusters = clusterStore.getByWorkspaceId(workspace.id).filter(cluster => cluster.enabled);
|
||||
const activeClusterId = clusterStore.activeCluster;
|
||||
const clusters = clusterStore.getByWorkspaceId(this.workspace.id).filter(cluster => cluster.enabled);
|
||||
|
||||
return (
|
||||
<div className={cssNames("ClustersMenu flex column", className)}>
|
||||
@ -88,26 +90,21 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
<Droppable droppableId="cluster-menu" type="CLUSTER">
|
||||
{({ innerRef, droppableProps, placeholder }: DroppableProvided) => (
|
||||
<div ref={innerRef} {...droppableProps}>
|
||||
{clusters.map((cluster, index) => {
|
||||
const isActive = cluster.id === activeClusterId;
|
||||
|
||||
return (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
|
||||
<div ref={innerRef} {...draggableProps} {...dragHandleProps}>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={isActive}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
{clusters.map((cluster, index) => (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
|
||||
<div ref={innerRef} {...draggableProps} {...dragHandleProps}>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -8,7 +8,6 @@ import { EventEmitter } from "../../../common/event-emitter";
|
||||
import { subscribeToBroadcast } from "../../../common/ipc";
|
||||
import { CommandDialog } from "./command-dialog";
|
||||
import { CommandRegistration, commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
|
||||
export type CommandDialogEvent = {
|
||||
@ -49,7 +48,7 @@ export class CommandContainer extends React.Component<{ clusterId?: string }> {
|
||||
|
||||
private runCommand(command: CommandRegistration) {
|
||||
command.action({
|
||||
cluster: clusterStore.active,
|
||||
cluster: workspaceStore.currentWorkspace.activeCluster,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import { computed, observable, toJS } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { CommandOverlay } from "./command-container";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
@ -16,30 +15,31 @@ export class CommandDialog extends React.Component {
|
||||
@observable menuIsOpen = true;
|
||||
|
||||
@computed get options() {
|
||||
const context = {
|
||||
cluster: clusterStore.active,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
};
|
||||
const activeCluster = workspaceStore.currentWorkspace.activeCluster;
|
||||
|
||||
return commandRegistry.getItems().filter((command) => {
|
||||
if (command.scope === "cluster" && !clusterStore.active) {
|
||||
return false;
|
||||
}
|
||||
return commandRegistry.getItems()
|
||||
.filter(command => {
|
||||
if (command.scope === "cluster" && !activeCluster) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command.isActive) {
|
||||
return true;
|
||||
}
|
||||
if (!command.isActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return command.isActive(context);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
try {
|
||||
return command.isActive({
|
||||
cluster: activeCluster,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}).map((command) => {
|
||||
return { value: command.id, label: command.title };
|
||||
}).sort((a, b) => a.label > b.label ? 1 : -1);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(({ id, title }) => ({ value: id, label: title }))
|
||||
.sort((a, b) => a.label > b.label ? 1 : -1);
|
||||
}
|
||||
|
||||
private onChange(value: string) {
|
||||
@ -49,6 +49,7 @@ export class CommandDialog extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeCluster = workspaceStore.currentWorkspace.activeCluster;
|
||||
const action = toJS(command.action);
|
||||
|
||||
try {
|
||||
@ -56,16 +57,16 @@ export class CommandDialog extends React.Component {
|
||||
|
||||
if (command.scope === "global") {
|
||||
action({
|
||||
cluster: clusterStore.active,
|
||||
cluster: activeCluster,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
});
|
||||
} else if(clusterStore.active) {
|
||||
} else if(activeCluster) {
|
||||
navigate(clusterViewURL({
|
||||
params: {
|
||||
clusterId: clusterStore.active.id
|
||||
clusterId: activeCluster.id
|
||||
}
|
||||
}));
|
||||
broadcastMessage(`command-palette:run-action:${clusterStore.active.id}`, command.id);
|
||||
broadcastMessage(`command-palette:run-action:${activeCluster.id}`, command.id);
|
||||
}
|
||||
} catch(error) {
|
||||
console.error("[COMMAND-DIALOG] failed to execute command", command.id, error);
|
||||
|
||||
@ -8,6 +8,7 @@ import { invalidKubeconfigHandler } from "./invalid-kubeconfig-handler";
|
||||
import { clusterStore } from "../../common/cluster-store";
|
||||
import { navigate } from "../navigation";
|
||||
import { clusterSettingsURL } from "../components/+cluster-settings";
|
||||
import logger from "../../main/logger";
|
||||
|
||||
function sendToBackchannel(backchannel: string, notificationId: string, data: BackchannelArg): void {
|
||||
notificationsStore.remove(notificationId);
|
||||
@ -59,6 +60,12 @@ const listNamespacesForbiddenHandlerDisplayedAt = new Map<string, number>();
|
||||
const intervalBetweenNotifications = 1000 * 60; // 60s
|
||||
|
||||
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
|
||||
if (!cluster) {
|
||||
return void logger.warn("[ListNamespacesForbiddenHandler]: received event but was given an unknown cluster ID", { clusterId });
|
||||
}
|
||||
|
||||
const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId);
|
||||
const wasDisplayed = Boolean(lastDisplayedAt);
|
||||
const now = Date.now();
|
||||
@ -76,7 +83,7 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]:
|
||||
(
|
||||
<div className="flex column gaps">
|
||||
<b>Add Accessible Namespaces</b>
|
||||
<p>Cluster <b>{clusterStore.active.name}</b> does not have permissions to list namespaces. Please add the namespaces you have access to.</p>
|
||||
<p>Cluster <b>{cluster.name}</b> does not have permissions to list namespaces. Please add the namespaces you have access to.</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button active outlined label="Go to Accessible Namespaces Settings" onClick={()=> {
|
||||
navigate(clusterSettingsURL({ params: { clusterId }, fragment: "accessible-namespaces" }));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user