diff --git a/src/common/cluster-store/cluster-store.ts b/src/common/cluster-store/cluster-store.ts index 1952f5ddc2..20929cf77e 100644 --- a/src/common/cluster-store/cluster-store.ts +++ b/src/common/cluster-store/cluster-store.ts @@ -4,14 +4,12 @@ */ -import { ipcMain, ipcRenderer, webFrame } from "electron"; -import { action, comparer, computed, makeObservable, observable, reaction } from "mobx"; +import { action, comparer, computed, makeObservable, observable } from "mobx"; import type { BaseStoreDependencies } from "../base-store/base-store"; import { BaseStore } from "../base-store/base-store"; import { Cluster } from "../cluster/cluster"; -import { disposer, toJS } from "../utils"; -import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types"; -import { requestInitialClusterStates } from "../../renderer/ipc"; +import { toJS } from "../utils"; +import type { ClusterModel, ClusterId } from "../cluster-types"; import type { CreateCluster } from "../cluster/create-cluster-injection-token"; import type { ReadClusterConfigSync } from "./read-cluster-config.injectable"; import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; @@ -29,8 +27,6 @@ interface Dependencies extends BaseStoreDependencies { export class ClusterStore extends BaseStore { readonly clusters = observable.map(); - protected readonly disposer = disposer(); - constructor(protected readonly dependencies: Dependencies) { super(dependencies, { configName: "lens-cluster-store", @@ -41,39 +37,6 @@ export class ClusterStore extends BaseStore { }); makeObservable(this); - this.load(); - this.pushStateToViewsAutomatically(); - } - - async loadInitialOnRenderer() { - this.dependencies.logger.info("[CLUSTER-STORE] requesting initial state sync"); - - for (const { id, state } of await requestInitialClusterStates()) { - this.getById(id)?.setState(state); - } - } - - protected pushStateToViewsAutomatically() { - if (ipcMain) { - this.disposer.push( - reaction(() => this.connectedClustersList, () => this.pushState()), - ); - } - } - - registerIpcListener() { - this.dependencies.logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`); - const ipc = ipcMain ?? ipcRenderer; - - ipc?.on("cluster:state", (event, clusterId: ClusterId, state: ClusterState) => { - this.getById(clusterId)?.setState(state); - }); - } - - pushState() { - this.clusters.forEach((c) => { - c.pushState(); - }); } @computed get clustersList(): Cluster[] { diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index 7f27025190..fe66c9fe1b 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -316,7 +316,6 @@ export class Cluster implements ClusterModel, ClusterState { const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes this.eventsDisposer.push( - reaction(() => this.getState(), state => this.pushState(state)), reaction( () => this.prometheusPreferences, prefs => this.contextHandler.setupPrometheus(prefs), @@ -349,7 +348,7 @@ export class Cluster implements ClusterModel, ClusterState { @action async activate(force = false) { if (this.activated && !force) { - return this.pushState(); + return; } this.dependencies.logger.info(`[CLUSTER]: activate`, this.getMeta()); @@ -395,7 +394,6 @@ export class Cluster implements ClusterModel, ClusterState { } this.activated = true; - this.pushState(); } /** @@ -437,7 +435,6 @@ export class Cluster implements ClusterModel, ClusterState { this.activated = false; this.allowedNamespaces = []; this.resourceAccessStatuses.clear(); - this.pushState(); this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id }); } @@ -448,7 +445,6 @@ export class Cluster implements ClusterModel, ClusterState { async refresh() { this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta()); await this.refreshConnectionStatus(); - this.pushState(); } /** @@ -614,16 +610,15 @@ export class Cluster implements ClusterModel, ClusterState { * @param state cluster state */ @action setState(state: ClusterState) { - Object.assign(this, state); - } - - /** - * @internal - * @param state cluster state - */ - pushState(state = this.getState()) { - this.dependencies.logger.silly(`[CLUSTER]: push-state`, state); - this.dependencies.broadcastMessage("cluster:state", this.id, state); + this.accessible = state.accessible; + this.allowedNamespaces = state.allowedNamespaces; + this.allowedResources = state.allowedResources; + this.apiUrl = state.apiUrl; + this.disconnected = state.disconnected; + this.isAdmin = state.isAdmin; + this.isGlobalWatchEnabled = state.isGlobalWatchEnabled; + this.online = state.online; + this.ready = state.ready; } // get cluster system meta, e.g. use in "logger" diff --git a/src/features/cluster/state-sync/common/channels.ts b/src/features/cluster/state-sync/common/channels.ts new file mode 100644 index 0000000000..7ceeb82f84 --- /dev/null +++ b/src/features/cluster/state-sync/common/channels.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ClusterId, ClusterState } from "../../../../common/cluster-types"; +import type { MessageChannel } from "../../../../common/utils/channel/message-channel-listener-injection-token"; +import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token"; + +export interface ClusterStateSync { + clusterId: ClusterId; + state: ClusterState; +} + +export const clusterStateSyncChannel: MessageChannel = { + id: "cluster-state-sync", +}; + +export const initialClusterStatesChannel: RequestChannel = { + id: "initial-cluster-state-sync", +}; diff --git a/src/features/cluster/state-sync/main/emit-update.injectable.ts b/src/features/cluster/state-sync/main/emit-update.injectable.ts new file mode 100644 index 0000000000..8cadd32864 --- /dev/null +++ b/src/features/cluster/state-sync/main/emit-update.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MessageChannelHandler } from "../../../../common/utils/channel/message-channel-listener-injection-token"; +import { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token"; +import { clusterStateSyncChannel } from "../common/channels"; + +export type EmitClusterStateUpdate = MessageChannelHandler; + +const emitClusterStateUpdateInjectable = getInjectable({ + id: "emit-cluster-state-update", + instantiate: (di): EmitClusterStateUpdate => { + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + + return (message) => sendMessageToChannel(clusterStateSyncChannel, message); + }, +}); + +export default emitClusterStateUpdateInjectable; diff --git a/src/features/cluster/state-sync/main/handle-initial.injectable.ts b/src/features/cluster/state-sync/main/handle-initial.injectable.ts new file mode 100644 index 0000000000..708f032d48 --- /dev/null +++ b/src/features/cluster/state-sync/main/handle-initial.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens"; +import { initialClusterStatesChannel } from "../common/channels"; + +const handleInitialClusterStateSyncInjectable = getRequestChannelListenerInjectable({ + channel: initialClusterStatesChannel, + handler: (di) => { + const clusterStore = di.inject(clusterStoreInjectable); + + return () => clusterStore.clustersList.map(cluster => ({ + clusterId: cluster.id, + state: cluster.getState(), + })); + }, +}); + +export default handleInitialClusterStateSyncInjectable; diff --git a/src/features/cluster/state-sync/main/setup-sync.injectable.ts b/src/features/cluster/state-sync/main/setup-sync.injectable.ts new file mode 100644 index 0000000000..bdff0e348f --- /dev/null +++ b/src/features/cluster/state-sync/main/setup-sync.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { reaction } from "mobx"; +import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import { beforeApplicationIsLoadingInjectionToken } from "../../../../main/start-main-application/runnable-tokens/before-application-is-loading-injection-token"; +import initClusterStoreInjectable from "../../store/main/init.injectable"; +import emitClusterStateUpdateInjectable from "./emit-update.injectable"; + +const setupClusterStateBroadcastingInjectable = getInjectable({ + id: "setup-cluster-state-broadcasting", + instantiate: (di) => { + const emitClusterStateUpdate = di.inject(emitClusterStateUpdateInjectable); + const clusterStore = di.inject(clusterStoreInjectable); + + return { + id: "setup-cluster-state-broadcasting", + run: () => { + reaction(() => clusterStore.connectedClustersList, () => { + for (const cluster of clusterStore.clusters.values()) { + emitClusterStateUpdate({ + clusterId: cluster.id, + state: cluster.getState(), + }); + } + }); + }, + runAfter: di.inject(initClusterStoreInjectable), + }; + }, + injectionToken: beforeApplicationIsLoadingInjectionToken, +}); + +export default setupClusterStateBroadcastingInjectable; diff --git a/src/features/cluster/state-sync/renderer/listener.injectable.ts b/src/features/cluster/state-sync/renderer/listener.injectable.ts new file mode 100644 index 0000000000..9863a391e8 --- /dev/null +++ b/src/features/cluster/state-sync/renderer/listener.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token"; +import { clusterStateSyncChannel } from "../common/channels"; + +const clusterStateListenerInjectable = getMessageChannelListenerInjectable({ + channel: clusterStateSyncChannel, + id: "main", + handler: (di) => { + const getClusterById = di.inject(getClusterByIdInjectable); + + return ({ clusterId, state }) => getClusterById(clusterId)?.setState(state); + }, +}); + +export default clusterStateListenerInjectable; diff --git a/src/features/cluster/state-sync/renderer/request-initial.injectable.ts b/src/features/cluster/state-sync/renderer/request-initial.injectable.ts new file mode 100644 index 0000000000..89f72fbcf5 --- /dev/null +++ b/src/features/cluster/state-sync/renderer/request-initial.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { RequestChannelHandler } from "../../../../main/utils/channel/channel-listeners/listener-tokens"; +import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable"; +import { initialClusterStatesChannel } from "../common/channels"; + +export type RequestInitialClusterStates = RequestChannelHandler; + +const requestInitialClusterStatesInjectable = getInjectable({ + id: "request-initial-cluster-states", + instantiate: (di): RequestInitialClusterStates => { + const requestFromChannel = di.inject(requestFromChannelInjectable); + + return () => requestFromChannel(initialClusterStatesChannel); + }, +}); + +export default requestInitialClusterStatesInjectable; diff --git a/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts new file mode 100644 index 0000000000..c66d14cbc5 --- /dev/null +++ b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import initClusterStoreInjectable from "../../store/renderer/init.injectable"; +import requestInitialClusterStatesInjectable from "./request-initial.injectable"; + +const setupClusterStateSyncInjectable = getInjectable({ + id: "setup-cluster-state-sync", + instantiate: (di) => { + const requestInitialClusterStates = di.inject(requestInitialClusterStatesInjectable); + const getClusterById = di.inject(getClusterByIdInjectable); + + return { + id: "setup-cluster-state-sync", + run: async () => { + const initalStates = await requestInitialClusterStates(); + + for (const { clusterId, state } of initalStates) { + getClusterById(clusterId)?.setState(state); + } + }, + runAfter: di.inject(initClusterStoreInjectable), + }; + }, + injectionToken: beforeFrameStartsInjectionToken, +}); + +export default setupClusterStateSyncInjectable; diff --git a/src/features/cluster/store/main/init.injectable.ts b/src/features/cluster/store/main/init.injectable.ts new file mode 100644 index 0000000000..7849ab6acd --- /dev/null +++ b/src/features/cluster/store/main/init.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import { beforeApplicationIsLoadingInjectionToken } from "../../../../main/start-main-application/runnable-tokens/before-application-is-loading-injection-token"; +import initUserStoreInjectable from "../../../../main/stores/init-user-store.injectable"; + +const initClusterStoreInjectable = getInjectable({ + id: "init-cluster-store", + instantiate: (di) => { + const clusterStore = di.inject(clusterStoreInjectable); + + return { + id: "init-cluster-store", + run: () => { + clusterStore.load(); + }, + runAfter: di.inject(initUserStoreInjectable), + }; + }, + injectionToken: beforeApplicationIsLoadingInjectionToken, +}); + +export default initClusterStoreInjectable; diff --git a/src/features/cluster/store/renderer/init.injectable.ts b/src/features/cluster/store/renderer/init.injectable.ts new file mode 100644 index 0000000000..1eab16903b --- /dev/null +++ b/src/features/cluster/store/renderer/init.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; + +const initClusterStoreInjectable = getInjectable({ + id: "init-cluster-store", + instantiate: (di) => { + const clusterStore = di.inject(clusterStoreInjectable); + + return { + id: "init-cluster-store", + run: () => { + clusterStore.load(); + }, + runAfter: di.inject(initUserStoreInjectable), + }; + }, + injectionToken: beforeFrameStartsInjectionToken, +}); + +export default initClusterStoreInjectable; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 33904d1b55..e9893fc602 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -20,7 +20,6 @@ import type { DiContainer } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable"; import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable"; import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable"; -import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable"; import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable"; import { Router } from "react-router"; @@ -47,11 +46,6 @@ export async function bootstrap(di: DiContainer) { extensionDiscovery.init(); - // ClusterStore depends on: UserStore - const clusterStore = di.inject(clusterStoreInjectable); - - await clusterStore.loadInitialOnRenderer(); - // HotbarStore depends on: ClusterStore di.inject(hotbarStoreInjectable).load(); @@ -63,9 +57,6 @@ export async function bootstrap(di: DiContainer) { extensionInstallationStateStore.bindIpcListeners(); - // Register additional store listeners - clusterStore.registerIpcListener(); - let App; let initializeApp; diff --git a/src/renderer/stores/init-user-store.injectable.ts b/src/renderer/stores/init-user-store.injectable.ts index 65ac9ec531..c03a4b2b63 100644 --- a/src/renderer/stores/init-user-store.injectable.ts +++ b/src/renderer/stores/init-user-store.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import userStoreInjectable from "../../common/user-store/user-store.injectable"; import setupAppPathsInjectable from "../app-paths/setup-app-paths.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token"; +import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable"; const initUserStoreInjectable = getInjectable({