diff --git a/virtweb_backend/src/app_config.rs b/virtweb_backend/src/app_config.rs index 37f1358..ddf2cda 100644 --- a/virtweb_backend/src/app_config.rs +++ b/virtweb_backend/src/app_config.rs @@ -245,7 +245,7 @@ impl AppConfig { storage_path.canonicalize().unwrap() } - /// Get iso storage directory + /// Get iso files storage directory pub fn iso_storage_path(&self) -> PathBuf { self.storage_path().join("iso") } @@ -265,15 +265,17 @@ impl AppConfig { self.vnc_sockets_path().join(format!("vnc-{}", name)) } - /// Get VM vnc sockets directory - pub fn disks_storage_path(&self) -> PathBuf { + /// Get VM root disks storage directory + pub fn root_vm_disks_storage_path(&self) -> PathBuf { self.storage_path().join("disks") } + /// Get specific VM disk storage directory pub fn vm_storage_path(&self, id: XMLUuid) -> PathBuf { - self.disks_storage_path().join(id.as_string()) + self.root_vm_disks_storage_path().join(id.as_string()) } + /// Get the path were VM definitions are backed up pub fn definitions_path(&self) -> PathBuf { self.storage_path().join("definitions") } diff --git a/virtweb_backend/src/controllers/disk_images_controller.rs b/virtweb_backend/src/controllers/disk_images_controller.rs index 94071bc..7e9e6c2 100644 --- a/virtweb_backend/src/controllers/disk_images_controller.rs +++ b/virtweb_backend/src/controllers/disk_images_controller.rs @@ -5,7 +5,7 @@ use crate::utils::file_disks_utils::DiskFileInfo; use crate::utils::files_utils; use actix_multipart::form::MultipartForm; use actix_multipart::form::tempfile::TempFile; -use actix_web::HttpResponse; +use actix_web::{HttpResponse, web}; #[derive(Debug, MultipartForm)] pub struct UploadDiskImageForm { @@ -67,3 +67,27 @@ pub async fn get_list() -> HttpResult { Ok(HttpResponse::Ok().json(list)) } + +#[derive(serde::Deserialize)] +pub struct DiskFilePath { + filename: String, +} + +/// Delete a disk image +pub async fn delete(p: web::Path) -> HttpResult { + if !files_utils::check_file_name(&p.filename) { + return Ok(HttpResponse::BadRequest().json("Invalid file name!")); + } + + let file_path = AppConfig::get() + .disk_images_storage_path() + .join(&p.filename); + + if !file_path.exists() { + return Ok(HttpResponse::NotFound().json("Disk image does not exists!")); + } + + std::fs::remove_file(file_path)?; + + Ok(HttpResponse::Accepted().finish()) +} diff --git a/virtweb_backend/src/controllers/iso_controller.rs b/virtweb_backend/src/controllers/iso_controller.rs index e6f7add..a8b7a19 100644 --- a/virtweb_backend/src/controllers/iso_controller.rs +++ b/virtweb_backend/src/controllers/iso_controller.rs @@ -132,12 +132,12 @@ pub async fn get_list() -> HttpResult { } #[derive(serde::Deserialize)] -pub struct DownloadFilePath { +pub struct IsoFilePath { filename: String, } /// Download ISO file -pub async fn download_file(p: web::Path, req: HttpRequest) -> HttpResult { +pub async fn download_file(p: web::Path, req: HttpRequest) -> HttpResult { if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid file name!")); } @@ -152,7 +152,7 @@ pub async fn download_file(p: web::Path, req: HttpRequest) -> } /// Delete ISO file -pub async fn delete_file(p: web::Path) -> HttpResult { +pub async fn delete_file(p: web::Path) -> HttpResult { if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid file name!")); } diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 855875a..77844c3 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -60,7 +60,8 @@ async fn main() -> std::io::Result<()> { files_utils::create_directory_if_missing(AppConfig::get().disk_images_storage_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap(); files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap(); - files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap(); + files_utils::create_directory_if_missing(AppConfig::get().root_vm_disks_storage_path()) + .unwrap(); files_utils::create_directory_if_missing(AppConfig::get().nat_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().api_tokens_path()).unwrap(); @@ -344,6 +345,10 @@ async fn main() -> std::io::Result<()> { "/api/disk_images/list", web::get().to(disk_images_controller::get_list), ) + .route( + "/api/disk_images/{filename}", + web::delete().to(disk_images_controller::delete), + ) // API tokens controller .route( "/api/token/create", diff --git a/virtweb_frontend/src/api/DiskImageApi.ts b/virtweb_frontend/src/api/DiskImageApi.ts index b4f248c..105166a 100644 --- a/virtweb_frontend/src/api/DiskImageApi.ts +++ b/virtweb_frontend/src/api/DiskImageApi.ts @@ -42,4 +42,14 @@ export class DiskImageApi { }) ).data; } + + /** + * Delete disk image file + */ + static async Delete(file: DiskImage): Promise { + await APIClient.exec({ + method: "DELETE", + uri: `/disk_images/${file.file_name}`, + }); + } } diff --git a/virtweb_frontend/src/routes/DiskImagesRoute.tsx b/virtweb_frontend/src/routes/DiskImagesRoute.tsx index b204581..8170147 100644 --- a/virtweb_frontend/src/routes/DiskImagesRoute.tsx +++ b/virtweb_frontend/src/routes/DiskImagesRoute.tsx @@ -14,6 +14,8 @@ import React from "react"; import { DiskImage, DiskImageApi } from "../api/DiskImageApi"; import { ServerApi } from "../api/ServerApi"; 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 { AsyncWidget } from "../widgets/AsyncWidget"; import { DateWidget } from "../widgets/DateWidget"; @@ -152,6 +154,41 @@ function DiskImageList(p: { list: DiskImage[]; onReload: () => void; }): React.ReactElement { + const alert = useAlert(); + const snackbar = useSnackbar(); + const confirm = useConfirm(); + const loadingMessage = useLoadingMessage(); + + // 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 }, { @@ -187,8 +224,8 @@ function DiskImageList(p: { - - deleteIso(params.row)}> + + deleteDiskImage(params.row)}>