mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Keep port-forward objects around when not running (#4607)
* adding disabled status to port forwards (WIP) Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * more work Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * almost working Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * working Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * refactoring and bug fixing, still issue with port-forward-dialog Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * further refactoring and bug fixing Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * fixed remaining issues around port-forward dialog, changed port-forward-item id to resource name, etc from local port Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * documentation, more cleanup Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * address review comments Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com>
This commit is contained in:
parent
fa3708c879
commit
446eb5ca43
@ -182,7 +182,6 @@ export class Router {
|
|||||||
// Port-forward API (the container port and local forwarding port are obtained from the query parameters)
|
// Port-forward API (the container port and local forwarding port are obtained from the query parameters)
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routePortForward);
|
this.router.add({ method: "post", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routePortForward);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForward);
|
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForward);
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forwards` }, PortForwardRoute.routeAllPortForwards);
|
|
||||||
this.router.add({ method: "delete", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForwardStop);
|
this.router.add({ method: "delete", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForwardStop);
|
||||||
|
|
||||||
// Helm API
|
// Helm API
|
||||||
|
|||||||
@ -188,31 +188,6 @@ export class PortForwardRoute {
|
|||||||
respondJson(response, { port: portForward?.forwardPort ?? null });
|
respondJson(response, { port: portForward?.forwardPort ?? null });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async routeAllPortForwards(request: LensApiRequest) {
|
|
||||||
const { query, response } = request;
|
|
||||||
const clusterId = query.get("clusterId");
|
|
||||||
|
|
||||||
let portForwards: PortForwardArgs[] = PortForward.portForwards.map(f => (
|
|
||||||
{
|
|
||||||
clusterId: f.clusterId,
|
|
||||||
kind: f.kind,
|
|
||||||
namespace: f.namespace,
|
|
||||||
name: f.name,
|
|
||||||
port: f.port,
|
|
||||||
forwardPort: f.forwardPort,
|
|
||||||
protocol: f.protocol,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clusterId) {
|
|
||||||
// filter out any not for this cluster
|
|
||||||
portForwards = portForwards.filter(pf => pf.clusterId == clusterId);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
respondJson(response, { portForwards });
|
|
||||||
}
|
|
||||||
|
|
||||||
static async routeCurrentPortForwardStop(request: LensApiRequest) {
|
static async routeCurrentPortForwardStop(request: LensApiRequest) {
|
||||||
const { params, query, response, cluster } = request;
|
const { params, query, response, cluster } = request;
|
||||||
const { namespace, resourceType, resourceName } = params;
|
const { namespace, resourceType, resourceName } = params;
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { boundMethod, cssNames } from "../../utils";
|
import { boundMethod, cssNames } from "../../utils";
|
||||||
import { openPortForward, PortForwardItem, removePortForward, PortForwardDialog } from "../../port-forward";
|
import { openPortForward, PortForwardItem, removePortForward, PortForwardDialog, startPortForward, stopPortForward } 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";
|
||||||
@ -44,6 +44,38 @@ export class PortForwardMenu extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startPortForwarding = async () => {
|
||||||
|
const { portForward } = this.props;
|
||||||
|
|
||||||
|
const pf = await startPortForward(portForward);
|
||||||
|
|
||||||
|
if (pf.status === "Disabled") {
|
||||||
|
const { name, kind, forwardPort } = portForward;
|
||||||
|
|
||||||
|
Notifications.error(`Error occurred starting port-forward, the local port ${forwardPort} may not be available or the ${kind} ${name} may not be reachable`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderStartStopMenuItem() {
|
||||||
|
const { portForward, toolbar } = this.props;
|
||||||
|
|
||||||
|
if (portForward.status === "Active") {
|
||||||
|
return (
|
||||||
|
<MenuItem onClick={() => stopPortForward(portForward)}>
|
||||||
|
<Icon material="stop" tooltip="Stop port-forward" interactive={toolbar} />
|
||||||
|
<span className="title">Stop</span>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem onClick={this.startPortForwarding}>
|
||||||
|
<Icon material="play_arrow" tooltip="Start port-forward" interactive={toolbar} />
|
||||||
|
<span className="title">Start</span>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const { portForward, toolbar } = this.props;
|
const { portForward, toolbar } = this.props;
|
||||||
|
|
||||||
@ -51,14 +83,17 @@ export class PortForwardMenu extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => openPortForward(this.props.portForward)}>
|
{ portForward.status === "Active" &&
|
||||||
<Icon material="open_in_browser" interactive={toolbar} tooltip="Open in browser" />
|
<MenuItem onClick={() => openPortForward(portForward)}>
|
||||||
<span className="title">Open</span>
|
<Icon material="open_in_browser" interactive={toolbar} tooltip="Open in browser" />
|
||||||
</MenuItem>
|
<span className="title">Open</span>
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
<MenuItem onClick={() => PortForwardDialog.open(portForward)}>
|
<MenuItem onClick={() => PortForwardDialog.open(portForward)}>
|
||||||
<Icon material="edit" tooltip="Change port or protocol" interactive={toolbar} />
|
<Icon material="edit" tooltip="Change port or protocol" interactive={toolbar} />
|
||||||
<span className="title">Edit</span>
|
<span className="title">Edit</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{this.renderStartStopMenuItem()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export class PortForwards extends React.Component<Props> {
|
|||||||
showDetails = (item: PortForwardItem) => {
|
showDetails = (item: PortForwardItem) => {
|
||||||
navigation.push(portForwardsURL({
|
navigation.push(portForwardsURL({
|
||||||
params: {
|
params: {
|
||||||
forwardport: String(item.getForwardPort()),
|
forwardport: item.getId(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,13 +24,14 @@ import "./service-port-component.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, 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 { observable, makeObservable, reaction } from "mobx";
|
import { observable, makeObservable, reaction, action } 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 { aboutPortForwarding, addPortForward, getPortForward, getPortForwards, openPortForward, PortForwardDialog, portForwardStore, predictProtocol, removePortForward } from "../../port-forward";
|
import { aboutPortForwarding, addPortForward, getPortForward, getPortForwards, notifyErrorPortForwarding, openPortForward, PortForwardDialog, predictProtocol, removePortForward, startPortForward } from "../../port-forward";
|
||||||
import type { ForwardedPort } from "../../port-forward";
|
import type { ForwardedPort } from "../../port-forward";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
service: Service;
|
service: Service;
|
||||||
@ -42,6 +43,7 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
@observable waiting = false;
|
@observable waiting = false;
|
||||||
@observable forwardPort = 0;
|
@observable forwardPort = 0;
|
||||||
@observable isPortForwarded = false;
|
@observable isPortForwarded = false;
|
||||||
|
@observable isActive = false;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -51,13 +53,14 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(() => [portForwardStore.portForwards, this.props.service], () => this.checkExistingPortForwarding()),
|
reaction(() => this.props.service, () => this.checkExistingPortForwarding()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async checkExistingPortForwarding() {
|
async checkExistingPortForwarding() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "service",
|
kind: "service",
|
||||||
name: service.getName(),
|
name: service.getName(),
|
||||||
namespace: service.getNs(),
|
namespace: service.getNs(),
|
||||||
@ -65,57 +68,66 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
forwardPort: this.forwardPort,
|
forwardPort: this.forwardPort,
|
||||||
};
|
};
|
||||||
|
|
||||||
let activePort: number;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
activePort = await getPortForward(portForward) ?? 0;
|
portForward = await getPortForward(portForward);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isPortForwarded = false;
|
this.isPortForwarded = false;
|
||||||
|
this.isActive = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forwardPort = activePort;
|
this.forwardPort = portForward.forwardPort;
|
||||||
this.isPortForwarded = activePort ? true : false;
|
this.isPortForwarded = true;
|
||||||
|
this.isActive = portForward.status === "Active";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "service",
|
kind: "service",
|
||||||
name: service.getName(),
|
name: service.getName(),
|
||||||
namespace: service.getNs(),
|
namespace: service.getNs(),
|
||||||
port: port.port,
|
port: port.port,
|
||||||
forwardPort: this.forwardPort,
|
forwardPort: this.forwardPort,
|
||||||
protocol: predictProtocol(port.name),
|
protocol: predictProtocol(port.name),
|
||||||
|
status: "Active",
|
||||||
};
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// determine how many port-forwards are already active
|
// determine how many port-forwards already exist
|
||||||
const { length } = await getPortForwards();
|
const { length } = getPortForwards();
|
||||||
|
|
||||||
this.forwardPort = await addPortForward(portForward);
|
if (!this.isPortForwarded) {
|
||||||
|
portForward = await addPortForward(portForward);
|
||||||
|
} else if (!this.isActive) {
|
||||||
|
portForward = await startPortForward(portForward);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.forwardPort) {
|
this.forwardPort = portForward.forwardPort;
|
||||||
portForward.forwardPort = this.forwardPort;
|
|
||||||
|
if (portForward.status === "Active") {
|
||||||
openPortForward(portForward);
|
openPortForward(portForward);
|
||||||
this.isPortForwarded = true;
|
|
||||||
|
|
||||||
// if this is the first port-forward show the about notification
|
// if this is the first port-forward show the about notification
|
||||||
if (!length) {
|
if (!length) {
|
||||||
aboutPortForwarding();
|
aboutPortForwarding();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
notifyErrorPortForwarding(`Error occurred starting port-forward, the local port may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(`Error occurred starting port-forward, the local port may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
logger.error("[SERVICE-PORT-COMPONENT]:", error, portForward);
|
||||||
this.checkExistingPortForwarding();
|
|
||||||
} finally {
|
} finally {
|
||||||
|
this.checkExistingPortForwarding();
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async stopPortForward() {
|
async stopPortForward() {
|
||||||
const { service, port } = this.props;
|
const { service, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
const portForward: ForwardedPort = {
|
||||||
@ -130,11 +142,11 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await removePortForward(portForward);
|
await removePortForward(portForward);
|
||||||
this.isPortForwarded = false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(`Error occurred stopping the port-forward from port ${portForward.forwardPort}.`);
|
Notifications.error(`Error occurred stopping the port-forward from port ${portForward.forwardPort}.`);
|
||||||
this.checkExistingPortForwarding();
|
|
||||||
} finally {
|
} finally {
|
||||||
|
this.checkExistingPortForwarding();
|
||||||
|
this.forwardPort = 0;
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,7 +154,7 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { port, service } = this.props;
|
const { port, service } = this.props;
|
||||||
|
|
||||||
const portForwardAction = async () => {
|
const portForwardAction = action(async () => {
|
||||||
if (this.isPortForwarded) {
|
if (this.isPortForwarded) {
|
||||||
await this.stopPortForward();
|
await this.stopPortForward();
|
||||||
} else {
|
} else {
|
||||||
@ -155,16 +167,16 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
protocol: predictProtocol(port.name),
|
protocol: predictProtocol(port.name),
|
||||||
};
|
};
|
||||||
|
|
||||||
PortForwardDialog.open(portForward, { openInBrowser: true });
|
PortForwardDialog.open(portForward, { openInBrowser: true, onClose: () => this.checkExistingPortForwarding() });
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
||||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||||
{port.toString()}
|
{port.toString()}
|
||||||
</span>
|
</span>
|
||||||
<Button primary onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop" : "Forward..."} </Button>
|
<Button primary onClick={portForwardAction}> {this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."} </Button>
|
||||||
{this.waiting && (
|
{this.waiting && (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ $service-status-color-list: (
|
|||||||
|
|
||||||
$port-forward-status-color-list: (
|
$port-forward-status-color-list: (
|
||||||
active: var(--colorOk),
|
active: var(--colorOk),
|
||||||
|
disabled: var(--colorSoftError)
|
||||||
);
|
);
|
||||||
|
|
||||||
@mixin port-forward-status-colors {
|
@mixin port-forward-status-colors {
|
||||||
|
|||||||
@ -24,13 +24,14 @@ import "./pod-container-port.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, 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 { observable, makeObservable, reaction } from "mobx";
|
import { action, observable, makeObservable, reaction } 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 { aboutPortForwarding, addPortForward, getPortForward, getPortForwards, openPortForward, PortForwardDialog, portForwardStore, predictProtocol, removePortForward } from "../../port-forward";
|
import { aboutPortForwarding, addPortForward, getPortForward, getPortForwards, notifyErrorPortForwarding, openPortForward, PortForwardDialog, predictProtocol, removePortForward, startPortForward } from "../../port-forward";
|
||||||
import type { ForwardedPort } from "../../port-forward";
|
import type { ForwardedPort } from "../../port-forward";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -46,6 +47,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
@observable waiting = false;
|
@observable waiting = false;
|
||||||
@observable forwardPort = 0;
|
@observable forwardPort = 0;
|
||||||
@observable isPortForwarded = false;
|
@observable isPortForwarded = false;
|
||||||
|
@observable isActive = false;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -55,13 +57,14 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(() => [portForwardStore.portForwards, this.props.pod], () => this.checkExistingPortForwarding()),
|
reaction(() => this.props.pod, () => this.checkExistingPortForwarding()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async checkExistingPortForwarding() {
|
async checkExistingPortForwarding() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "pod",
|
kind: "pod",
|
||||||
name: pod.getName(),
|
name: pod.getName(),
|
||||||
namespace: pod.getNs(),
|
namespace: pod.getNs(),
|
||||||
@ -69,57 +72,64 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
forwardPort: this.forwardPort,
|
forwardPort: this.forwardPort,
|
||||||
};
|
};
|
||||||
|
|
||||||
let activePort: number;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
activePort = await getPortForward(portForward) ?? 0;
|
portForward = await getPortForward(portForward);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isPortForwarded = false;
|
this.isPortForwarded = false;
|
||||||
|
this.isActive = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forwardPort = activePort;
|
this.forwardPort = portForward.forwardPort;
|
||||||
this.isPortForwarded = activePort ? true : false;
|
this.isPortForwarded = true;
|
||||||
|
this.isActive = portForward.status === "Active";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async portForward() {
|
async portForward() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
let portForward: ForwardedPort = {
|
||||||
kind: "pod",
|
kind: "pod",
|
||||||
name: pod.getName(),
|
name: pod.getName(),
|
||||||
namespace: pod.getNs(),
|
namespace: pod.getNs(),
|
||||||
port: port.containerPort,
|
port: port.containerPort,
|
||||||
forwardPort: this.forwardPort,
|
forwardPort: this.forwardPort,
|
||||||
protocol: predictProtocol(port.name),
|
protocol: predictProtocol(port.name),
|
||||||
|
status: "Active",
|
||||||
};
|
};
|
||||||
|
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// determine how many port-forwards are already active
|
// determine how many port-forwards already exist
|
||||||
const { length } = await getPortForwards();
|
const { length } = getPortForwards();
|
||||||
|
|
||||||
this.forwardPort = await addPortForward(portForward);
|
if (!this.isPortForwarded) {
|
||||||
|
portForward = await addPortForward(portForward);
|
||||||
|
} else if (!this.isActive) {
|
||||||
|
portForward = await startPortForward(portForward);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.forwardPort) {
|
if (portForward.status === "Active") {
|
||||||
portForward.forwardPort = this.forwardPort;
|
|
||||||
openPortForward(portForward);
|
openPortForward(portForward);
|
||||||
this.isPortForwarded = true;
|
|
||||||
|
|
||||||
// if this is the first port-forward show the about notification
|
// if this is the first port-forward show the about notification
|
||||||
if (!length) {
|
if (!length) {
|
||||||
aboutPortForwarding();
|
aboutPortForwarding();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
notifyErrorPortForwarding(`Error occurred starting port-forward, the local port may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(`Error occurred starting port-forward, the local port may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
logger.error("[POD-CONTAINER-PORT]:", error, portForward);
|
||||||
this.checkExistingPortForwarding();
|
|
||||||
} finally {
|
} finally {
|
||||||
|
this.checkExistingPortForwarding();
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async stopPortForward() {
|
async stopPortForward() {
|
||||||
const { pod, port } = this.props;
|
const { pod, port } = this.props;
|
||||||
const portForward: ForwardedPort = {
|
const portForward: ForwardedPort = {
|
||||||
@ -134,11 +144,11 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await removePortForward(portForward);
|
await removePortForward(portForward);
|
||||||
this.isPortForwarded = false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(`Error occurred stopping the port-forward from port ${portForward.forwardPort}.`);
|
Notifications.error(`Error occurred stopping the port-forward from port ${portForward.forwardPort}.`);
|
||||||
this.checkExistingPortForwarding();
|
|
||||||
} finally {
|
} finally {
|
||||||
|
this.checkExistingPortForwarding();
|
||||||
|
this.forwardPort = 0;
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +158,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
const { name, containerPort, protocol } = port;
|
const { name, containerPort, protocol } = port;
|
||||||
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
|
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
|
||||||
|
|
||||||
const portForwardAction = async () => {
|
const portForwardAction = action(async () => {
|
||||||
if (this.isPortForwarded) {
|
if (this.isPortForwarded) {
|
||||||
await this.stopPortForward();
|
await this.stopPortForward();
|
||||||
} else {
|
} else {
|
||||||
@ -161,16 +171,16 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
protocol: predictProtocol(port.name),
|
protocol: predictProtocol(port.name),
|
||||||
};
|
};
|
||||||
|
|
||||||
PortForwardDialog.open(portForward, { openInBrowser: true });
|
PortForwardDialog.open(portForward, { openInBrowser: true, onClose: () => this.checkExistingPortForwarding() });
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
||||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
<Button primary onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop" : "Forward..."} </Button>
|
<Button primary onClick={portForwardAction}> {this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."} </Button>
|
||||||
{this.waiting && (
|
{this.waiting && (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -27,19 +27,20 @@ import { observer } from "mobx-react";
|
|||||||
import { Dialog, DialogProps } from "../components/dialog";
|
import { Dialog, DialogProps } from "../components/dialog";
|
||||||
import { Wizard, WizardStep } from "../components/wizard";
|
import { Wizard, WizardStep } from "../components/wizard";
|
||||||
import { Input } from "../components/input";
|
import { Input } from "../components/input";
|
||||||
import { Notifications } from "../components/notifications";
|
import { cssNames, noop } from "../utils";
|
||||||
import { cssNames } from "../utils";
|
|
||||||
import { addPortForward, getPortForwards, modifyPortForward } from "./port-forward.store";
|
import { addPortForward, getPortForwards, modifyPortForward } from "./port-forward.store";
|
||||||
import type { ForwardedPort } from "./port-forward-item";
|
import type { ForwardedPort } from "./port-forward-item";
|
||||||
import { openPortForward } from "./port-forward-utils";
|
import { openPortForward } from "./port-forward-utils";
|
||||||
import { aboutPortForwarding } from "./port-forward-notify";
|
import { aboutPortForwarding, notifyErrorPortForwarding } from "./port-forward-notify";
|
||||||
import { Checkbox } from "../components/checkbox";
|
import { Checkbox } from "../components/checkbox";
|
||||||
|
import logger from "../../common/logger";
|
||||||
|
|
||||||
interface Props extends Partial<DialogProps> {
|
interface Props extends Partial<DialogProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortForwardDialogOpenOptions {
|
interface PortForwardDialogOpenOptions {
|
||||||
openInBrowser: boolean
|
openInBrowser: boolean;
|
||||||
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogState = observable.object({
|
const dialogState = observable.object({
|
||||||
@ -47,6 +48,7 @@ const dialogState = observable.object({
|
|||||||
data: null as ForwardedPort,
|
data: null as ForwardedPort,
|
||||||
useHttps: false,
|
useHttps: false,
|
||||||
openInBrowser: false,
|
openInBrowser: false,
|
||||||
|
onClose: noop,
|
||||||
});
|
});
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -59,11 +61,12 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static open(portForward: ForwardedPort, options: PortForwardDialogOpenOptions = { openInBrowser: false }) {
|
static open(portForward: ForwardedPort, options: PortForwardDialogOpenOptions = { openInBrowser: false, onClose: noop }) {
|
||||||
dialogState.isOpen = true;
|
dialogState.isOpen = true;
|
||||||
dialogState.data = portForward;
|
dialogState.data = portForward;
|
||||||
dialogState.useHttps = portForward.protocol === "https";
|
dialogState.useHttps = portForward.protocol === "https";
|
||||||
dialogState.openInBrowser = options.openInBrowser;
|
dialogState.openInBrowser = options.openInBrowser;
|
||||||
|
dialogState.onClose = options.onClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static close() {
|
static close() {
|
||||||
@ -85,43 +88,47 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
this.desiredPort = this.currentPort;
|
this.desiredPort = this.currentPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
|
||||||
};
|
|
||||||
|
|
||||||
changePort = (value: string) => {
|
changePort = (value: string) => {
|
||||||
this.desiredPort = Number(value);
|
this.desiredPort = Number(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
startPortForward = async () => {
|
startPortForward = async () => {
|
||||||
const { portForward } = this;
|
let { portForward } = this;
|
||||||
const { currentPort, desiredPort, close } = this;
|
const { currentPort, desiredPort, close } = this;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// determine how many port-forwards are already active
|
// determine how many port-forwards already exist
|
||||||
const { length } = await getPortForwards();
|
const { length } = getPortForwards();
|
||||||
|
|
||||||
let port: number;
|
|
||||||
|
|
||||||
portForward.protocol = dialogState.useHttps ? "https" : "http";
|
portForward.protocol = dialogState.useHttps ? "https" : "http";
|
||||||
|
|
||||||
if (currentPort) {
|
if (currentPort) {
|
||||||
port = await modifyPortForward(portForward, desiredPort);
|
const wasRunning = portForward.status === "Active";
|
||||||
|
|
||||||
|
portForward = await modifyPortForward(portForward, desiredPort);
|
||||||
|
|
||||||
|
if (wasRunning && portForward.status === "Disabled") {
|
||||||
|
notifyErrorPortForwarding(`Error occurred starting port-forward, the local port ${portForward.forwardPort} may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
portForward.forwardPort = desiredPort;
|
portForward.forwardPort = desiredPort;
|
||||||
port = await addPortForward(portForward);
|
portForward = await addPortForward(portForward);
|
||||||
|
|
||||||
// if this is the first port-forward show the about notification
|
if (portForward.status === "Disabled") {
|
||||||
if (!length) {
|
notifyErrorPortForwarding(`Error occurred starting port-forward, the local port ${portForward.forwardPort} may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
||||||
aboutPortForwarding();
|
} else {
|
||||||
|
// if this is the first port-forward show the about notification
|
||||||
|
if (!length) {
|
||||||
|
aboutPortForwarding();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialogState.openInBrowser) {
|
if (portForward.status === "Active" && dialogState.openInBrowser) {
|
||||||
portForward.forwardPort = port;
|
|
||||||
openPortForward(portForward);
|
openPortForward(portForward);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
Notifications.error(`Error occurred starting port-forward, the local port may not be available or the ${portForward.kind} ${portForward.name} may not be reachable`);
|
logger.error(`[PORT-FORWARD-DIALOG]: ${error}`, portForward);
|
||||||
} finally {
|
} finally {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@ -176,14 +183,14 @@ export class PortForwardDialog extends Component<Props> {
|
|||||||
isOpen={dialogState.isOpen}
|
isOpen={dialogState.isOpen}
|
||||||
className={cssNames("PortForwardDialog", className)}
|
className={cssNames("PortForwardDialog", className)}
|
||||||
onOpen={this.onOpen}
|
onOpen={this.onOpen}
|
||||||
onClose={this.onClose}
|
onClose={dialogState.onClose}
|
||||||
close={this.close}
|
close={this.close}
|
||||||
>
|
>
|
||||||
<Wizard header={header} done={this.close}>
|
<Wizard header={header} done={this.close}>
|
||||||
<WizardStep
|
<WizardStep
|
||||||
contentClass="flex gaps column"
|
contentClass="flex gaps column"
|
||||||
next={this.startPortForward}
|
next={this.startPortForward}
|
||||||
nextLabel={this.currentPort === 0 ? "Start" : "Restart"}
|
nextLabel={this.currentPort === 0 ? "Start" : "Modify"}
|
||||||
>
|
>
|
||||||
{this.renderContents()}
|
{this.renderContents()}
|
||||||
</WizardStep>
|
</WizardStep>
|
||||||
|
|||||||
@ -23,33 +23,34 @@
|
|||||||
import type { ItemObject } from "../../common/item.store";
|
import type { ItemObject } from "../../common/item.store";
|
||||||
import { autoBind } from "../../common/utils";
|
import { autoBind } from "../../common/utils";
|
||||||
|
|
||||||
|
export type ForwardedPortStatus = "Active" | "Disabled";
|
||||||
export interface ForwardedPort {
|
export interface ForwardedPort {
|
||||||
clusterId?: string;
|
|
||||||
kind: string;
|
kind: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
port: number;
|
port: number;
|
||||||
forwardPort: number;
|
forwardPort: number;
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
|
status?: ForwardedPortStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PortForwardItem implements ItemObject {
|
export class PortForwardItem implements ItemObject {
|
||||||
clusterId: string;
|
|
||||||
kind: string;
|
kind: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
port: number;
|
port: number;
|
||||||
forwardPort: number;
|
forwardPort: number;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
status: ForwardedPortStatus;
|
||||||
|
|
||||||
constructor(pf: ForwardedPort) {
|
constructor(pf: ForwardedPort) {
|
||||||
this.clusterId = pf.clusterId;
|
|
||||||
this.kind = pf.kind;
|
this.kind = pf.kind;
|
||||||
this.namespace = pf.namespace;
|
this.namespace = pf.namespace;
|
||||||
this.name = pf.name;
|
this.name = pf.name;
|
||||||
this.port = pf.port;
|
this.port = pf.port;
|
||||||
this.forwardPort = pf.forwardPort;
|
this.forwardPort = pf.forwardPort;
|
||||||
this.protocol = pf.protocol ?? "http";
|
this.protocol = pf.protocol ?? "http";
|
||||||
|
this.status = pf.status ?? "Active";
|
||||||
|
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
@ -62,12 +63,8 @@ export class PortForwardItem implements ItemObject {
|
|||||||
return this.namespace;
|
return this.namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
|
||||||
return this.forwardPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return String(this.forwardPort);
|
return `${this.namespace}-${this.kind}-${this.name}:${this.port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getKind() {
|
getKind() {
|
||||||
@ -87,16 +84,17 @@ export class PortForwardItem implements ItemObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return "Active"; // to-do allow port-forward-items to be stopped (without removing them)
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearchFields() {
|
getSearchFields() {
|
||||||
return [
|
return [
|
||||||
this.name,
|
this.name,
|
||||||
this.id,
|
this.namespace,
|
||||||
this.kind,
|
this.kind,
|
||||||
this.port,
|
this.port,
|
||||||
this.forwardPort,
|
this.forwardPort,
|
||||||
|
this.status,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,3 +56,34 @@ export function aboutPortForwarding() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function notifyErrorPortForwarding(msg: string) {
|
||||||
|
const notificationId = `port-forward-error-notification-${getHostedClusterId()}`;
|
||||||
|
|
||||||
|
Notifications.error(
|
||||||
|
(
|
||||||
|
<div className="flex column gaps">
|
||||||
|
<b>Port Forwarding</b>
|
||||||
|
<p>
|
||||||
|
{msg}
|
||||||
|
</p>
|
||||||
|
<div className="flex gaps row align-left box grow">
|
||||||
|
<Button
|
||||||
|
active
|
||||||
|
outlined
|
||||||
|
label="Check Port Forwarding"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(portForwardsURL());
|
||||||
|
notificationsStore.remove(notificationId);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: notificationId,
|
||||||
|
timeout: 10_000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,6 @@ export function openPortForward(portForward: ForwardedPort) {
|
|||||||
openExternal(browseTo)
|
openExternal(browseTo)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error(`failed to open in browser: ${error}`, {
|
logger.error(`failed to open in browser: ${error}`, {
|
||||||
clusterId: portForward.clusterId,
|
|
||||||
port: portForward.port,
|
port: portForward.port,
|
||||||
kind: portForward.kind,
|
kind: portForward.kind,
|
||||||
namespace: portForward.namespace,
|
namespace: portForward.namespace,
|
||||||
|
|||||||
@ -20,10 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { makeObservable, observable, reaction } from "mobx";
|
import { action, makeObservable, observable, reaction } from "mobx";
|
||||||
import { ItemStore } from "../../common/item.store";
|
import { ItemStore } from "../../common/item.store";
|
||||||
import { autoBind, createStorage, disposer, getHostedClusterId } from "../utils";
|
import { autoBind, createStorage, disposer } from "../utils";
|
||||||
import { ForwardedPort, PortForwardItem } from "./port-forward-item";
|
import { ForwardedPort, PortForwardItem } from "./port-forward-item";
|
||||||
|
import { notifyErrorPortForwarding } from "./port-forward-notify";
|
||||||
import { apiBase } from "../api";
|
import { apiBase } from "../api";
|
||||||
import { waitUntilFree } from "tcp-port-used";
|
import { waitUntilFree } from "tcp-port-used";
|
||||||
import logger from "../../common/logger";
|
import logger from "../../common/logger";
|
||||||
@ -31,7 +32,7 @@ import logger from "../../common/logger";
|
|||||||
export class PortForwardStore extends ItemStore<PortForwardItem> {
|
export class PortForwardStore extends ItemStore<PortForwardItem> {
|
||||||
private storage = createStorage<ForwardedPort[] | undefined>("port_forwards", undefined);
|
private storage = createStorage<ForwardedPort[] | undefined>("port_forwards", undefined);
|
||||||
|
|
||||||
@observable portForwards: PortForwardItem[];
|
@observable portForwards: PortForwardItem[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -48,33 +49,42 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
|
|||||||
|
|
||||||
if (Array.isArray(savedPortForwards)) {
|
if (Array.isArray(savedPortForwards)) {
|
||||||
logger.info("[PORT-FORWARD-STORE] starting saved port-forwards");
|
logger.info("[PORT-FORWARD-STORE] starting saved port-forwards");
|
||||||
await Promise.all(savedPortForwards.map(addPortForward));
|
|
||||||
|
// add the disabled ones
|
||||||
|
await Promise.all(savedPortForwards.filter(pf => pf.status === "Disabled").map(addPortForward));
|
||||||
|
|
||||||
|
// add the active ones and check if they started successfully
|
||||||
|
const results = await Promise.allSettled(savedPortForwards.filter(pf => pf.status === "Active").map(addPortForward));
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
if (result.status === "rejected" || result.value.status === "Disabled") {
|
||||||
|
notifyErrorPortForwarding("One or more port-forwards could not be started");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch() {
|
watch() {
|
||||||
return disposer(
|
return disposer(
|
||||||
reaction(() => this.portForwards, () => this.loadAll()),
|
reaction(() => portForwardStore.portForwards.slice(), () => portForwardStore.loadAll()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAll() {
|
loadAll() {
|
||||||
return this.loadItems(async () => {
|
return this.loadItems(() => {
|
||||||
const portForwards = await getPortForwards(getHostedClusterId());
|
const portForwards = getPortForwards();
|
||||||
|
|
||||||
this.storage.set(portForwards);
|
this.storage.set(portForwards);
|
||||||
|
|
||||||
this.reset();
|
this.portForwards = [];
|
||||||
portForwards.map(pf => this.portForwards.push(new PortForwardItem(pf)));
|
portForwards.map(pf => this.portForwards.push(new PortForwardItem(pf)));
|
||||||
|
|
||||||
return this.portForwards;
|
return this.portForwards;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.portForwards = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeSelectedItems() {
|
async removeSelectedItems() {
|
||||||
return Promise.all(this.selectedItems.map(removePortForward));
|
return Promise.all(this.selectedItems.map(removePortForward));
|
||||||
}
|
}
|
||||||
@ -94,82 +104,248 @@ interface PortForwardResult {
|
|||||||
port: number;
|
port: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortForwardsResult {
|
function portForwardsEqual(portForward: ForwardedPort) {
|
||||||
portForwards: ForwardedPort[];
|
return (pf: ForwardedPort) => (
|
||||||
|
pf.kind == portForward.kind &&
|
||||||
|
pf.name == portForward.name &&
|
||||||
|
pf.namespace == portForward.namespace &&
|
||||||
|
pf.port == portForward.port
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addPortForward(portForward: ForwardedPort): Promise<number> {
|
function findPortForward(portForward: ForwardedPort) {
|
||||||
const { port, forwardPort } = portForward;
|
return portForwardStore.portForwards.find(portForwardsEqual(portForward));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPortForward = action((portForward: ForwardedPort) => {
|
||||||
|
const index = portForwardStore.portForwards.findIndex(portForwardsEqual(portForward));
|
||||||
|
|
||||||
|
if (index < 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
portForwardStore.portForwards[index] = new PortForwardItem(portForward);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start an existing port-forward
|
||||||
|
* @param portForward the port-forward to start. If the forwardPort field is 0 then an arbitrary port will be
|
||||||
|
* used
|
||||||
|
*
|
||||||
|
* @returns the port-forward with updated status ("Active" if successfully started, "Disabled" otherwise) and
|
||||||
|
* forwardPort
|
||||||
|
*
|
||||||
|
* @throws if the port-forward does not already exist in the store
|
||||||
|
*/
|
||||||
|
export const startPortForward = action( async (portForward: ForwardedPort): Promise<ForwardedPort> => {
|
||||||
|
const pf = findPortForward(portForward);
|
||||||
|
|
||||||
|
if (!pf) {
|
||||||
|
throw new Error("cannot start non-existent port-forward");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { port, forwardPort } = pf;
|
||||||
let response: PortForwardResult;
|
let response: PortForwardResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const protocol = portForward.protocol ?? "http";
|
const protocol = pf.protocol ?? "http";
|
||||||
|
|
||||||
response = await apiBase.post<PortForwardResult>(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
|
response = await apiBase.post<PortForwardResult>(`/pods/port-forward/${pf.namespace}/${pf.kind}/${pf.name}`, { query: { port, forwardPort, protocol }});
|
||||||
|
|
||||||
// expecting the received port to be the specified port, unless the specified port is 0, which indicates any available port is suitable
|
// expecting the received port to be the specified port, unless the specified port is 0, which indicates any available port is suitable
|
||||||
if (portForward.forwardPort && response?.port && response.port != +portForward.forwardPort) {
|
if (pf.forwardPort && response?.port && response.port != +pf.forwardPort) {
|
||||||
logger.warn(`[PORT-FORWARD-STORE] specified ${portForward.forwardPort} got ${response.port}`);
|
logger.warn(`[PORT-FORWARD-STORE] specified ${pf.forwardPort}, got ${response.port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pf.forwardPort = response.port;
|
||||||
|
pf.status = "Active";
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("[PORT-FORWARD-STORE] Error adding port-forward:", error, portForward);
|
logger.warn(`[PORT-FORWARD-STORE] Error starting port-forward: ${error}`, pf);
|
||||||
throw (error);
|
pf.status = "Disabled";
|
||||||
}
|
}
|
||||||
portForwardStore.reset();
|
|
||||||
|
|
||||||
return response?.port;
|
setPortForward(pf);
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPortForward(portForward: ForwardedPort): Promise<number> {
|
return pf as ForwardedPort;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a port-forward to the store and optionally start it
|
||||||
|
* @param portForward the port-forward to add. If the port-forward already exists in the store it will be
|
||||||
|
* returned with its current state. If the forwardPort field is 0 then an arbitrary port will be
|
||||||
|
* used. If the status field is "Active" or not present then an attempt is made to start the port-forward.
|
||||||
|
*
|
||||||
|
* @returns the port-forward with updated status ("Active" if successfully started, "Disabled" otherwise) and
|
||||||
|
* forwardPort
|
||||||
|
*/
|
||||||
|
export const addPortForward = action(async (portForward: ForwardedPort): Promise<ForwardedPort> => {
|
||||||
|
const pf = findPortForward(portForward);
|
||||||
|
|
||||||
|
if (pf) {
|
||||||
|
return pf;
|
||||||
|
}
|
||||||
|
|
||||||
|
portForwardStore.portForwards.push(new PortForwardItem(portForward));
|
||||||
|
|
||||||
|
if (!portForward.status) {
|
||||||
|
portForward.status = "Active";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portForward.status === "Active") {
|
||||||
|
portForward = await startPortForward(portForward);
|
||||||
|
}
|
||||||
|
|
||||||
|
return portForward;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getActivePortForward(portForward: ForwardedPort): Promise<ForwardedPort> {
|
||||||
const { port, forwardPort, protocol } = portForward;
|
const { port, forwardPort, protocol } = portForward;
|
||||||
let response: PortForwardResult;
|
let response: PortForwardResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await apiBase.get<PortForwardResult>(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
|
response = await apiBase.get<PortForwardResult>(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("[PORT-FORWARD-STORE] Error getting port-forward:", error, portForward);
|
logger.warn(`[PORT-FORWARD-STORE] Error getting active port-forward: ${error}`, portForward);
|
||||||
throw (error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response?.port;
|
portForward.status = response?.port ? "Active" : "Disabled";
|
||||||
|
portForward.forwardPort = response?.port;
|
||||||
|
|
||||||
|
return portForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function modifyPortForward(portForward: ForwardedPort, desiredPort: number): Promise<number> {
|
/**
|
||||||
let port = 0;
|
* get a port-forward from the store, with up-to-date status
|
||||||
|
* @param portForward the port-forward to get.
|
||||||
|
*
|
||||||
|
* @returns the port-forward with updated status ("Active" if running, "Disabled" if not) and
|
||||||
|
* forwardPort used.
|
||||||
|
*
|
||||||
|
* @throws if the port-forward does not exist in the store
|
||||||
|
*/
|
||||||
|
export async function getPortForward(portForward: ForwardedPort): Promise<ForwardedPort> {
|
||||||
|
if (!findPortForward(portForward)) {
|
||||||
|
throw new Error("port-forward not found");
|
||||||
|
}
|
||||||
|
|
||||||
await removePortForward(portForward);
|
let pf: ForwardedPort;
|
||||||
portForward.forwardPort = desiredPort;
|
|
||||||
port = await addPortForward(portForward);
|
|
||||||
|
|
||||||
portForwardStore.reset();
|
try {
|
||||||
|
// check if the port-forward is active, and if so check if it has the same local port
|
||||||
|
pf = await getActivePortForward(portForward);
|
||||||
|
|
||||||
return port;
|
if (pf.forwardPort && pf.forwardPort !== portForward.forwardPort) {
|
||||||
|
logger.warn(`[PORT-FORWARD-STORE] local port, expected ${pf.forwardPort}, got ${portForward.forwardPort}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// port is not active
|
||||||
|
}
|
||||||
|
|
||||||
|
return pf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modifies a port-forward in the store, including the forwardPort and protocol
|
||||||
|
* @param portForward the port-forward to modify.
|
||||||
|
*
|
||||||
|
* @returns the port-forward after being modified.
|
||||||
|
*/
|
||||||
|
export const modifyPortForward = action(async (portForward: ForwardedPort, desiredPort: number): Promise<ForwardedPort> => {
|
||||||
|
const pf = findPortForward(portForward);
|
||||||
|
|
||||||
|
if (!pf) {
|
||||||
|
throw new Error("port-forward not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pf.status === "Active") {
|
||||||
|
try {
|
||||||
|
await stopPortForward(pf);
|
||||||
|
} catch {
|
||||||
|
// ignore, assume it is stopped and proceed to restart it
|
||||||
|
}
|
||||||
|
|
||||||
|
pf.forwardPort = desiredPort;
|
||||||
|
pf.protocol = portForward.protocol ?? "http";
|
||||||
|
setPortForward(pf);
|
||||||
|
|
||||||
|
return await startPortForward(pf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pf.forwardPort = desiredPort;
|
||||||
|
setPortForward(pf);
|
||||||
|
|
||||||
|
return pf as ForwardedPort;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stop an existing port-forward. Its status is set to "Disabled" after successfully stopped.
|
||||||
|
* @param portForward the port-forward to stop.
|
||||||
|
*
|
||||||
|
* @throws if the port-forward could not be stopped. Its status is unchanged
|
||||||
|
*/
|
||||||
|
export const stopPortForward = action(async (portForward: ForwardedPort) => {
|
||||||
|
const pf = findPortForward(portForward);
|
||||||
|
|
||||||
|
if (!pf) {
|
||||||
|
logger.warn("[PORT-FORWARD-STORE] Error getting port-forward: port-forward not found", portForward);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
export async function removePortForward(portForward: ForwardedPort) {
|
|
||||||
const { port, forwardPort } = portForward;
|
const { port, forwardPort } = portForward;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiBase.del(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort }});
|
await apiBase.del(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort }});
|
||||||
await waitUntilFree(+forwardPort, 200, 1000);
|
await waitUntilFree(+forwardPort, 200, 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("[PORT-FORWARD-STORE] Error removing port-forward:", error, portForward);
|
logger.warn(`[PORT-FORWARD-STORE] Error stopping active port-forward: ${error}`, portForward);
|
||||||
throw (error);
|
throw (error);
|
||||||
}
|
}
|
||||||
portForwardStore.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPortForwards(clusterId?: string): Promise<ForwardedPort[]> {
|
pf.status = "Disabled";
|
||||||
try {
|
setPortForward(pf);
|
||||||
const response = await apiBase.get<PortForwardsResult>("/pods/port-forwards", { query: { clusterId }});
|
});
|
||||||
|
|
||||||
return response.portForwards;
|
/**
|
||||||
} catch (error) {
|
* remove and stop an existing port-forward.
|
||||||
logger.warn("[PORT-FORWARD-STORE] Error getting all port-forwards:", error);
|
* @param portForward the port-forward to remove.
|
||||||
|
*/
|
||||||
|
export const removePortForward = action(async (portForward: ForwardedPort) => {
|
||||||
|
const pf = findPortForward(portForward);
|
||||||
|
|
||||||
return [];
|
if (!pf) {
|
||||||
|
const error = new Error("port-forward not found");
|
||||||
|
|
||||||
|
logger.warn(`[PORT-FORWARD-STORE] Error getting port-forward: ${error}`, portForward);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await stopPortForward(portForward);
|
||||||
|
} catch (error) {
|
||||||
|
if (pf.status === "Active") {
|
||||||
|
logger.warn(`[PORT-FORWARD-STORE] Error removing port-forward: ${error}`, portForward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = portForwardStore.portForwards.findIndex(portForwardsEqual(portForward));
|
||||||
|
|
||||||
|
if (index >= 0 ) {
|
||||||
|
portForwardStore.portForwards.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the list of port-forwards in the store
|
||||||
|
*
|
||||||
|
* @returns the port-forwards
|
||||||
|
*/
|
||||||
|
export function getPortForwards(): ForwardedPort[] {
|
||||||
|
return portForwardStore.portForwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const portForwardStore = new PortForwardStore();
|
export const portForwardStore = new PortForwardStore();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user