mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
more refactoring, don't always open port-forwards in browser, refined reused port-forward dialog
Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com>
This commit is contained in:
parent
cd943bd34c
commit
7b15a5526c
@ -22,7 +22,6 @@
|
|||||||
import type { LensApiRequest } from "../router";
|
import type { LensApiRequest } from "../router";
|
||||||
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
|
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
|
||||||
import { Kubectl } from "../kubectl";
|
import { Kubectl } from "../kubectl";
|
||||||
import { shell } from "electron";
|
|
||||||
import * as tcpPortUsed from "tcp-port-used";
|
import * as tcpPortUsed from "tcp-port-used";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { getPortFrom } from "../utils/get-port";
|
import { getPortFrom } from "../utils/get-port";
|
||||||
@ -112,17 +111,6 @@ class PortForward {
|
|||||||
public async stop() {
|
public async stop() {
|
||||||
this.process.kill();
|
this.process.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open() {
|
|
||||||
shell.openExternal(`http://localhost:${this.internalPort}`)
|
|
||||||
.catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, {
|
|
||||||
clusterId: this.clusterId,
|
|
||||||
port: this.port,
|
|
||||||
kind: this.kind,
|
|
||||||
namespace: this.namespace,
|
|
||||||
name: this.name,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PortForwardRoute {
|
export class PortForwardRoute {
|
||||||
@ -165,7 +153,6 @@ export class PortForwardRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
portForward.open();
|
|
||||||
respondJson(response, {port: portForward.internalPort});
|
respondJson(response, {port: portForward.internalPort});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName });
|
logger.error(`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName });
|
||||||
|
|||||||
@ -20,13 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { boundMethod, cssNames, openExternal } from "../../utils";
|
import { boundMethod, cssNames } from "../../utils";
|
||||||
import { PortForwardItem, removePortForward } from "../../port-forward";
|
import { openPortForward, PortForwardItem, removePortForward } from "../../port-forward";
|
||||||
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
|
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Notifications } from "../notifications";
|
import { PortForwardDialog } from "../../port-forward";
|
||||||
import { PortForwardDialog } from "./port-forward-dialog";
|
|
||||||
|
|
||||||
interface Props extends MenuActionsProps {
|
interface Props extends MenuActionsProps {
|
||||||
portForward: PortForwardItem;
|
portForward: PortForwardItem;
|
||||||
@ -39,25 +38,6 @@ export class PortForwardMenu extends React.Component<Props> {
|
|||||||
return removePortForward(this.props.portForward);
|
return removePortForward(this.props.portForward);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
openInBrowser() {
|
|
||||||
const { portForward } = this.props;
|
|
||||||
const browseTo = `http://localhost:${portForward.forwardPort}`;
|
|
||||||
|
|
||||||
openExternal(browseTo)
|
|
||||||
.catch(error => {
|
|
||||||
console.error(`failed to open in browser: ${error}`, {
|
|
||||||
clusterId: portForward.clusterId,
|
|
||||||
port: portForward.port,
|
|
||||||
kind: portForward.kind,
|
|
||||||
namespace: portForward.namespace,
|
|
||||||
name: portForward.name,
|
|
||||||
});
|
|
||||||
Notifications.error(`Failed to open ${browseTo} in browser`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const { portForward, toolbar } = this.props;
|
const { portForward, toolbar } = this.props;
|
||||||
|
|
||||||
@ -65,7 +45,7 @@ export class PortForwardMenu extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={this.openInBrowser}>
|
<MenuItem onClick={() => openPortForward(this.props.portForward)}>
|
||||||
<Icon material="open_in_browser" interactive={toolbar} tooltip="Open in browser" />
|
<Icon material="open_in_browser" interactive={toolbar} tooltip="Open in browser" />
|
||||||
<span className="title">Open</span>
|
<span className="title">Open</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { ServicePortComponent } from "./service-port-component";
|
|||||||
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
||||||
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
import { portForwardStore } from "../../port-forward";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Service> {
|
interface Props extends KubeObjectDetailsProps<Service> {
|
||||||
}
|
}
|
||||||
@ -46,6 +47,7 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
preload: true,
|
preload: true,
|
||||||
namespaces: [service.getNs()],
|
namespaces: [service.getNs()],
|
||||||
}),
|
}),
|
||||||
|
portForwardStore.watch(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,24 +22,22 @@
|
|||||||
import "./service-port-component.scss";
|
import "./service-port-component.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import type { Service, ServicePort } from "../../../common/k8s-api/endpoints";
|
import type { Service, ServicePort } from "../../../common/k8s-api/endpoints";
|
||||||
import { apiBase } from "../../api";
|
import { observable, makeObservable, IReactionDisposer, reaction } from "mobx";
|
||||||
import { observable, makeObservable } from "mobx";
|
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Input } from "../input";
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
|
import { addPortForward, getPortForward, openPortForward, PortForwardDialog, portForwardStore, removePortForward } from "../../port-forward";
|
||||||
|
import type { ForwardedPort } from "../../port-forward";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
import { Spinner } from "../spinner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
service: Service;
|
service: Service;
|
||||||
port: ServicePort;
|
port: ServicePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortForwardResult {
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ServicePortComponent extends React.Component<Props> {
|
export class ServicePortComponent extends React.Component<Props> {
|
||||||
@observable waiting = false;
|
@observable waiting = false;
|
||||||
@ -52,26 +50,54 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
this.watch(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch() {
|
||||||
|
const disposers: IReactionDisposer[] = [
|
||||||
|
reaction(() => portForwardStore.portForwards, () => this.init()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => disposers.forEach((dispose) => dispose());
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.checkExistingPortForwarding().catch(error => {
|
this.checkExistingPortForwarding().catch(error => {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkExistingPortForwarding() {
|
async checkExistingPortForwarding() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
const response = await apiBase.get<PortForwardResult>(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "service",
|
||||||
|
name: service.getName(),
|
||||||
|
namespace: service.getNs(),
|
||||||
|
port: port.port.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
let activePort = await getPortForward(portForward);
|
||||||
|
|
||||||
const activePort = response.port;
|
if (!activePort) {
|
||||||
|
activePort = 0;
|
||||||
if (activePort) {
|
|
||||||
this.forwardPort = activePort;
|
|
||||||
this.isPortForwarded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.forwardPort = activePort;
|
||||||
|
this.isPortForwarded = activePort ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "service",
|
||||||
|
name: service.getName(),
|
||||||
|
namespace: service.getNs(),
|
||||||
|
port: port.port.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
@ -80,10 +106,13 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiBase.post<PortForwardResult>(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
this.forwardPort = await addPortForward(portForward);
|
||||||
|
|
||||||
this.forwardPort = response.port;
|
if (this.forwardPort) {
|
||||||
this.isPortForwarded = true;
|
portForward.forwardPort = this.forwardPort.toString();
|
||||||
|
openPortForward(portForward);
|
||||||
|
this.isPortForwarded = true;
|
||||||
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -93,11 +122,18 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
|
|
||||||
async stopPortForward() {
|
async stopPortForward() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "service",
|
||||||
|
name: service.getName(),
|
||||||
|
namespace: service.getNs(),
|
||||||
|
port: port.port.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiBase.del(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
await removePortForward(portForward);
|
||||||
this.isPortForwarded = false;
|
this.isPortForwarded = false;
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
@ -107,31 +143,34 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { port } = this.props;
|
const { port, service } = this.props;
|
||||||
|
|
||||||
const portForwardAction = async () => {
|
const portForwardAction = async () => {
|
||||||
if (this.isPortForwarded) {
|
if (this.isPortForwarded) {
|
||||||
await this.stopPortForward();
|
await this.stopPortForward();
|
||||||
}else {
|
}else {
|
||||||
await this.portForward();
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "service",
|
||||||
|
name: service.getName(),
|
||||||
|
namespace: service.getNs(),
|
||||||
|
port: port.port.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
PortForwardDialog.open(portForward, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
||||||
{port.toString()}
|
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||||
|
{port.toString()}
|
||||||
|
{this.waiting && (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
{" "}
|
{" "}
|
||||||
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
|
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward..."} </Button>
|
||||||
<text> local port:</text>
|
|
||||||
<Input className={"portInput"}
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
max="65535"
|
|
||||||
value= {this.forwardPort != 0? String(this.forwardPort) : ""}
|
|
||||||
disabled={this.isPortForwarded}
|
|
||||||
placeholder={"Random"}
|
|
||||||
onChange={(value) => this.forwardPort = Number(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,15 +22,16 @@
|
|||||||
import "./pod-container-port.scss";
|
import "./pod-container-port.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import type { Pod } from "../../../common/k8s-api/endpoints";
|
import type { Pod } from "../../../common/k8s-api/endpoints";
|
||||||
import { apiBase } from "../../api";
|
import { observable, makeObservable, reaction, IReactionDisposer } from "mobx";
|
||||||
import { observable, makeObservable } from "mobx";
|
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Input } from "../input";
|
import { addPortForward, getPortForward, openPortForward, PortForwardDialog, portForwardStore, removePortForward } from "../../port-forward";
|
||||||
import { portForwardStore } from "../../port-forward/port-forward.store";
|
import type { ForwardedPort } from "../../port-forward";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
import { Spinner } from "../spinner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -41,10 +42,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortForwardResult {
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PodContainerPort extends React.Component<Props> {
|
export class PodContainerPort extends React.Component<Props> {
|
||||||
@observable waiting = false;
|
@observable waiting = false;
|
||||||
@ -57,26 +54,54 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
this.watch(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch() {
|
||||||
|
const disposers: IReactionDisposer[] = [
|
||||||
|
reaction(() => portForwardStore.portForwards, () => this.init()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => disposers.forEach((dispose) => dispose());
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.checkExistingPortForwarding().catch(error => {
|
this.checkExistingPortForwarding().catch(error => {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkExistingPortForwarding() {
|
async checkExistingPortForwarding() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
const response = await apiBase.get<PortForwardResult>(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "pod",
|
||||||
|
name: pod.getName(),
|
||||||
|
namespace: pod.getNs(),
|
||||||
|
port: port.containerPort.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
let activePort = await getPortForward(portForward);
|
||||||
|
|
||||||
const activePort = response.port;
|
if (!activePort) {
|
||||||
|
activePort = 0;
|
||||||
if (activePort) {
|
|
||||||
this.forwardPort = activePort;
|
|
||||||
this.isPortForwarded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.forwardPort = activePort;
|
||||||
|
this.isPortForwarded = activePort ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "pod",
|
||||||
|
name: pod.getName(),
|
||||||
|
namespace: pod.getNs(),
|
||||||
|
port: port.containerPort.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
@ -85,11 +110,13 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiBase.post<PortForwardResult>(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
this.forwardPort = await addPortForward(portForward);
|
||||||
|
|
||||||
this.forwardPort = response.port;
|
if (this.forwardPort) {
|
||||||
this.isPortForwarded = true;
|
portForward.forwardPort = this.forwardPort.toString();
|
||||||
portForwardStore.reset();
|
openPortForward(portForward);
|
||||||
|
this.isPortForwarded = true;
|
||||||
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -99,13 +126,19 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
|
|
||||||
async stopPortForward() {
|
async stopPortForward() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "pod",
|
||||||
|
name: pod.getName(),
|
||||||
|
namespace: pod.getNs(),
|
||||||
|
port: port.containerPort.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
await removePortForward(portForward);
|
||||||
this.isPortForwarded = false;
|
this.isPortForwarded = false;
|
||||||
portForwardStore.reset();
|
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -114,7 +147,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { port } = this.props;
|
const { pod, port } = this.props;
|
||||||
const { name, containerPort, protocol } = port;
|
const { name, containerPort, protocol } = port;
|
||||||
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
|
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
|
||||||
|
|
||||||
@ -122,25 +155,28 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
if (this.isPortForwarded) {
|
if (this.isPortForwarded) {
|
||||||
await this.stopPortForward();
|
await this.stopPortForward();
|
||||||
} else {
|
} else {
|
||||||
await this.portForward();
|
const portForward: ForwardedPort = {
|
||||||
|
kind: "pod",
|
||||||
|
name: pod.getName(),
|
||||||
|
namespace: pod.getNs(),
|
||||||
|
port: port.containerPort.toString(),
|
||||||
|
forwardPort: this.forwardPort.toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
PortForwardDialog.open(portForward, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
||||||
{text}
|
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||||
|
{text}
|
||||||
|
{this.waiting && (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
{" "}
|
{" "}
|
||||||
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
|
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward..."} </Button>
|
||||||
<text> local port:</text>
|
|
||||||
<Input className={"portInput"}
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
max="65535"
|
|
||||||
value={this.forwardPort != 0? String(this.forwardPort) : ""}
|
|
||||||
disabled={this.isPortForwarded}
|
|
||||||
placeholder={"Random"}
|
|
||||||
onChange={(value) => this.forwardPort = Number(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import { LocaleDate } from "../locale-date";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { portForwardStore } from "../../port-forward/port-forward.store";
|
import { portForwardStore } from "../../port-forward/port-forward.store";
|
||||||
import { disposeOnUnmount } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -44,6 +44,7 @@ interface Props {
|
|||||||
metrics?: { [key: string]: IMetrics };
|
metrics?: { [key: string]: IMetrics };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
export class PodDetailsContainer extends React.Component<Props> {
|
export class PodDetailsContainer extends React.Component<Props> {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|||||||
@ -74,7 +74,7 @@ import { ClusterStore } from "../../common/cluster-store";
|
|||||||
import type { ClusterId } from "../../common/cluster-types";
|
import type { ClusterId } from "../../common/cluster-types";
|
||||||
import { watchHistoryState } from "../remote-helpers/history-updater";
|
import { watchHistoryState } from "../remote-helpers/history-updater";
|
||||||
import { unmountComponentAtNode } from "react-dom";
|
import { unmountComponentAtNode } from "react-dom";
|
||||||
import { PortForwardDialog } from "./+network-port-forwards/port-forward-dialog";
|
import { PortForwardDialog } from "../port-forward";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class App extends React.Component {
|
export class App extends React.Component {
|
||||||
|
|||||||
@ -21,3 +21,4 @@
|
|||||||
|
|
||||||
export * from "./port-forward.store";
|
export * from "./port-forward.store";
|
||||||
export * from "./port-forward-item";
|
export * from "./port-forward-item";
|
||||||
|
export * from "./port-forward-dialog";
|
||||||
|
|||||||
@ -66,5 +66,12 @@
|
|||||||
margin-right: $margin;
|
margin-right: $margin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.portInput {
|
||||||
|
display: inline-block !important;
|
||||||
|
width: 70px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
} }
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -24,21 +24,22 @@ import "./port-forward-dialog.scss";
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { computed, observable, makeObservable } from "mobx";
|
import { computed, observable, makeObservable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Dialog, DialogProps } from "../dialog";
|
import { Dialog, DialogProps } from "../components/dialog";
|
||||||
import { Wizard, WizardStep } from "../wizard";
|
import { Wizard, WizardStep } from "../components/wizard";
|
||||||
import { Icon } from "../icon";
|
import { Input } from "../components/input";
|
||||||
import { Input } from "../input";
|
import { Notifications } from "../components/notifications";
|
||||||
import { Notifications } from "../notifications";
|
import { cssNames } from "../utils";
|
||||||
import { cssNames } from "../../utils";
|
import { addPortForward, modifyPortForward } from "./port-forward.store";
|
||||||
import { modifyPortForward, PortForwardItem } from "../../port-forward";
|
import type { ForwardedPort } from "./port-forward.store";
|
||||||
import { isNumber } from "../input/input_validators";
|
import { openPortForward } from ".";
|
||||||
|
|
||||||
interface Props extends Partial<DialogProps> {
|
interface Props extends Partial<DialogProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogState = observable.object({
|
const dialogState = observable.object({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
data: null as PortForwardItem,
|
data: null as ForwardedPort,
|
||||||
|
openInBrowser: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -52,9 +53,10 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static open(portForward: PortForwardItem) {
|
static open(portForward: ForwardedPort, openInBrowser = false) {
|
||||||
dialogState.isOpen = true;
|
dialogState.isOpen = true;
|
||||||
dialogState.data = portForward;
|
dialogState.data = portForward;
|
||||||
|
dialogState.openInBrowser = openInBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
static close() {
|
static close() {
|
||||||
@ -83,24 +85,40 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
|
|
||||||
this.currentPort = +portForward.forwardPort;
|
this.currentPort = +portForward.forwardPort;
|
||||||
this.desiredPort = this.currentPort;
|
this.desiredPort = this.currentPort;
|
||||||
this.ready = true;
|
this.ready = this.currentPort ? false : true;
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
changePort = async () => {
|
changePort = (value: string) => {
|
||||||
|
this.desiredPort = Number(value);
|
||||||
|
this.ready = Boolean(this.desiredPort == 0 || this.currentPort !== this.desiredPort);
|
||||||
|
};
|
||||||
|
|
||||||
|
startPortForward = async () => {
|
||||||
const { portForward } = this;
|
const { portForward } = this;
|
||||||
const { currentPort, desiredPort, close } = this;
|
const { currentPort, desiredPort, close } = this;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentPort !== desiredPort) {
|
let port: number;
|
||||||
await modifyPortForward(portForward, desiredPort);
|
|
||||||
|
if (currentPort) {
|
||||||
|
port = await modifyPortForward(portForward, desiredPort);
|
||||||
|
} else {
|
||||||
|
portForward.forwardPort = String(desiredPort);
|
||||||
|
port = await addPortForward(portForward);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogState.openInBrowser) {
|
||||||
|
portForward.forwardPort = String(port);
|
||||||
|
openPortForward(portForward);
|
||||||
}
|
}
|
||||||
close();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Notifications.error(err);
|
Notifications.error(err);
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,32 +128,28 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
<div className="input-container flex align-center">
|
<div className="input-container flex align-center">
|
||||||
<div className="current-port" data-testid="current-port">
|
<div className="current-port" data-testid="current-port">
|
||||||
Current port: {this.currentPort}
|
Local port to forward from:
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input className={"portInput"}
|
||||||
required autoFocus
|
type="number"
|
||||||
iconLeft="import_export"
|
min="0"
|
||||||
placeholder="Desired port"
|
max="65535"
|
||||||
trim
|
value={this.desiredPort === 0 ? "" : String(this.desiredPort)}
|
||||||
validators={isNumber}
|
placeholder={"Random"}
|
||||||
onChange={v => this.desiredPort = +v}
|
onChange={this.changePort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="warning" data-testid="warning">
|
|
||||||
<Icon material="warning" />
|
|
||||||
Current port-forwarding will be removed and a new one will be started
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, ...dialogProps } = this.props;
|
const { className, ...dialogProps } = this.props;
|
||||||
const resourceName = this.portForward ? this.portForward.getName() : "";
|
const resourceName = this.portForward ? this.portForward.name : "";
|
||||||
const header = (
|
const header = (
|
||||||
<h5>
|
<h5>
|
||||||
Change Port <span>{resourceName}</span>
|
Port Forwarding for <span>{resourceName}</span>
|
||||||
</h5>
|
</h5>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -151,8 +165,8 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
<Wizard header={header} done={this.close}>
|
<Wizard header={header} done={this.close}>
|
||||||
<WizardStep
|
<WizardStep
|
||||||
contentClass="flex gaps column"
|
contentClass="flex gaps column"
|
||||||
next={this.changePort}
|
next={this.startPortForward}
|
||||||
nextLabel="Change Port"
|
nextLabel={this.currentPort === 0 ? "Start" : "Restart"}
|
||||||
disabledNext={!this.ready}
|
disabledNext={!this.ready}
|
||||||
>
|
>
|
||||||
{this.renderContents()}
|
{this.renderContents()}
|
||||||
@ -22,14 +22,15 @@
|
|||||||
|
|
||||||
import { computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
|
import { computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
|
||||||
import { ItemObject, ItemStore } from "../../common/item.store";
|
import { ItemObject, ItemStore } from "../../common/item.store";
|
||||||
import { autoBind, createStorage } from "../utils";
|
import { autoBind, createStorage, getHostedClusterId, openExternal } from "../utils";
|
||||||
import { getHostedClusterId } from "../utils";
|
|
||||||
import { PortForwardItem } from "./port-forward-item";
|
import { PortForwardItem } from "./port-forward-item";
|
||||||
import { apiBase } from "../api";
|
import { apiBase } from "../api";
|
||||||
import { waitUntilFree } from "tcp-port-used";
|
import { waitUntilFree } from "tcp-port-used";
|
||||||
|
import { Notifications } from "../components/notifications";
|
||||||
|
import logger from "../../common/logger";
|
||||||
|
|
||||||
export interface ForwardedPort {
|
export interface ForwardedPort {
|
||||||
clusterId: string;
|
clusterId?: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -54,6 +55,7 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
|
|||||||
const savedPortForwards = this.storage.get(); // undefined on first load
|
const savedPortForwards = this.storage.get(); // undefined on first load
|
||||||
|
|
||||||
if (Array.isArray(savedPortForwards)) {
|
if (Array.isArray(savedPortForwards)) {
|
||||||
|
logger.info("[PORT_FORWARD] starting saved port-forwards");
|
||||||
await Promise.all(savedPortForwards.map(pf => {
|
await Promise.all(savedPortForwards.map(pf => {
|
||||||
const port = new PortForwardItem;
|
const port = new PortForwardItem;
|
||||||
|
|
||||||
@ -128,41 +130,57 @@ interface PortForwardsResult {
|
|||||||
portForwards: ForwardedPort[];
|
portForwards: ForwardedPort[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addPortForward(portForward: PortForwardItem): Promise<number> {
|
export async function addPortForward(portForward: ForwardedPort): Promise<number> {
|
||||||
let response;
|
let response: PortForwardResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await apiBase.post<PortForwardResult>(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.getForwardPort()}`, {});
|
response = await apiBase.post<PortForwardResult>(`/pods/${portForward.namespace}/${portForward.kind}/${portForward.name}/port-forward/${portForward.port}/${portForward.forwardPort}`, {});
|
||||||
|
|
||||||
if (response?.port != +portForward.getForwardPort()) {
|
if (response?.port != +portForward.forwardPort) {
|
||||||
console.log(`specified ${portForward.getForwardPort()} got ${response.port}`);
|
logger.info(`specified ${portForward.forwardPort} got ${response.port}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
}
|
}
|
||||||
portForwardStore.reset();
|
portForwardStore.reset();
|
||||||
|
|
||||||
return response?.port;
|
return response?.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function modifyPortForward(portForward: PortForwardItem, desiredPort: number) {
|
export async function getPortForward(portForward: ForwardedPort): Promise<number> {
|
||||||
|
let response: PortForwardResult;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await apiBase.get<PortForwardResult>(`/pods/${portForward.namespace}/${portForward.kind}/${portForward.name}/port-forward/${portForward.port}/${portForward.forwardPort}`, {});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response?.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function modifyPortForward(portForward: ForwardedPort, desiredPort: number): Promise<number> {
|
||||||
|
let port = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await removePortForward(portForward);
|
await removePortForward(portForward);
|
||||||
portForward.forwardPort = desiredPort.toString();
|
portForward.forwardPort = desiredPort.toString();
|
||||||
await addPortForward(portForward);
|
port = await addPortForward(portForward);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
}
|
}
|
||||||
portForwardStore.reset();
|
portForwardStore.reset();
|
||||||
|
|
||||||
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function removePortForward(portForward: PortForwardItem) {
|
export async function removePortForward(portForward: ForwardedPort) {
|
||||||
try {
|
try {
|
||||||
await apiBase.del(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.forwardPort}`, {});
|
await apiBase.del(`/pods/${portForward.namespace}/${portForward.kind}/${portForward.name}/port-forward/${portForward.port}/${portForward.forwardPort}`, {});
|
||||||
await waitUntilFree(+portForward.getForwardPort(), 200, 1000);
|
await waitUntilFree(+portForward.forwardPort, 200, 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
}
|
}
|
||||||
portForwardStore.reset();
|
portForwardStore.reset();
|
||||||
}
|
}
|
||||||
@ -173,10 +191,28 @@ export async function getPortForwards(): Promise<ForwardedPort[]> {
|
|||||||
|
|
||||||
return response.portForwards;
|
return response.portForwards;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openPortForward(portForward: ForwardedPort) {
|
||||||
|
const browseTo = `http://localhost:${portForward.forwardPort}`;
|
||||||
|
|
||||||
|
openExternal(browseTo)
|
||||||
|
.catch(error => {
|
||||||
|
logger.error(`failed to open in browser: ${error}`, {
|
||||||
|
clusterId: portForward.clusterId,
|
||||||
|
port: portForward.port,
|
||||||
|
kind: portForward.kind,
|
||||||
|
namespace: portForward.namespace,
|
||||||
|
name: portForward.name,
|
||||||
|
});
|
||||||
|
Notifications.error(`Failed to open ${browseTo} in browser`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export const portForwardStore = new PortForwardStore();
|
export const portForwardStore = new PortForwardStore();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user