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
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
e4c56512e7
commit
4dd6d587ab
@ -70,7 +70,7 @@ describe("empty config", () => {
|
|||||||
|
|
||||||
describe("with prod and dev clusters added", () => {
|
describe("with prod and dev clusters added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clusterStore.addCluster(
|
clusterStore.addClusters(
|
||||||
new Cluster({
|
new Cluster({
|
||||||
id: "prod",
|
id: "prod",
|
||||||
contextName: "prod",
|
contextName: "prod",
|
||||||
|
|||||||
@ -33,11 +33,12 @@ export type ClusterId = string;
|
|||||||
|
|
||||||
export interface ClusterModel {
|
export interface ClusterModel {
|
||||||
id: ClusterId;
|
id: ClusterId;
|
||||||
|
kubeConfigPath: string;
|
||||||
workspace?: WorkspaceId;
|
workspace?: WorkspaceId;
|
||||||
contextName?: string;
|
contextName?: string;
|
||||||
preferences?: ClusterPreferences;
|
preferences?: ClusterPreferences;
|
||||||
metadata?: ClusterMetadata;
|
metadata?: ClusterMetadata;
|
||||||
kubeConfigPath: string;
|
ownerRef?: string;
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
kubeConfig?: string; // yaml
|
kubeConfig?: string; // yaml
|
||||||
@ -78,6 +79,12 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
migrations: migrations,
|
migrations: migrations,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!ipcRenderer) {
|
||||||
|
setInterval(() => {
|
||||||
|
this.pushState()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable activeClusterId: ClusterId;
|
@observable activeClusterId: ClusterId;
|
||||||
@ -86,11 +93,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
registerIpcListener() {
|
registerIpcListener() {
|
||||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
||||||
ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
|
ipcRenderer.on("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
||||||
this.applyWithoutSync(() => {
|
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
|
||||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, model);
|
this.getById(clusterId)?.setState(state)
|
||||||
this.getById(model.id)?.updateModel(model);
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +104,12 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
ipcRenderer.removeAllListeners("cluster:state")
|
ipcRenderer.removeAllListeners("cluster:state")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushState() {
|
||||||
|
this.clusters.forEach((c) => {
|
||||||
|
c.pushState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@computed get activeCluster(): Cluster | null {
|
@computed get activeCluster(): Cluster | null {
|
||||||
return this.getById(this.activeClusterId);
|
return this.getById(this.activeClusterId);
|
||||||
}
|
}
|
||||||
@ -107,6 +118,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return Array.from(this.clusters.values());
|
return Array.from(this.clusters.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get enabledClustersList(): Cluster[] {
|
||||||
|
return this.clustersList.filter((c) => c.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
isActive(id: ClusterId) {
|
isActive(id: ClusterId) {
|
||||||
return this.activeClusterId === id;
|
return this.activeClusterId === id;
|
||||||
}
|
}
|
||||||
@ -145,12 +160,29 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addCluster(...models: ClusterModel[]) {
|
addClusters(...models: ClusterModel[]): Cluster[] {
|
||||||
|
const clusters: Cluster[] = []
|
||||||
models.forEach(model => {
|
models.forEach(model => {
|
||||||
appEventBus.emit({name: "cluster", action: "add"})
|
clusters.push(this.addCluster(model))
|
||||||
const cluster = new Cluster(model);
|
|
||||||
this.clusters.set(model.id, cluster);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return clusters
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
addCluster(model: ClusterModel): Cluster {
|
||||||
|
appEventBus.emit({name: "cluster", action: "add"})
|
||||||
|
let cluster: Cluster = null
|
||||||
|
if (model instanceof Cluster === false) {
|
||||||
|
cluster = new Cluster(model)
|
||||||
|
}
|
||||||
|
this.clusters.set(model.id, cluster);
|
||||||
|
|
||||||
|
return cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeCluster(model: ClusterModel) {
|
||||||
|
await this.removeById(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -189,6 +221,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
cluster.updateModel(clusterModel);
|
cluster.updateModel(clusterModel);
|
||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(clusterModel);
|
cluster = new Cluster(clusterModel);
|
||||||
|
if (!cluster.isManaged()) {
|
||||||
|
cluster.enabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newClusters.set(clusterModel.id, cluster);
|
newClusters.set(clusterModel.id, cluster);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,9 @@ export interface IpcBroadcastParams<A extends any[] = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function broadcastIpc({ channel, frameId, frameOnly, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
export function broadcastIpc({ channel, frameId, frameOnly, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
||||||
|
if (!webContents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
||||||
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
|||||||
@ -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 { 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 { broadcastIpc } from "../common/ipc";
|
||||||
|
import logger from "../main/logger";
|
||||||
|
|
||||||
export type WorkspaceId = string;
|
export type WorkspaceId = string;
|
||||||
|
|
||||||
export interface WorkspaceStoreModel {
|
export interface WorkspaceStoreModel {
|
||||||
currentWorkspace?: WorkspaceId;
|
currentWorkspace?: WorkspaceId;
|
||||||
workspaces: Workspace[]
|
workspaces: WorkspaceModel[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Workspace {
|
export interface WorkspaceModel {
|
||||||
id: WorkspaceId;
|
id: WorkspaceId;
|
||||||
name: string;
|
name: string;
|
||||||
description?: 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||||
@ -23,15 +81,34 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
super({
|
super({
|
||||||
configName: "lens-workspace-store",
|
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) => {
|
||||||
|
console.log(workspaceId, state)
|
||||||
|
this.getById(workspaceId)?.setState(state)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterIpcListener() {
|
||||||
|
super.unregisterIpcListener()
|
||||||
|
ipcRenderer.removeAllListeners("workspace:state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||||
|
|
||||||
@observable workspaces = observable.map<WorkspaceId, Workspace>({
|
@observable workspaces = observable.map<WorkspaceId, Workspace>({
|
||||||
[WorkspaceStore.defaultId]: {
|
[WorkspaceStore.defaultId]: new Workspace({
|
||||||
id: WorkspaceStore.defaultId,
|
id: WorkspaceStore.defaultId,
|
||||||
name: "default"
|
name: "default"
|
||||||
}
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@computed get currentWorkspace(): Workspace {
|
@computed get currentWorkspace(): Workspace {
|
||||||
@ -42,6 +119,16 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
return Array.from(this.workspaces.values());
|
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) {
|
isDefault(id: WorkspaceId) {
|
||||||
return id === WorkspaceStore.defaultId;
|
return id === WorkspaceStore.defaultId;
|
||||||
}
|
}
|
||||||
@ -65,7 +152,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
saveWorkspace(workspace: Workspace) {
|
addWorkspace(workspace: Workspace) {
|
||||||
const { id, name } = workspace;
|
const { id, name } = workspace;
|
||||||
const existingWorkspace = this.getById(id);
|
const existingWorkspace = this.getById(id);
|
||||||
if (!name.trim() || this.getByName(name.trim())) {
|
if (!name.trim() || this.getByName(name.trim())) {
|
||||||
@ -82,7 +169,12 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeWorkspace(id: WorkspaceId) {
|
removeWorkspace(workspace: Workspace) {
|
||||||
|
this.removeWorkspaceById(workspace.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
removeWorkspaceById(id: WorkspaceId) {
|
||||||
const workspace = this.getById(id);
|
const workspace = this.getById(id);
|
||||||
if (!workspace) return;
|
if (!workspace) return;
|
||||||
if (this.isDefault(id)) {
|
if (this.isDefault(id)) {
|
||||||
@ -103,7 +195,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
}
|
}
|
||||||
if (workspaces.length) {
|
if (workspaces.length) {
|
||||||
this.workspaces.clear();
|
this.workspaces.clear();
|
||||||
workspaces.forEach(workspace => {
|
workspaces.forEach(ws => {
|
||||||
|
const workspace = new Workspace(ws)
|
||||||
|
if (!workspace.ownerRef) {
|
||||||
|
workspace.enabled = true
|
||||||
|
}
|
||||||
this.workspaces.set(workspace.id, workspace)
|
this.workspaces.set(workspace.id, workspace)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -112,7 +208,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
toJSON(): WorkspaceStoreModel {
|
toJSON(): WorkspaceStoreModel {
|
||||||
return toJS({
|
return toJS({
|
||||||
currentWorkspace: this.currentWorkspaceId,
|
currentWorkspace: this.currentWorkspaceId,
|
||||||
workspaces: this.workspacesList,
|
workspaces: this.workspacesList.map((w) => w.toJSON()),
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
})
|
})
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export class ClusterManager {
|
|||||||
constructor(public readonly port: number) {
|
constructor(public readonly port: number) {
|
||||||
// auto-init clusters
|
// auto-init clusters
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
clusterStore.clusters.forEach(cluster => {
|
clusterStore.enabledClustersList.forEach(cluster => {
|
||||||
if (!cluster.initialized) {
|
if (!cluster.initialized) {
|
||||||
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
||||||
cluster.init(port);
|
cluster.init(port);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ipcMain } from "electron"
|
||||||
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||||
import type { WorkspaceId } from "../common/workspace-store";
|
import type { WorkspaceId } from "../common/workspace-store";
|
||||||
@ -33,7 +34,7 @@ export type ClusterRefreshOptions = {
|
|||||||
refreshMetadata?: boolean
|
refreshMetadata?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterState extends ClusterModel {
|
export interface ClusterState {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
@ -47,11 +48,12 @@ export interface ClusterState extends ClusterModel {
|
|||||||
allowedResources: string[]
|
allowedResources: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Cluster implements ClusterModel {
|
export class Cluster implements ClusterModel, ClusterState {
|
||||||
public id: ClusterId;
|
public id: ClusterId;
|
||||||
public frameId: number;
|
public frameId: number;
|
||||||
public kubeCtl: Kubectl
|
public kubeCtl: Kubectl
|
||||||
public contextHandler: ContextHandler;
|
public contextHandler: ContextHandler;
|
||||||
|
public ownerRef: string;
|
||||||
protected kubeconfigManager: KubeconfigManager;
|
protected kubeconfigManager: KubeconfigManager;
|
||||||
protected eventDisposers: Function[] = [];
|
protected eventDisposers: Function[] = [];
|
||||||
protected activated = false;
|
protected activated = false;
|
||||||
@ -65,6 +67,7 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable kubeConfigPath: string;
|
@observable kubeConfigPath: string;
|
||||||
@observable apiUrl: string; // cluster server url
|
@observable apiUrl: string; // cluster server url
|
||||||
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
||||||
|
@observable enabled = false;
|
||||||
@observable online = false;
|
@observable online = false;
|
||||||
@observable accessible = false;
|
@observable accessible = false;
|
||||||
@observable ready = false;
|
@observable ready = false;
|
||||||
@ -81,6 +84,7 @@ export class Cluster implements ClusterModel {
|
|||||||
@computed get available() {
|
@computed get available() {
|
||||||
return this.accessible && !this.disconnected;
|
return this.accessible && !this.disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
get version(): string {
|
get version(): string {
|
||||||
return String(this.metadata?.version) || ""
|
return String(this.metadata?.version) || ""
|
||||||
}
|
}
|
||||||
@ -93,6 +97,10 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isManaged(): boolean {
|
||||||
|
return !!this.ownerRef
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateModel(model: ClusterModel) {
|
updateModel(model: ClusterModel) {
|
||||||
Object.assign(this, model);
|
Object.assign(this, model);
|
||||||
@ -123,13 +131,15 @@ export class Cluster implements ClusterModel {
|
|||||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||||
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventDisposers.push(
|
if (ipcMain) {
|
||||||
reaction(this.getState, this.pushState),
|
this.eventDisposers.push(
|
||||||
() => {
|
reaction(() => this.getState(), () => this.pushState()),
|
||||||
clearInterval(refreshTimer);
|
() => {
|
||||||
clearInterval(refreshMetadataTimer);
|
clearInterval(refreshTimer);
|
||||||
},
|
clearInterval(refreshMetadataTimer);
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected unbindEvents() {
|
protected unbindEvents() {
|
||||||
@ -361,6 +371,7 @@ export class Cluster implements ClusterModel {
|
|||||||
workspace: this.workspace,
|
workspace: this.workspace,
|
||||||
preferences: this.preferences,
|
preferences: this.preferences,
|
||||||
metadata: this.metadata,
|
metadata: this.metadata,
|
||||||
|
ownerRef: this.ownerRef
|
||||||
};
|
};
|
||||||
return toJS(model, {
|
return toJS(model, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
@ -368,9 +379,8 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// serializable cluster-state used for sync btw main <-> renderer
|
// serializable cluster-state used for sync btw main <-> renderer
|
||||||
getState = (): ClusterState => {
|
getState(): ClusterState {
|
||||||
const state: ClusterState = {
|
const state: ClusterState = {
|
||||||
...this.toJSON(),
|
|
||||||
initialized: this.initialized,
|
initialized: this.initialized,
|
||||||
apiUrl: this.apiUrl,
|
apiUrl: this.apiUrl,
|
||||||
online: this.online,
|
online: this.online,
|
||||||
@ -388,14 +398,18 @@ export class Cluster implements ClusterModel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pushState = (state = this.getState()): ClusterState => {
|
@action
|
||||||
|
setState(state: ClusterState) {
|
||||||
|
Object.assign(this, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pushState(state = this.getState()) {
|
||||||
logger.silly(`[CLUSTER]: push-state`, state);
|
logger.silly(`[CLUSTER]: push-state`, state);
|
||||||
broadcastIpc({
|
broadcastIpc({
|
||||||
channel: "cluster:state",
|
channel: "cluster:state",
|
||||||
frameId: this.frameId,
|
frameId: this.frameId,
|
||||||
args: [state],
|
args: [this.id, state],
|
||||||
});
|
})
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get cluster system meta, e.g. use in "logger"
|
// get cluster system meta, e.g. use in "logger"
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Clusters",
|
label: "Clusters",
|
||||||
submenu: workspaceStore.workspacesList
|
submenu: workspaceStore.enabledWorkspacesList
|
||||||
.filter(workspace => clusterStore.getByWorkspaceId(workspace.id).length > 0) // hide empty workspaces
|
.filter(workspace => clusterStore.getByWorkspaceId(workspace.id).length > 0) // hide empty workspaces
|
||||||
.map(workspace => {
|
.map(workspace => {
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
|
|
||||||
// Register additional store listeners
|
// Register additional store listeners
|
||||||
clusterStore.registerIpcListener();
|
clusterStore.registerIpcListener();
|
||||||
|
workspaceStore.registerIpcListener();
|
||||||
|
|
||||||
// init app's dependencies if any
|
// init app's dependencies if any
|
||||||
if (App.init) {
|
if (App.init) {
|
||||||
|
|||||||
@ -163,7 +163,7 @@ export class AddCluster extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
clusterStore.addCluster(...newClusters);
|
clusterStore.addClusters(...newClusters);
|
||||||
if (newClusters.length === 1) {
|
if (newClusters.length === 1) {
|
||||||
const clusterId = newClusters[0].id;
|
const clusterId = newClusters[0].id;
|
||||||
clusterStore.setActive(clusterId);
|
clusterStore.setActive(clusterId);
|
||||||
|
|||||||
@ -26,11 +26,11 @@ export class ClusterWorkspaceSetting extends React.Component<Props> {
|
|||||||
<Select
|
<Select
|
||||||
value={this.props.cluster.workspace}
|
value={this.props.cluster.workspace}
|
||||||
onChange={({value}) => this.props.cluster.workspace = value}
|
onChange={({value}) => this.props.cluster.workspace = value}
|
||||||
options={workspaceStore.workspacesList.map(w =>
|
options={workspaceStore.enabledWorkspacesList.map(w =>
|
||||||
({value: w.id, label: w.name})
|
({value: w.id, label: w.name})
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,16 +22,17 @@ export class RemoveClusterButton extends React.Component<Props> {
|
|||||||
labelOk: <Trans>Yes</Trans>,
|
labelOk: <Trans>Yes</Trans>,
|
||||||
labelCancel: <Trans>No</Trans>,
|
labelCancel: <Trans>No</Trans>,
|
||||||
ok: async () => {
|
ok: async () => {
|
||||||
await clusterStore.removeById(cluster.id);
|
await clusterStore.removeById(cluster.id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { cluster } = this.props;
|
||||||
return (
|
return (
|
||||||
<Button accent onClick={this.confirmRemoveCluster} className="button-area">
|
<Button accent onClick={this.confirmRemoveCluster} className="button-area" disabled={cluster.isManaged()}>
|
||||||
Remove Cluster
|
Remove Cluster
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class WorkspaceMenu extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, ...menuProps } = this.props;
|
const { className, ...menuProps } = this.props;
|
||||||
const { workspacesList, currentWorkspace } = workspaceStore;
|
const { enabledWorkspacesList, currentWorkspace } = workspaceStore;
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
@ -32,7 +32,7 @@ export class WorkspaceMenu extends React.Component<Props> {
|
|||||||
<Link className="workspaces-title" to={workspacesURL()}>
|
<Link className="workspaces-title" to={workspacesURL()}>
|
||||||
<Trans>Workspaces</Trans>
|
<Trans>Workspaces</Trans>
|
||||||
</Link>
|
</Link>
|
||||||
{workspacesList.map(({ id: workspaceId, name, description }) => {
|
{enabledWorkspacesList.map(({ id: workspaceId, name, description }) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={workspaceId}
|
key={workspaceId}
|
||||||
|
|||||||
@ -19,8 +19,12 @@ export class Workspaces extends React.Component {
|
|||||||
@observable editingWorkspaces = observable.map<WorkspaceId, Workspace>();
|
@observable editingWorkspaces = observable.map<WorkspaceId, Workspace>();
|
||||||
|
|
||||||
@computed get workspaces(): Workspace[] {
|
@computed get workspaces(): Workspace[] {
|
||||||
|
const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map()
|
||||||
|
workspaceStore.enabledWorkspacesList.forEach((w) => {
|
||||||
|
currentWorkspaces.set(w.id, w)
|
||||||
|
})
|
||||||
const allWorkspaces = new Map([
|
const allWorkspaces = new Map([
|
||||||
...workspaceStore.workspaces,
|
...currentWorkspaces,
|
||||||
...this.editingWorkspaces,
|
...this.editingWorkspaces,
|
||||||
]);
|
]);
|
||||||
return Array.from(allWorkspaces.values());
|
return Array.from(allWorkspaces.values());
|
||||||
@ -42,7 +46,7 @@ export class Workspaces extends React.Component {
|
|||||||
|
|
||||||
saveWorkspace = (id: WorkspaceId) => {
|
saveWorkspace = (id: WorkspaceId) => {
|
||||||
const draft = toJS(this.editingWorkspaces.get(id));
|
const draft = toJS(this.editingWorkspaces.get(id));
|
||||||
const workspace = workspaceStore.saveWorkspace(draft);
|
const workspace = workspaceStore.addWorkspace(draft);
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
this.clearEditing(id);
|
this.clearEditing(id);
|
||||||
}
|
}
|
||||||
@ -50,11 +54,11 @@ export class Workspaces extends React.Component {
|
|||||||
|
|
||||||
addWorkspace = () => {
|
addWorkspace = () => {
|
||||||
const workspaceId = uuid();
|
const workspaceId = uuid();
|
||||||
this.editingWorkspaces.set(workspaceId, {
|
this.editingWorkspaces.set(workspaceId, new Workspace({
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: ""
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
editWorkspace = (id: WorkspaceId) => {
|
editWorkspace = (id: WorkspaceId) => {
|
||||||
@ -76,7 +80,7 @@ export class Workspaces extends React.Component {
|
|||||||
},
|
},
|
||||||
ok: () => {
|
ok: () => {
|
||||||
this.clearEditing(id);
|
this.clearEditing(id);
|
||||||
workspaceStore.removeWorkspace(id);
|
workspaceStore.removeWorkspace(workspace);
|
||||||
},
|
},
|
||||||
message: (
|
message: (
|
||||||
<div className="confirm flex column gaps">
|
<div className="confirm flex column gaps">
|
||||||
@ -107,11 +111,12 @@ export class Workspaces extends React.Component {
|
|||||||
<Trans>Workspaces</Trans>
|
<Trans>Workspaces</Trans>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="items flex column gaps">
|
<div className="items flex column gaps">
|
||||||
{this.workspaces.map(({ id: workspaceId, name, description }) => {
|
{this.workspaces.map(({ id: workspaceId, name, description, ownerRef }) => {
|
||||||
const isActive = workspaceStore.currentWorkspaceId === workspaceId;
|
const isActive = workspaceStore.currentWorkspaceId === workspaceId;
|
||||||
const isDefault = workspaceStore.isDefault(workspaceId);
|
const isDefault = workspaceStore.isDefault(workspaceId);
|
||||||
const isEditing = this.editingWorkspaces.has(workspaceId);
|
const isEditing = this.editingWorkspaces.has(workspaceId);
|
||||||
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
|
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
|
||||||
|
const managed = !!ownerRef
|
||||||
const className = cssNames("workspace flex gaps", {
|
const className = cssNames("workspace flex gaps", {
|
||||||
active: isActive,
|
active: isActive,
|
||||||
editing: isEditing,
|
editing: isEditing,
|
||||||
@ -130,7 +135,7 @@ export class Workspaces extends React.Component {
|
|||||||
{isActive && <span> <Trans>(current)</Trans></span>}
|
{isActive && <span> <Trans>(current)</Trans></span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="description">{description}</span>
|
<span className="description">{description}</span>
|
||||||
{!isDefault && (
|
{!isDefault && !managed && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Icon
|
<Icon
|
||||||
material="edit"
|
material="edit"
|
||||||
|
|||||||
@ -103,6 +103,7 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
const { className } = this.props;
|
||||||
const { newContexts } = userStore;
|
const { newContexts } = userStore;
|
||||||
|
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId)
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ClustersMenu flex column", className)}>
|
<div className={cssNames("ClustersMenu flex column", className)}>
|
||||||
@ -136,11 +137,11 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</div>
|
</div>
|
||||||
<div className="add-cluster" onClick={this.addCluster}>
|
<div className="add-cluster" >
|
||||||
<Tooltip targetId="add-cluster-icon">
|
<Tooltip targetId="add-cluster-icon">
|
||||||
<Trans>Add Cluster</Trans>
|
<Trans>Add Cluster</Trans>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Icon big material="add" id="add-cluster-icon"/>
|
<Icon big material="add" id="add-cluster-icon" disabled={workspace.isManaged()} onClick={this.addCluster}/>
|
||||||
{newContexts.size > 0 && (
|
{newContexts.size > 0 && (
|
||||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user