UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

145 lines (144 loc) 6.76 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 Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Stack from '@mui/material/Stack'; import _ from 'lodash'; import { isValidElement, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import { useHistory } from 'react-router-dom'; import { isElectron } from '../../../helpers/isElectron'; import { getCluster } from '../../../lib/cluster'; import { deletePlugin } from '../../../lib/k8s/apiProxy'; import { ConfigStore } from '../../../plugin/configStore'; import { reloadPage } from '../../../plugin/pluginsSlice'; import { useTypedSelector } from '../../../redux/reducers/reducers'; import NotFoundComponent from '../../404'; import { ConfirmDialog } from '../../common/Dialog'; import ErrorBoundary from '../../common/ErrorBoundary'; import { SectionBox } from '../../common/SectionBox'; import { setNotifications } from '../Notifications/notificationsSlice'; const PluginSettingsDetailsInitializer = (props) => { const { plugin } = props; const store = new ConfigStore(plugin.name); const pluginConf = store.useConfig(); const config = pluginConf(); function handleSave(data) { store.set(data); } function handleDeleteConfirm() { const dispatch = useDispatch(); const name = plugin.name.split('/').splice(-1)[0]; deletePlugin(name) .then(() => { // update the plugin list dispatch(reloadPage()); }) .catch(error => { dispatch(setNotifications({ cluster: getCluster(), date: new Date().toISOString(), deleted: false, id: Math.random().toString(36).substring(2), message: `Failed to delete plugin: ${error.message || 'Unknown error'}`, seen: false, })); }) .finally(() => { // redirect /plugins page window.location.pathname = '/settings/plugins'; }); } return (_jsx(PluginSettingsDetailsPure, { config: config, plugin: plugin, onSave: handleSave, onDelete: handleDeleteConfirm })); }; export default function PluginSettingsDetails() { const pluginSettings = useTypedSelector(state => state.plugins.pluginSettings); const { name } = useParams(); const plugin = useMemo(() => { const decodedName = decodeURIComponent(name); return pluginSettings.find(plugin => plugin.name === decodedName); }, [pluginSettings, name]); if (!plugin) { return _jsx(NotFoundComponent, {}); } return _jsx(PluginSettingsDetailsInitializer, { plugin: plugin }); } const ScrollableBox = (props) => (_jsx(Box, { sx: { overflowY: 'scroll', msOverflowStyle: 'none', scrollbarWidth: 'none', '&::-webkit-scrollbar': { display: 'none', }, }, ...props })); export function PluginSettingsDetailsPure(props) { const { config, plugin, onSave, onDelete } = props; const { t } = useTranslation(['translation']); const [data, setData] = useState(config); const [enableSaveButton, setEnableSaveButton] = useState(false); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const history = useHistory(); const [author, name] = plugin.name.includes('@') ? plugin.name.substring(1).split(/\/(.+)/) : [null, plugin.name]; useEffect(() => { if (!_.isEqual(config, data)) { setEnableSaveButton(true); } else { setEnableSaveButton(false); } }, [data, config]); function onDataChange(data) { setData(data); } async function handleSave() { if (onSave && data) { await onSave(data); history.push('/settings/plugins'); } } function handleDelete() { setOpenDeleteDialog(true); } function handleDeleteConfirm() { onDelete(); } async function handleCancel() { await setData(config); history.push('/settings/plugins'); } let component; if (isValidElement(plugin.settingsComponent)) { component = plugin.settingsComponent; } else if (typeof plugin.settingsComponent === 'function') { const Comp = plugin.settingsComponent; if (plugin.displaySettingsComponentWithSaveButton) { component = _jsx(Comp, { onDataChange: onDataChange, data: data }); } else { component = _jsx(Comp, {}); } } else { component = null; } return (_jsxs(_Fragment, { children: [_jsxs(SectionBox, { "aria-live": "polite", title: name, subtitle: author ? `${t('translation|By')}: ${author}` : undefined, backLink: '/settings/plugins', children: [plugin.description, _jsxs(ScrollableBox, { style: { height: '70vh' }, py: 0, children: [_jsx(ConfirmDialog, { open: openDeleteDialog, title: t('translation|Delete Plugin'), description: t('translation|Are you sure you want to delete this plugin?'), handleClose: () => setOpenDeleteDialog(false), onConfirm: () => handleDeleteConfirm() }), _jsx(ErrorBoundary, { children: component })] })] }), _jsx(Box, { py: 0, children: _jsxs(Stack, { direction: "row", spacing: 2, justifyContent: "space-between", alignItems: "center", sx: { borderTop: '2px solid', borderColor: 'silver', padding: '10px' }, children: [_jsx(Stack, { direction: "row", spacing: 1, children: plugin.displaySettingsComponentWithSaveButton && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "contained", disabled: !enableSaveButton, style: { backgroundColor: 'silver', color: 'black' }, onClick: handleSave, children: t('translation|Save') }), _jsx(Button, { style: { color: 'silver' }, onClick: handleCancel, children: t('translation|Cancel') })] })) }), isElectron() ? (_jsx(Button, { variant: "text", color: "error", onClick: handleDelete, children: t('translation|Delete Plugin') })) : null] }) })] })); }