mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
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:
1 parent
0066ca4ea2
commit
2657df2293
3321 files changed
+28699
-11447
No files matched your search
+20
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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"}
|
||||
…
|
||||
</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),
|
||||
}),
|
||||
});
|
||||
+21
@@ -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;
|
||||
+23
@@ -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),
|
||||
}),
|
||||
});
|
||||
|
||||
+21
@@ -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";
|
||||
Reference in new issue
Block a user