Can upload disk images on the server
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		@@ -38,6 +38,7 @@ import { LoginRoute } from "./routes/auth/LoginRoute";
 | 
			
		||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginPage";
 | 
			
		||||
import { DiskImagesRoute } from "./routes/DiskImagesRoute";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -63,6 +64,8 @@ export function App() {
 | 
			
		||||
        <Route path="*" element={<BaseAuthenticatedPage />}>
 | 
			
		||||
          <Route path="" element={<HomeRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="disk_images" element={<DiskImagesRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="iso" element={<IsoFilesRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="vms" element={<VMListRoute />} />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								virtweb_frontend/src/api/DiskImageApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								virtweb_frontend/src/api/DiskImageApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
 | 
			
		||||
export interface DiskImage {}
 | 
			
		||||
 | 
			
		||||
export class DiskImageApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Upload a new disk image file to the server
 | 
			
		||||
   */
 | 
			
		||||
  static async Upload(
 | 
			
		||||
    file: File,
 | 
			
		||||
    progress: (progress: number) => void
 | 
			
		||||
  ): Promise<void> {
 | 
			
		||||
    const fd = new FormData();
 | 
			
		||||
    fd.append("file", file);
 | 
			
		||||
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      uri: "/disk_images/upload",
 | 
			
		||||
      formData: fd,
 | 
			
		||||
      upProgress: progress,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the list of disk images
 | 
			
		||||
   */
 | 
			
		||||
  static async GetList(): Promise<DiskImage[]> {
 | 
			
		||||
    // TODO
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ export interface ServerConfig {
 | 
			
		||||
  local_auth_enabled: boolean;
 | 
			
		||||
  oidc_auth_enabled: boolean;
 | 
			
		||||
  iso_mimetypes: string[];
 | 
			
		||||
  disk_images_mimetypes: string[];
 | 
			
		||||
  net_mac_prefix: string;
 | 
			
		||||
  builtin_nwfilter_rules: string[];
 | 
			
		||||
  nwfilter_chains: string[];
 | 
			
		||||
@@ -13,6 +14,7 @@ export interface ServerConfig {
 | 
			
		||||
 | 
			
		||||
export interface ServerConstraints {
 | 
			
		||||
  iso_max_size: number;
 | 
			
		||||
  disk_image_max_size: number;
 | 
			
		||||
  vnc_token_duration: number;
 | 
			
		||||
  vm_name_size: LenConstraint;
 | 
			
		||||
  vm_title_size: LenConstraint;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										151
									
								
								virtweb_frontend/src/routes/DiskImagesRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								virtweb_frontend/src/routes/DiskImagesRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
import RefreshIcon from "@mui/icons-material/Refresh";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  LinearProgress,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { filesize } from "filesize";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { DiskImage, DiskImageApi } from "../api/DiskImageApi";
 | 
			
		||||
import { ServerApi } from "../api/ServerApi";
 | 
			
		||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
			
		||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { FileInput } from "../widgets/forms/FileInput";
 | 
			
		||||
import { VirtWebPaper } from "../widgets/VirtWebPaper";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
 | 
			
		||||
export function DiskImagesRoute(): React.ReactElement {
 | 
			
		||||
  const [list, setList] = React.useState<DiskImage[] | undefined>();
 | 
			
		||||
 | 
			
		||||
  const loadKey = React.useRef(1);
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setList(await DiskImageApi.GetList());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const reload = () => {
 | 
			
		||||
    loadKey.current += 1;
 | 
			
		||||
    setList(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer label="Disk images">
 | 
			
		||||
      <AsyncWidget
 | 
			
		||||
        loadKey={loadKey.current}
 | 
			
		||||
        errMsg="Failed to load disk images list!"
 | 
			
		||||
        load={load}
 | 
			
		||||
        ready={list !== undefined}
 | 
			
		||||
        build={() => (
 | 
			
		||||
          <VirtWebRouteContainer
 | 
			
		||||
            label="Disk images management"
 | 
			
		||||
            actions={
 | 
			
		||||
              <span>
 | 
			
		||||
                <Tooltip title="Refresh Disk images list">
 | 
			
		||||
                  <IconButton onClick={reload}>
 | 
			
		||||
                    <RefreshIcon />
 | 
			
		||||
                  </IconButton>
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
              </span>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <UploadDiskImageCard onFileUploaded={reload} />
 | 
			
		||||
            <DiskImageList list={list!} onReload={reload} />
 | 
			
		||||
          </VirtWebRouteContainer>
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function UploadDiskImageCard(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.disk_image_max_size
 | 
			
		||||
    ) {
 | 
			
		||||
      alert(
 | 
			
		||||
        `The file is too big (max size allowed: ${filesize(
 | 
			
		||||
          ServerApi.Config.constraints.disk_image_max_size
 | 
			
		||||
        )}`
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      newValue &&
 | 
			
		||||
      !ServerApi.Config.disk_images_mimetypes.includes(newValue.type)
 | 
			
		||||
    ) {
 | 
			
		||||
      alert(`Selected file mimetype is not allowed! (${newValue.type})`);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setValue(newValue);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const upload = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      setUploadProgress(0);
 | 
			
		||||
      await DiskImageApi.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" noHorizontalMargin>
 | 
			
		||||
        <Typography variant="body1">
 | 
			
		||||
          Upload in progress ({Math.floor(uploadProgress * 100)}%)...
 | 
			
		||||
        </Typography>
 | 
			
		||||
        <LinearProgress variant="determinate" value={uploadProgress * 100} />
 | 
			
		||||
      </VirtWebPaper>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebPaper label="Disk image upload" noHorizontalMargin>
 | 
			
		||||
      <div style={{ display: "flex", alignItems: "center" }}>
 | 
			
		||||
        <FileInput
 | 
			
		||||
          value={value}
 | 
			
		||||
          onChange={handleChange}
 | 
			
		||||
          style={{ flex: 1 }}
 | 
			
		||||
          slotProps={{
 | 
			
		||||
            htmlInput: {
 | 
			
		||||
              accept: ServerApi.Config.disk_images_mimetypes.join(","),
 | 
			
		||||
            },
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {value && <Button onClick={upload}>Upload</Button>}
 | 
			
		||||
      </div>
 | 
			
		||||
    </VirtWebPaper>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function DiskImageList(p: {
 | 
			
		||||
  list: DiskImage[];
 | 
			
		||||
  onReload: () => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  return <>todo</>;
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,15 @@ import { RouterLink } from "../widgets/RouterLink";
 | 
			
		||||
 | 
			
		||||
export function NotFoundRoute(): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ textAlign: "center" }}>
 | 
			
		||||
    <div
 | 
			
		||||
      style={{
 | 
			
		||||
        textAlign: "center",
 | 
			
		||||
        flex: 1,
 | 
			
		||||
        justifyContent: "center",
 | 
			
		||||
        display: "flex",
 | 
			
		||||
        flexDirection: "column",
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <h1>404 Not found</h1>
 | 
			
		||||
      <p>The page you requested was not found!</p>
 | 
			
		||||
      <RouterLink to="/">
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import {
 | 
			
		||||
  mdiApi,
 | 
			
		||||
  mdiBoxShadow,
 | 
			
		||||
  mdiDisc,
 | 
			
		||||
  mdiHarddisk,
 | 
			
		||||
  mdiHome,
 | 
			
		||||
  mdiInformation,
 | 
			
		||||
  mdiLan,
 | 
			
		||||
@@ -66,6 +67,11 @@ export function BaseAuthenticatedPage(): React.ReactElement {
 | 
			
		||||
            uri="/nwfilter"
 | 
			
		||||
            icon={<Icon path={mdiSecurityNetwork} size={1} />}
 | 
			
		||||
          />
 | 
			
		||||
          <NavLink
 | 
			
		||||
            label="Disk images"
 | 
			
		||||
            uri="/disk_images"
 | 
			
		||||
            icon={<Icon path={mdiHarddisk} size={1} />}
 | 
			
		||||
          />
 | 
			
		||||
          <NavLink
 | 
			
		||||
            label="ISO files"
 | 
			
		||||
            uri="/iso"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ export function FileInput(
 | 
			
		||||
              <InputAdornment position="start">
 | 
			
		||||
                <AttachFileIcon />
 | 
			
		||||
                  
 | 
			
		||||
                {p.value ? p.value.name : "Insert a file"}
 | 
			
		||||
                {p.value ? p.value.name : "Select a file"}
 | 
			
		||||
              </InputAdornment>
 | 
			
		||||
            </>
 | 
			
		||||
          ),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user