From 08b59b6f674a458e50a148d6e4fbeedb77ac4da3 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Wed, 6 Sep 2023 14:21:26 +0200 Subject: [PATCH] Display the list of ISO files --- .../src/controllers/iso_controller.rs | 21 ++++++ virtweb_backend/src/main.rs | 1 + virtweb_frontend/package-lock.json | 48 +++++++++++++ virtweb_frontend/package.json | 1 + virtweb_frontend/src/api/IsoFilesApi.ts | 17 +++++ virtweb_frontend/src/routes/IsoFilesRoute.tsx | 71 +++++++++++++++++-- 6 files changed, 152 insertions(+), 7 deletions(-) diff --git a/virtweb_backend/src/controllers/iso_controller.rs b/virtweb_backend/src/controllers/iso_controller.rs index bc038f3..bb0cc46 100644 --- a/virtweb_backend/src/controllers/iso_controller.rs +++ b/virtweb_backend/src/controllers/iso_controller.rs @@ -8,6 +8,7 @@ use actix_web::{web, HttpResponse}; use futures_util::StreamExt; use std::fs::File; use std::io::Write; +use std::os::unix::fs::MetadataExt; #[derive(Debug, MultipartForm)] pub struct UploadIsoForm { @@ -105,3 +106,23 @@ pub async fn upload_from_url(req: web::Json) -> HttpResult { Ok(HttpResponse::Accepted().finish()) } + +#[derive(serde::Serialize)] +struct IsoFile { + filename: String, + size: u64, +} + +/// Get ISO files list +pub async fn get_list() -> HttpResult { + let mut list = vec![]; + for entry in AppConfig::get().iso_storage_path().read_dir()? { + let entry = entry?; + list.push(IsoFile { + filename: entry.file_name().to_string_lossy().to_string(), + size: entry.path().metadata()?.size(), + }) + } + + Ok(HttpResponse::Ok().json(list)) +} diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 20afedb..75806c1 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -108,6 +108,7 @@ async fn main() -> std::io::Result<()> { "/api/iso/upload_from_url", web::post().to(iso_controller::upload_from_url), ) + .route("/api/iso/list", web::get().to(iso_controller::get_list)) }) .bind(&AppConfig::get().listen_address)? .run() diff --git a/virtweb_frontend/package-lock.json b/virtweb_frontend/package-lock.json index 933f268..cc2386b 100644 --- a/virtweb_frontend/package-lock.json +++ b/virtweb_frontend/package-lock.json @@ -15,6 +15,7 @@ "@mdi/react": "^1.6.1", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", + "@mui/x-data-grid": "^6.12.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3589,6 +3590,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/@mui/x-data-grid": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.12.1.tgz", + "integrity": "sha512-lSY1dFBv6yh2ffkkWeMM9c0ajpwIWn/da/ec0kY8OfLa79Hboh53mN09nopylZO8MHJGfDlPvduwuWcgWs+zFw==", + "dependencies": { + "@babel/runtime": "^7.22.11", + "@mui/utils": "^5.14.7", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -15303,6 +15329,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -20424,6 +20455,18 @@ } } }, + "@mui/x-data-grid": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.12.1.tgz", + "integrity": "sha512-lSY1dFBv6yh2ffkkWeMM9c0ajpwIWn/da/ec0kY8OfLa79Hboh53mN09nopylZO8MHJGfDlPvduwuWcgWs+zFw==", + "requires": { + "@babel/runtime": "^7.22.11", + "@mui/utils": "^5.14.7", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -28769,6 +28812,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", diff --git a/virtweb_frontend/package.json b/virtweb_frontend/package.json index d95528a..80235fb 100644 --- a/virtweb_frontend/package.json +++ b/virtweb_frontend/package.json @@ -10,6 +10,7 @@ "@mdi/react": "^1.6.1", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", + "@mui/x-data-grid": "^6.12.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/virtweb_frontend/src/api/IsoFilesApi.ts b/virtweb_frontend/src/api/IsoFilesApi.ts index fb909f4..1246f4d 100644 --- a/virtweb_frontend/src/api/IsoFilesApi.ts +++ b/virtweb_frontend/src/api/IsoFilesApi.ts @@ -1,5 +1,10 @@ import { APIClient } from "./ApiClient"; +export interface IsoFile { + filename: string; + size: number; +} + export class IsoFilesApi { /** * Upload a new ISO file to the server @@ -29,4 +34,16 @@ export class IsoFilesApi { jsonData: { url: url, filename: filename }, }); } + + /** + * Get iso files list + */ + static async GetList(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/iso/list", + }) + ).data; + } } diff --git a/virtweb_frontend/src/routes/IsoFilesRoute.tsx b/virtweb_frontend/src/routes/IsoFilesRoute.tsx index a056199..9b350fa 100644 --- a/virtweb_frontend/src/routes/IsoFilesRoute.tsx +++ b/virtweb_frontend/src/routes/IsoFilesRoute.tsx @@ -2,22 +2,44 @@ import { Button, LinearProgress, TextField, Typography } from "@mui/material"; import { filesize } from "filesize"; import { MuiFileInput } from "mui-file-input"; import React from "react"; -import { IsoFilesApi } from "../api/IsoFilesApi"; +import { IsoFile, IsoFilesApi } from "../api/IsoFilesApi"; import { ServerApi } from "../api/ServerApi"; import { useAlert } from "../hooks/providers/AlertDialogProvider"; import { useSnackbar } from "../hooks/providers/SnackbarProvider"; import { VirtWebPaper } from "../widgets/VirtWebPaper"; import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; +import { AsyncWidget } from "../widgets/AsyncWidget"; +import { DataGrid, GridColDef, GridRowsProp } from "@mui/x-data-grid"; export function IsoFilesRoute(): React.ReactElement { + const [list, setList] = React.useState(); + + const loadKey = React.useRef(1); + + const load = async () => { + setList(await IsoFilesApi.GetList()); + }; + + const reload = () => { + loadKey.current += 1; + setList(undefined); + }; + return ( - - alert("file uploaded!")} /> - alert("file uploaded!")} - /> - + ( + + + + + + )} + /> ); } @@ -145,3 +167,38 @@ function UploadIsoFileFromUrlCard(p: { ); } + +function IsoFilesList(p: { + list: IsoFile[]; + onReload: () => void; +}): React.ReactElement { + if (p.list.length === 0) + return ( + + No ISO file uploaded for now. + + ); + + const columns: GridColDef[] = [ + { field: "filename", headerName: "File name", flex: 3 }, + { + field: "size", + headerName: "File size", + flex: 1, + renderCell(params) { + return filesize(params.row.size); + }, + }, + ]; + + return ( + + c.filename} + rows={p.list} + columns={columns} + autoHeight={true} + /> + + ); +}