mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
more work
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
3cf0538905
commit
170e19e7cd
@ -11,7 +11,7 @@ import { appEventBus } from "./event-bus";
|
||||
import { dumpConfigYaml } from "./kube-helpers";
|
||||
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||
import { createTypedInvoker, isEmptyArgs, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||
import _ from "lodash";
|
||||
import move from "array-move";
|
||||
import type { WorkspaceId } from "./workspace-store";
|
||||
@ -90,6 +90,24 @@ export interface ClusterPrometheusPreferences {
|
||||
};
|
||||
}
|
||||
|
||||
interface ClusterStateSync {
|
||||
id: string;
|
||||
state: ClusterState;
|
||||
}
|
||||
|
||||
function ClusterStoreStateHandler(): ClusterStateSync[] {
|
||||
return clusterStore.clustersList.map(cluster => ({
|
||||
state: cluster.getState(),
|
||||
id: cluster.id,
|
||||
}));
|
||||
}
|
||||
|
||||
const clusterStoreStateRequest = createTypedInvoker({
|
||||
channel: "cluster:states",
|
||||
handler: ClusterStoreStateHandler,
|
||||
verifier: isEmptyArgs,
|
||||
});
|
||||
|
||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
static getCustomKubeConfigPath(clusterId: ClusterId): string {
|
||||
return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs", clusterId);
|
||||
@ -108,8 +126,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
private static stateRequestChannel = "cluster:states";
|
||||
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
@ -125,35 +141,13 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
async load() {
|
||||
await super.load();
|
||||
type clusterStateSync = {
|
||||
id: string;
|
||||
state: ClusterState;
|
||||
};
|
||||
|
||||
if (ipcRenderer) {
|
||||
logger.info("[CLUSTER-STORE] requesting initial state sync");
|
||||
const clusterStates: clusterStateSync[] = await requestMain(ClusterStore.stateRequestChannel);
|
||||
|
||||
clusterStates.forEach((clusterState) => {
|
||||
const cluster = this.getById(clusterState.id);
|
||||
|
||||
if (cluster) {
|
||||
cluster.setState(clusterState.state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
handleRequest(ClusterStore.stateRequestChannel, (): clusterStateSync[] => {
|
||||
const states: clusterStateSync[] = [];
|
||||
|
||||
this.clustersList.forEach((cluster) => {
|
||||
states.push({
|
||||
state: cluster.getState(),
|
||||
id: cluster.id
|
||||
});
|
||||
});
|
||||
|
||||
return states;
|
||||
});
|
||||
for (const { id, state } of await clusterStoreStateRequest.invoke()) {
|
||||
this.getById(id)?.setState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,13 @@ import { ipcMain, ipcRenderer, webContents, remote } from "electron";
|
||||
import { toJS } from "mobx";
|
||||
import logger from "../../main/logger";
|
||||
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
|
||||
import { createTypedInvoker, isEmptyArgs } from "./type-enforced-ipc";
|
||||
|
||||
const subFramesChannel = "ipc:get-sub-frames";
|
||||
const subFrames = createTypedInvoker({
|
||||
channel: "ipc:get-sub-frames",
|
||||
handler: getSubFrames,
|
||||
verifier: isEmptyArgs,
|
||||
});
|
||||
|
||||
export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||
ipcMain.handle(channel, listener);
|
||||
@ -39,11 +44,11 @@ export async function broadcastMessage(channel: string, ...args: any[]) {
|
||||
view.send(channel, ...args);
|
||||
|
||||
try {
|
||||
const subFrames: ClusterFrameInfo[] = ipcRenderer
|
||||
? await requestMain(subFramesChannel)
|
||||
const childFrames: ClusterFrameInfo[] = ipcRenderer
|
||||
? await subFrames.invoke()
|
||||
: getSubFrames();
|
||||
|
||||
for (const frameInfo of subFrames) {
|
||||
for (const frameInfo of childFrames) {
|
||||
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -77,9 +82,3 @@ export function unsubscribeAllFromBroadcast(channel: string) {
|
||||
ipcMain.removeAllListeners(channel);
|
||||
}
|
||||
}
|
||||
|
||||
export function bindBroadcastHandlers() {
|
||||
handleRequest(subFramesChannel, () => {
|
||||
return getSubFrames();
|
||||
});
|
||||
}
|
||||
|
||||
@ -8,6 +8,10 @@ export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
||||
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
||||
export type IpcListener<E extends Event, Args extends any[]> = (e: E, ...args: Args) => void;
|
||||
|
||||
export function isEmptyArgs(args: unknown[]): args is [] {
|
||||
return args.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to `source` that waits for the first IPC message with the correct
|
||||
* argument data is sent.
|
||||
@ -210,7 +214,7 @@ export function createTypedSender<
|
||||
source: ipcMain ?? ipcRenderer,
|
||||
channel,
|
||||
listener,
|
||||
verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly
|
||||
verifier: verifier as ListVerifier<Rest<[e: Event, ...args: Args]>>,
|
||||
});
|
||||
},
|
||||
once(listener) {
|
||||
@ -218,7 +222,7 @@ export function createTypedSender<
|
||||
source: ipcMain ?? ipcRenderer,
|
||||
channel,
|
||||
listener,
|
||||
verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly
|
||||
verifier: verifier as ListVerifier<Rest<[e: Event, ...args: Args]>>,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -107,8 +107,14 @@ export function isNull(val: unknown): val is null {
|
||||
* This is useful for when using `hasOptionalProperty` and `hasTypedProperty`
|
||||
* @param fn A typescript user predicate function to be bound
|
||||
* @param boundArgs the set of arguments to be passed to `fn` in the new function
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* bindTypeGuard(isTypedArray, isString); // Predicate<string[]>
|
||||
* bindTypeGuard(isRecord, isString, isBoolean); // Predicate<Record<string, boolean>>
|
||||
* ```
|
||||
*/
|
||||
export function bindTypeGuard<FnArgs extends any[], T>(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): (arg1: unknown) => arg1 is T {
|
||||
export function bindTypeGuard<FnArgs extends any[], T>(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): Predicate<T> {
|
||||
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
|
||||
}
|
||||
|
||||
@ -122,6 +128,11 @@ type OrReturnPredicateType<T extends Predicate<any>[]> = ReturnPredicateType<Fir
|
||||
* Create a new type-guard for the union of the types that each of the
|
||||
* predicates are type-guarding for
|
||||
* @param predicates a list of predicates that should be executed in order
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* createUnionGuard(isString, isBoolean); // Predicate<string | boolean>
|
||||
* ```
|
||||
*/
|
||||
export function createUnionGuard<Predicates extends Predicate<any>[]>(...predicates: Predicates): Predicate<OrReturnPredicateType<Predicates>> {
|
||||
return (arg: unknown): arg is OrReturnPredicateType<Predicates> => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { action, computed, observable, toJS, reaction } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { clusterStore } from "./cluster-store";
|
||||
import { appEventBus } from "./event-bus";
|
||||
import { broadcastMessage, handleRequest, requestMain } from "../common/ipc";
|
||||
import { broadcastMessage, createTypedInvoker, isEmptyArgs } from "../common/ipc";
|
||||
import logger from "../main/logger";
|
||||
import type { ClusterId } from "./cluster-store";
|
||||
|
||||
@ -141,9 +141,26 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
}
|
||||
}
|
||||
|
||||
interface WorkspaceStateSync {
|
||||
id: string;
|
||||
state: WorkspaceState;
|
||||
}
|
||||
|
||||
function WorkspaceStoreStateHandler(): WorkspaceStateSync[] {
|
||||
return clusterStore.clustersList.map(cluster => ({
|
||||
state: cluster.getState(),
|
||||
id: cluster.id,
|
||||
}));
|
||||
}
|
||||
|
||||
const workspaceStoreStateRequest = createTypedInvoker({
|
||||
channel: "workspace:states",
|
||||
handler: WorkspaceStoreStateHandler,
|
||||
verifier: isEmptyArgs,
|
||||
});
|
||||
|
||||
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
static readonly defaultId: WorkspaceId = "default";
|
||||
private static stateRequestChannel = "workspace:states";
|
||||
|
||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||
@observable workspaces = observable.map<WorkspaceId, Workspace>();
|
||||
@ -161,35 +178,13 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
|
||||
async load() {
|
||||
await super.load();
|
||||
type workspaceStateSync = {
|
||||
id: string;
|
||||
state: WorkspaceState;
|
||||
};
|
||||
|
||||
if (ipcRenderer) {
|
||||
logger.info("[WORKSPACE-STORE] requesting initial state sync");
|
||||
const workspaceStates: workspaceStateSync[] = await requestMain(WorkspaceStore.stateRequestChannel);
|
||||
|
||||
workspaceStates.forEach((workspaceState) => {
|
||||
const workspace = this.getById(workspaceState.id);
|
||||
|
||||
if (workspace) {
|
||||
workspace.setState(workspaceState.state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
handleRequest(WorkspaceStore.stateRequestChannel, (): workspaceStateSync[] => {
|
||||
const states: workspaceStateSync[] = [];
|
||||
|
||||
this.workspacesList.forEach((workspace) => {
|
||||
states.push({
|
||||
state: workspace.getState(),
|
||||
id: workspace.id
|
||||
});
|
||||
});
|
||||
|
||||
return states;
|
||||
});
|
||||
for (const { id, state } of await workspaceStoreStateRequest.invoke()) {
|
||||
this.getById(id)?.setState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,9 @@ import fs from "fs-extra";
|
||||
import { observable, reaction, toJS, when } from "mobx";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
||||
import { createTypedInvoker, createTypedSender, isEmptyArgs } from "../common/ipc";
|
||||
import { getBundledExtensions } from "../common/utils/app-version";
|
||||
import { hasTypedProperty, isBoolean } from "../common/utils/type-narrowing";
|
||||
import logger from "../main/logger";
|
||||
import { extensionInstaller, PackageJson } from "./extension-installer";
|
||||
import { extensionsStore } from "./extensions-store";
|
||||
@ -31,15 +32,35 @@ const logModule = "[EXTENSION-DISCOVERY]";
|
||||
|
||||
export const manifestFilename = "package.json";
|
||||
|
||||
interface ExtensionDiscoveryChannelMessage {
|
||||
isLoaded: boolean;
|
||||
type DiscoveryLoadingState = [isLoaded: boolean];
|
||||
|
||||
function isExtensionDiscoveryChannelMessage(args: unknown[]): args is DiscoveryLoadingState {
|
||||
return hasTypedProperty(args, 0, isBoolean)
|
||||
&& args.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the lstat is for a directory-like file (e.g. isDirectory or symbolic link)
|
||||
* @param lstat the stats to compare
|
||||
*/
|
||||
const isDirectoryLike = (lstat: fs.Stats) => lstat.isDirectory() || lstat.isSymbolicLink();
|
||||
function isDirectoryLike(lstat: fs.Stats): boolean {
|
||||
return lstat.isDirectory() || lstat.isSymbolicLink();
|
||||
}
|
||||
|
||||
const extensionDiscoveryState = createTypedSender({
|
||||
channel: "extension-discovery:state",
|
||||
verifier: isExtensionDiscoveryChannelMessage,
|
||||
});
|
||||
|
||||
function ExtensionDiscoveryInitState(): boolean {
|
||||
return extensionDiscovery.isLoaded;
|
||||
}
|
||||
|
||||
const extensionDiscoveryInitState = createTypedInvoker({
|
||||
channel: "extension-discovery:init-state",
|
||||
handler: ExtensionDiscoveryInitState,
|
||||
verifier: isEmptyArgs,
|
||||
});
|
||||
|
||||
/**
|
||||
* Discovers installed bundled and local extensions from the filesystem.
|
||||
@ -61,9 +82,6 @@ export class ExtensionDiscovery {
|
||||
@observable isLoaded = false;
|
||||
whenLoaded = when(() => this.isLoaded);
|
||||
|
||||
// IPC channel to broadcast changes to extension-discovery from main
|
||||
protected static readonly extensionDiscoveryChannel = "extension-discovery:main";
|
||||
|
||||
public events: EventEmitter;
|
||||
|
||||
constructor() {
|
||||
@ -95,31 +113,18 @@ export class ExtensionDiscovery {
|
||||
*/
|
||||
async init() {
|
||||
if (ipcRenderer) {
|
||||
await this.initRenderer();
|
||||
extensionDiscoveryState.on((event, isLoaded) => {
|
||||
this.isLoaded = isLoaded;
|
||||
});
|
||||
|
||||
this.isLoaded = await extensionDiscoveryInitState.invoke();
|
||||
} else {
|
||||
await this.initMain();
|
||||
reaction(() => this.toJSON(), loadingState => {
|
||||
extensionDiscoveryState.broadcast(...loadingState);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async initRenderer() {
|
||||
const onMessage = ({ isLoaded }: ExtensionDiscoveryChannelMessage) => {
|
||||
this.isLoaded = isLoaded;
|
||||
};
|
||||
|
||||
requestMain(ExtensionDiscovery.extensionDiscoveryChannel).then(onMessage);
|
||||
subscribeToBroadcast(ExtensionDiscovery.extensionDiscoveryChannel, (_event, message: ExtensionDiscoveryChannelMessage) => {
|
||||
onMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
async initMain() {
|
||||
handleRequest(ExtensionDiscovery.extensionDiscoveryChannel, () => this.toJSON());
|
||||
|
||||
reaction(() => this.toJSON(), () => {
|
||||
this.broadcast();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches for added/removed local extensions.
|
||||
* Dependencies are installed automatically after an extension folder is copied.
|
||||
@ -450,17 +455,13 @@ export class ExtensionDiscovery {
|
||||
return this.getByManifest(manifestPath, { isBundled });
|
||||
}
|
||||
|
||||
toJSON(): ExtensionDiscoveryChannelMessage {
|
||||
return toJS({
|
||||
isLoaded: this.isLoaded
|
||||
}, {
|
||||
toJSON(): DiscoveryLoadingState {
|
||||
return toJS([
|
||||
this.isLoaded
|
||||
], {
|
||||
recurseEverything: true
|
||||
});
|
||||
}
|
||||
|
||||
broadcast() {
|
||||
broadcastMessage(ExtensionDiscovery.extensionDiscoveryChannel, this.toJSON());
|
||||
}
|
||||
}
|
||||
|
||||
export const extensionDiscovery = new ExtensionDiscovery();
|
||||
|
||||
@ -26,8 +26,8 @@ import type { LensExtensionId } from "../extensions/lens-extension";
|
||||
import { installDeveloperTools } from "./developer-tools";
|
||||
import { filesystemProvisionerStore } from "./extension-filesystem";
|
||||
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
||||
import { bindBroadcastHandlers } from "../common/ipc";
|
||||
import { startUpdateChecking } from "./app-updater";
|
||||
import "../common/ipc"; // make sure that the handlers are registered
|
||||
|
||||
const workingDir = path.join(app.getPath("appData"), appName);
|
||||
let proxyPort: number;
|
||||
@ -66,8 +66,6 @@ app.on("ready", async () => {
|
||||
logger.info("🐚 Syncing shell environment");
|
||||
await shellSync();
|
||||
|
||||
bindBroadcastHandlers();
|
||||
|
||||
powerMonitor.on("shutdown", () => {
|
||||
app.exit();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user