mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
refactor
Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com>
This commit is contained in:
parent
ff7084ec81
commit
cd943bd34c
@ -138,6 +138,7 @@ export class PortForwardRoute {
|
|||||||
|
|
||||||
let thePort: string; // undefined
|
let thePort: string; // undefined
|
||||||
const portNum = Number(forwardPort);
|
const portNum = Number(forwardPort);
|
||||||
|
|
||||||
if (portNum > 0 && portNum < 65536) {
|
if (portNum > 0 && portNum < 65536) {
|
||||||
thePort = forwardPort;
|
thePort = forwardPort;
|
||||||
}
|
}
|
||||||
@ -201,7 +202,8 @@ export class PortForwardRoute {
|
|||||||
port: f.port,
|
port: f.port,
|
||||||
forwardPort: f.forwardPort,
|
forwardPort: f.forwardPort,
|
||||||
};
|
};
|
||||||
portForwards.push(pf)
|
|
||||||
|
portForwards.push(pf);
|
||||||
});
|
});
|
||||||
|
|
||||||
respondJson(response, {portForwards});
|
respondJson(response, {portForwards});
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import "./port-forward-details.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { PortForwardItem } from "./port-forward.store";
|
import type { PortForwardItem } from "../../port-forward";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
portForward: PortForwardItem;
|
portForward: PortForwardItem;
|
||||||
|
|||||||
@ -30,87 +30,87 @@ import { Icon } from "../icon";
|
|||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { PortForwardItem, portForwardStore } from "./port-forward.store";
|
import { modifyPortForward, PortForwardItem } from "../../port-forward";
|
||||||
import { isNumber } from "../input/input_validators";
|
import { isNumber } from "../input/input_validators";
|
||||||
|
|
||||||
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 PortForwardItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PortForwardDialog extends Component<Props> {
|
export class PortForwardDialog extends Component<Props> {
|
||||||
@observable ready = false;
|
@observable ready = false;
|
||||||
@observable currentPort = 0;
|
@observable currentPort = 0;
|
||||||
@observable desiredPort = 0;
|
@observable desiredPort = 0;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static open(portForward: PortForwardItem) {
|
static open(portForward: PortForwardItem) {
|
||||||
dialogState.isOpen = true;
|
dialogState.isOpen = true;
|
||||||
dialogState.data = portForward;
|
dialogState.data = portForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
static close() {
|
static close() {
|
||||||
dialogState.isOpen = false;
|
dialogState.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get portForward() {
|
get portForward() {
|
||||||
return dialogState.data;
|
return dialogState.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
PortForwardDialog.close();
|
PortForwardDialog.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
@computed get scaleMax() {
|
@computed get scaleMax() {
|
||||||
const { currentPort } = this;
|
const { currentPort } = this;
|
||||||
const defaultMax = 50;
|
const defaultMax = 50;
|
||||||
|
|
||||||
return currentPort <= defaultMax
|
return currentPort <= defaultMax
|
||||||
? defaultMax * 2
|
? defaultMax * 2
|
||||||
: currentPort * 2;
|
: currentPort * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpen = async () => {
|
onOpen = async () => {
|
||||||
const { portForward } = this;
|
const { portForward } = this;
|
||||||
|
|
||||||
this.currentPort = +portForward.forwardPort;
|
this.currentPort = +portForward.forwardPort;
|
||||||
this.desiredPort = this.currentPort;
|
this.desiredPort = this.currentPort;
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
changePort = async () => {
|
changePort = async () => {
|
||||||
const { portForward } = this;
|
const { portForward } = this;
|
||||||
const { currentPort, desiredPort, close } = this;
|
const { currentPort, desiredPort, close } = this;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentPort !== desiredPort) {
|
if (currentPort !== desiredPort) {
|
||||||
await portForwardStore.modify(portForward, desiredPort);
|
await modifyPortForward(portForward, desiredPort);
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Notifications.error(err);
|
Notifications.error(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderContents() {
|
renderContents() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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}
|
Current port: {this.currentPort}
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
required autoFocus
|
required autoFocus
|
||||||
@ -123,43 +123,42 @@ const dialogState = observable.object({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="warning" data-testid="warning">
|
<div className="warning" data-testid="warning">
|
||||||
<Icon material="warning"/>
|
<Icon material="warning" />
|
||||||
Current port-forwarding will be removed and a new one will be started
|
Current port-forwarding will be removed and a new one will be started
|
||||||
</div>
|
</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.getName() : "";
|
||||||
const header = (
|
const header = (
|
||||||
<h5>
|
<h5>
|
||||||
Change Port <span>{resourceName}</span>
|
Change Port <span>{resourceName}</span>
|
||||||
</h5>
|
</h5>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
{...dialogProps}
|
{...dialogProps}
|
||||||
isOpen={dialogState.isOpen}
|
isOpen={dialogState.isOpen}
|
||||||
className={cssNames("PortForwardDialog", className)}
|
className={cssNames("PortForwardDialog", className)}
|
||||||
onOpen={this.onOpen}
|
onOpen={this.onOpen}
|
||||||
onClose={this.onClose}
|
onClose={this.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.changePort}
|
next={this.changePort}
|
||||||
nextLabel="Change Port"
|
nextLabel="Change Port"
|
||||||
disabledNext={!this.ready}
|
disabledNext={!this.ready}
|
||||||
>
|
>
|
||||||
{this.renderContents()}
|
{this.renderContents()}
|
||||||
</WizardStep>
|
</WizardStep>
|
||||||
</Wizard>
|
</Wizard>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,22 +21,22 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { boundMethod, cssNames, openExternal } from "../../utils";
|
import { boundMethod, cssNames, openExternal } from "../../utils";
|
||||||
import { PortForwardItem, portForwardStore } from "./port-forward.store";
|
import { 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 { Notifications } from "../notifications";
|
||||||
import { PortForwardDialog } from "./port-forward-dialog";
|
import { PortForwardDialog } from "./port-forward-dialog";
|
||||||
|
|
||||||
interface Props extends MenuActionsProps {
|
interface Props extends MenuActionsProps {
|
||||||
portForward: PortForwardItem;
|
portForward: PortForwardItem;
|
||||||
hideDetails?(): void;
|
hideDetails?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PortForwardMenu extends React.Component<Props> {
|
export class PortForwardMenu extends React.Component<Props> {
|
||||||
@boundMethod
|
@boundMethod
|
||||||
remove() {
|
remove() {
|
||||||
return portForwardStore.remove(this.props.portForward);
|
return removePortForward(this.props.portForward);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -55,40 +55,39 @@ export class PortForwardMenu extends React.Component<Props> {
|
|||||||
});
|
});
|
||||||
Notifications.error(`Failed to open ${browseTo} in browser`);
|
Notifications.error(`Failed to open ${browseTo} in browser`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const { portForward, toolbar } = this.props;
|
const { portForward, toolbar } = this.props;
|
||||||
|
|
||||||
if (!portForward) return null;
|
if (!portForward) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={this.openInBrowser}>
|
<MenuItem onClick={this.openInBrowser}>
|
||||||
<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>
|
||||||
<MenuItem onClick={() => PortForwardDialog.open(portForward)}>
|
<MenuItem onClick={() => PortForwardDialog.open(portForward)}>
|
||||||
<Icon material="edit" tooltip="Change port" interactive={toolbar}/>
|
<Icon material="edit" tooltip="Change port" interactive={toolbar} />
|
||||||
<span className="title">Edit</span>
|
<span className="title">Edit</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, ...menuProps } = this.props;
|
const { className, ...menuProps } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuActions
|
<MenuActions
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
className={cssNames("PortForwardMenu", className)}
|
className={cssNames("PortForwardMenu", className)}
|
||||||
removeAction={this.remove}
|
removeAction={this.remove}
|
||||||
>
|
>
|
||||||
{this.renderContent()}
|
{this.renderContent()}
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,242 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2021 OpenLens Authors
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to
|
|
||||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import { computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
|
|
||||||
import { ItemObject, ItemStore } from "../../../common/item.store";
|
|
||||||
import { autoBind, createStorage, delay } from "../../utils";
|
|
||||||
import { apiBase } from "../../api";
|
|
||||||
import { waitUntilFree } from "tcp-port-used";
|
|
||||||
import { getHostedClusterId } from "../../utils";
|
|
||||||
import { podsApi } from "../../../common/k8s-api/endpoints";
|
|
||||||
|
|
||||||
interface PortForwardResult {
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ForwardedPort {
|
|
||||||
clusterId: string;
|
|
||||||
kind: string;
|
|
||||||
namespace: string;
|
|
||||||
name: string;
|
|
||||||
port: string;
|
|
||||||
forwardPort: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PortForwardsResult {
|
|
||||||
portForwards: ForwardedPort[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PortForwardItem implements ItemObject {
|
|
||||||
clusterId: string;
|
|
||||||
kind: string;
|
|
||||||
namespace: string;
|
|
||||||
name: string;
|
|
||||||
port: string;
|
|
||||||
forwardPort: string;
|
|
||||||
|
|
||||||
interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
autoBind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNs() {
|
|
||||||
return this.namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
|
||||||
return this.forwardPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
getId() {
|
|
||||||
return this.forwardPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
getKind() {
|
|
||||||
return this.kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPort() {
|
|
||||||
return this.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
getForwardPort() {
|
|
||||||
return this.forwardPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSearchFields() {
|
|
||||||
return [
|
|
||||||
this.name,
|
|
||||||
this.id,
|
|
||||||
this.kind,
|
|
||||||
this.port,
|
|
||||||
this.forwardPort,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor() {
|
|
||||||
// for now only support pods
|
|
||||||
if (this.kind === "pod") {
|
|
||||||
this.interval = setInterval( this.keepAlive, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async keepAlive() {
|
|
||||||
console.log("keepAlive() called");
|
|
||||||
let pod = await podsApi.get({name: this.getName(), namespace: this.getNs()});
|
|
||||||
if (!pod || pod.metadata.deletionTimestamp) {
|
|
||||||
console.log("keepAlive() pod lost!");
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.interval = undefined;
|
|
||||||
// wait for resource
|
|
||||||
while (true) {
|
|
||||||
await delay(1000);
|
|
||||||
pod = await podsApi.get({name: this.getName(), namespace: this.getNs()});
|
|
||||||
if (pod) {
|
|
||||||
if (pod.getContainerStatuses()?.[0]?.ready) {
|
|
||||||
console.log("keepAlive() pod found!");
|
|
||||||
// restart port forward
|
|
||||||
await portForwardStore.modify(this, +this.getForwardPort());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PortForwardStore extends ItemStore<PortForwardItem> {
|
|
||||||
private storage = createStorage<ForwardedPort[] | undefined>("port_forwards", undefined);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
makeObservable(this);
|
|
||||||
autoBind(this);
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async init() {
|
|
||||||
await this.storage.whenReady;
|
|
||||||
|
|
||||||
const savedPortForwards = this.storage.get(); // undefined on first load
|
|
||||||
|
|
||||||
if (Array.isArray(savedPortForwards)) {
|
|
||||||
await Promise.all(savedPortForwards.map(pf => {
|
|
||||||
const port = new PortForwardItem;
|
|
||||||
port.clusterId = pf.clusterId;
|
|
||||||
port.kind = pf.kind;
|
|
||||||
port.namespace = pf.namespace;
|
|
||||||
port.name = pf.name;
|
|
||||||
port.port = pf.port;
|
|
||||||
port.forwardPort = pf.forwardPort;
|
|
||||||
return this.add(port);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.portForwards = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable selectedItemId?: string;
|
|
||||||
@observable portForwards: PortForwardItem[];
|
|
||||||
|
|
||||||
@computed get selectedItem() {
|
|
||||||
return this.portForwards.find((e: ItemObject) => e.getId() === this.selectedItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch() {
|
|
||||||
const disposers: IReactionDisposer[] = [
|
|
||||||
reaction(() => this.portForwards, () => this.loadAll()),
|
|
||||||
];
|
|
||||||
|
|
||||||
return () => disposers.forEach((dispose) => dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAll() {
|
|
||||||
return this.loadItems(async () => {
|
|
||||||
const response = await apiBase.get<PortForwardsResult>(`/port-forwards`, {});
|
|
||||||
|
|
||||||
const portForwards = response.portForwards?.filter(pf => pf.clusterId == getHostedClusterId());
|
|
||||||
this.storage.set(portForwards);
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
portForwards?.forEach(pf => {
|
|
||||||
const port = new PortForwardItem;
|
|
||||||
port.clusterId = pf.clusterId;
|
|
||||||
port.kind = pf.kind;
|
|
||||||
port.namespace = pf.namespace;
|
|
||||||
port.name = pf.name;
|
|
||||||
port.port = pf.port;
|
|
||||||
port.forwardPort = pf.forwardPort;
|
|
||||||
port.monitor();
|
|
||||||
this.portForwards.push(port);
|
|
||||||
})
|
|
||||||
return this.portForwards;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.portForwards?.forEach(pf => {
|
|
||||||
if (pf.interval) clearInterval(pf.interval)
|
|
||||||
});
|
|
||||||
this.portForwards = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async add(portForward: PortForwardItem) {
|
|
||||||
await add(portForward);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
async modify(portForward: PortForwardItem, desiredPort: number) {
|
|
||||||
await remove(portForward);
|
|
||||||
portForward.forwardPort = desiredPort.toString();
|
|
||||||
await add(portForward);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(portForward: PortForwardItem) {
|
|
||||||
await remove(portForward);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeSelectedItems() {
|
|
||||||
return Promise.all(this.selectedItems.map(this.remove));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function add(portForward: PortForwardItem) {
|
|
||||||
const response = await apiBase.post<PortForwardResult>(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.getForwardPort()}`, {});
|
|
||||||
if (response?.port != +portForward.getForwardPort() ) {
|
|
||||||
console.log(`specified ${portForward.getForwardPort()} got ${response.port}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function remove(portForward: PortForwardItem) {
|
|
||||||
await apiBase.del(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.forwardPort}`, {})
|
|
||||||
await waitUntilFree(+portForward.getForwardPort(), 200, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const portForwardStore = new PortForwardStore();
|
|
||||||
@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import type { RouteComponentProps } from "react-router-dom";
|
import type { RouteComponentProps } from "react-router-dom";
|
||||||
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
||||||
import { PortForwardItem, portForwardStore } from "./port-forward.store";
|
import { PortForwardItem, portForwardStore } from "../../port-forward";
|
||||||
import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes";
|
import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
import { PortForwardDetails } from "./port-forward-details";
|
import { PortForwardDetails } from "./port-forward-details";
|
||||||
|
|||||||
@ -30,6 +30,7 @@ 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 { Input } from "../input";
|
||||||
|
import { portForwardStore } from "../../port-forward/port-forward.store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -88,6 +89,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
|
|
||||||
this.forwardPort = response.port;
|
this.forwardPort = response.port;
|
||||||
this.isPortForwarded = true;
|
this.isPortForwarded = true;
|
||||||
|
portForwardStore.reset();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -103,6 +105,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
try {
|
try {
|
||||||
await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
||||||
this.isPortForwarded = false;
|
this.isPortForwarded = false;
|
||||||
|
portForwardStore.reset();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -35,6 +35,8 @@ import { ContainerCharts } from "./container-charts";
|
|||||||
import { LocaleDate } from "../locale-date";
|
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 { disposeOnUnmount } from "mobx-react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -44,6 +46,12 @@ interface Props {
|
|||||||
|
|
||||||
export class PodDetailsContainer extends React.Component<Props> {
|
export class PodDetailsContainer extends React.Component<Props> {
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
portForwardStore.watch(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
renderStatus(state: string, status: IPodContainerStatus) {
|
renderStatus(state: string, status: IPodContainerStatus) {
|
||||||
const ready = status ? status.ready : "";
|
const ready = status ? status.ready : "";
|
||||||
|
|
||||||
|
|||||||
23
src/renderer/port-forward/index.ts
Normal file
23
src/renderer/port-forward/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./port-forward.store";
|
||||||
|
export * from "./port-forward-item";
|
||||||
75
src/renderer/port-forward/port-forward-item.ts
Normal file
75
src/renderer/port-forward/port-forward-item.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import type { ItemObject } from "../../common/item.store";
|
||||||
|
import { autoBind } from "../../common/utils";
|
||||||
|
|
||||||
|
export class PortForwardItem implements ItemObject {
|
||||||
|
clusterId: string;
|
||||||
|
kind: string;
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
port: string;
|
||||||
|
forwardPort: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
autoBind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNs() {
|
||||||
|
return this.namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.forwardPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.forwardPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKind() {
|
||||||
|
return this.kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPort() {
|
||||||
|
return this.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
getForwardPort() {
|
||||||
|
return this.forwardPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchFields() {
|
||||||
|
return [
|
||||||
|
this.name,
|
||||||
|
this.id,
|
||||||
|
this.kind,
|
||||||
|
this.port,
|
||||||
|
this.forwardPort,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
182
src/renderer/port-forward/port-forward.store.ts
Normal file
182
src/renderer/port-forward/port-forward.store.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import { computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
|
||||||
|
import { ItemObject, ItemStore } from "../../common/item.store";
|
||||||
|
import { autoBind, createStorage } from "../utils";
|
||||||
|
import { getHostedClusterId } from "../utils";
|
||||||
|
import { PortForwardItem } from "./port-forward-item";
|
||||||
|
import { apiBase } from "../api";
|
||||||
|
import { waitUntilFree } from "tcp-port-used";
|
||||||
|
|
||||||
|
export interface ForwardedPort {
|
||||||
|
clusterId: string;
|
||||||
|
kind: string;
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
port: string;
|
||||||
|
forwardPort: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PortForwardStore extends ItemStore<PortForwardItem> {
|
||||||
|
private storage = createStorage<ForwardedPort[] | undefined>("port_forwards", undefined);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
makeObservable(this);
|
||||||
|
autoBind(this);
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init() {
|
||||||
|
await this.storage.whenReady;
|
||||||
|
|
||||||
|
const savedPortForwards = this.storage.get(); // undefined on first load
|
||||||
|
|
||||||
|
if (Array.isArray(savedPortForwards)) {
|
||||||
|
await Promise.all(savedPortForwards.map(pf => {
|
||||||
|
const port = new PortForwardItem;
|
||||||
|
|
||||||
|
port.clusterId = pf.clusterId;
|
||||||
|
port.kind = pf.kind;
|
||||||
|
port.namespace = pf.namespace;
|
||||||
|
port.name = pf.name;
|
||||||
|
port.port = pf.port;
|
||||||
|
port.forwardPort = pf.forwardPort;
|
||||||
|
|
||||||
|
return addPortForward(port);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.portForwards = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable selectedItemId?: string;
|
||||||
|
@observable portForwards: PortForwardItem[];
|
||||||
|
|
||||||
|
@computed get selectedItem() {
|
||||||
|
return this.portForwards.find((e: ItemObject) => e.getId() === this.selectedItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch() {
|
||||||
|
const disposers: IReactionDisposer[] = [
|
||||||
|
reaction(() => this.portForwards, () => this.loadAll()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => disposers.forEach((dispose) => dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAll() {
|
||||||
|
return this.loadItems(async () => {
|
||||||
|
let portForwards = await getPortForwards();
|
||||||
|
|
||||||
|
// filter out any not for this cluster
|
||||||
|
portForwards = portForwards?.filter(pf => pf.clusterId == getHostedClusterId());
|
||||||
|
this.storage.set(portForwards);
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
portForwards?.forEach(pf => {
|
||||||
|
const port = new PortForwardItem;
|
||||||
|
|
||||||
|
port.clusterId = pf.clusterId;
|
||||||
|
port.kind = pf.kind;
|
||||||
|
port.namespace = pf.namespace;
|
||||||
|
port.name = pf.name;
|
||||||
|
port.port = pf.port;
|
||||||
|
port.forwardPort = pf.forwardPort;
|
||||||
|
this.portForwards.push(port);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.portForwards;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.portForwards = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeSelectedItems() {
|
||||||
|
return Promise.all(this.selectedItems.map(removePortForward));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PortForwardResult {
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PortForwardsResult {
|
||||||
|
portForwards: ForwardedPort[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addPortForward(portForward: PortForwardItem): Promise<number> {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await apiBase.post<PortForwardResult>(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.getForwardPort()}`, {});
|
||||||
|
|
||||||
|
if (response?.port != +portForward.getForwardPort()) {
|
||||||
|
console.log(`specified ${portForward.getForwardPort()} got ${response.port}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
portForwardStore.reset();
|
||||||
|
|
||||||
|
return response?.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function modifyPortForward(portForward: PortForwardItem, desiredPort: number) {
|
||||||
|
try {
|
||||||
|
await removePortForward(portForward);
|
||||||
|
portForward.forwardPort = desiredPort.toString();
|
||||||
|
await addPortForward(portForward);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
portForwardStore.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function removePortForward(portForward: PortForwardItem) {
|
||||||
|
try {
|
||||||
|
await apiBase.del(`/pods/${portForward.getNs()}/${portForward.getKind()}/${portForward.getName()}/port-forward/${portForward.getPort()}/${portForward.forwardPort}`, {});
|
||||||
|
await waitUntilFree(+portForward.getForwardPort(), 200, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
portForwardStore.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPortForwards(): Promise<ForwardedPort[]> {
|
||||||
|
try {
|
||||||
|
const response = await apiBase.get<PortForwardsResult>(`/port-forwards`, {});
|
||||||
|
|
||||||
|
return response.portForwards;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const portForwardStore = new PortForwardStore();
|
||||||
Loading…
Reference in New Issue
Block a user