1
0
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:
Jim Ehrismann 2021-09-30 21:20:12 -04:00
parent cd943bd34c
commit 7b15a5526c
11 changed files with 261 additions and 158 deletions

View File

@ -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 });

View File

@ -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>

View File

@ -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(),
]); ]);
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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() {

View File

@ -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 {

View File

@ -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";

View File

@ -66,5 +66,12 @@
margin-right: $margin; margin-right: $margin;
} }
} }
}
.portInput {
display: inline-block !important;
width: 70px;
margin-left: 10px;
margin-right: 10px;
} }
} }

View File

@ -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()}

View File

@ -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();