UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

120 lines (119 loc) 7.27 kB
import { jsx as _jsx, jsxs as _jsxs } 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 } from '@iconify/react'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import Fab from '@mui/material/Fab'; import Grid from '@mui/material/Grid'; import OutlinedInput from '@mui/material/OutlinedInput'; import { styled, useTheme } from '@mui/material/styles'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { clusterAction } from '../../../redux/clusterActionSlice'; import { EventStatus, HeadlampEventType, useEventCallback, } from '../../../redux/headlampEventSlice'; import ActionButton from '../ActionButton'; import { LightTooltip } from '../Tooltip'; import AuthVisible from './AuthVisible'; export default function ScaleButton(props) { const dispatch = useDispatch(); const { item, buttonStyle, options = {} } = props; const [openDialog, setOpenDialog] = React.useState(false); const location = useLocation(); const { t } = useTranslation(); async function updateFunc(numReplicas) { try { await item.scale(numReplicas); } catch (err) { throw err; } } // eslint-disable-next-line react-hooks/use-memo const applyFunc = React.useCallback(updateFunc, [item]); function handleSave(numReplicas) { const cancelUrl = location.pathname; const itemName = item.metadata.name; setOpenDialog(false); // setOpenDialog(false); dispatch(clusterAction(() => applyFunc(numReplicas), { startMessage: t('Scaling {{ itemName }}…', { itemName }), cancelledMessage: t('Cancelled scaling {{ itemName }}.', { itemName }), successMessage: t('Scaled {{ itemName }}.', { itemName }), errorMessage: t('Failed to scale {{ itemName }}.', { itemName }), cancelUrl, errorUrl: cancelUrl, ...options, })); } function handleClose() { setOpenDialog(false); } if (!item || !item?.isScalable) { return null; } return (_jsxs(AuthVisible, { item: item, authVerb: "patch", subresource: "scale", onError: (err) => { console.error(`Error while getting authorization for scaling button in ${item}:`, err); }, children: [_jsx(ActionButton, { description: t('translation|Scale'), buttonStyle: buttonStyle, onClick: () => { setOpenDialog(true); }, icon: "mdi:expand-all" }), _jsx(ScaleDialog, { resource: item, open: openDialog, onClose: handleClose, onSave: handleSave })] })); } const Input = styled(OutlinedInput)({ '& input[type=number]': { MozAppearance: 'textfield', textAlign: 'center', }, '& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button': { display: 'none', }, width: '80px', }); function ScaleDialog(props) { const { open, resource, onClose, onSave } = props; const [numReplicas, setNumReplicas] = React.useState(getNumReplicas()); const { t } = useTranslation(['translation']); const theme = useTheme(); const desiredNumReplicasLabel = 'desired-number-replicas-label'; const numReplicasForWarning = 100; const dispatchHeadlampEvent = useEventCallback(HeadlampEventType.SCALE_RESOURCE); function getNumReplicas() { if (!('spec' in resource)) { return -1; } return parseInt(resource.spec.replicas); } const currentNumReplicas = getNumReplicas(); return (_jsxs(Dialog, { open: open, onClose: onClose, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Scale Replicas') }), _jsx(DialogContent, { sx: { paddingBottom: '30px', // Prevent the content from overflowing }, children: _jsxs(Grid, { container: true, spacing: 2, children: [_jsx(Grid, { item: true, xs: 12, children: _jsx(DialogContentText, { children: t('translation|Current number of replicas: {{ numReplicas }}', { numReplicas: currentNumReplicas === -1 ? t('translation|Unknown') : currentNumReplicas, }) }) }), _jsxs(Grid, { item: true, container: true, alignItems: "center", spacing: 1, children: [_jsx(Grid, { item: true, sm: "auto", xs: 12, children: _jsx(DialogContentText, { id: desiredNumReplicasLabel, children: t('translation|Desired number of replicas:') }) }), _jsxs(Grid, { item: true, spacing: 2, sm: "auto", sx: { padding: '6px', textAlign: 'left' }, children: [_jsx(Fab, { size: "small", color: "secondary", onClick: () => setNumReplicas(numReplicas => Math.max(0, numReplicas - 1)), "aria-label": t('translation|Decrement'), disabled: numReplicas <= 0, sx: { boxShadow: 'none' }, children: _jsx(Icon, { icon: "mdi:minus", width: "22px" }) }), _jsx(Input, { size: "small", type: "number", value: numReplicas, sx: { marginLeft: '6px', marginRight: '6px' }, onChange: e => setNumReplicas(Math.max(0, Number(e.target.value))), "aria-labelledby": desiredNumReplicasLabel, inputProps: { min: 0, step: 1, } }), _jsx(Fab, { size: "small", color: "secondary", onClick: () => setNumReplicas(numReplicas => numReplicas + 1), "aria-label": t('translation|Increment'), sx: { boxShadow: 'none' }, children: _jsx(Icon, { icon: "mdi:plus", width: "22px" }) })] }), _jsx(Grid, { item: true, xs: "auto", children: numReplicas >= numReplicasForWarning && (_jsx(LightTooltip, { title: t("A large number of replicas may negatively impact the cluster's performance"), children: _jsx(Icon, { icon: "mdi:warning", width: "28px", color: theme.palette.warning.main }) })) })] })] }) }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: onClose, color: "secondary", variant: "contained", children: t('translation|Cancel') }), _jsx(Button, { onClick: () => { onSave(numReplicas); dispatchHeadlampEvent({ resource: resource, status: EventStatus.CONFIRMED, }); }, variant: "contained", color: "primary", children: t('translation|Apply') })] })] })); }