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
eca03ddd6f
commit
b07e19e6a4
@ -180,7 +180,9 @@ export class Router {
|
|||||||
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||||
|
|
||||||
// Port-forward API
|
// 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
|
// Helm API
|
||||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
|
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
|
||||||
|
|||||||
@ -34,6 +34,7 @@ interface PortForwardArgs {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
port: string;
|
port: string;
|
||||||
|
forwardPort: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalPortRegex = /^forwarding from (?<address>.+) ->/i;
|
const internalPortRegex = /^forwarding from (?<address>.+) ->/i;
|
||||||
@ -58,6 +59,7 @@ class PortForward {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public port: string;
|
public port: string;
|
||||||
public internalPort?: number;
|
public internalPort?: number;
|
||||||
|
public forwardPort: string;
|
||||||
|
|
||||||
constructor(public kubeConfig: string, args: PortForwardArgs) {
|
constructor(public kubeConfig: string, args: PortForwardArgs) {
|
||||||
this.clusterId = args.clusterId;
|
this.clusterId = args.clusterId;
|
||||||
@ -65,6 +67,7 @@ class PortForward {
|
|||||||
this.namespace = args.namespace;
|
this.namespace = args.namespace;
|
||||||
this.name = args.name;
|
this.name = args.name;
|
||||||
this.port = args.port;
|
this.port = args.port;
|
||||||
|
this.forwardPort = args.forwardPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
@ -74,7 +77,7 @@ class PortForward {
|
|||||||
"port-forward",
|
"port-forward",
|
||||||
"-n", this.namespace,
|
"-n", this.namespace,
|
||||||
`${this.kind}/${this.name}`,
|
`${this.kind}/${this.name}`,
|
||||||
`:${this.port}`
|
`${this.forwardPort??""}:${this.port}`
|
||||||
];
|
];
|
||||||
|
|
||||||
this.process = spawn(kubectlBin, args, {
|
this.process = spawn(kubectlBin, args, {
|
||||||
@ -104,6 +107,10 @@ class PortForward {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
this.process.kill();
|
||||||
|
}
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
shell.openExternal(`http://localhost:${this.internalPort}`)
|
shell.openExternal(`http://localhost:${this.internalPort}`)
|
||||||
.catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, {
|
.catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, {
|
||||||
@ -119,10 +126,12 @@ class PortForward {
|
|||||||
export class PortForwardRoute {
|
export class PortForwardRoute {
|
||||||
static async routePortForward(request: LensApiRequest) {
|
static async routePortForward(request: LensApiRequest) {
|
||||||
const { params, response, cluster } = request;
|
const { params, response, cluster } = request;
|
||||||
const { namespace, port, resourceType, resourceName } = params;
|
const { namespace, port, forwardPort, resourceType, resourceName } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
let portForward = PortForward.getPortforward({
|
let portForward = PortForward.getPortforward({
|
||||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||||
namespace, port
|
namespace, port, forwardPort,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!portForward) {
|
if (!portForward) {
|
||||||
@ -133,18 +142,56 @@ export class PortForwardRoute {
|
|||||||
namespace,
|
namespace,
|
||||||
name: resourceName,
|
name: resourceName,
|
||||||
port,
|
port,
|
||||||
|
forwardPort
|
||||||
});
|
});
|
||||||
|
|
||||||
const started = await portForward.start();
|
const started = await portForward.start();
|
||||||
|
|
||||||
if (!started) {
|
if (!started) {
|
||||||
return respondJson(response, {
|
return respondJson(response, {
|
||||||
message: "Failed to open port-forward"
|
message: `Failed to forward port ${port} to ${forwardPort}`
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
portForward.open();
|
portForward.open();
|
||||||
|
respondJson(response, {port: portForward.forwardPort});
|
||||||
|
} catch (e) {
|
||||||
|
return respondJson(response, {
|
||||||
|
message: `Failed to forward port ${port} to ${forwardPort}`
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
respondJson(response, {});
|
static async routeCurrentPortForward(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
|
||||||
|
});
|
||||||
|
|
||||||
|
let forwardedPort = -1;
|
||||||
|
|
||||||
|
if (portForward) {
|
||||||
|
forwardedPort = Number(portForward.forwardPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
respondJson(response, {port: forwardedPort});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Spinner {
|
.portInput {
|
||||||
--spinner-size: #{$unit * 2};
|
display: inline-block !important;
|
||||||
margin-left: $margin;
|
width: 65px;
|
||||||
position: absolute;
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,20 +28,44 @@ import { apiBase } from "../../api";
|
|||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Spinner } from "../spinner";
|
import { Input } from "../input";
|
||||||
|
import { Button } from "../button";
|
||||||
|
|
||||||
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;
|
||||||
|
@observable forwardPort = -1;
|
||||||
|
@observable isPortForwarded = false;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
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() {
|
async portForward() {
|
||||||
@ -50,7 +74,26 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -61,14 +104,32 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { port } = this.props;
|
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 (
|
return (
|
||||||
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
||||||
<span title="Open in a browser" onClick={() => this.portForward() }>
|
|
||||||
{port.toString()}
|
{port.toString()}
|
||||||
{this.waiting && (
|
{" "}
|
||||||
<Spinner />
|
<text>to</text>
|
||||||
)}
|
<Input className={"portInput"}
|
||||||
</span>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,9 +36,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Spinner {
|
.portInput {
|
||||||
--spinner-size: #{$unit * 2};
|
display: inline-block !important;
|
||||||
margin-left: $margin;
|
width: 65px;
|
||||||
position: absolute;
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +28,8 @@ import { apiBase } from "../../api";
|
|||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Spinner } from "../spinner";
|
import { Button } from "../button";
|
||||||
|
import { Input } from "../input";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -39,13 +40,36 @@ 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;
|
||||||
|
@observable forwardPort = -1;
|
||||||
|
@observable isPortForwarded = false;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
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() {
|
async portForward() {
|
||||||
@ -54,7 +78,25 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -67,14 +109,32 @@ 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}`;
|
||||||
|
|
||||||
|
if (this.forwardPort == -1) {
|
||||||
|
this.forwardPort = containerPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const portForwardAction = async () => {
|
||||||
|
if (this.isPortForwarded) {
|
||||||
|
await this.stopPortForward();
|
||||||
|
}else {
|
||||||
|
await this.portForward();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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() }>
|
|
||||||
{text}
|
{text}
|
||||||
{this.waiting && (
|
{" "}
|
||||||
<Spinner />
|
<text>to</text>
|
||||||
)}
|
<Input className={"portInput"}
|
||||||
</span>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user