mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Added ability to add custom port on pod and service port forwarding
Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com>
This commit is contained in:
parent
8ac3203924
commit
015bb0821b
@ -180,7 +180,9 @@ export class Router {
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||
|
||||
// Port-forward API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, PortForwardRoute.routePortForward);
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routePortForward);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routeCurrentPortForward);
|
||||
this.router.add({ method: "delete", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routeCurrentPortForwardStop);
|
||||
|
||||
// Helm API
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
|
||||
|
||||
@ -34,6 +34,7 @@ interface PortForwardArgs {
|
||||
namespace: string;
|
||||
name: string;
|
||||
port: string;
|
||||
forwardPort: string;
|
||||
}
|
||||
|
||||
const internalPortRegex = /^forwarding from (?<address>.+) ->/i;
|
||||
@ -58,6 +59,7 @@ class PortForward {
|
||||
public name: string;
|
||||
public port: string;
|
||||
public internalPort?: number;
|
||||
public forwardPort: string;
|
||||
|
||||
constructor(public kubeConfig: string, args: PortForwardArgs) {
|
||||
this.clusterId = args.clusterId;
|
||||
@ -65,6 +67,7 @@ class PortForward {
|
||||
this.namespace = args.namespace;
|
||||
this.name = args.name;
|
||||
this.port = args.port;
|
||||
this.forwardPort = args.forwardPort;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
@ -74,7 +77,7 @@ class PortForward {
|
||||
"port-forward",
|
||||
"-n", this.namespace,
|
||||
`${this.kind}/${this.name}`,
|
||||
`:${this.port}`
|
||||
`${this.forwardPort??""}:${this.port}`
|
||||
];
|
||||
|
||||
this.process = spawn(kubectlBin, args, {
|
||||
@ -104,6 +107,10 @@ class PortForward {
|
||||
}
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
this.process.kill();
|
||||
}
|
||||
|
||||
public open() {
|
||||
shell.openExternal(`http://localhost:${this.internalPort}`)
|
||||
.catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, {
|
||||
@ -118,33 +125,73 @@ class PortForward {
|
||||
|
||||
export class PortForwardRoute {
|
||||
static async routePortForward(request: LensApiRequest) {
|
||||
const { params, response, cluster } = request;
|
||||
const { namespace, port, forwardPort, resourceType, resourceName } = params;
|
||||
|
||||
try {
|
||||
let portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port, forwardPort,
|
||||
});
|
||||
|
||||
if (!portForward) {
|
||||
logger.info(`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`);
|
||||
portForward = new PortForward(await cluster.getProxyKubeconfigPath(), {
|
||||
clusterId: cluster.id,
|
||||
kind: resourceType,
|
||||
namespace,
|
||||
name: resourceName,
|
||||
port,
|
||||
forwardPort
|
||||
});
|
||||
|
||||
const started = await portForward.start();
|
||||
|
||||
if (!started) {
|
||||
return respondJson(response, {
|
||||
message: `Failed to forward port ${port} to ${forwardPort}`
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
portForward.open();
|
||||
respondJson(response, {port: portForward.forwardPort});
|
||||
} catch (e) {
|
||||
return respondJson(response, {
|
||||
message: `Failed to forward port ${port} to ${forwardPort}`
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
static async routeCurrentPortForward(request: LensApiRequest) {
|
||||
const { params, response, cluster} = request;
|
||||
const { namespace, port, resourceType, resourceName } = params;
|
||||
let portForward = PortForward.getPortforward({
|
||||
const { namespace, port, forwardPort, resourceType, resourceName } = params;
|
||||
|
||||
const portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port
|
||||
namespace, port, forwardPort
|
||||
});
|
||||
|
||||
if (!portForward) {
|
||||
logger.info(`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`);
|
||||
portForward = new PortForward(await cluster.getProxyKubeconfigPath(), {
|
||||
clusterId: cluster.id,
|
||||
kind: resourceType,
|
||||
namespace,
|
||||
name: resourceName,
|
||||
port,
|
||||
});
|
||||
const started = await portForward.start();
|
||||
let forwardedPort = -1;
|
||||
|
||||
if (!started) {
|
||||
return respondJson(response, {
|
||||
message: "Failed to open port-forward"
|
||||
}, 400);
|
||||
}
|
||||
if (portForward) {
|
||||
forwardedPort = Number(portForward.forwardPort);
|
||||
}
|
||||
|
||||
portForward.open();
|
||||
respondJson(response, {port: forwardedPort});
|
||||
}
|
||||
|
||||
respondJson(response, {});
|
||||
static async routeCurrentPortForwardStop(request: LensApiRequest) {
|
||||
const { params, response, cluster} = request;
|
||||
const { namespace, port, forwardPort, resourceType, resourceName } = params;
|
||||
|
||||
const portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port, forwardPort,
|
||||
});
|
||||
|
||||
portForward.stop().then( () => {
|
||||
respondJson(response, {status: true});
|
||||
}).catch(error => logger.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,9 +35,10 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.Spinner {
|
||||
--spinner-size: #{$unit * 2};
|
||||
margin-left: $margin;
|
||||
position: absolute;
|
||||
.portInput {
|
||||
display: inline-block !important;
|
||||
width: 65px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,20 +28,44 @@ import { apiBase } from "../../api";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Input } from "../input";
|
||||
import { Button } from "../button";
|
||||
|
||||
interface Props {
|
||||
service: Service;
|
||||
port: ServicePort;
|
||||
}
|
||||
|
||||
interface PortForwardResult {
|
||||
port: number;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ServicePortComponent extends React.Component<Props> {
|
||||
@observable waiting = false;
|
||||
@observable forwardPort = -1;
|
||||
@observable isPortForwarded = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.checkExistingPortForwarding().then();
|
||||
}
|
||||
|
||||
async checkExistingPortForwarding() {
|
||||
const { service, port } = this.props;
|
||||
const response = await apiBase.get<PortForwardResult>(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
||||
|
||||
const activePort = response.port;
|
||||
|
||||
if (activePort && activePort != -1) {
|
||||
this.forwardPort = activePort;
|
||||
this.isPortForwarded = true;
|
||||
}
|
||||
}
|
||||
|
||||
async portForward() {
|
||||
@ -50,7 +74,26 @@ export class ServicePortComponent extends React.Component<Props> {
|
||||
this.waiting = true;
|
||||
|
||||
try {
|
||||
await apiBase.post(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}`, {});
|
||||
const response = await apiBase.post<PortForwardResult>(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
||||
|
||||
this.forwardPort = response.port;
|
||||
this.isPortForwarded = true;
|
||||
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
this.waiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
async stopPortForward() {
|
||||
const { service, port } = this.props;
|
||||
|
||||
this.waiting = true;
|
||||
|
||||
try {
|
||||
await apiBase.del(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
|
||||
this.isPortForwarded = false;
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
@ -61,14 +104,32 @@ export class ServicePortComponent extends React.Component<Props> {
|
||||
render() {
|
||||
const { port } = this.props;
|
||||
|
||||
if (this.forwardPort == -1) {
|
||||
this.forwardPort = port.port;
|
||||
}
|
||||
|
||||
const portForwardAction = async () => {
|
||||
if (this.isPortForwarded) {
|
||||
await this.stopPortForward();
|
||||
}else {
|
||||
await this.portForward();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
||||
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||
{port.toString()}
|
||||
{this.waiting && (
|
||||
<Spinner />
|
||||
)}
|
||||
</span>
|
||||
{port.toString()}
|
||||
{" "}
|
||||
<text>to</text>
|
||||
<Input className={"portInput"}
|
||||
type="number"
|
||||
min="0"
|
||||
max="65535"
|
||||
value= {String(this.forwardPort)}
|
||||
disabled={this.isPortForwarded}
|
||||
onChange={(value) => this.forwardPort = Number(value)}
|
||||
/>
|
||||
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -36,9 +36,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Spinner {
|
||||
--spinner-size: #{$unit * 2};
|
||||
margin-left: $margin;
|
||||
position: absolute;
|
||||
.portInput {
|
||||
display: inline-block !important;
|
||||
width: 65px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +28,8 @@ import { apiBase } from "../../api";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Button } from "../button";
|
||||
import { Input } from "../input";
|
||||
|
||||
interface Props {
|
||||
pod: Pod;
|
||||
@ -39,13 +40,36 @@ interface Props {
|
||||
}
|
||||
}
|
||||
|
||||
interface PortForwardResult {
|
||||
port: number;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class PodContainerPort extends React.Component<Props> {
|
||||
@observable waiting = false;
|
||||
@observable forwardPort = -1;
|
||||
@observable isPortForwarded = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.checkExistingPortForwarding().then();
|
||||
}
|
||||
|
||||
async checkExistingPortForwarding() {
|
||||
const { pod, port } = this.props;
|
||||
const response = await apiBase.get<PortForwardResult>(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
||||
|
||||
const activePort = response.port;
|
||||
|
||||
if (activePort && activePort != -1) {
|
||||
this.forwardPort = activePort;
|
||||
this.isPortForwarded = true;
|
||||
}
|
||||
}
|
||||
|
||||
async portForward() {
|
||||
@ -54,7 +78,25 @@ export class PodContainerPort extends React.Component<Props> {
|
||||
this.waiting = true;
|
||||
|
||||
try {
|
||||
await apiBase.post(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}`, {});
|
||||
const response = await apiBase.post<PortForwardResult>(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
||||
|
||||
this.forwardPort = response.port;
|
||||
this.isPortForwarded = true;
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
this.waiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
async stopPortForward() {
|
||||
const { pod, port } = this.props;
|
||||
|
||||
this.waiting = true;
|
||||
|
||||
try {
|
||||
await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
||||
this.isPortForwarded = false;
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
@ -67,14 +109,32 @@ export class PodContainerPort extends React.Component<Props> {
|
||||
const { name, containerPort, protocol } = port;
|
||||
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
|
||||
|
||||
if (this.forwardPort == -1) {
|
||||
this.forwardPort = containerPort;
|
||||
}
|
||||
|
||||
const portForwardAction = async () => {
|
||||
if (this.isPortForwarded) {
|
||||
await this.stopPortForward();
|
||||
}else {
|
||||
await this.portForward();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
||||
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||
{text}
|
||||
{this.waiting && (
|
||||
<Spinner />
|
||||
)}
|
||||
</span>
|
||||
{text}
|
||||
{" "}
|
||||
<text>to</text>
|
||||
<Input className={"portInput"}
|
||||
type="number"
|
||||
min="0"
|
||||
max="65535"
|
||||
value= {String(this.forwardPort)}
|
||||
disabled={this.isPortForwarded}
|
||||
onChange={(value) => this.forwardPort = Number(value)}
|
||||
/>
|
||||
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user