Restructure to monorepo (#6907)

* wip: restructure to monorepo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor create-release-pr to a package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* github workflow fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix typo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* add webpack-env types to core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix github workflows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor/fix integration tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* lint fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* yarn run dev

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* eslint settings for vscode

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* move templates to right package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* open-lens build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* integration test fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix nx task dependencies

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* use bash shell for unit tests in test workflow

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix test:unit for windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix build:app on windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove ELECTRON_BUILDER_EXTRA_ARGS

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* sync src/ from master

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove Makefile from core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2023-01-24 10:46:26 -08:00
1 parent 0066ca4ea2
commit 2657df2293
3321 files changed
+28699 -11447

No files matched your search

@@ -0,0 +1,20 @@
/**
* 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 loggerInjectable from "../../../common/logger.injectable";
import { ClusterFrameHandler } from "./cluster-frame-handler";
import emitClusterVisibilityInjectable from "./emit-cluster-visibility.injectable";
const clusterFrameHandlerInjectable = getInjectable({
id: "cluster-frame-handler",
instantiate: (di) => new ClusterFrameHandler({
emitClusterVisibility: di.inject(emitClusterVisibilityInjectable),
getClusterById: di.inject(getClusterByIdInjectable),
logger: di.inject(loggerInjectable),
}),
});
export default clusterFrameHandlerInjectable;
@@ -0,0 +1,148 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, makeObservable, observable, when } from "mobx";
import type { ClusterId } from "../../../common/cluster-types";
import type { Disposer } from "../../utils";
import { getClusterFrameUrl, onceDefined } from "../../utils";
import assert from "assert";
import type { Logger } from "../../../common/logger";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import type { EmitClusterVisibility } from "./emit-cluster-visibility.injectable";
export interface LensView {
isLoaded: boolean;
frame: HTMLIFrameElement;
}
interface Dependencies {
readonly logger: Logger;
getClusterById: GetClusterById;
emitClusterVisibility: EmitClusterVisibility;
}
export class ClusterFrameHandler {
private readonly views = observable.map<string, LensView>();
constructor(protected readonly dependencies: Dependencies) {
makeObservable(this);
}
public hasLoadedView(clusterId: string): boolean {
return Boolean(this.views.get(clusterId)?.isLoaded);
}
@action
public initView(clusterId: ClusterId) {
const cluster = this.dependencies.getClusterById(clusterId);
if (!cluster) {
this.dependencies.logger.warn(`[LENS-VIEW]: not initializing view; unknown clusterId="${clusterId}"`);
return;
}
const parentElem = document.getElementById("lens-views");
assert(parentElem, "DOM with #lens-views must be present");
if (this.views.has(clusterId)) {
return;
}
this.dependencies.logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`);
const iframe = document.createElement("iframe");
iframe.id = `cluster-frame-${cluster.id}`;
iframe.name = cluster.contextName;
iframe.setAttribute("src", getClusterFrameUrl(clusterId));
iframe.addEventListener("load", action(() => {
this.dependencies.logger.info(`[LENS-VIEW]: frame for clusterId=${clusterId} has loaded`);
const view = this.views.get(clusterId);
assert(view, `view for ${clusterId} MUST still exist here`);
view.isLoaded = true;
}), { once: true });
this.views.set(clusterId, { frame: iframe, isLoaded: false });
parentElem.appendChild(iframe);
this.dependencies.logger.info(`[LENS-VIEW]: waiting cluster to be ready, clusterId=${clusterId}`);
const dispose = when(
() => cluster.ready,
() => this.dependencies.logger.info(`[LENS-VIEW]: cluster is ready, clusterId=${clusterId}`),
);
when(
// cluster.disconnect is set to `false` when the cluster starts to connect
() => !cluster.disconnected,
() => {
when(
() => {
const cluster = this.dependencies.getClusterById(clusterId);
return Boolean(!cluster || (cluster.disconnected && this.views.get(clusterId)?.isLoaded));
},
() => {
this.dependencies.logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`);
this.views.delete(clusterId);
// Must only remove iframe from DOM after it unloads old code. Else it crashes
iframe.addEventListener("load", () => parentElem.removeChild(iframe), {
once: true,
});
// This causes the old code to be unloaded.
iframe.setAttribute("src", "");
dispose();
},
);
},
);
}
private prevVisibleClusterChange?: Disposer;
public setVisibleCluster(clusterId: ClusterId | null): void {
// Clear the previous when ASAP
this.prevVisibleClusterChange?.();
this.dependencies.logger.info(`[LENS-VIEW]: refreshing iframe views, visible cluster id=${clusterId}`);
this.dependencies.emitClusterVisibility(null);
for (const { frame: view } of this.views.values()) {
view.classList.add("hidden");
}
const cluster = clusterId
? this.dependencies.getClusterById(clusterId)
: undefined;
if (cluster && clusterId) {
this.prevVisibleClusterChange = onceDefined(
() => {
const view = this.views.get(clusterId);
if (cluster.available && cluster.ready && view?.isLoaded) {
return view;
}
return undefined;
},
(view: LensView) => {
this.dependencies.logger.info(`[LENS-VIEW]: cluster id=${clusterId} should now be visible`);
view.frame.classList.remove("hidden");
view.frame.focus();
this.dependencies.emitClusterVisibility(clusterId);
},
);
}
}
public clearVisibleCluster() {
this.setVisibleCluster(null);
}
}
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { getInjectable } from "@ogre-tools/injectable";
import { rootFrameChildComponentInjectionToken } from "../../frames/root-frame/root-frame-child-component-injection-token";
import { ClusterManager } from "./cluster-manager";
import { computed } from "mobx";
import { ErrorBoundary } from "../error-boundary";
const clusterManagerRootFrameChildComponentInjectable = getInjectable({
id: "cluster-manager-root-frame-child-component",
instantiate: () => ({
id: "cluster-manager",
shouldRender: computed(() => true),
Component: () => (
<ErrorBoundary>
<ClusterManager />
</ErrorBoundary>
),
}),
injectionToken: rootFrameChildComponentInjectionToken,
});
export default clusterManagerRootFrameChildComponentInjectable;
@@ -0,0 +1,59 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.ClusterManager {
--hotbar-width: 75px;
display: grid;
grid-template-areas:
"topbar topbar"
"menu main"
"status-bar status-bar";
grid-template-rows: auto 1fr min-content;
grid-template-columns: min-content 1fr;
main {
grid-area: main;
position: relative;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.HotbarMenu {
grid-area: menu;
}
.error {
z-index: 1;
}
#lens-views {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
background-color: var(--mainBackground);
iframe {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
flex: 1;
// when updating font settings in the "Preferences -> Terminal" cluster's iframe
// must be accessible in DOM (e.g. elem.getBoundingClientRect() must work)
&.hidden {
opacity: 0;
pointer-events: none;
}
}
}
}
@@ -0,0 +1,93 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./cluster-manager.scss";
import React from "react";
import { Redirect } from "react-router";
import { disposeOnUnmount, observer } from "mobx-react";
import { StatusBar } from "../status-bar/status-bar";
import { HotbarMenu } from "../hotbar/hotbar-menu";
import { DeleteClusterDialog } from "../delete-cluster-dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import { TopBar } from "../layout/top-bar/top-bar";
import catalogPreviousActiveTabStorageInjectable from "../+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
import type { IComputedValue } from "mobx";
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
import welcomeRouteInjectable from "../../../common/front-end-routing/routes/welcome/welcome-route.injectable";
import { buildURL } from "../../../common/utils/buildUrl";
import type { StorageLayer } from "../../utils";
import type { WatchForGeneralEntityNavigation } from "../../api/helpers/watch-for-general-entity-navigation.injectable";
import watchForGeneralEntityNavigationInjectable from "../../api/helpers/watch-for-general-entity-navigation.injectable";
import currentPathInjectable from "../../routes/current-path.injectable";
interface Dependencies {
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
currentRouteComponent: IComputedValue<React.ElementType | undefined>;
welcomeUrl: string;
watchForGeneralEntityNavigation: WatchForGeneralEntityNavigation;
currentPath: IComputedValue<string>;
}
@observer
class NonInjectedClusterManager extends React.Component<Dependencies> {
componentDidMount() {
disposeOnUnmount(this, [
this.props.watchForGeneralEntityNavigation(),
]);
}
renderMainComponent() {
const Component = this.props.currentRouteComponent.get();
if (Component) {
return <Component />;
}
const currentPath = this.props.currentPath.get();
if (currentPath !== this.props.welcomeUrl) {
return <Redirect exact to={this.props.welcomeUrl} />;
}
return (
<div className="error">
<h2>ERROR!!</h2>
<p>
No matching route for the current path:
{" "}
<code>{currentPath}</code>
{" "}
which is the welcomeUrl. This is a bug.
</p>
</div>
);
}
render() {
return (
<div className="ClusterManager">
<TopBar />
<main>
<div id="lens-views" />
{this.renderMainComponent()}
</main>
<HotbarMenu />
<StatusBar />
<DeleteClusterDialog />
</div>
);
}
}
export const ClusterManager = withInjectables<Dependencies>(NonInjectedClusterManager, {
getProps: (di) => ({
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
currentRouteComponent: di.inject(currentRouteComponentInjectable),
welcomeUrl: buildURL(di.inject(welcomeRouteInjectable).path),
watchForGeneralEntityNavigation: di.inject(watchForGeneralEntityNavigationInjectable),
currentPath: di.inject(currentPathInjectable),
}),
});
@@ -0,0 +1,39 @@
.status {
--flex-gap: 16px;
position: relative;
min-width: 350px;
margin: auto;
text-align: center;
z-index: 1;
background: var(--mainBackground);
width: 100%;
height: 100%;
pre {
max-width: 70vw;
max-height: 40vh;
white-space: pre-line;
overflow: auto;
line-height: 1.7;
p {
margin-bottom: var(--margin);
}
}
a {
cursor: pointer;
}
}
.spinner {
--spinner-size: 38px;
--spinner-border: calc(var(--spinner-size) / 10);
}
.icon {
--size: 70px;
margin: auto;
color: var(--colorError);
}
@@ -0,0 +1,177 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./cluster-status.module.scss";
import { computed, observable, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import React from "react";
import { ipcRendererOn } from "../../../common/ipc";
import type { Cluster } from "../../../common/cluster/cluster";
import type { IClassName } from "../../utils";
import { isBoolean, hasTypedProperty, isObject, isString, cssNames } from "../../utils";
import { Button } from "../button";
import { Icon } from "../icon";
import { Spinner } from "../spinner";
import type { KubeAuthUpdate } from "../../../common/cluster-types";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { requestClusterActivation } from "../../ipc";
import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
export interface ClusterStatusProps {
className?: IClassName;
cluster: Cluster;
}
interface Dependencies {
navigateToEntitySettings: NavigateToEntitySettings;
entityRegistry: CatalogEntityRegistry;
}
@observer
class NonInjectedClusterStatus extends React.Component<ClusterStatusProps & Dependencies> {
@observable authOutput: KubeAuthUpdate[] = [];
@observable isReconnecting = false;
constructor(props: ClusterStatusProps & Dependencies) {
super(props);
makeObservable(this);
}
get cluster(): Cluster {
return this.props.cluster;
}
@computed get entity() {
return this.props.entityRegistry.getById(this.cluster.id);
}
@computed get hasErrors(): boolean {
return this.authOutput.some(({ isError }) => isError);
}
componentDidMount() {
disposeOnUnmount(this, [
ipcRendererOn(`cluster:${this.cluster.id}:connection-update`, (evt, res: unknown) => {
if (
isObject(res)
&& hasTypedProperty(res, "message", isString)
&& hasTypedProperty(res, "isError", isBoolean)
) {
this.authOutput.push(res);
} else {
console.warn(`Got invalid connection update for ${this.cluster.id}`, { update: res });
}
}),
]);
}
componentDidUpdate(prevProps: Readonly<ClusterStatusProps>): void {
if (prevProps.cluster.id !== this.props.cluster.id) {
this.isReconnecting = false;
this.authOutput = [];
}
}
reconnect = async () => {
this.authOutput = [];
this.isReconnecting = true;
try {
await requestClusterActivation(this.cluster.id, true);
} catch (error) {
this.authOutput.push({
message: String(error),
isError: true,
});
} finally {
this.isReconnecting = false;
}
};
manageProxySettings = () => {
this.props.navigateToEntitySettings(this.cluster.id, "proxy");
};
renderAuthenticationOutput() {
return (
<pre>
{
this.authOutput.map(({ message, isError }, index) => (
<p key={index} className={cssNames({ error: isError })}>
{message.trim()}
</p>
))
}
</pre>
);
}
renderStatusIcon() {
if (this.hasErrors) {
return <Icon material="cloud_off" className={styles.icon} />;
}
return (
<>
<Spinner singleColor={false} className={styles.spinner} />
<pre className="kube-auth-out">
<p>
{this.isReconnecting ? "Reconnecting" : "Connecting"}
&hellip;
</p>
</pre>
</>
);
}
renderReconnectionHelp() {
if (this.hasErrors && !this.isReconnecting) {
return (
<>
<Button
primary
label="Reconnect"
className="box center"
onClick={this.reconnect}
waiting={this.isReconnecting}
/>
<a
className="box center interactive"
onClick={this.manageProxySettings}
>
Manage Proxy Settings
</a>
</>
);
}
return undefined;
}
render() {
return (
<div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}>
<div className="flex items-center column gaps">
<h2>{this.entity?.getName() ?? this.cluster.name}</h2>
{this.renderStatusIcon()}
{this.renderAuthenticationOutput()}
{this.renderReconnectionHelp()}
</div>
</div>
);
}
}
export const ClusterStatus = withInjectables<Dependencies, ClusterStatusProps>(NonInjectedClusterStatus, {
getProps: (di, props) => ({
...props,
navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}),
});
@@ -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 { ClusterView } from "./cluster-view";
import clusterViewRouteInjectable from "../../../common/front-end-routing/routes/cluster-view/cluster-view-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const clusterViewRouteComponentInjectable = getInjectable({
id: "cluster-view-route-component",
instantiate: (di) => ({
route: di.inject(clusterViewRouteInjectable),
Component: ClusterView,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default clusterViewRouteComponentInjectable;
@@ -0,0 +1,23 @@
/**
* 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 { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import clusterViewRouteInjectable from "../../../common/front-end-routing/routes/cluster-view/cluster-view-route.injectable";
const clusterViewRouteParametersInjectable = getInjectable({
id: "cluster-view-route-parameters",
instantiate: (di) => {
const route = di.inject(clusterViewRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
clusterId: computed(() => pathParameters.get().clusterId),
};
},
});
export default clusterViewRouteParametersInjectable;
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.ClusterView {
flex: 1;
&:empty {
display: none;
}
}
@@ -0,0 +1,120 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./cluster-view.scss";
import React from "react";
import type { IComputedValue } from "mobx";
import { computed, makeObservable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./cluster-frame-handler";
import type { Cluster } from "../../../common/cluster/cluster";
import { requestClusterActivation } from "../../ipc";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable";
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
interface Dependencies {
clusterId: IComputedValue<string>;
clusterFrames: ClusterFrameHandler;
navigateToCatalog: NavigateToCatalog;
entityRegistry: CatalogEntityRegistry;
getClusterById: GetClusterById;
}
@observer
class NonInjectedClusterView extends React.Component<Dependencies> {
constructor(props: Dependencies) {
super(props);
makeObservable(this);
}
get clusterId() {
return this.props.clusterId.get();
}
@computed get cluster(): Cluster | undefined {
return this.props.getClusterById(this.clusterId);
}
private readonly isViewLoaded = computed(() => this.props.clusterFrames.hasLoadedView(this.clusterId), {
keepAlive: true,
requiresReaction: true,
});
@computed get isReady(): boolean {
const { cluster } = this;
return (cluster?.ready && cluster?.available && this.isViewLoaded.get()) ?? false;
}
componentDidMount() {
this.bindEvents();
}
componentWillUnmount() {
this.props.clusterFrames.clearVisibleCluster();
this.props.entityRegistry.activeEntity = undefined;
}
bindEvents() {
disposeOnUnmount(this, [
reaction(() => this.clusterId, async (clusterId) => {
// TODO: replace with better handling
if (clusterId && !this.props.entityRegistry.getById(clusterId)) {
return this.props.navigateToCatalog(); // redirect to catalog when the clusterId does not correspond to an entity
}
this.props.clusterFrames.setVisibleCluster(clusterId);
this.props.clusterFrames.initView(clusterId);
requestClusterActivation(clusterId, false); // activate and fetch cluster's state from main
this.props.entityRegistry.activeEntity = clusterId;
}, {
fireImmediately: true,
}),
reaction(() => [this.cluster?.ready, this.cluster?.disconnected], ([, disconnected]) => {
if (this.isViewLoaded.get() && disconnected) {
this.props.navigateToCatalog(); // redirect to catalog when active cluster get disconnected/not available
}
}),
]);
}
renderStatus(): React.ReactNode {
const { cluster, isReady } = this;
if (cluster && !isReady) {
return <ClusterStatus cluster={cluster} className="box center"/>;
}
return null;
}
render() {
return (
<div className="ClusterView flex column align-center">
{this.renderStatus()}
</div>
);
}
}
export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView, {
getProps: (di) => ({
...di.inject(clusterViewRouteParametersInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFrameHandlerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
getClusterById: di.inject(getClusterByIdInjectable),
}),
});
@@ -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 { clusterVisibilityChannel } from "../../../common/cluster/visibility-channel";
export type EmitClusterVisibility = MessageChannelHandler<typeof clusterVisibilityChannel>;
const emitClusterVisibilityInjectable = getInjectable({
id: "emit-cluster-visibility",
instantiate: (di): EmitClusterVisibility => {
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
return (id) => sendMessageToChannel(clusterVisibilityChannel, id);
},
});
export default emitClusterVisibilityInjectable;
@@ -0,0 +1,6 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./cluster-manager";