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
|
||||
const portNum = Number(forwardPort);
|
||||
|
||||
if (portNum > 0 && portNum < 65536) {
|
||||
thePort = forwardPort;
|
||||
}
|
||||
@ -201,7 +202,8 @@ export class PortForwardRoute {
|
||||
port: f.port,
|
||||
forwardPort: f.forwardPort,
|
||||
};
|
||||
portForwards.push(pf)
|
||||
|
||||
portForwards.push(pf);
|
||||
});
|
||||
|
||||
respondJson(response, {portForwards});
|
||||
|
||||
@ -23,7 +23,7 @@ import "./port-forward-details.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { PortForwardItem } from "./port-forward.store";
|
||||
import type { PortForwardItem } from "../../port-forward";
|
||||
|
||||
interface Props {
|
||||
portForward: PortForwardItem;
|
||||
|
||||
@ -30,87 +30,87 @@ import { Icon } from "../icon";
|
||||
import { Input } from "../input";
|
||||
import { Notifications } from "../notifications";
|
||||
import { cssNames } from "../../utils";
|
||||
import { PortForwardItem, portForwardStore } from "./port-forward.store";
|
||||
import { modifyPortForward, PortForwardItem } from "../../port-forward";
|
||||
import { isNumber } from "../input/input_validators";
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
const dialogState = observable.object({
|
||||
isOpen: false,
|
||||
data: null as PortForwardItem,
|
||||
});
|
||||
|
||||
@observer
|
||||
export class PortForwardDialog extends Component<Props> {
|
||||
@observable ready = false;
|
||||
@observable currentPort = 0;
|
||||
@observable desiredPort = 0;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
static open(portForward: PortForwardItem) {
|
||||
dialogState.isOpen = true;
|
||||
dialogState.data = portForward;
|
||||
}
|
||||
|
||||
static close() {
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
get portForward() {
|
||||
return dialogState.data;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
PortForwardDialog.close();
|
||||
};
|
||||
|
||||
@computed get scaleMax() {
|
||||
const { currentPort } = this;
|
||||
const defaultMax = 50;
|
||||
|
||||
return currentPort <= defaultMax
|
||||
? defaultMax * 2
|
||||
: currentPort * 2;
|
||||
}
|
||||
|
||||
onOpen = async () => {
|
||||
const { portForward } = this;
|
||||
|
||||
this.currentPort = +portForward.forwardPort;
|
||||
this.desiredPort = this.currentPort;
|
||||
this.ready = true;
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.ready = false;
|
||||
};
|
||||
|
||||
changePort = async () => {
|
||||
const { portForward } = this;
|
||||
const { currentPort, desiredPort, close } = this;
|
||||
|
||||
|
||||
@observer
|
||||
export class PortForwardDialog extends Component<Props> {
|
||||
@observable ready = false;
|
||||
@observable currentPort = 0;
|
||||
@observable desiredPort = 0;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
static open(portForward: PortForwardItem) {
|
||||
dialogState.isOpen = true;
|
||||
dialogState.data = portForward;
|
||||
}
|
||||
|
||||
static close() {
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
get portForward() {
|
||||
return dialogState.data;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
PortForwardDialog.close();
|
||||
};
|
||||
|
||||
@computed get scaleMax() {
|
||||
const { currentPort } = this;
|
||||
const defaultMax = 50;
|
||||
|
||||
return currentPort <= defaultMax
|
||||
? defaultMax * 2
|
||||
: currentPort * 2;
|
||||
}
|
||||
|
||||
onOpen = async () => {
|
||||
const { portForward } = this;
|
||||
|
||||
this.currentPort = +portForward.forwardPort;
|
||||
this.desiredPort = this.currentPort;
|
||||
this.ready = true;
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.ready = false;
|
||||
};
|
||||
|
||||
changePort = async () => {
|
||||
const { portForward } = this;
|
||||
const { currentPort, desiredPort, close } = this;
|
||||
|
||||
try {
|
||||
if (currentPort !== desiredPort) {
|
||||
await portForwardStore.modify(portForward, desiredPort);
|
||||
await modifyPortForward(portForward, desiredPort);
|
||||
}
|
||||
close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
renderContents() {
|
||||
return (
|
||||
<>
|
||||
|
||||
renderContents() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gaps align-center">
|
||||
<div className="input-container flex align-center">
|
||||
<div className="current-port" data-testid="current-port">
|
||||
Current port: {this.currentPort}
|
||||
Current port: {this.currentPort}
|
||||
</div>
|
||||
<Input
|
||||
required autoFocus
|
||||
@ -123,43 +123,42 @@ const dialogState = observable.object({
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, ...dialogProps } = this.props;
|
||||
const resourceName = this.portForward ? this.portForward.getName() : "";
|
||||
const header = (
|
||||
<h5>
|
||||
Change Port <span>{resourceName}</span>
|
||||
</h5>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
isOpen={dialogState.isOpen}
|
||||
className={cssNames("PortForwardDialog", className)}
|
||||
onOpen={this.onOpen}
|
||||
onClose={this.onClose}
|
||||
close={this.close}
|
||||
>
|
||||
<Wizard header={header} done={this.close}>
|
||||
<WizardStep
|
||||
contentClass="flex gaps column"
|
||||
next={this.changePort}
|
||||
nextLabel="Change Port"
|
||||
disabledNext={!this.ready}
|
||||
>
|
||||
{this.renderContents()}
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { className, ...dialogProps } = this.props;
|
||||
const resourceName = this.portForward ? this.portForward.getName() : "";
|
||||
const header = (
|
||||
<h5>
|
||||
Change Port <span>{resourceName}</span>
|
||||
</h5>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
isOpen={dialogState.isOpen}
|
||||
className={cssNames("PortForwardDialog", className)}
|
||||
onOpen={this.onOpen}
|
||||
onClose={this.onClose}
|
||||
close={this.close}
|
||||
>
|
||||
<Wizard header={header} done={this.close}>
|
||||
<WizardStep
|
||||
contentClass="flex gaps column"
|
||||
next={this.changePort}
|
||||
nextLabel="Change Port"
|
||||
disabledNext={!this.ready}
|
||||
>
|
||||
{this.renderContents()}
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,22 +21,22 @@
|
||||
|
||||
import React from "react";
|
||||
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 { MenuItem } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { Notifications } from "../notifications";
|
||||
import { PortForwardDialog } from "./port-forward-dialog";
|
||||
|
||||
|
||||
interface Props extends MenuActionsProps {
|
||||
portForward: PortForwardItem;
|
||||
hideDetails?(): void;
|
||||
}
|
||||
|
||||
|
||||
export class PortForwardMenu extends React.Component<Props> {
|
||||
@boundMethod
|
||||
remove() {
|
||||
return portForwardStore.remove(this.props.portForward);
|
||||
return removePortForward(this.props.portForward);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -55,40 +55,39 @@ export class PortForwardMenu extends React.Component<Props> {
|
||||
});
|
||||
Notifications.error(`Failed to open ${browseTo} in browser`);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderContent() {
|
||||
const { portForward, toolbar } = this.props;
|
||||
|
||||
|
||||
if (!portForward) return null;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => PortForwardDialog.open(portForward)}>
|
||||
<Icon material="edit" tooltip="Change port" interactive={toolbar}/>
|
||||
<span className="title">Edit</span>
|
||||
</MenuItem>
|
||||
<Icon material="edit" tooltip="Change port" interactive={toolbar} />
|
||||
<span className="title">Edit</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, ...menuProps } = this.props;
|
||||
|
||||
return (
|
||||
<MenuActions
|
||||
{...menuProps}
|
||||
className={cssNames("PortForwardMenu", className)}
|
||||
removeAction={this.remove}
|
||||
>
|
||||
{this.renderContent()}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, ...menuProps } = this.props;
|
||||
|
||||
return (
|
||||
<MenuActions
|
||||
{...menuProps}
|
||||
className={cssNames("PortForwardMenu", className)}
|
||||
removeAction={this.remove}
|
||||
>
|
||||
{this.renderContent()}
|
||||
</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 type { RouteComponentProps } from "react-router-dom";
|
||||
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 { navigation } from "../../navigation";
|
||||
import { PortForwardDetails } from "./port-forward-details";
|
||||
|
||||
@ -30,6 +30,7 @@ import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import { Input } from "../input";
|
||||
import { portForwardStore } from "../../port-forward/port-forward.store";
|
||||
|
||||
interface Props {
|
||||
pod: Pod;
|
||||
@ -88,6 +89,7 @@ export class PodContainerPort extends React.Component<Props> {
|
||||
|
||||
this.forwardPort = response.port;
|
||||
this.isPortForwarded = true;
|
||||
portForwardStore.reset();
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
@ -103,6 +105,7 @@ export class PodContainerPort extends React.Component<Props> {
|
||||
try {
|
||||
await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
|
||||
this.isPortForwarded = false;
|
||||
portForwardStore.reset();
|
||||
} catch(error) {
|
||||
Notifications.error(error);
|
||||
} finally {
|
||||
|
||||
@ -35,6 +35,8 @@ import { ContainerCharts } from "./container-charts";
|
||||
import { LocaleDate } from "../locale-date";
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { portForwardStore } from "../../port-forward/port-forward.store";
|
||||
import { disposeOnUnmount } from "mobx-react";
|
||||
|
||||
interface Props {
|
||||
pod: Pod;
|
||||
@ -44,6 +46,12 @@ interface Props {
|
||||
|
||||
export class PodDetailsContainer extends React.Component<Props> {
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
portForwardStore.watch(),
|
||||
]);
|
||||
}
|
||||
|
||||
renderStatus(state: string, status: IPodContainerStatus) {
|
||||
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