@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
145 lines (144 loc) • 6.76 kB
JavaScript
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] }) })] }));
}