@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
143 lines (142 loc) • 7.52 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 { Icon, InlineIcon } from '@iconify/react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { DropZoneBox } from '../DropZoneBox';
import Tabs from '../Tabs';
const ActionButtons = ({ onBack, onLoad, disabled, }) => {
const { t } = useTranslation();
return (_jsxs(Box, { sx: { display: 'flex', mt: 2, justifyContent: 'space-between' }, children: [_jsxs(Button, { onClick: onBack, color: "inherit", variant: "outlined", children: [_jsx(Icon, { icon: "mdi:arrow-left", width: "18", height: "18", style: { display: 'inline-block', marginRight: '8px' } }), t('translation|Back')] }), _jsx(Button, { onClick: onLoad, color: "inherit", variant: "contained", disabled: disabled, children: t('translation|Load') })] }));
};
const UploadFromFilesystem = ({ onLoaded, onCancel, }) => {
const { t } = useTranslation();
const [files, setFiles] = React.useState([]);
const [dragOver, setDragOver] = React.useState(false);
const [error, setError] = React.useState('');
const fileInputRef = React.useRef(null);
const onFilesPicked = (picked) => {
setError('');
setFiles(picked ? Array.from(picked) : []);
};
const readFileAsText = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(String(e.target?.result ?? ''));
reader.onerror = () => reject(new Error(t('translation|Failed to read file.')));
reader.onabort = () => reject(new Error(t('translation|File read was aborted.')));
reader.readAsText(file);
});
const handleLoadFiles = async () => {
try {
setError('');
if (files.every(f => f.size === 0)) {
setError(t('translation|Error: All of the files are empty.'));
}
const texts = await Promise.all(files.map(readFileAsText));
const merged = texts.join('---\n');
onLoaded(merged);
}
catch (e) {
setError(e.message || t('translation|Unexpected error while reading files.'));
}
};
const handleDrop = (e) => {
e.preventDefault();
setDragOver(false);
onFilesPicked(e.dataTransfer.files);
};
const handleDragOver = (e) => {
e.preventDefault();
setDragOver(true);
};
const handleDragLeave = () => setDragOver(false);
return (_jsxs(_Fragment, { children: [_jsxs(DropZoneBox, { onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, children: [_jsx(Typography, { sx: { m: 2 }, children: dragOver
? t('translation|Drop the file here...')
: t('translation|Select a file or drag and drop here') }), _jsx("input", { ref: fileInputRef, type: "file", accept: ".yaml,.yml,application/yaml", multiple: true, hidden: true, onChange: e => onFilesPicked(e.target.files) }), _jsx(Button, { variant: "contained", onClick: () => fileInputRef.current?.click(), startIcon: _jsx(InlineIcon, { icon: "mdi:upload", width: 32 }), sx: { fontWeight: 500 }, children: t('translation|Select File') })] }), !!files.length && (_jsxs(Box, { sx: {
borderRadius: 1,
mt: 2,
p: 1,
width: '100%',
border: '1px',
fontWeight: 'bold',
}, children: [files.length === 1
? files[0].name
: t('translation|{{count}} files selected', { count: files.length }), ' ', files.length > 1 && (_jsx(Typography, { variant: "body2", component: "div", sx: { mt: 1 }, children: files.map(f => f.name).join(', ') }))] })), error && (_jsx(Typography, { sx: { mt: 1 }, color: "error", children: error })), _jsx(ActionButtons, { onBack: onCancel, onLoad: handleLoadFiles, disabled: !files.length })] }));
};
const UploadFromUrl = ({ onLoaded, onCancel, }) => {
const { t } = useTranslation();
const [url, setUrl] = React.useState('');
const [error, setError] = React.useState('');
const parseUrl = (raw) => {
try {
const u = new URL(raw.trim());
return u.protocol === 'http:' || u.protocol === 'https:' ? u : null;
}
catch {
return null;
}
};
const loadFromUrl = async () => {
setError('');
const u = parseUrl(url);
if (!u) {
setError(t('translation|Please enter a valid URL.'));
return;
}
try {
const res = await fetch(u.toString());
if (!res.ok) {
setError(t(`translation|Failed to fetch file: ${res.statusText}`));
}
const text = await res.text();
onLoaded(text);
}
catch (e) {
setError(e.message || t('translation|Unexpected error while fetching the file.'));
}
};
return (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { pt: 1 }, children: _jsx(TextField, { label: t('translation|Enter URL'), variant: "outlined", fullWidth: true, value: url, onChange: e => {
setUrl(e.target.value);
setError('');
}, sx: { mb: 2 }, error: !!error }) }), error && (_jsx(Typography, { sx: { mt: 1 }, color: "error", children: error })), _jsx(ActionButtons, { onBack: onCancel, onLoad: loadFromUrl, disabled: !url })] }));
};
export function UploadDialog(props) {
const { setUploadFiles, setCode } = props;
const { t } = useTranslation();
const finishLoad = (text) => {
setCode({ format: 'yaml', code: text });
setUploadFiles(false);
};
return (_jsx(Dialog, { open: true, onClose: () => setUploadFiles(false), fullWidth: true, slotProps: {
backdrop: { sx: { backdropFilter: 'blur(2px)' } },
}, children: _jsx(DialogContent, { sx: { pt: 1 }, children: _jsx(Tabs, { ariaLabel: t('translation|Upload File/URL'), tabs: [
{
label: t('translation|Upload File'),
component: (_jsx(Box, { pt: 2, children: _jsx(UploadFromFilesystem, { onLoaded: finishLoad, onCancel: () => setUploadFiles(false) }) })),
},
{
label: t('translation|Load from URL'),
component: (_jsx(Box, { pt: 2, children: _jsx(UploadFromUrl, { onLoaded: finishLoad, onCancel: () => setUploadFiles(false) }) })),
},
] }) }) }));
}