1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

add TypedSender and TypedInvoker

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-02-19 16:56:33 -05:00
parent 4cfdb60ce0
commit 04c36b4b5b
13 changed files with 446 additions and 132 deletions

View File

@ -1,62 +0,0 @@
import { handleRequest } from "./ipc";
import { ClusterId, clusterStore } from "./cluster-store";
import { appEventBus } from "./event-bus";
import { ResourceApplier } from "../main/resource-applier";
import { ipcMain, IpcMainInvokeEvent } from "electron";
import { clusterFrameMap } from "./cluster-frames";
export const clusterActivateHandler = "cluster:activate";
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
export const clusterRefreshHandler = "cluster:refresh";
export const clusterDisconnectHandler = "cluster:disconnect";
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
if (ipcMain) {
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
return cluster.activate(force);
}
});
handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
return cluster.pushState();
}
});
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) return cluster.refresh({ refreshMetadata: true });
});
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
appEventBus.emit({name: "cluster", action: "stop"});
const cluster = clusterStore.getById(clusterId);
if (cluster) {
cluster.disconnect();
clusterFrameMap.delete(cluster.id);
}
});
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
const cluster = clusterStore.getById(clusterId);
if (cluster) {
const applier = new ResourceApplier(cluster);
applier.kubectlApplyAll(resources);
} else {
throw `${clusterId} is not a valid cluster id`;
}
});
}

View File

@ -1,11 +1,92 @@
import { IpcMainInvokeEvent } from "electron";
import { ResourceApplier } from "../../main/resource-applier";
import { clusterFrameMap } from "../cluster-frames";
import { ClusterId, clusterStore } from "../cluster-store";
import { appEventBus } from "../event-bus";
import { hasOptionalProperty, hasTypedProperty, isString, isBoolean, bindPredicate, isTypedArray } from "../utils/type-narrowing";
import { createTypedInvoker, createTypedSender } from "./type-enforced-ipc";
export type ClusterIdArgList = [clusterId: ClusterId];
function isClusterIdArgList(args: unknown[]): args is ClusterIdArgList {
return hasTypedProperty(args, 0, isString)
&& args.length === 1;
}
/**
* This channel is broadcast on whenever the cluster fails to list namespaces
* during a refresh and no `accessibleNamespaces` have been set.
*/
export const ClusterListNamespaceForbiddenChannel = "cluster:list-namespace-forbidden";
export const clusterListNamespacesForbidden = createTypedSender({
channel: "cluster:list-namespace-forbidden",
verifier: isClusterIdArgList,
});
export type ListNamespaceForbiddenArgs = [clusterId: string];
export const clusterActivate = createTypedInvoker({
channel: "cluster:activate",
handler(event, clusterId: ClusterId, force?: boolean) {
return clusterStore.getById(clusterId)?.activate(force ?? false);
},
verifier(args: unknown[]): args is [clusterId: ClusterId, force?: boolean] {
return hasTypedProperty(args, 0, isString)
&& hasOptionalProperty(args, 1, isBoolean)
&& args.length <= 2;
}
});
export function argArgsListNamespaceFordiddenArgs(args: unknown[]): args is ListNamespaceForbiddenArgs {
return args.length === 1 && typeof args[0] === "string";
}
export const clusterSetFrameId = createTypedInvoker({
channel: "cluster:set-frame-id",
handler({ frameId, processId }: IpcMainInvokeEvent, clusterId: ClusterId) {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
clusterFrameMap.set(cluster.id, { frameId, processId });
return cluster.pushState();
}
},
verifier: isClusterIdArgList,
});
export const clusterRefresh = createTypedInvoker({
channel: "cluster:refresh",
handler(event, clusterId: ClusterId) {
return clusterStore.getById(clusterId)?.refresh({ refreshMetadata: true });
},
verifier: isClusterIdArgList,
});
export const clusterDisconnect = createTypedInvoker({
channel: "cluster:disconnect",
handler(event, clusterId: ClusterId) {
appEventBus.emit({ name: "cluster", action: "stop" });
const cluster = clusterStore.getById(clusterId);
if (cluster) {
cluster.disconnect();
clusterFrameMap.delete(cluster.id);
}
},
verifier: isClusterIdArgList,
});
export const clusterKubectlApplyAll = createTypedInvoker({
channel: "cluster:kubectl-apply-all",
handler(event, clusterId: ClusterId, resources: string[]) {
appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" });
const cluster = clusterStore.getById(clusterId);
if (cluster) {
const applier = new ResourceApplier(cluster);
applier.kubectlApplyAll(resources);
} else {
throw new Error(`${clusterId} is not a valid cluster id`);
}
},
verifier(args: unknown[]): args is [clusterId: ClusterId, resources: string[]] {
return hasTypedProperty(args, 0, isString)
&& hasTypedProperty(args, 1, bindPredicate(isTypedArray, isString))
&& args.length === 2;
},
});

