From cd943bd34c842b2da28478067dd9fd01d65165f3 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann Date: Wed, 29 Sep 2021 22:35:00 -0400 Subject: [PATCH] refactor Signed-off-by: Jim Ehrismann --- src/main/routes/port-forward-route.ts | 4 +- .../port-forward-details.tsx | 2 +- .../port-forward-dialog.tsx | 199 +++++++------- .../port-forward-menu.tsx | 59 +++-- .../port-forward.store.ts | 242 ------------------ .../+network-port-forwards/port-forwards.tsx | 2 +- .../+workloads-pods/pod-container-port.tsx | 3 + .../+workloads-pods/pod-details-container.tsx | 8 + src/renderer/port-forward/index.ts | 23 ++ .../port-forward/port-forward-item.ts | 75 ++++++ .../port-forward/port-forward.store.ts | 182 +++++++++++++ 11 files changed, 424 insertions(+), 375 deletions(-) delete mode 100644 src/renderer/components/+network-port-forwards/port-forward.store.ts create mode 100644 src/renderer/port-forward/index.ts create mode 100644 src/renderer/port-forward/port-forward-item.ts create mode 100644 src/renderer/port-forward/port-forward.store.ts diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts index c2bc609ccd..9a6535bdd5 100644 --- a/src/main/routes/port-forward-route.ts +++ b/src/main/routes/port-forward-route.ts @@ -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}); diff --git a/src/renderer/components/+network-port-forwards/port-forward-details.tsx b/src/renderer/components/+network-port-forwards/port-forward-details.tsx index 4a9d6dac59..8979a201c1 100644 --- a/src/renderer/components/+network-port-forwards/port-forward-details.tsx +++ b/src/renderer/components/+network-port-forwards/port-forward-details.tsx @@ -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; diff --git a/src/renderer/components/+network-port-forwards/port-forward-dialog.tsx b/src/renderer/components/+network-port-forwards/port-forward-dialog.tsx index 48356f7734..f06d72ccff 100644 --- a/src/renderer/components/+network-port-forwards/port-forward-dialog.tsx +++ b/src/renderer/components/+network-port-forwards/port-forward-dialog.tsx @@ -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 { - } - + +interface Props extends Partial { +} + const dialogState = observable.object({ isOpen: false, data: null as PortForwardItem, }); - - @observer - export class PortForwardDialog extends Component { - @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 { + @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 ( + <>
- Current port: {this.currentPort} + Current port: {this.currentPort}
- + Current port-forwarding will be removed and a new one will be started
); } - - render() { - const { className, ...dialogProps } = this.props; - const resourceName = this.portForward ? this.portForward.getName() : ""; - const header = ( -
- Change Port {resourceName} -
- ); - - return ( - - - - {this.renderContents()} - - - - ); - } - } - \ No newline at end of file + + render() { + const { className, ...dialogProps } = this.props; + const resourceName = this.portForward ? this.portForward.getName() : ""; + const header = ( +
+ Change Port {resourceName} +
+ ); + + return ( + + + + {this.renderContents()} + + + + ); + } +} diff --git a/src/renderer/components/+network-port-forwards/port-forward-menu.tsx b/src/renderer/components/+network-port-forwards/port-forward-menu.tsx index 30273e64b1..3750468922 100644 --- a/src/renderer/components/+network-port-forwards/port-forward-menu.tsx +++ b/src/renderer/components/+network-port-forwards/port-forward-menu.tsx @@ -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 { @boundMethod remove() { - return portForwardStore.remove(this.props.portForward); + return removePortForward(this.props.portForward); } @boundMethod @@ -55,40 +55,39 @@ export class PortForwardMenu extends React.Component { }); Notifications.error(`Failed to open ${browseTo} in browser`); } - ); + ); } - + renderContent() { const { portForward, toolbar } = this.props; - + if (!portForward) return null; - + return ( <> - + Open PortForwardDialog.open(portForward)}> - - Edit - + + Edit + - ); - } - - render() { - const { className, ...menuProps } = this.props; - - return ( - - {this.renderContent()} - - ); - } - } - \ No newline at end of file + ); + } + + render() { + const { className, ...menuProps } = this.props; + + return ( + + {this.renderContent()} + + ); + } +} diff --git a/src/renderer/components/+network-port-forwards/port-forward.store.ts b/src/renderer/components/+network-port-forwards/port-forward.store.ts deleted file mode 100644 index 46a95365fe..0000000000 --- a/src/renderer/components/+network-port-forwards/port-forward.store.ts +++ /dev/null @@ -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 { - private storage = createStorage("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(`/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(`/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(); diff --git a/src/renderer/components/+network-port-forwards/port-forwards.tsx b/src/renderer/components/+network-port-forwards/port-forwards.tsx index 8acfeba829..c5123156de 100644 --- a/src/renderer/components/+network-port-forwards/port-forwards.tsx +++ b/src/renderer/components/+network-port-forwards/port-forwards.tsx @@ -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"; diff --git a/src/renderer/components/+workloads-pods/pod-container-port.tsx b/src/renderer/components/+workloads-pods/pod-container-port.tsx index 85075f2a1f..ff2a745e45 100644 --- a/src/renderer/components/+workloads-pods/pod-container-port.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-port.tsx @@ -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 { 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 { 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 { diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index c39ed80751..cc41d9a183 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -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 { + componentDidMount() { + disposeOnUnmount(this, [ + portForwardStore.watch(), + ]); + } + renderStatus(state: string, status: IPodContainerStatus) { const ready = status ? status.ready : ""; diff --git a/src/renderer/port-forward/index.ts b/src/renderer/port-forward/index.ts new file mode 100644 index 0000000000..9b1a2b0d1e --- /dev/null +++ b/src/renderer/port-forward/index.ts @@ -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"; diff --git a/src/renderer/port-forward/port-forward-item.ts b/src/renderer/port-forward/port-forward-item.ts new file mode 100644 index 0000000000..4d5390af75 --- /dev/null +++ b/src/renderer/port-forward/port-forward-item.ts @@ -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, + ]; + } +} diff --git a/src/renderer/port-forward/port-forward.store.ts b/src/renderer/port-forward/port-forward.store.ts new file mode 100644 index 0000000000..f298d581cb --- /dev/null +++ b/src/renderer/port-forward/port-forward.store.ts @@ -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 { + private storage = createStorage("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 { + let response; + + try { + response = await apiBase.post(`/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 { + try { + const response = await apiBase.get(`/port-forwards`, {}); + + return response.portForwards; + } catch (error) { + console.error(error); + + return []; + } +} + +export const portForwardStore = new PortForwardStore();