310 lines
8.1 KiB
TypeScript
310 lines
8.1 KiB
TypeScript
import DeleteIcon from "@mui/icons-material/Delete";
|
|
import {
|
|
Alert,
|
|
Button,
|
|
CircularProgress,
|
|
IconButton,
|
|
LinearProgress,
|
|
TextField,
|
|
Tooltip,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import DownloadIcon from "@mui/icons-material/Download";
|
|
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
|
import { filesize } from "filesize";
|
|
import { MuiFileInput } from "mui-file-input";
|
|
import React from "react";
|
|
import { IsoFile, IsoFilesApi } from "../api/IsoFilesApi";
|
|
import { ServerApi } from "../api/ServerApi";
|
|
import { useAlert } from "../hooks/providers/AlertDialogProvider";
|
|
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
|
|
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
|
import { VirtWebPaper } from "../widgets/VirtWebPaper";
|
|
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
|
|
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
|
|
import { downloadBlob } from "../utils/FilesUtils";
|
|
|
|
export function IsoFilesRoute(): React.ReactElement {
|
|
const [list, setList] = React.useState<IsoFile[] | undefined>();
|
|
|
|
const loadKey = React.useRef(1);
|
|
|
|
const load = async () => {
|
|
setList(await IsoFilesApi.GetList());
|
|
};
|
|
|
|
const reload = () => {
|
|
loadKey.current += 1;
|
|
setList(undefined);
|
|
};
|
|
|
|
return (
|
|
<AsyncWidget
|
|
loadKey={loadKey.current}
|
|
errMsg="Failed to load ISO files list!"
|
|
load={load}
|
|
ready={list !== undefined}
|
|
build={() => (
|
|
<VirtWebRouteContainer label="ISO files management">
|
|
<UploadIsoFileCard onFileUploaded={reload} />
|
|
<UploadIsoFileFromUrlCard onFileUploaded={reload} />
|
|
<IsoFilesList list={list!} onReload={reload} />
|
|
</VirtWebRouteContainer>
|
|
)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function UploadIsoFileCard(p: {
|
|
onFileUploaded: () => void;
|
|
}): React.ReactElement {
|
|
const alert = useAlert();
|
|
const snackbar = useSnackbar();
|
|
|
|
const [value, setValue] = React.useState<File | null>(null);
|
|
const [uploadProgress, setUploadProgress] = React.useState<number | null>(
|
|
null
|
|
);
|
|
|
|
const handleChange = (newValue: File | null) => {
|
|
if (newValue && newValue.size > ServerApi.Config.constraints.iso_max_size) {
|
|
alert(
|
|
`The file is too big (max size allowed: ${filesize(
|
|
ServerApi.Config.constraints.iso_max_size
|
|
)}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (newValue && !ServerApi.Config.iso_mimetypes.includes(newValue.type)) {
|
|
alert(`Selected file mimetype is not allowed! (${newValue.type})`);
|
|
return;
|
|
}
|
|
|
|
setValue(newValue);
|
|
};
|
|
|
|
const upload = async () => {
|
|
try {
|
|
setUploadProgress(0);
|
|
await IsoFilesApi.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 (
|
|
<VirtWebPaper label="File upload">
|
|
<Typography variant="body1">
|
|
Upload in progress ({Math.floor(uploadProgress * 100)}%)...
|
|
</Typography>
|
|
<LinearProgress variant="determinate" value={uploadProgress * 100} />
|
|
</VirtWebPaper>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<VirtWebPaper label="File upload">
|
|
<div style={{ display: "flex", alignItems: "center" }}>
|
|
<MuiFileInput
|
|
value={value}
|
|
onChange={handleChange}
|
|
style={{ flex: 1 }}
|
|
inputProps={{ accept: ServerApi.Config.iso_mimetypes.join(",") }}
|
|
/>
|
|
|
|
{value && <Button onClick={upload}>Upload file</Button>}
|
|
</div>
|
|
</VirtWebPaper>
|
|
);
|
|
}
|
|
|
|
function UploadIsoFileFromUrlCard(p: {
|
|
onFileUploaded: () => void;
|
|
}): React.ReactElement {
|
|
const alert = useAlert();
|
|
const snackbar = useSnackbar();
|
|
const loadingMessage = useLoadingMessage();
|
|
|
|
const [url, setURL] = React.useState("");
|
|
const [filename, setFilename] = React.useState<null | string>(null);
|
|
|
|
const autoFileName = url.split("/").slice(-1)[0];
|
|
const actualFileName = filename ?? autoFileName;
|
|
|
|
const upload = async () => {
|
|
try {
|
|
loadingMessage.show("Downloading file from URL...");
|
|
await IsoFilesApi.UploadFromURL(url, actualFileName);
|
|
|
|
setURL("");
|
|
setFilename(null);
|
|
snackbar("Successfully downloaded file!");
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert("Failed to download file!");
|
|
}
|
|
loadingMessage.hide();
|
|
};
|
|
|
|
return (
|
|
<VirtWebPaper label="File upload from URL">
|
|
<div style={{ display: "flex", alignItems: "center" }}>
|
|
<TextField
|
|
label="URL"
|
|
value={url}
|
|
style={{ flex: 3 }}
|
|
onChange={(e) => setURL(e.target.value)}
|
|
/>
|
|
<span style={{ width: "10px" }}></span>
|
|
<TextField
|
|
label="Filename"
|
|
value={actualFileName}
|
|
style={{ flex: 2 }}
|
|
onChange={(e) => setFilename(e.target.value)}
|
|
/>
|
|
{url !== "" && actualFileName !== "" && (
|
|
<Button onClick={upload}>Upload file</Button>
|
|
)}
|
|
</div>
|
|
</VirtWebPaper>
|
|
);
|
|
}
|
|
|
|
function IsoFilesList(p: {
|
|
list: IsoFile[];
|
|
onReload: () => void;
|
|
}): React.ReactElement {
|
|
const confirm = useConfirm();
|
|
const alert = useAlert();
|
|
const loadingMessage = useLoadingMessage();
|
|
const snackbar = useSnackbar();
|
|
|
|
const [dlProgress, setDlProgress] = React.useState<undefined | number>();
|
|
|
|
const downloadIso = async (entry: IsoFile) => {
|
|
setDlProgress(0);
|
|
|
|
try {
|
|
const blob = await IsoFilesApi.Download(entry, setDlProgress);
|
|
|
|
await downloadBlob(blob, entry.filename);
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert("Failed to download iso file!");
|
|
}
|
|
|
|
setDlProgress(undefined);
|
|
};
|
|
|
|
const deleteIso = async (entry: IsoFile) => {
|
|
if (
|
|
!(await confirm(
|
|
`Do you really want to delete this file (${entry.filename}) ?`
|
|
))
|
|
)
|
|
return;
|
|
|
|
loadingMessage.show("Deleting ISO file...");
|
|
|
|
try {
|
|
await IsoFilesApi.Delete(entry);
|
|
snackbar("The file has been successfully deleted!");
|
|
p.onReload();
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert(`Failed to delete file!\n${e}`);
|
|
}
|
|
|
|
loadingMessage.hide();
|
|
};
|
|
|
|
if (p.list.length === 0)
|
|
return (
|
|
<Typography variant="body1" style={{ textAlign: "center" }}>
|
|
No ISO file uploaded for now.
|
|
</Typography>
|
|
);
|
|
|
|
const columns: GridColDef[] = [
|
|
{ field: "filename", headerName: "File name", flex: 3 },
|
|
{
|
|
field: "size",
|
|
headerName: "File size",
|
|
flex: 1,
|
|
renderCell(params) {
|
|
return filesize(params.row.size);
|
|
},
|
|
},
|
|
{
|
|
field: "actions",
|
|
headerName: "",
|
|
width: 120,
|
|
renderCell(params) {
|
|
return (
|
|
<>
|
|
<Tooltip title="Download file">
|
|
<IconButton onClick={() => downloadIso(params.row)}>
|
|
<DownloadIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Delete file">
|
|
<IconButton onClick={() => deleteIso(params.row)}>
|
|
<DeleteIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<VirtWebPaper label="Files list">
|
|
{/* Download notification */}
|
|
{dlProgress !== undefined && (
|
|
<Alert severity="info">
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<Typography variant="body1">
|
|
Downloading... {dlProgress}%
|
|
</Typography>
|
|
<CircularProgress
|
|
variant="determinate"
|
|
size={"1.5rem"}
|
|
style={{ marginLeft: "10px" }}
|
|
value={dlProgress}
|
|
/>
|
|
</div>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Files list table */}
|
|
<DataGrid
|
|
getRowId={(c) => c.filename}
|
|
rows={p.list}
|
|
columns={columns}
|
|
autoHeight={true}
|
|
/>
|
|
</VirtWebPaper>
|
|
</>
|
|
);
|
|
}
|