View File

@ -1,9 +1,12 @@
import { ipcMain, ipcRenderer } from "electron";
import { EventEmitter } from "events";
import logger from "../../main/logger";
import { broadcastMessage } from "./ipc";
export type HandlerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
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;
/**
* Adds a listener to `source` that waits for the first IPC message with the correct
@ -14,7 +17,7 @@ export type Rest<T> = T extends [any, ...infer R] ? R : [];
*/
export function onceCorrect<
EM extends EventEmitter,
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
Listener extends IpcListener<Event, any[]>,
>({
source,
channel,
@ -23,8 +26,8 @@ export function onceCorrect<
}: {
source: EM,
channel: string | symbol,
listener: L,
verifier: ListVerifier<Rest<Parameters<L>>>,
listener: Listener,
verifier: ListVerifier<Rest<Parameters<Listener>>>,
}): void {
function handler(event: HandlerEvent<EM>, ...args: unknown[]): void {
if (verifier(args)) {
@ -48,7 +51,7 @@ export function onceCorrect<
*/
export function onCorrect<
EM extends EventEmitter,
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
Listener extends IpcListener<Event, any[]>,
>({
source,
channel,
@ -57,8 +60,8 @@ export function onCorrect<
}: {
source: EM,
channel: string | symbol,
listener: L,
verifier: ListVerifier<Rest<Parameters<L>>>,
listener: Listener,
verifier: ListVerifier<Rest<Parameters<Listener>>>,
}): void {
source.on(channel, (event, ...args: unknown[]) => {
if (verifier(args)) {
@ -69,3 +72,154 @@ export function onCorrect<
}
});
}
interface IPCEncodedError {
name: string,
message: string,
extra: Record<string, any>,
}
function encodeError(e: Error): IPCEncodedError {
delete e.stack;
return {
name: e.name,
message: e.message,
extra: { ...e },
};
}
function decodeError({ extra, message, name }: IPCEncodedError): Error {
const e = new Error(message);
e.name = name;
Object.assign(e, extra);
return e;
}
export function handleCorrect<
Handler extends (event: Event, ...args: any[]) => any
>({
channel,
handler,
verifier,
}: {
channel: string,
handler: Handler,
verifier: ListVerifier<Rest<Parameters<Handler>>>,
}): void {
ipcMain.handle(channel, async (event, ...args: unknown[]) => {
try {
if (verifier(args)) {
const result = await Promise.resolve(handler(event, ...args));
return { result };
} else {
throw new TypeError("Arguments are wrong type");
}
} catch (error) {
return { error: encodeError(error) };
}
});
}
export async function invokeWithDecode<
L extends any[],
>({
channel,
args
}: {
channel: string,
args: L
}): Promise<any> {
const { error, result } = await ipcRenderer.invoke(channel, ...args);
if (error) {
throw decodeError(error);
}
return result;
}
export interface TypedInvoker<
Handler extends (event: Event, ...args: any[]) => any
> {
invoke: (...args: Rest<Parameters<Handler>>) => Promise<ReturnType<Handler>>,
}
export function createTypedInvoker<
Handler extends (event: Event, ...args: any[]) => any
>({
channel,
handler,
verifier,
}: {
channel: string,
handler: Handler,
verifier: ListVerifier<Rest<Parameters<Handler>>>,
}): TypedInvoker<Handler> {
if (ipcMain) {
handleCorrect({
channel,
handler,
verifier,
});
} else if (ipcRenderer) {
return {
invoke(...args) {
return invokeWithDecode({
channel,
args
});
}
};
}
return {
invoke() {
throw new TypeError("invoke called in main");
}
};
}
export interface TypedSender<
Args extends any[]
> {
broadcast: (...args: Args) => void,
on: (listener: IpcListener<Event, Args>) => void,
once: (listener: IpcListener<Event, Args>) => void,
}
export function createTypedSender<
Args extends any[]
>({
channel,
verifier,
}: {
channel: string,
verifier: ListVerifier<Args>,
}): TypedSender<Args> {
return {
broadcast(...args) {
broadcastMessage(channel, ...args);
},
on(listener) {
onCorrect({
source: ipcMain ?? ipcRenderer,
channel,
listener,
verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly
});
},
once(listener) {
onceCorrect({
source: ipcMain ?? ipcRenderer,
channel,
listener,
verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly
});
}
};
}

View File

@ -1,25 +1,45 @@
import { UpdateInfo } from "electron-updater";
import { UpdateFileInfo, ReleaseNoteInfo } from "builder-util-runtime";
import { bindPredicate, bindPredicateOr, hasOptionalProperty, hasTypedProperty, isNull, isObject, isString, isTypedArray, isNumber, isBoolean } from "../utils/type-narrowing";
import { createTypedSender } from "./type-enforced-ipc";
export const UpdateAvailableChannel = "update-available";
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
export const updateAvailale = createTypedSender({
channel: "update-available",
verifier: isUpdateAvailableArgs,
});
export type UpdateAvailableFromMain = [backChannel: string, updateInfo: UpdateInfo];
export type UpdateAvailableArgs = [backChannel: string, updateInfo: UpdateInfo];
export function areArgsUpdateAvailableFromMain(args: unknown[]): args is UpdateAvailableFromMain {
if (args.length !== 2) {
return false;
}
function isUpdateFileInfo(src: unknown): src is UpdateFileInfo {
return isObject(src)
&& hasTypedProperty(src, "sha512", isString)
&& hasTypedProperty(src, "url", isString)
&& hasOptionalProperty(src, "size", isNumber)
&& hasOptionalProperty(src, "blockMapSize", isNumber)
&& hasOptionalProperty(src, "isAdminRightsRequired", isBoolean);
}
if (typeof args[0] !== "string") {
return false;
}
function isReleaseNoteInfo(src: unknown): src is ReleaseNoteInfo {
return isObject(src)
&& hasTypedProperty(src, "version", isString)
&& hasTypedProperty(src, "note", bindPredicateOr(isString, isNull));
}
if (typeof args[1] !== "object" || args[1] === null) {
// TODO: improve this checking
return false;
}
function isUpdateInfo(src: unknown): src is UpdateInfo {
return isObject(src)
&& hasTypedProperty(src, "version", isString)
&& hasTypedProperty(src, "releaseDate", isString)
&& hasTypedProperty(src, "files", bindPredicate(isTypedArray, isUpdateFileInfo))
&& hasOptionalProperty(src, "releaseName", bindPredicateOr(isString, isNull))
&& hasOptionalProperty(src, "stagingPercentage", isNumber)
&& hasOptionalProperty(src, "releaseNotes", bindPredicateOr(isString, isReleaseNoteInfo, isNull));
}
return true;
export function isUpdateAvailableArgs(args: unknown[]): args is UpdateAvailableArgs {
return hasTypedProperty(args, 0, isString)
&& hasTypedProperty(args, 1, isUpdateInfo)
&& args.length === 2;
}
export type BackchannelArg = {
@ -31,15 +51,19 @@ export type BackchannelArg = {
export type UpdateAvailableToBackchannel = [updateDecision: BackchannelArg];
export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel {
if (args.length !== 1) {
function isBackChannelArg(src: unknown): src is BackchannelArg {
if (!(
isObject(src)
&& hasTypedProperty(src, "doUpdate", isBoolean)
)) {
return false;
}
if (typeof args[0] !== "object" || args[0] === null) {
// TODO: improve this checking
return false;
}
return true;
return !src.doUpdate
|| hasTypedProperty(src, "now", isBoolean);
}
export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel {
return hasTypedProperty(args, 0, isBackChannelArg)
&& args.length === 1;
}

View File

@ -0,0 +1,132 @@
/**
* Narrows `val` to include the property `key` (if true is returned)
* @param val The object to be tested
* @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful)
*/
export function hasOwnProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: unknown }) {
// this call syntax is for when `val` was created by `Object.create(null)`
return Object.prototype.hasOwnProperty.call(val, key);
}
/**
* Narrows `val` to a static type that includes fields of names in `keys`
* @param val the value that we are trying to type narrow
* @param keys the key names (must be literals for tsc to do any type meaningful)
*/
export function hasOwnProperties<S extends object, K extends PropertyKey>(val: S, ...keys: K[]): val is (S & { [key in K]: unknown }) {
return keys.every(key => hasOwnProperty(val, key));
}
/**
* Narrows `val` to include the property `key` with type `V`
* @param val the value that we are trying to type narrow
* @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful)
* @param isValid a function to check if the field is valid
*/
export function hasTypedProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]: V }) {
return hasOwnProperty(val, key) && isValid(val[key]);
}
/**
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
* @param val the value that we are trying to type narrow
* @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful)
* @param isValid a function to check if the field (when present) is valid
*/
export function hasOptionalProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]?: V }) {
if (hasOwnProperty(val, key)) {
return typeof val[key] === "undefined" || isValid(val[key]);
}
return true;
}
/**
* isRecord checks if `val` matches the signature `Record<T, V>` or `{ [label in T]: V }`
* @param val The value to be checked
* @param isKey a function for checking if the key is of the correct type
* @param isValue a function for checking if a value is of the correct type
*/
export function isRecord<T extends PropertyKey, V>(val: unknown, isKey: (key: unknown) => key is T, isValue: (value: unknown) => value is V): val is Record<T, V> {
return isObject(val) && Object.entries(val).every(([key, value]) => isKey(key) && isValue(value));
}
/**
* isTypedArray checks if `val` is an array and all of its entries are of type `T`
* @param val The value to be checked
* @param isEntry a function for checking if an entry is the correct type
*/
export function isTypedArray<T>(val: unknown, isEntry: (entry: unknown) => entry is T): val is T[] {
return Array.isArray(val) && val.every(isEntry);
}
/**
* checks if val is of type string
* @param val the value to be checked
*/
export function isString(val: unknown): val is string {
return typeof val === "string";
}
/**
* checks if val is of type boolean
* @param val the value to be checked
*/
export function isBoolean(val: unknown): val is boolean {
return typeof val === "boolean";
}
/**
* checks if val is of type number
* @param val the value to be checked
*/
export function isNumber(val: unknown): val is number {
return typeof val === "number";
}
/**
* checks if val is of type object and isn't null
* @param val the value to be checked
*/
export function isObject(val: unknown): val is object {
return typeof val === "object" && val !== null;
}
/**
* checks if val is null
* @param val the value to be checked
*/
export function isNull(val: unknown): val is null {
return val === null;
}
/**
* Creates a new predicate function (with the same predicate) from `fn`. Such
* that it can be called with just the value to be tested.
*
* 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
*/
export function bindPredicate<FnArgs extends any[], T>(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): (arg1: unknown) => arg1 is T {
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
}
type Predicate<T> = (arg: unknown) => arg is T;
export function bindPredicateOr<T1>(p1: Predicate<T1>): Predicate<T1>;
export function bindPredicateOr<T1, T2>(p1: Predicate<T1>, p2: Predicate<T2>): Predicate<T1 | T2>;
export function bindPredicateOr<T1, T2, T3>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>): Predicate<T1 | T2 | T3>;
export function bindPredicateOr<T1, T2, T3, T4>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>): Predicate<T1 | T2 | T3 | T4>;
export function bindPredicateOr<T extends any[]>(...predicates: T): Predicate<T> {
return (arg: unknown): arg is any => {
for (const predicate of predicates) {
if (predicate) {
return true;
}
}
return false;
};
}

