Can delete uploaded ISO files
This commit is contained in:
		@@ -126,3 +126,25 @@ pub async fn get_list() -> HttpResult {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(list))
 | 
					    Ok(HttpResponse::Ok().json(list))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct DeleteFilePath {
 | 
				
			||||||
 | 
					    filename: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Delete ISO file
 | 
				
			||||||
 | 
					pub async fn delete_file(p: web::Path<DeleteFilePath>) -> HttpResult {
 | 
				
			||||||
 | 
					    if !files_utils::check_file_name(&p.filename) {
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let file_path = AppConfig::get().iso_storage_path().join(&p.filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !file_path.exists() {
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::NotFound().json("File does not exists!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::fs::remove_file(file_path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Accepted().finish())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -109,6 +109,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                web::post().to(iso_controller::upload_from_url),
 | 
					                web::post().to(iso_controller::upload_from_url),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .route("/api/iso/list", web::get().to(iso_controller::get_list))
 | 
					            .route("/api/iso/list", web::get().to(iso_controller::get_list))
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/iso/{filename}",
 | 
				
			||||||
 | 
					                web::delete().to(iso_controller::delete_file),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .bind(&AppConfig::get().listen_address)?
 | 
					    .bind(&AppConfig::get().listen_address)?
 | 
				
			||||||
    .run()
 | 
					    .run()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,4 +46,14 @@ export class IsoFilesApi {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Delete iso file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async Delete(file: IsoFile): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      method: "DELETE",
 | 
				
			||||||
 | 
					      uri: `/iso/${file.filename}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Dialog,
 | 
				
			||||||
 | 
					  DialogActions,
 | 
				
			||||||
 | 
					  DialogContent,
 | 
				
			||||||
 | 
					  DialogContentText,
 | 
				
			||||||
 | 
					  DialogTitle,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import React, { PropsWithChildren } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ConfirmContext = (
 | 
				
			||||||
 | 
					  message: string,
 | 
				
			||||||
 | 
					  title?: string,
 | 
				
			||||||
 | 
					  confirmButton?: string
 | 
				
			||||||
 | 
					) => Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ConfirmContextK = React.createContext<ConfirmContext | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ConfirmDialogProvider(
 | 
				
			||||||
 | 
					  p: PropsWithChildren
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [title, setTitle] = React.useState<string | undefined>(undefined);
 | 
				
			||||||
 | 
					  const [message, setMessage] = React.useState("");
 | 
				
			||||||
 | 
					  const [confirmButton, setConfirmButton] = React.useState<string | undefined>(
 | 
				
			||||||
 | 
					    undefined
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cb = React.useRef<null | ((a: boolean) => void)>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClose = (confirm: boolean) => {
 | 
				
			||||||
 | 
					    setOpen(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (cb.current !== null) cb.current(confirm);
 | 
				
			||||||
 | 
					    cb.current = null;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const hook: ConfirmContext = (message, title, confirmButton) => {
 | 
				
			||||||
 | 
					    setTitle(title);
 | 
				
			||||||
 | 
					    setMessage(message);
 | 
				
			||||||
 | 
					    setConfirmButton(confirmButton);
 | 
				
			||||||
 | 
					    setOpen(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Promise((res) => {
 | 
				
			||||||
 | 
					      cb.current = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <ConfirmContextK.Provider value={hook}>
 | 
				
			||||||
 | 
					        {p.children}
 | 
				
			||||||
 | 
					      </ConfirmContextK.Provider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Dialog
 | 
				
			||||||
 | 
					        open={open}
 | 
				
			||||||
 | 
					        onClose={() => handleClose(false)}
 | 
				
			||||||
 | 
					        aria-labelledby="alert-dialog-title"
 | 
				
			||||||
 | 
					        aria-describedby="alert-dialog-description"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {title && <DialogTitle id="alert-dialog-title">{title}</DialogTitle>}
 | 
				
			||||||
 | 
					        <DialogContent>
 | 
				
			||||||
 | 
					          <DialogContentText id="alert-dialog-description">
 | 
				
			||||||
 | 
					            {message}
 | 
				
			||||||
 | 
					          </DialogContentText>
 | 
				
			||||||
 | 
					        </DialogContent>
 | 
				
			||||||
 | 
					        <DialogActions>
 | 
				
			||||||
 | 
					          <Button onClick={() => handleClose(false)} autoFocus>
 | 
				
			||||||
 | 
					            Cancel
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <Button onClick={() => handleClose(true)} color="error">
 | 
				
			||||||
 | 
					            {confirmButton ?? "Confirm"}
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </DialogActions>
 | 
				
			||||||
 | 
					      </Dialog>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useConfirm(): ConfirmContext {
 | 
				
			||||||
 | 
					  return React.useContext(ConfirmContextK)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,6 +13,7 @@ import { ThemeProvider, createTheme } from "@mui/material";
 | 
				
			|||||||
import { LoadingMessageProvider } from "./hooks/providers/LoadingMessageProvider";
 | 
					import { LoadingMessageProvider } from "./hooks/providers/LoadingMessageProvider";
 | 
				
			||||||
import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider";
 | 
					import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider";
 | 
				
			||||||
import { SnackbarProvider } from "./hooks/providers/SnackbarProvider";
 | 
					import { SnackbarProvider } from "./hooks/providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { ConfirmDialogProvider } from "./hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const darkTheme = createTheme({
 | 
					const darkTheme = createTheme({
 | 
				
			||||||
  palette: {
 | 
					  palette: {
 | 
				
			||||||
@@ -26,15 +27,17 @@ const root = ReactDOM.createRoot(
 | 
				
			|||||||
root.render(
 | 
					root.render(
 | 
				
			||||||
  <React.StrictMode>
 | 
					  <React.StrictMode>
 | 
				
			||||||
    <ThemeProvider theme={darkTheme}>
 | 
					    <ThemeProvider theme={darkTheme}>
 | 
				
			||||||
      <AlertDialogProvider>
 | 
					      <ConfirmDialogProvider>
 | 
				
			||||||
        <SnackbarProvider>
 | 
					        <AlertDialogProvider>
 | 
				
			||||||
          <LoadingMessageProvider>
 | 
					          <SnackbarProvider>
 | 
				
			||||||
            <LoadServerConfig>
 | 
					            <LoadingMessageProvider>
 | 
				
			||||||
              <App />
 | 
					              <LoadServerConfig>
 | 
				
			||||||
            </LoadServerConfig>
 | 
					                <App />
 | 
				
			||||||
          </LoadingMessageProvider>
 | 
					              </LoadServerConfig>
 | 
				
			||||||
        </SnackbarProvider>{" "}
 | 
					            </LoadingMessageProvider>
 | 
				
			||||||
      </AlertDialogProvider>
 | 
					          </SnackbarProvider>
 | 
				
			||||||
 | 
					        </AlertDialogProvider>
 | 
				
			||||||
 | 
					      </ConfirmDialogProvider>
 | 
				
			||||||
    </ThemeProvider>
 | 
					    </ThemeProvider>
 | 
				
			||||||
  </React.StrictMode>
 | 
					  </React.StrictMode>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,24 @@
 | 
				
			|||||||
import { Button, LinearProgress, TextField, Typography } from "@mui/material";
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  LinearProgress,
 | 
				
			||||||
 | 
					  TextField,
 | 
				
			||||||
 | 
					  Typography,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import { DataGrid, GridColDef } from "@mui/x-data-grid";
 | 
				
			||||||
import { filesize } from "filesize";
 | 
					import { filesize } from "filesize";
 | 
				
			||||||
import { MuiFileInput } from "mui-file-input";
 | 
					import { MuiFileInput } from "mui-file-input";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { IsoFile, IsoFilesApi } from "../api/IsoFilesApi";
 | 
					import { IsoFile, IsoFilesApi } from "../api/IsoFilesApi";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
				
			||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { VirtWebPaper } from "../widgets/VirtWebPaper";
 | 
					import { VirtWebPaper } from "../widgets/VirtWebPaper";
 | 
				
			||||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
					import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
				
			||||||
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
					import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					 | 
				
			||||||
import { DataGrid, GridColDef, GridRowsProp } from "@mui/x-data-grid";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function IsoFilesRoute(): React.ReactElement {
 | 
					export function IsoFilesRoute(): React.ReactElement {
 | 
				
			||||||
  const [list, setList] = React.useState<IsoFile[] | undefined>();
 | 
					  const [list, setList] = React.useState<IsoFile[] | undefined>();
 | 
				
			||||||
@@ -172,6 +180,33 @@ function IsoFilesList(p: {
 | 
				
			|||||||
  list: IsoFile[];
 | 
					  list: IsoFile[];
 | 
				
			||||||
  onReload: () => void;
 | 
					  onReload: () => void;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
 | 
					  const alert = useAlert();
 | 
				
			||||||
 | 
					  const loadingMessage = useLoadingMessage();
 | 
				
			||||||
 | 
					  const snackbar = useSnackbar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loadingMessage.hide();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (p.list.length === 0)
 | 
					  if (p.list.length === 0)
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Typography variant="body1" style={{ textAlign: "center" }}>
 | 
					      <Typography variant="body1" style={{ textAlign: "center" }}>
 | 
				
			||||||
@@ -189,6 +224,20 @@ function IsoFilesList(p: {
 | 
				
			|||||||
        return filesize(params.row.size);
 | 
					        return filesize(params.row.size);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      field: "actions",
 | 
				
			||||||
 | 
					      headerName: "",
 | 
				
			||||||
 | 
					      width: 70,
 | 
				
			||||||
 | 
					      renderCell(params) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <>
 | 
				
			||||||
 | 
					            <IconButton onClick={() => deleteIso(params.row)}>
 | 
				
			||||||
 | 
					              <DeleteIcon />
 | 
				
			||||||
 | 
					            </IconButton>
 | 
				
			||||||
 | 
					          </>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user