Can download ISO files
This commit is contained in:
		
							
								
								
									
										55
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										55
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -34,6 +34,29 @@ dependencies = [
 | 
				
			|||||||
 "smallvec",
 | 
					 "smallvec",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "actix-files"
 | 
				
			||||||
 | 
					version = "0.6.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "actix-http",
 | 
				
			||||||
 | 
					 "actix-service",
 | 
				
			||||||
 | 
					 "actix-utils",
 | 
				
			||||||
 | 
					 "actix-web",
 | 
				
			||||||
 | 
					 "askama_escape",
 | 
				
			||||||
 | 
					 "bitflags 1.3.2",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "derive_more",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "http-range",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					 "mime",
 | 
				
			||||||
 | 
					 "mime_guess",
 | 
				
			||||||
 | 
					 "percent-encoding",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "actix-http"
 | 
					name = "actix-http"
 | 
				
			||||||
version = "3.4.0"
 | 
					version = "3.4.0"
 | 
				
			||||||
@@ -418,6 +441,12 @@ version = "1.0.75"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 | 
					checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "askama_escape"
 | 
				
			||||||
 | 
					version = "0.10.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "async-trait"
 | 
					name = "async-trait"
 | 
				
			||||||
version = "0.1.73"
 | 
					version = "0.1.73"
 | 
				
			||||||
@@ -1023,6 +1052,12 @@ dependencies = [
 | 
				
			|||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "http-range"
 | 
				
			||||||
 | 
					version = "0.1.5"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "httparse"
 | 
					name = "httparse"
 | 
				
			||||||
version = "1.8.0"
 | 
					version = "1.8.0"
 | 
				
			||||||
@@ -1241,6 +1276,16 @@ version = "0.3.17"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 | 
					checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "mime_guess"
 | 
				
			||||||
 | 
					version = "2.0.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "mime",
 | 
				
			||||||
 | 
					 "unicase",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "miniz_oxide"
 | 
					name = "miniz_oxide"
 | 
				
			||||||
version = "0.7.1"
 | 
					version = "0.7.1"
 | 
				
			||||||
@@ -1922,6 +1967,15 @@ version = "1.16.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 | 
					checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "unicase"
 | 
				
			||||||
 | 
					version = "2.7.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "version_check",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "unicode-bidi"
 | 
					name = "unicode-bidi"
 | 
				
			||||||
version = "0.3.13"
 | 
					version = "0.3.13"
 | 
				
			||||||
@@ -1999,6 +2053,7 @@ name = "virtweb_backend"
 | 
				
			|||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "actix-cors",
 | 
					 "actix-cors",
 | 
				
			||||||
 | 
					 "actix-files",
 | 
				
			||||||
 "actix-identity",
 | 
					 "actix-identity",
 | 
				
			||||||
 "actix-multipart",
 | 
					 "actix-multipart",
 | 
				
			||||||
 "actix-remote-ip",
 | 
					 "actix-remote-ip",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ actix-remote-ip = "0.1.0"
 | 
				
			|||||||
actix-session = { version = "0.7.2", features = ["cookie-session"] }
 | 
					actix-session = { version = "0.7.2", features = ["cookie-session"] }
 | 
				
			||||||
actix-identity = "0.5.2"
 | 
					actix-identity = "0.5.2"
 | 
				
			||||||
actix-cors = "0.6.4"
 | 
					actix-cors = "0.6.4"
 | 
				
			||||||
 | 
					actix-files = "0.6.2"
 | 
				
			||||||
serde = { version = "1.0.175", features = ["derive"] }
 | 
					serde = { version = "1.0.175", features = ["derive"] }
 | 
				
			||||||
serde_json = "1.0.105"
 | 
					serde_json = "1.0.105"
 | 
				
			||||||
futures-util = "0.3.28"
 | 
					futures-util = "0.3.28"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,10 @@ use crate::app_config::AppConfig;
 | 
				
			|||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::controllers::HttpResult;
 | 
					use crate::controllers::HttpResult;
 | 
				
			||||||
use crate::utils::files_utils;
 | 
					use crate::utils::files_utils;
 | 
				
			||||||
 | 
					use actix_files::NamedFile;
 | 
				
			||||||
use actix_multipart::form::tempfile::TempFile;
 | 
					use actix_multipart::form::tempfile::TempFile;
 | 
				
			||||||
use actix_multipart::form::MultipartForm;
 | 
					use actix_multipart::form::MultipartForm;
 | 
				
			||||||
use actix_web::{web, HttpResponse};
 | 
					use actix_web::{web, HttpRequest, HttpResponse};
 | 
				
			||||||
use futures_util::StreamExt;
 | 
					use futures_util::StreamExt;
 | 
				
			||||||
use std::fs::File;
 | 
					use std::fs::File;
 | 
				
			||||||
use std::io::Write;
 | 
					use std::io::Write;
 | 
				
			||||||
@@ -128,12 +129,27 @@ pub async fn get_list() -> HttpResult {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct DeleteFilePath {
 | 
					pub struct DownloadFilePath {
 | 
				
			||||||
    filename: String,
 | 
					    filename: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Download ISO file
 | 
				
			||||||
 | 
					pub async fn download_file(p: web::Path<DownloadFilePath>, req: HttpRequest) -> 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!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(NamedFile::open(file_path)?.into_response(&req))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Delete ISO file
 | 
					/// Delete ISO file
 | 
				
			||||||
pub async fn delete_file(p: web::Path<DeleteFilePath>) -> HttpResult {
 | 
					pub async fn delete_file(p: web::Path<DownloadFilePath>) -> HttpResult {
 | 
				
			||||||
    if !files_utils::check_file_name(&p.filename) {
 | 
					    if !files_utils::check_file_name(&p.filename) {
 | 
				
			||||||
        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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::get().to(iso_controller::download_file),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/iso/{filename}",
 | 
					                "/api/iso/{filename}",
 | 
				
			||||||
                web::delete().to(iso_controller::delete_file),
 | 
					                web::delete().to(iso_controller::delete_file),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,8 @@ interface RequestParams {
 | 
				
			|||||||
  allowFail?: boolean;
 | 
					  allowFail?: boolean;
 | 
				
			||||||
  jsonData?: any;
 | 
					  jsonData?: any;
 | 
				
			||||||
  formData?: FormData;
 | 
					  formData?: FormData;
 | 
				
			||||||
  progress?: (progress: number) => void;
 | 
					  upProgress?: (progress: number) => void;
 | 
				
			||||||
 | 
					  downProgress?: (e: { progress: number; total: number }) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface APIResponse {
 | 
					interface APIResponse {
 | 
				
			||||||
@@ -62,11 +63,11 @@ export class APIClient {
 | 
				
			|||||||
    let status: number;
 | 
					    let status: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Make the request with XMLHttpRequest
 | 
					    // Make the request with XMLHttpRequest
 | 
				
			||||||
    if (args.progress) {
 | 
					    if (args.upProgress) {
 | 
				
			||||||
      const res: XMLHttpRequest = await new Promise((resolve, reject) => {
 | 
					      const res: XMLHttpRequest = await new Promise((resolve, reject) => {
 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
        xhr.upload.addEventListener("progress", (e) =>
 | 
					        xhr.upload.addEventListener("progress", (e) =>
 | 
				
			||||||
          args.progress!(e.loaded / e.total)
 | 
					          args.upProgress!(e.loaded / e.total)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        xhr.addEventListener("load", () => resolve(xhr));
 | 
					        xhr.addEventListener("load", () => resolve(xhr));
 | 
				
			||||||
        xhr.addEventListener("error", () =>
 | 
					        xhr.addEventListener("error", () =>
 | 
				
			||||||
@@ -104,6 +105,48 @@ export class APIClient {
 | 
				
			|||||||
      // Process response
 | 
					      // Process response
 | 
				
			||||||
      if (res.headers.get("content-type") === "application/json")
 | 
					      if (res.headers.get("content-type") === "application/json")
 | 
				
			||||||
        data = await res.json();
 | 
					        data = await res.json();
 | 
				
			||||||
 | 
					      // Binary file
 | 
				
			||||||
 | 
					      else if (res.body !== null && args.downProgress) {
 | 
				
			||||||
 | 
					        // Track download progress
 | 
				
			||||||
 | 
					        const contentEncoding = res.headers.get("content-encoding");
 | 
				
			||||||
 | 
					        const contentLength = contentEncoding
 | 
				
			||||||
 | 
					          ? null
 | 
				
			||||||
 | 
					          : res.headers.get("content-length");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const total = parseInt(contentLength ?? "0", 10);
 | 
				
			||||||
 | 
					        let loaded = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const resInt = new Response(
 | 
				
			||||||
 | 
					          new ReadableStream({
 | 
				
			||||||
 | 
					            start(controller) {
 | 
				
			||||||
 | 
					              const reader = res.body!.getReader();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const read = async () => {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                  const ret = await reader.read();
 | 
				
			||||||
 | 
					                  if (ret.done) {
 | 
				
			||||||
 | 
					                    controller.close();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  loaded += ret.value.byteLength;
 | 
				
			||||||
 | 
					                  args.downProgress!({ progress: loaded, total });
 | 
				
			||||||
 | 
					                  controller.enqueue(ret.value);
 | 
				
			||||||
 | 
					                  read();
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                  console.error(e);
 | 
				
			||||||
 | 
					                  controller.error(e);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              read();
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = await resInt.blob();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Do not track progress
 | 
				
			||||||
      else data = await res.blob();
 | 
					      else data = await res.blob();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      status = res.status;
 | 
					      status = res.status;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ export class IsoFilesApi {
 | 
				
			|||||||
      method: "POST",
 | 
					      method: "POST",
 | 
				
			||||||
      uri: "/iso/upload",
 | 
					      uri: "/iso/upload",
 | 
				
			||||||
      formData: fd,
 | 
					      formData: fd,
 | 
				
			||||||
      progress: progress,
 | 
					      upProgress: progress,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,6 +47,24 @@ export class IsoFilesApi {
 | 
				
			|||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Download an ISO file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async Download(
 | 
				
			||||||
 | 
					    file: IsoFile,
 | 
				
			||||||
 | 
					    progress: (p: number) => void
 | 
				
			||||||
 | 
					  ): Promise<Blob> {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      await APIClient.exec({
 | 
				
			||||||
 | 
					        method: "GET",
 | 
				
			||||||
 | 
					        uri: `/iso/${file.filename}`,
 | 
				
			||||||
 | 
					        downProgress(e) {
 | 
				
			||||||
 | 
					          progress(Math.floor(100 * (e.progress / e.total)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Delete iso file
 | 
					   * Delete iso file
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,15 @@
 | 
				
			|||||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  Alert,
 | 
				
			||||||
  Button,
 | 
					  Button,
 | 
				
			||||||
 | 
					  CircularProgress,
 | 
				
			||||||
  IconButton,
 | 
					  IconButton,
 | 
				
			||||||
  LinearProgress,
 | 
					  LinearProgress,
 | 
				
			||||||
  TextField,
 | 
					  TextField,
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
  Typography,
 | 
					  Typography,
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import DownloadIcon from "@mui/icons-material/Download";
 | 
				
			||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
 | 
					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";
 | 
				
			||||||
@@ -19,6 +23,7 @@ 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 { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					import { downloadBlob } from "../utils/FilesUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function IsoFilesRoute(): React.ReactElement {
 | 
					export function IsoFilesRoute(): React.ReactElement {
 | 
				
			||||||
  const [list, setList] = React.useState<IsoFile[] | undefined>();
 | 
					  const [list, setList] = React.useState<IsoFile[] | undefined>();
 | 
				
			||||||
@@ -185,6 +190,23 @@ function IsoFilesList(p: {
 | 
				
			|||||||
  const loadingMessage = useLoadingMessage();
 | 
					  const loadingMessage = useLoadingMessage();
 | 
				
			||||||
  const snackbar = useSnackbar();
 | 
					  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) => {
 | 
					  const deleteIso = async (entry: IsoFile) => {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      !(await confirm(
 | 
					      !(await confirm(
 | 
				
			||||||
@@ -227,13 +249,20 @@ function IsoFilesList(p: {
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
      field: "actions",
 | 
					      field: "actions",
 | 
				
			||||||
      headerName: "",
 | 
					      headerName: "",
 | 
				
			||||||
      width: 70,
 | 
					      width: 120,
 | 
				
			||||||
      renderCell(params) {
 | 
					      renderCell(params) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
 | 
					            <Tooltip title="Download file">
 | 
				
			||||||
 | 
					              <IconButton onClick={() => downloadIso(params.row)}>
 | 
				
			||||||
 | 
					                <DownloadIcon />
 | 
				
			||||||
 | 
					              </IconButton>
 | 
				
			||||||
 | 
					            </Tooltip>
 | 
				
			||||||
 | 
					            <Tooltip title="Delete file">
 | 
				
			||||||
              <IconButton onClick={() => deleteIso(params.row)}>
 | 
					              <IconButton onClick={() => deleteIso(params.row)}>
 | 
				
			||||||
                <DeleteIcon />
 | 
					                <DeleteIcon />
 | 
				
			||||||
              </IconButton>
 | 
					              </IconButton>
 | 
				
			||||||
 | 
					            </Tooltip>
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -241,7 +270,33 @@ function IsoFilesList(p: {
 | 
				
			|||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
      <VirtWebPaper label="Files list">
 | 
					      <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
 | 
					        <DataGrid
 | 
				
			||||||
          getRowId={(c) => c.filename}
 | 
					          getRowId={(c) => c.filename}
 | 
				
			||||||
          rows={p.list}
 | 
					          rows={p.list}
 | 
				
			||||||
@@ -249,5 +304,6 @@ function IsoFilesList(p: {
 | 
				
			|||||||
          autoHeight={true}
 | 
					          autoHeight={true}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </VirtWebPaper>
 | 
					      </VirtWebPaper>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								virtweb_frontend/src/utils/FilesUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								virtweb_frontend/src/utils/FilesUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					export async function downloadBlob(blob: Blob, filename: string) {
 | 
				
			||||||
 | 
					  const url = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const link = document.createElement("a");
 | 
				
			||||||
 | 
					  link.href = url;
 | 
				
			||||||
 | 
					  link.target = "_blank";
 | 
				
			||||||
 | 
					  link.rel = "noopener";
 | 
				
			||||||
 | 
					  link.download = filename;
 | 
				
			||||||
 | 
					  link.click();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user