View File

@ -6,8 +6,7 @@ import { ResourceApplier } from "../main/resource-applier";
import { Cluster } from "../main/cluster";
import logger from "../main/logger";
import { app } from "electron";
import { requestMain } from "../common/ipc";
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
import { clusterKubectlApplyAll } from "../common/ipc";
export interface ClusterFeatureStatus {
/** feature's current version, as set by the implementation */
@ -94,7 +93,7 @@ export abstract class ClusterFeature {
if (app) {
await new ResourceApplier(cluster).kubectlApplyAll(resources);
} else {
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources);
await clusterKubectlApplyAll.invoke(cluster.id, resources);
}
}

View File

@ -37,9 +37,9 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater
.on("update-available", (args: UpdateInfo) => {
.on("update-available", (updateInfo: UpdateInfo) => {
try {
const backchannel = `auto-update:${args.version}`;
const backchannel = `auto-update:${updateInfo.version}`;
ipcMain.removeAllListeners(backchannel); // only one handler should be present
@ -50,8 +50,8 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
listener: handleAutoUpdateBackChannel,
verifier: areArgsUpdateAvailableToBackchannel,
});
logger.info(`${AutoUpdateLogPrefix}: broadcasting update available`, { backchannel, version: args.version });
broadcastMessage(UpdateAvailableChannel, backchannel, args);
logger.info(`${AutoUpdateLogPrefix}: broadcasting update available`, { backchannel, version: updateInfo.version });
broadcastMessage(UpdateAvailableChannel, backchannel, updateInfo);
} catch (error) {
logger.error(`${AutoUpdateLogPrefix}: broadcasting failed`, { error });
}

View File

@ -4,7 +4,7 @@ import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store";
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars";
import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
import { broadcastMessage, clusterListNamespacesForbidden } from "../common/ipc";
import { ContextHandler } from "./context-handler";
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
import { Kubectl } from "./kubectl";
@ -685,7 +685,7 @@ export class Cluster implements ClusterModel, ClusterState {
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id });
broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id);
clusterListNamespacesForbidden.broadcast(this.id);
}
return namespaceList;

