import DeleteIcon from "@mui/icons-material/Delete"; import DownloadIcon from "@mui/icons-material/Download"; import LoopIcon from "@mui/icons-material/Loop"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import RefreshIcon from "@mui/icons-material/Refresh"; import { Alert, Button, CircularProgress, IconButton, LinearProgress, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip, Typography, } from "@mui/material"; import { DataGrid, GridColDef } from "@mui/x-data-grid"; import { filesize } from "filesize"; import React from "react"; import { DiskImage, DiskImageApi } from "../api/DiskImageApi"; import { ServerApi } from "../api/ServerApi"; import { ConvertDiskImageDialog } from "../dialogs/ConvertDiskImageDialog"; import { useAlert } from "../hooks/providers/AlertDialogProvider"; import { useConfirm } from "../hooks/providers/ConfirmDialogProvider"; import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; import { useSnackbar } from "../hooks/providers/SnackbarProvider"; import { downloadBlob } from "../utils/FilesUtils"; import { AsyncWidget } from "../widgets/AsyncWidget"; import { DateWidget } from "../widgets/DateWidget"; import { FileInput } from "../widgets/forms/FileInput"; import { VirtWebPaper } from "../widgets/VirtWebPaper"; import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; export function DiskImagesRoute(): React.ReactElement { const [list, setList] = React.useState(); const loadKey = React.useRef(1); const load = async () => { setList(await DiskImageApi.GetList()); }; const reload = () => { loadKey.current += 1; setList(undefined); }; return ( } > ( <> )} /> ); } function UploadDiskImageCard(p: { onFileUploaded: () => void; }): React.ReactElement { const alert = useAlert(); const snackbar = useSnackbar(); const [value, setValue] = React.useState(null); const [uploadProgress, setUploadProgress] = React.useState( null ); const handleChange = (newValue: File | null) => { if ( newValue && newValue.size > ServerApi.Config.constraints.disk_image_max_size ) { alert( `The file is too big (max size allowed: ${filesize( ServerApi.Config.constraints.disk_image_max_size )}` ); return; } if ( newValue && newValue.type.length > 0 && !ServerApi.Config.disk_images_mimetypes.includes(newValue.type) ) { alert(`Selected file mimetype is not allowed! (${newValue.type})`); return; } setValue(newValue); }; const upload = async () => { try { setUploadProgress(0); await DiskImageApi.Upload(value!, setUploadProgress); setValue(null); snackbar("The file was successfully uploaded!"); p.onFileUploaded(); } catch (e) { console.error(e); await alert(`Failed to perform file upload! ${e}`); } setUploadProgress(null); }; if (uploadProgress !== null) { return ( Upload in progress ({Math.floor(uploadProgress * 100)}%)... ); } return (
{value && }
); } function DiskImageList(p: { list: DiskImage[]; onReload: () => void; }): React.ReactElement { const alert = useAlert(); const snackbar = useSnackbar(); const confirm = useConfirm(); const loadingMessage = useLoadingMessage(); const [dlProgress, setDlProgress] = React.useState(); const [currConversion, setCurrConversion] = React.useState< DiskImage | undefined >(); // Download disk image file const downloadDiskImage = async (entry: DiskImage) => { setDlProgress(0); try { const blob = await DiskImageApi.Download(entry, setDlProgress); downloadBlob(blob, entry.file_name); } catch (e) { console.error(e); alert(`Failed to download disk image file! ${e}`); } setDlProgress(undefined); }; // Convert disk image file const convertDiskImage = (entry: DiskImage) => { setCurrConversion(entry); }; // Delete disk image const deleteDiskImage = async (entry: DiskImage) => { if ( !(await confirm( `Do you really want to delete this disk image (${entry.file_name}) ?` )) ) return; loadingMessage.show("Deleting disk image file..."); try { await DiskImageApi.Delete(entry); snackbar("The disk image has been successfully deleted!"); p.onReload(); } catch (e) { console.error(e); alert(`Failed to delete disk image!\n${e}`); } loadingMessage.hide(); }; if (p.list.length === 0) return ( No disk image uploaded for now. ); const columns: GridColDef<(typeof p.list)[number]>[] = [ { field: "file_name", headerName: "File name", flex: 3, editable: true }, { field: "format", headerName: "Format", flex: 1, renderCell(params) { let content = params.row.format; if (params.row.format === "Raw") { content += params.row.is_sparse ? " (Sparse)" : " (Fixed)"; } return content; }, }, { field: "file_size", headerName: "File size", flex: 1, renderCell(params) { let res = filesize(params.row.file_size); if (params.row.format === "QCow2") { res += ` (${filesize(params.row.virtual_size!)})`; } return res; }, }, { field: "created", headerName: "Created", flex: 1, renderCell(params) { return ; }, }, { field: "actions", type: "actions", headerName: "", width: 55, cellClassName: "actions", editable: false, getActions: (params) => { return [ , ]; }, }, ]; return ( <> {/* Download notification */} {dlProgress !== undefined && (
Downloading... {dlProgress}%
)} {/* Disk image conversion dialog */} {currConversion && ( { setCurrConversion(undefined); }} onFinished={() => { setCurrConversion(undefined); p.onReload(); }} /> )} {/* The table itself */} getRowId={(c) => c.file_name} rows={p.list} columns={columns} processRowUpdate={async (n, o) => { try { await DiskImageApi.Rename(o, n.file_name); return n; } catch (e) { console.error("Failed to rename disk image!", e); alert(`Failed to rename disk image! ${e}`); throw e; } finally { p.onReload(); } }} /> ); } function DiskImageActionMenu(p: { diskImage: DiskImage; onDownload: (d: DiskImage) => void; onConvert: (d: DiskImage) => void; onDelete: (d: DiskImage) => void; }): React.ReactElement { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <> {/* Download disk image */} { handleClose(); p.onDownload(p.diskImage); }} > Download {/* Convert disk image */} { handleClose(); p.onConvert(p.diskImage); }} > Convert {/* Delete disk image */} { handleClose(); p.onDelete(p.diskImage); }} > Delete ); }