mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
part 2
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
e5138e7c5d
commit
d04c8d3045
@ -156,11 +156,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
this.activeClusterId = newClusters.has(activeCluster) ? activeCluster : null;
|
this.activeClusterId = newClusters.has(activeCluster) ? activeCluster : null;
|
||||||
this.clusters.replace(newClusters);
|
this.clusters.replace(newClusters);
|
||||||
this.removedClusters.replace(removedClusters);
|
this.removedClusters.replace(removedClusters);
|
||||||
|
|
||||||
// "auto-select" first cluster if available
|
|
||||||
if (!this.activeClusterId && newClusters.size) {
|
|
||||||
this.activeClusterId = Array.from(newClusters.values())[0].id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): ClusterStoreModel {
|
toJSON(): ClusterStoreModel {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { action, computed, observable, reaction, toJS } from "mobx";
|
import { action, computed, observable, toJS } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import { clusterStore } from "./cluster-store"
|
import { clusterStore } from "./cluster-store"
|
||||||
|
|
||||||
@ -22,15 +22,6 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
super({
|
super({
|
||||||
configName: "lens-workspace-store",
|
configName: "lens-workspace-store",
|
||||||
});
|
});
|
||||||
|
|
||||||
// switch to first available cluster in current workspace
|
|
||||||
reaction(() => this.currentWorkspaceId, workspaceId => {
|
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspaceId);
|
|
||||||
const activeClusterInWorkspace = clusters.some(cluster => cluster.id === clusterStore.activeClusterId);
|
|
||||||
if (!activeClusterInWorkspace) {
|
|
||||||
clusterStore.activeClusterId = clusters.length ? clusters[0].id : null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||||
|
|||||||
@ -67,11 +67,6 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable allowedNamespaces: string[] = [];
|
@observable allowedNamespaces: string[] = [];
|
||||||
@observable allowedResources: string[] = [];
|
@observable allowedResources: string[] = [];
|
||||||
|
|
||||||
@computed get host() {
|
|
||||||
const proxyHost = new URL(this.kubeProxyUrl).host;
|
|
||||||
return `${this.id}.${proxyHost}`
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
}
|
}
|
||||||
@ -223,7 +218,7 @@ export class Cluster implements ClusterModel {
|
|||||||
json: true,
|
json: true,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
headers: {
|
headers: {
|
||||||
Host: this.host, // provide cluster-id for ClusterManager.getClusterForRequest()
|
Host: `${this.id}.${new URL(this.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||||
...(options.headers || {}),
|
...(options.headers || {}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export class WindowManager {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
enableRemoteModule: true,
|
enableRemoteModule: true,
|
||||||
|
webviewTag: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.windowState.manage(this.mainView);
|
this.windowState.manage(this.mainView);
|
||||||
|
|||||||
@ -1,32 +1,34 @@
|
|||||||
import "./cluster-manager.scss"
|
import "./cluster-manager.scss"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Redirect, Route, Switch } from "react-router";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ClustersMenu } from "./clusters-menu";
|
import { ClustersMenu } from "./clusters-menu";
|
||||||
import { BottomBar } from "./bottom-bar";
|
import { BottomBar } from "./bottom-bar";
|
||||||
import { cssNames, IClassName } from "../../utils";
|
import { LandingPage, landingRoute, landingURL } from "../+landing-page";
|
||||||
import { ClusterId } from "../../../common/cluster-store";
|
|
||||||
import { Route, Switch } from "react-router";
|
|
||||||
import { LandingPage, landingRoute } from "../+landing-page";
|
|
||||||
import { Preferences, preferencesRoute } from "../+preferences";
|
import { Preferences, preferencesRoute } from "../+preferences";
|
||||||
import { Workspaces, workspacesRoute } from "../+workspaces";
|
import { Workspaces, workspacesRoute } from "../+workspaces";
|
||||||
import { AddCluster, addClusterRoute } from "../+add-cluster";
|
import { AddCluster, addClusterRoute } from "../+add-cluster";
|
||||||
import { ClusterStatus } from "./cluster-status";
|
import { ClusterView } from "./cluster-view";
|
||||||
import { clusterStatusRoute } from "./cluster-status.route";
|
import { clusterViewRoute, clusterViewURL } from "./cluster-view.route";
|
||||||
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
interface Props {
|
|
||||||
className?: IClassName;
|
|
||||||
contentClass?: IClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterManager extends React.Component<Props> {
|
export class ClusterManager extends React.Component {
|
||||||
activateView(clusterId: ClusterId) {
|
get startUrl() {
|
||||||
|
const { activeClusterId } = clusterStore;
|
||||||
|
if (activeClusterId) {
|
||||||
|
return clusterViewURL({
|
||||||
|
params: {
|
||||||
|
clusterId: activeClusterId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return landingURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ClusterManager", className)}>
|
<div className="ClusterManager">
|
||||||
<div id="draggable-top"/>
|
<div id="draggable-top"/>
|
||||||
<div id="lens-view">
|
<div id="lens-view">
|
||||||
<Switch>
|
<Switch>
|
||||||
@ -34,8 +36,8 @@ export class ClusterManager extends React.Component<Props> {
|
|||||||
<Route component={Preferences} {...preferencesRoute}/>
|
<Route component={Preferences} {...preferencesRoute}/>
|
||||||
<Route component={Workspaces} {...workspacesRoute}/>
|
<Route component={Workspaces} {...workspacesRoute}/>
|
||||||
<Route component={AddCluster} {...addClusterRoute}/>
|
<Route component={AddCluster} {...addClusterRoute}/>
|
||||||
<Route component={ClusterStatus} {...clusterStatusRoute}/>
|
<Route component={ClusterView} {...clusterViewRoute}/>
|
||||||
<Route render={() => <p>Lens</p>}/>
|
<Redirect exact from="/" to={this.startUrl}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<ClustersMenu/>
|
<ClustersMenu/>
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
import { RouteProps } from "react-router";
|
|
||||||
import { buildURL } from "../../navigation";
|
|
||||||
|
|
||||||
export const clusterStatusRoute: RouteProps = {
|
|
||||||
path: "/cluster-status"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clusterStatusURL = buildURL(clusterStatusRoute.path)
|
|
||||||
@ -2,37 +2,37 @@ import type { KubeAuthProxyLog } from "../../../main/kube-auth-proxy";
|
|||||||
|
|
||||||
import "./cluster-status.scss"
|
import "./cluster-status.scss"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { autorun, computed, observable } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import { Cluster } from "../../../main/cluster";
|
import { Cluster } from "../../../main/cluster";
|
||||||
|
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
clusterId: ClusterId;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterStatus extends React.Component {
|
export class ClusterStatus extends React.Component<Props> {
|
||||||
@observable authOutput: KubeAuthProxyLog[] = [];
|
@observable authOutput: KubeAuthProxyLog[] = [];
|
||||||
@observable isReconnecting = false;
|
@observable isReconnecting = false;
|
||||||
|
|
||||||
// fixme
|
@computed get clusterId() {
|
||||||
|
return this.props.clusterId;
|
||||||
|
}
|
||||||
|
|
||||||
@computed get cluster(): Cluster {
|
@computed get cluster(): Cluster {
|
||||||
return null;
|
return clusterStore.getById(this.clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get hasErrors(): boolean {
|
@computed get hasErrors(): boolean {
|
||||||
return this.authOutput.some(({ error }) => error) || !!this.cluster.failureReason;
|
return this.authOutput.some(({ error }) => error) || !!this.cluster.failureReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
autoRedirectToMain = autorun(() => {
|
|
||||||
if (this.cluster.accessible && !this.hasErrors) {
|
|
||||||
navigate("/");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.cluster.disconnected) {
|
if (this.cluster.disconnected) {
|
||||||
return;
|
return;
|
||||||
@ -48,11 +48,11 @@ export class ClusterStatus extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ipcRenderer.removeAllListeners(`kube-auth:${this.cluster.id}`);
|
ipcRenderer.removeAllListeners(`kube-auth:${this.clusterId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshClusterState() {
|
async refreshClusterState() {
|
||||||
return clusterIpc.activate.invokeFromRenderer();
|
return clusterIpc.activate.invokeFromRenderer(this.clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnect = async () => {
|
reconnect = async () => {
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { matchPath, RouteProps } from "react-router";
|
||||||
|
import { buildURL, navigation } from "../../navigation";
|
||||||
|
|
||||||
|
export interface IClusterViewRouteParams {
|
||||||
|
clusterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clusterViewRoute: RouteProps = {
|
||||||
|
path: "/cluster/:clusterId"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clusterViewURL = buildURL<IClusterViewRouteParams>(clusterViewRoute.path)
|
||||||
|
|
||||||
|
export function getMatchedClusterId(): string {
|
||||||
|
const matched = matchPath<IClusterViewRouteParams>(navigation.location.pathname, {
|
||||||
|
...clusterViewRoute,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
if (matched) {
|
||||||
|
return matched.params.clusterId;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/renderer/components/cluster-manager/cluster-view.scss
Normal file
10
src/renderer/components/cluster-manager/cluster-view.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.ClusterView {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
//display: none;
|
||||||
|
|
||||||
|
&.loaded {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/renderer/components/cluster-manager/cluster-view.tsx
Normal file
46
src/renderer/components/cluster-manager/cluster-view.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { autorun, computed, observable } from "mobx";
|
||||||
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||||
|
import { getMatchedClusterId } from "./cluster-view.route";
|
||||||
|
import { Cluster } from "../../../main/cluster";
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class ClusterView extends React.Component {
|
||||||
|
static views = observable.map<ClusterId, /*HTMLIFrameElement*/ any>()
|
||||||
|
static isLoaded = observable.map<ClusterId, boolean>()
|
||||||
|
|
||||||
|
@computed get cluster() {
|
||||||
|
return clusterStore.getById(getMatchedClusterId())
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get clusterView() {
|
||||||
|
return ClusterView.views.get(this.cluster?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
autorun(() => this.activateView(this.cluster))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
activateView = (cluster: Cluster) => {
|
||||||
|
if (!cluster || ClusterView.views.has(cluster.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const view = document.createElement("webview");
|
||||||
|
view.className = "ClusterView"
|
||||||
|
view.src = `${location.protocol}://${cluster.id}.${location.host}`
|
||||||
|
view.onload = () => console.log('CLUSTER VIEW READY!', cluster);
|
||||||
|
document.body.appendChild(view);
|
||||||
|
ClusterView.views.set(cluster.id, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { cluster } = this;
|
||||||
|
if (cluster && cluster.accessible) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ import { landingURL } from "../+landing-page";
|
|||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||||
import { clusterStatusURL } from "./cluster-status.route";
|
import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route";
|
||||||
|
|
||||||
// fixme: allow to rearrange clusters with drag&drop
|
// fixme: allow to rearrange clusters with drag&drop
|
||||||
|
|
||||||
@ -32,12 +32,9 @@ interface Props {
|
|||||||
export class ClustersMenu extends React.Component<Props> {
|
export class ClustersMenu extends React.Component<Props> {
|
||||||
@observable showHint = true;
|
@observable showHint = true;
|
||||||
|
|
||||||
showCluster = (clusterId: ClusterId) => {
|
activateCluster = (clusterId: ClusterId) => {
|
||||||
if (clusterStore.activeClusterId === clusterId) {
|
clusterStore.activeClusterId = clusterId;
|
||||||
navigate("/"); // redirect to index
|
navigate(clusterViewURL({ params: { clusterId } }))
|
||||||
} else {
|
|
||||||
clusterStore.activeClusterId = clusterId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCluster = () => {
|
addCluster = () => {
|
||||||
@ -57,9 +54,6 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
label: _i18n._(t`Disconnect`),
|
label: _i18n._(t`Disconnect`),
|
||||||
click: async () => {
|
click: async () => {
|
||||||
await clusterIpc.disconnect.invokeFromRenderer(cluster.id);
|
await clusterIpc.disconnect.invokeFromRenderer(cluster.id);
|
||||||
if (cluster.id === clusterStore.activeClusterId) {
|
|
||||||
navigate(clusterStatusURL());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -110,8 +104,8 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
key={cluster.id}
|
key={cluster.id}
|
||||||
showErrors={true}
|
showErrors={true}
|
||||||
cluster={cluster}
|
cluster={cluster}
|
||||||
isActive={cluster.id === clusterStore.activeClusterId}
|
isActive={cluster.id === getMatchedClusterId()}
|
||||||
onClick={() => this.showCluster(cluster.id)}
|
onClick={() => this.activateCluster(cluster.id)}
|
||||||
onContextMenu={() => this.showContextMenu(cluster)}
|
onContextMenu={() => this.showContextMenu(cluster)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user