View File

@ -13,8 +13,7 @@ import { ClusterIcon } from "../cluster-icon";
import { IClusterSettingsRouteParams } from "./cluster-settings.route";
import { clusterStore } from "../../../common/cluster-store";
import { PageLayout } from "../layout/page-layout";
import { requestMain } from "../../../common/ipc";
import { clusterActivateHandler, clusterRefreshHandler } from "../../../common/cluster-ipc";
import { clusterActivate, clusterRefresh } from "../../../common/ipc";
import { navigation } from "../../navigation";
interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
@ -47,8 +46,8 @@ export class ClusterSettings extends React.Component<Props> {
refreshCluster = async () => {
if (this.cluster) {
await requestMain(clusterActivateHandler, this.cluster.id);
await requestMain(clusterRefreshHandler, this.cluster.id);
await clusterActivate.invoke(this.cluster.id);
await clusterRefresh.invoke(this.cluster.id);
}
};

View File

@ -36,9 +36,8 @@ import { webFrame } from "electron";
import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus";
import { broadcastMessage, requestMain } from "../../common/ipc";
import { broadcastMessage, clusterSetFrameId } from "../../common/ipc";
import whatInput from "what-input";
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries";
import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog";
@ -60,7 +59,7 @@ export class App extends React.Component {
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`);
await Terminal.preloadFonts();
await requestMain(clusterSetFrameIdHandler, clusterId);
await clusterSetFrameId.invoke(clusterId);
await getHostedCluster().whenReady; // cluster.activate() is done at this point
extensionLoader.loadOnClusterRenderer();
setTimeout(() => {

View File

@ -5,14 +5,13 @@ import React from "react";
import { observer } from "mobx-react";
import { ipcRenderer } from "electron";
import { computed, observable } from "mobx";
import { requestMain, subscribeToBroadcast } from "../../../common/ipc";
import { clusterActivate, subscribeToBroadcast } from "../../../common/ipc";
import { Icon } from "../icon";
import { Button } from "../button";
import { cssNames, IClassName } from "../../utils";
import { Cluster } from "../../../main/cluster";
import { ClusterId, clusterStore } from "../../../common/cluster-store";
import { CubeSpinner } from "../spinner";
import { clusterActivateHandler } from "../../../common/cluster-ipc";
interface Props {
className?: IClassName;
@ -50,7 +49,7 @@ export class ClusterStatus extends React.Component<Props> {
}
activateCluster = async (force = false) => {
await requestMain(clusterActivateHandler, this.props.clusterId, force);
await clusterActivate.invoke(this.props.clusterId, force);
};
reconnect = async () => {

View File

@ -2,7 +2,7 @@ import "./clusters-menu.scss";
import React from "react";
import { remote } from "electron";
import { requestMain } from "../../../common/ipc";
import { clusterDisconnect } from "../../../common/ipc";
import type { Cluster } from "../../../main/cluster";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { observer } from "mobx-react";
@ -21,7 +21,6 @@ import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog";
import { clusterViewURL } from "./cluster-view.route";
import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
import { clusterDisconnectHandler } from "../../../common/cluster-ipc";
import { commandRegistry } from "../../../extensions/registries/command-registry";
import { CommandOverlay } from "../command-palette/command-container";
import { computed } from "mobx";
@ -64,7 +63,7 @@ export class ClustersMenu extends React.Component<Props> {
navigate(landingURL());
clusterStore.setActive(null);
}
await requestMain(clusterDisconnectHandler, cluster.id);
await clusterDisconnect.invoke(cluster.id);
}
}));
}

View File

@ -1,6 +1,6 @@
import React from "react";
import { ipcRenderer, IpcRendererEvent } from "electron";
import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, UpdateAvailableFromMain, BackchannelArg, ClusterListNamespaceForbiddenChannel, argArgsListNamespaceFordiddenArgs, ListNamespaceForbiddenArgs } from "../../common/ipc";
import { UpdateAvailableArgs, BackchannelArg, ClusterIdArgList, updateAvailale, clusterListNamespacesForbidden } from "../../common/ipc";
import { Notifications, notificationsStore } from "../components/notifications";
import { Button } from "../components/button";
import { isMac } from "../../common/vars";
@ -32,7 +32,7 @@ function RenderYesButtons(props: { backchannel: string, notificationId: string }
);
}
function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableFromMain): void {
function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableArgs): void {
const notificationId = uuid.v4();
Notifications.info(
@ -58,7 +58,7 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update
const listNamespacesForbiddenHandlerDisplayedAt = new Map<string, number>();
const intervalBetweenNotifications = 1000 * 60; // 60s
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void {
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ClusterIdArgList): void {
const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId);
const wasDisplayed = Boolean(lastDisplayedAt);
const now = Date.now();
@ -92,16 +92,6 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]:
}
export function registerIpcHandlers() {
onCorrect({
source: ipcRenderer,
channel: UpdateAvailableChannel,
listener: UpdateAvailableHandler,
verifier: areArgsUpdateAvailableFromMain,
});
onCorrect({
source: ipcRenderer,
channel: ClusterListNamespaceForbiddenChannel,
listener: ListNamespacesForbiddenHandler,
verifier: argArgsListNamespaceFordiddenArgs,
});
updateAvailale.on(UpdateAvailableHandler);
clusterListNamespacesForbidden.on(ListNamespacesForbiddenHandler);
}