diff --git a/package.json b/package.json index d453512caa..bcc3eb4387 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "uuid": "^8.1.0", "win-ca": "^3.2.0", "winston": "^3.2.1", + "winston-transport-browserconsole": "^1.0.5", "ws": "^7.3.0" }, "devDependencies": { diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 51610a5d03..f5e6e7d02e 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -2,24 +2,24 @@ import path from "path" import Config from "conf" import { Options as ConfOptions } from "conf/dist/source/types" import produce from "immer"; -import { app, remote } from "electron" +import { app, ipcMain, ipcRenderer, remote } from "electron" import { action, observable, reaction, toJS, when } from "mobx"; import Singleton from "./utils/singleton"; import isEqual from "lodash/isEqual" import { getAppVersion } from "./utils/app-version"; import logger from "../main/logger"; +import { broadcastMessage } from "./ipc-helpers"; -export interface BaseStoreParams { - configName: string; +export interface BaseStoreParams extends ConfOptions { autoLoad?: boolean; syncEnabled?: boolean; - confOptions?: ConfOptions; } export class BaseStore extends Singleton { protected storeConfig: Config; protected syncDisposers: Function[] = []; + whenLoaded = when(() => this.isLoaded); @observable isLoaded = false; @observable protected data: T; @@ -30,8 +30,6 @@ export class BaseStore extends Singleton { syncEnabled: true, ...params, } - this.onConfigChange = this.onConfigChange.bind(this) - this.onModelChange = this.onModelChange.bind(this) this.init(); } @@ -39,10 +37,8 @@ export class BaseStore extends Singleton { return path.basename(this.storeConfig.path); } - get storeModel(): T { - const storeModel = { ...(this.storeConfig.store || {}) }; - Reflect.deleteProperty(storeModel, "__internal__"); // fixme: avoid "external-internals" - return storeModel as T; + get syncEvent() { + return `[STORE]:[SYNC]:${this.name}` } protected async init() { @@ -50,39 +46,46 @@ export class BaseStore extends Singleton { await this.load(); } if (this.params.syncEnabled) { - await when(() => this.isLoaded); + await this.whenLoaded; this.enableSync(); } } async load() { - const { configName, syncEnabled, confOptions = {} } = this.params; - - // use "await" to make pseudo-async "load" for more future-proof use-cases - this.storeConfig = await new Config({ + const { autoLoad, syncEnabled, ...confOptions } = this.params; + this.storeConfig = new Config({ + ...confOptions, projectName: "lens", projectVersion: getAppVersion(), - configName: configName, - watch: syncEnabled, // watch for changes in multi-process app (e.g. main/renderer) get cwd() { return (app || remote.app).getPath("userData"); }, - ...confOptions, }); - const jsonModel = this.storeConfig.store; - logger.info(`💿 Store loaded from ${this.storeConfig.path}`); - this.fromStore(jsonModel); + const storeModel = Object.assign({}, this.storeConfig.store); + Reflect.deleteProperty(storeModel, "__internal__"); // fixme: avoid "external-internals" + logger.info(`[STORE]: loaded ${this.storeConfig.path}`); + this.fromStore(storeModel); this.isLoaded = true; } enableSync() { - const onConfigChangeStop = this.storeConfig.onDidAnyChange(this.onConfigChange); - const onModelChangeStop = reaction(() => this.toJSON(), this.onModelChange); - this.syncDisposers.push( - onConfigChangeStop, // watch for changes from file-system updates - onModelChangeStop, // refresh config file from runtime + reaction(() => this.toJSON(), this.onModelChange.bind(this)), ); + if (ipcMain) { + ipcMain.on(this.syncEvent, (event, model: T) => { + logger.info(`[STORE]: ${this.name} sync update from renderer`, model); + this.onSync(model); + }); + this.syncDisposers.push(() => ipcMain.removeAllListeners(this.syncEvent)); + } + if (ipcRenderer) { + ipcRenderer.on(this.syncEvent, (event, model: T) => { + logger.info(`[STORE]: ${this.name} sync update from main`, model); + this.onSync(model); + }); + this.syncDisposers.push(() => ipcRenderer.removeAllListeners(this.syncEvent)); + } } disableSync() { @@ -90,24 +93,26 @@ export class BaseStore extends Singleton { this.syncDisposers.length = 0; } - protected onConfigChange(data: T, oldValue: Partial) { - if (!isEqual(this.toJSON(), data)) { - logger.info(`💿 Store received update from ${this.name}`, { data, oldValue }); - this.fromStore(data); + protected onSync(model: T) { + if (!isEqual(this.toJSON(), model)) { + logger.info(`[STORE]: ${this.name} received update from main`, model); + this.fromStore(model); } } - protected onModelChange(model: T) { - if (!isEqual(this.storeModel, model)) { - logger.info(`💿 Store ${this.name} is saving updates from app runtime`, { - data: model, - oldValue: this.storeModel - }); + protected async onModelChange(model: T) { + // update views and save to config file + if (ipcMain) { + broadcastMessage(this.syncEvent, model); // fixme: https://github.com/sindresorhus/conf/issues/114 Object.entries(model).forEach(([key, value]) => { this.storeConfig.set(key, value); }); } + // sends "update-request" event to main-process + if (ipcRenderer) { + ipcRenderer.send(this.syncEvent, model); + } } @action diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index be58448feb..5773f8cf43 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -42,10 +42,8 @@ export class ClusterStore extends BaseStore { private constructor() { super({ configName: "lens-cluster-store", - confOptions: { - migrations: migrations, - accessPropertiesByDotNotation: false, // To make dots safe in cluster context names - } + accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + migrations: migrations, }); } diff --git a/src/common/ipc-helpers.ts b/src/common/ipc-helpers.ts index b63ab9dc89..e5a664555e 100644 --- a/src/common/ipc-helpers.ts +++ b/src/common/ipc-helpers.ts @@ -11,26 +11,26 @@ export interface IpcMessageOptions { timeout?: number; } -export interface IpcMessageHandler { - (...args: any[]): any; +export interface IpcMessageHandler { + (...args: T): any; } -export function sendMessage(channel: IpcChannel, ...args: any[]) { - const webContent = webContents.getFocusedWebContents(); - if (webContent) { +export function broadcastMessage(channel: IpcChannel, ...args: any[]) { + webContents.getAllWebContents().forEach(webContent => { webContent.send(channel, ...args); - } + }) } -export async function invokeMessage(channel: IpcChannel, ...args: any[]) { - logger.debug(`[IPC]: invoke channel "${channel}"`, { args }); +export async function invokeMessage(channel: IpcChannel, ...args: any[]): Promise { + logger.info(`[IPC]: invoke channel "${channel}"`, { args }); return ipcRenderer.invoke(channel, ...args); } -export function handleMessage(channel: IpcChannel, handler: IpcMessageHandler, options: IpcMessageOptions = {}) { +// todo: make isomorphic api +export function handleMessage(channel: IpcChannel, handler: IpcMessageHandler, options: IpcMessageOptions = {}) { const { timeout = 0 } = options; - ipcMain.handle(channel, async (event, ...args: any[]) => { - logger.debug(`[IPC]: handle "${channel}"`, { event, args }); + ipcMain.handle(channel, async (event, ...args: T) => { + logger.info(`[IPC]: handle "${channel}"`, { event, args }); return new Promise(async (resolve, reject) => { let timerId; if (timeout) { diff --git a/src/common/ipc-messages.ts b/src/common/ipc-messages.ts index 2012abc1a2..9f818d011f 100644 --- a/src/common/ipc-messages.ts +++ b/src/common/ipc-messages.ts @@ -1,11 +1,12 @@ // IPC messages (all channels) +// All values must be unique export enum ClusterIpcMessage { - CLUSTER_ADD = "cluster-add", - CLUSTER_STOP = "cluster-stop", - CLUSTER_REMOVE = "cluster-remove", - CLUSTER_REMOVE_WORKSPACE = "cluster-remove-all-from-workspace", - CLUSTER_REFRESH = "cluster-refresh", + ADD = "cluster-add", + STOP = "cluster-stop", + REFRESH = "cluster-refresh", + REMOVE = "cluster-remove", + REMOVE_WORKSPACE = "cluster-remove-all-from-workspace", FEATURE_INSTALL = "cluster-feature-install", FEATURE_UPGRADE = "cluster-feature-upgrade", FEATURE_REMOVE = "cluster-feature-remove", diff --git a/src/common/user-store.ts b/src/common/user-store.ts index bc06e3c10a..396b41c27e 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -23,9 +23,7 @@ export class UserStore extends BaseStore { private constructor() { super({ configName: "lens-user-store", - confOptions: { - migrations: migrations - } + migrations: migrations, }); // track telemetry availability diff --git a/src/common/utils/randomFileName.ts b/src/common/utils/getRandId.ts similarity index 54% rename from src/common/utils/randomFileName.ts rename to src/common/utils/getRandId.ts index 0a32825f59..afe075085d 100644 --- a/src/common/utils/randomFileName.ts +++ b/src/common/utils/getRandId.ts @@ -1,6 +1,6 @@ -// Create random filename +// Create random system name -export function randomFileName({ prefix = "", suffix = "", sep = "__" } = {}) { +export function getRandId({ prefix = "", suffix = "", sep = "_" } = {}) { const randId = () => Math.random().toString(16).substr(2); return [prefix, randId(), suffix].filter(s => s).join(sep); } diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index f3c23800e8..580a8f15c2 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -3,5 +3,5 @@ export * from "./base64" export * from "./camelCase" export * from "./splitArray" -export * from "./randomFileName" +export * from "./getRandId" export * from "./cloneJson" diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 4f12b3d4d0..71010b288e 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -160,11 +160,11 @@ export class ClusterManager { static ipcListen(clusterManager: ClusterManager) { const handlers = { - [ClusterIpcMessage.CLUSTER_ADD]: clusterManager.addCluster, - [ClusterIpcMessage.CLUSTER_STOP]: clusterManager.stopCluster, - [ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster, - [ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace, - [ClusterIpcMessage.CLUSTER_REFRESH]: clusterManager.refreshCluster, + [ClusterIpcMessage.ADD]: clusterManager.addCluster, + [ClusterIpcMessage.STOP]: clusterManager.stopCluster, + [ClusterIpcMessage.REMOVE]: clusterManager.removeCluster, + [ClusterIpcMessage.REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace, + [ClusterIpcMessage.REFRESH]: clusterManager.refreshCluster, [ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature, [ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature, [ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature, diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 9b9c214d70..01dee90f3c 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -62,14 +62,14 @@ export class Cluster implements ClusterModel { this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`; this.webContentUrl = `http://${this.id}.localhost:${port}`; this.initialized = true; - logger.info(`✅ ️Cluster init success`, { + logger.info(`[CLUSTER]: init success`, { id: this.id, serverUrl: this.apiUrl, webContentUrl: this.webContentUrl, kubeProxyUrl: this.kubeProxyUrl, }); } catch (err) { - logger.error(`💣 Cluster init failed: ${err}`, { + logger.error(`[CLUSTER]: init failed: ${err}`, { id: this.id, error: err, }); diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index a12113bbaa..b5422ed200 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -1,6 +1,6 @@ import { ChildProcess, spawn } from "child_process" import { waitUntilUsed } from "tcp-port-used"; -import { sendMessage } from "../common/ipc-helpers"; +import { broadcastMessage } from "../common/ipc-helpers"; import type { Cluster } from "./cluster" import { bundledKubectl, Kubectl } from "./kubectl" import logger from "./logger" @@ -84,7 +84,7 @@ export class KubeAuthProxy { const channel = `kube-auth:${this.cluster.id}` const message = { data, stream }; logger.debug(channel, message); - sendMessage(channel, message); + broadcastMessage(channel, message); } public exit() { diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 099c0090f1..508733a76d 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -42,13 +42,8 @@ export class WindowManager { }); }), // auto-show active cluster view - reaction(() => clusterStore.activeCluster, activeCluster => { - if (activeCluster) { - this.activateView(activeCluster.id); - } - }, { + reaction(() => clusterStore.activeClusterId, clusterId => this.activateView(clusterId), { fireImmediately: true, - delay: 250, }) ) } @@ -71,10 +66,14 @@ export class WindowManager { if (!cluster) return; try { const activeView = this.activeView; - const isFresh = !this.getView(clusterId); + const isLoadedBefore = !!this.getView(clusterId); const view = this.initView(clusterId); + logger.info(`Activating cluster(${cluster.id}) view`, { + contextName: cluster.contextName, + isLoadedBefore: isLoadedBefore, + }); if (view !== activeView) { - if (isFresh) { + if (!isLoadedBefore) { await cluster.whenReady; await view.loadURL(cluster.webContentUrl); } diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index f10c6b39ee..befb84d2cf 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -24,7 +24,7 @@ interface Props { export class ClustersMenu extends React.Component { showCluster = (cluster: Cluster) => { clusterStore.activeClusterId = cluster.id; - console.log('load lens for cluster:', cluster.id); + console.log('load lens for cluster:', cluster.toJSON()); } addCluster = () => { @@ -42,7 +42,7 @@ export class ClustersMenu extends React.Component { if (cluster.initialized) { menu.append(new MenuItem({ label: _i18n._(t`Disconnect`), - click: () => console.log(`disconnect cluster and navigate to workspaces`, cluster) + click: () => console.log(`disconnect cluster and navigate to workspaces`, cluster.contextName) })) } diff --git a/yarn.lock b/yarn.lock index 5d94051a79..479edcfa20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11785,6 +11785,14 @@ win-ca@^3.2.0: node-forge "^0.8.2" split "^1.0.1" +winston-transport-browserconsole@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/winston-transport-browserconsole/-/winston-transport-browserconsole-1.0.5.tgz#8ef1bc32da5fb0a66604f2b8b6f127ed725108c9" + integrity sha512-BFZDvknATAmsqaRY3WatB2S0eEb0ziwqBYIv+H0Fnu9oe7GnPUVQzEJC1frmLdNsfEkfnyR1PCNDBAFtZE3SuQ== + dependencies: + winston "^3.2.1" + winston-transport "^4.3.0" + winston-transport@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66"