UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

227 lines (226 loc) 11.5 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /* * Copyright 2025 The Kubernetes Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Icon, InlineIcon } from '@iconify/react'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import MuiLink from '@mui/material/Link'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import { useTheme } from '@mui/system'; import { useSnackbar } from 'notistack'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { isDockerDesktop } from '../../helpers/isDockerDesktop'; import { listPortForward, startPortForward, stopOrDeletePortForward } from '../../lib/k8s/apiProxy'; import { getCluster } from '../../lib/util'; import { Link, Loader, SectionBox, SimpleTable, StatusLabel } from '../common'; import { PORT_FORWARD_RUNNING_STATUS, PORT_FORWARD_STOP_STATUS, PORT_FORWARDS_STORAGE_KEY, } from '../common/Resource/PortForward'; var PortForwardAction; (function (PortForwardAction) { PortForwardAction["Start"] = "Start"; PortForwardAction["Stop"] = "Stop"; PortForwardAction["Delete"] = "Delete"; })(PortForwardAction || (PortForwardAction = {})); export default function PortForwardingList() { const [portforwards, setPortForwards] = React.useState([]); const theme = useTheme(); const [anchorEl, setAnchorEl] = React.useState(null); const [portForwardInAction, setPortForwardInAction] = React.useState(null); const { enqueueSnackbar } = useSnackbar(); const cluster = getCluster(); const { t, i18n } = useTranslation(['translation', 'glossary']); const optionsTranslated = React.useMemo(() => ({ [PortForwardAction.Start]: t('translation|Start'), [PortForwardAction.Stop]: t('translation|Stop'), [PortForwardAction.Delete]: t('translation|Delete'), }), [i18n.language]); const options = Object.keys(optionsTranslated); function fetchPortForwardList(showError) { const cluster = getCluster(); if (!cluster) return; // fetch port forwarding list listPortForward(cluster).then(portforwards => { const massagedPortForwards = portforwards === null ? [] : portforwards; massagedPortForwards.forEach((portforward) => { if (portForwardInAction?.id === portforward.id) { if (portforward.Error && showError) { enqueueSnackbar(portforward.Error, { key: 'portforward-error', preventDuplicate: true, autoHideDuration: 3000, variant: 'error', }); } } }); // sync portforwards from backend with localStorage const portforwardInStorage = localStorage.getItem(PORT_FORWARDS_STORAGE_KEY); const parsedPortForwards = JSON.parse(portforwardInStorage || '[]'); parsedPortForwards.forEach((portforward) => { const index = massagedPortForwards.findIndex((pf) => pf.id === portforward.id); if (index === -1) { portforward.status = PORT_FORWARD_STOP_STATUS; massagedPortForwards.push(portforward); } }); localStorage.setItem(PORT_FORWARDS_STORAGE_KEY, JSON.stringify( // in the locaStorage we store portforward status as stop // this is because the correct status is always present on the backend // the localStorage portforwards are used specifically when the user relaunches the app massagedPortForwards.map((portforward) => { const newPortforward = { ...portforward }; newPortforward.status = PORT_FORWARD_STOP_STATUS; return newPortforward; }))); setPortForwards(massagedPortForwards); }); } React.useEffect(() => { fetchPortForwardList(); }, []); const handleClick = (event, portforward) => { setPortForwardInAction(portforward); setAnchorEl(event.currentTarget); }; const handleClose = (option) => { setAnchorEl(null); if (!option || typeof option !== 'string') { return; } const { id, namespace, cluster, port, targetPort, pod, service, serviceNamespace } = portForwardInAction; let address = 'localhost'; if (isDockerDesktop()) { address = '0.0.0.0'; } portForwardInAction.loading = true; setPortForwardInAction(portForwardInAction); if (option === PortForwardAction.Start) { // start portforward startPortForward(cluster, namespace, pod, targetPort, service, serviceNamespace, port, address, id).then(() => { portForwardInAction.loading = false; setPortForwardInAction(portForwardInAction); // update portforward list item fetchPortForwardList(true); }); } if (option === PortForwardAction.Stop) { // stop portforward stopOrDeletePortForward(cluster, id, true).finally(() => { portForwardInAction.loading = false; setPortForwardInAction(portForwardInAction); // update portforward list item fetchPortForwardList(true); }); } if (option === PortForwardAction.Delete) { // delete portforward stopOrDeletePortForward(cluster, id, false).finally(() => { portForwardInAction.loading = false; setPortForwardInAction(portForwardInAction); // remove portforward from storage too const portforwardInStorage = localStorage.getItem(PORT_FORWARDS_STORAGE_KEY); const parsedPortForwards = JSON.parse(portforwardInStorage || '[]'); const index = parsedPortForwards.findIndex((pf) => pf.id === id); if (index !== -1) { parsedPortForwards.splice(index, 1); } localStorage.setItem(PORT_FORWARDS_STORAGE_KEY, JSON.stringify(parsedPortForwards)); // update portforward list item fetchPortForwardList(true); }); } }; function prepareStatusLabel(portforward) { if (portForwardInAction?.id === portforward.id && portForwardInAction.loading) { return _jsx(Loader, { noContainer: true, title: t('translation|Loading port forwarding'), size: 30 }); } const error = portforward.error; if (error) { return _jsx(StatusLabel, { status: "error", children: t('translation|Error') }); } return (_jsx(StatusLabel, { status: portforward.status === PORT_FORWARD_RUNNING_STATUS ? 'success' : '', children: portforward.status })); } return (_jsx(SectionBox, { title: t('glossary|Port Forwarding'), children: _jsx(SimpleTable, { columns: [ { label: t('translation|Name'), getter: portforward => { const podOrService = portforward.service ? 'service' : 'pod'; const name = portforward.service || portforward.pod; const namespace = portforward.serviceNamespace || portforward.namespace; return (_jsx(Link, { routeName: podOrService, params: { name, namespace }, children: name })); }, }, { label: t('glossary|Namespace'), getter: portforward => { return portforward.serviceNamespace || portforward.namespace; }, }, { label: t('glossary|Kind'), getter: portforward => { return !!portforward.service ? 'Service' : 'Pod'; }, }, { label: t('translation|Pod Port'), getter: portforward => { return portforward.targetPort; }, }, { label: t('translation|Local Port'), getter: portforward => { return (_jsx(Box, { display: 'flex', alignItems: "center", children: _jsxs(MuiLink, { onClick: () => { window.open(`http://localhost:${portforward.port}`, '_blank'); }, sx: theme => portforward.status === PORT_FORWARD_RUNNING_STATUS ? { cursor: 'pointer', marginRight: theme.spacing(1), } : { pointerEvents: 'none', color: theme.palette.text.disabled, }, children: [portforward.port, _jsx(InlineIcon, { icon: 'mdi:open-in-new', style: { marginLeft: theme.spacing(0.5) } })] }) })); }, }, { label: t('translation|Status'), getter: portforward => { return prepareStatusLabel(portforward); }, }, { label: t('translation|Actions'), getter: portforward => { const filteredOptions = options.filter(option => { if (portForwardInAction?.error) { return option === PortForwardAction.Delete; } if (portForwardInAction?.status === PORT_FORWARD_RUNNING_STATUS) { return option !== PortForwardAction.Start; } else if (portForwardInAction?.status === PORT_FORWARD_STOP_STATUS) { return option !== PortForwardAction.Stop; } }); return (_jsxs(_Fragment, { children: [_jsx(IconButton, { "aria-label": t('translation|More'), onClick: e => handleClick(e, portforward), size: "medium", children: _jsx(Icon, { icon: 'mdi:dots-vertical' }) }), _jsx(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: handleClose, children: filteredOptions.map(option => (_jsx(MenuItem, { onClick: () => handleClose(option), children: optionsTranslated[option] }))) })] })); }, }, ], data: portforwards.filter((pf) => pf.cluster === cluster) }) })); }