Skip to content

Commit

Permalink
feat(iso) add upload progress
Browse files Browse the repository at this point in the history
  • Loading branch information
edlerd committed Aug 8, 2023
1 parent 938f426 commit 23846ba
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 73 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@monaco-editor/react": "^4.4.6",
"@tanstack/react-query": "^4.14.5",
"@use-it/event-listener": "^0.1.7",
"axios": "1.3.2",
"cytoscape": "3.23.0",
"cytoscape-popper": "2.0.0",
"formik": "2.2.9",
Expand Down
35 changes: 24 additions & 11 deletions src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
LxdStoragePool,
LxdStoragePoolResources,
LxdStorageVolume,
UploadState,
} from "types/storage";
import { LxdApiResponse } from "types/apiResponse";
import { LxdOperationResponse } from "types/operation";
import { TIMEOUT_300, watchOperation } from "api/operations";
import axios, { AxiosResponse } from "axios";

export const fetchStoragePool = (
pool: string,
Expand Down Expand Up @@ -122,19 +124,30 @@ export const createIsoStorageVolume = (
pool: string,
isoFile: File,
name: string,
project: string
project: string,
setUploadState: (value: UploadState) => void
): Promise<LxdOperationResponse> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${pool}/volumes/custom?project=${project}`, {
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
"X-LXD-name": name,
"X-LXD-type": "iso",
},
body: isoFile,
})
.then(handleResponse)
axios
.post(
`/1.0/storage-pools/${pool}/volumes/custom?project=${project}`,
isoFile,
{
headers: {
"Content-Type": "application/octet-stream",
"X-LXD-name": name,
"X-LXD-type": "iso",
},
onUploadProgress: (event) => {
setUploadState({
percentage: event.progress ? Math.floor(event.progress * 100) : 0,
loaded: event.loaded,
total: event.total,
});
},
}
)
.then((response: AxiosResponse<LxdOperationResponse>) => response.data)
.then((data: LxdOperationResponse) => {
watchOperation(data.operation, TIMEOUT_300).then(resolve).catch(reject);
})
Expand Down
15 changes: 15 additions & 0 deletions src/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { FC } from "react";

interface Props {
percentage: number;
}

const ProgressBar: FC<Props> = ({ percentage }: Props) => {
return (
<div className="p-progress-bar">
<div style={{ width: `${percentage}%` }} />
</div>
);
};

export default ProgressBar;
117 changes: 56 additions & 61 deletions src/pages/storage/UploadIsoImage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import React, { FC, useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import {
Button,
Input,
Notification,
Select,
useNotify,
} from "@canonical/react-components";
import { Button, Input, Select, useNotify } from "@canonical/react-components";
import { createIsoStorageVolume, fetchStoragePools } from "api/storage-pools";
import SubmitButton from "components/SubmitButton";
import { useProject } from "context/project";
import Loader from "components/Loader";
import NotificationRow from "components/NotificationRow";
import ProgressBar from "components/ProgressBar";
import { UploadState } from "types/storage";
import { humanFileSize } from "util/helpers";
import UploadIsoImageHint from "pages/storage/UploadIsoImageHint";

interface Props {
onFinish: (name: string, pool: string) => void;
Expand All @@ -27,6 +25,7 @@ const UploadIsoImage: FC<Props> = ({ onCancel, onFinish }) => {
const [name, setName] = useState<string>("");
const [isLoading, setLoading] = useState(false);
const [pool, setPool] = useState("");
const [uploadState, setUploadState] = useState<UploadState | null>(null);

useEffect(() => {
notify.clear();
Expand Down Expand Up @@ -58,8 +57,15 @@ const UploadIsoImage: FC<Props> = ({ onCancel, onFinish }) => {
if (!file) {
return;
}
notify.clear();
setLoading(true);
createIsoStorageVolume(pool, file, name, project?.name ?? "")
createIsoStorageVolume(
pool,
file,
name,
project?.name ?? "",
setUploadState
)
.then(() => {
onFinish(file.name, pool);
})
Expand All @@ -68,6 +74,7 @@ const UploadIsoImage: FC<Props> = ({ onCancel, onFinish }) => {
})
.finally(() => {
setLoading(false);
setUploadState(null);
void queryClient.invalidateQueries([
queryKeys.storage,
pool,
Expand All @@ -86,62 +93,50 @@ const UploadIsoImage: FC<Props> = ({ onCancel, onFinish }) => {

return (
<>
{!notify.notification ? (
<Notification
severity="caution"
title="Image must be prepared with distrobuilder"
>
For a windows image with name <code>WindowsIsoImage.iso</code> use the
command
<pre className="p-code-snippet__block--icon">
<code>
sudo distrobuilder repack-windows WindowsIsoImage.iso
win11.lxd.iso
</code>
</pre>
{notify.notification ? <NotificationRow /> : <UploadIsoImageHint />}
<div className={uploadState === null ? "" : "u-hide"}>
<Input
name="iso"
type="file"
id="iso-image"
label="ISO image"
onChange={changeFile}
/>
<Input
name="name"
type="text"
id="name"
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={file === null}
/>
<Select
label="Storage pool"
id="storagePool"
options={storagePools.map((pool) => ({
label: pool.name,
value: pool.name,
}))}
onChange={(e) => {
setPool(e.target.value);
}}
value={pool}
disabled={file === null}
/>
</div>
{uploadState && (
<>
<ProgressBar percentage={Math.floor(uploadState.percentage)} />
<p>
and upload the resulting <code>win11.lxd.iso</code> file.
{humanFileSize(uploadState.loaded)} loaded of{" "}
{humanFileSize(uploadState.total ?? 0)}
</p>
<a
href="https://discourse.ubuntu.com/t/how-to-install-a-windows-11-vm-using-lxd/28940"
target="_blank"
rel="noreferrer"
>
Learn how to install a Windows 11 VM using LXD
</a>
</Notification>
) : (
<NotificationRow />
{uploadState.loaded === uploadState.total && (
<Loader text="Validating ISO" />
)}
</>
)}
<Input
name="iso"
type="file"
id="iso-image"
label="ISO image"
onChange={changeFile}
/>
<Input
name="name"
type="text"
id="name"
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={file === null}
/>
<Select
label="Storage pool"
id="storagePool"
options={storagePools.map((pool) => ({
label: pool.name,
value: pool.name,
}))}
onChange={(e) => {
setPool(e.target.value);
}}
value={pool}
disabled={file === null}
/>
<footer className="p-modal__footer">
<Button onClick={onCancel}>Cancel</Button>
<SubmitButton
Expand Down
34 changes: 34 additions & 0 deletions src/pages/storage/UploadIsoImageHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { FC } from "react";

const UploadIsoImageHint: FC = () => {
return (
<div className={`p-notification--caution`}>
<div className="p-notification__content">
<h5 className="p-notification__title">
Image must be prepared with distrobuilder
</h5>
<p>
For a windows image with name <code>WindowsIsoImage.iso</code> use the
command
</p>
<pre className="p-code-snippet__block--icon">
<code>
sudo distrobuilder repack-windows WindowsIsoImage.iso win11.lxd.iso
</code>
</pre>
<p>
and upload the resulting <code>win11.lxd.iso</code> file.
</p>
<a
href="https://discourse.ubuntu.com/t/how-to-install-a-windows-11-vm-using-lxd/28940"
target="_blank"
rel="noreferrer"
>
Learn how to install a Windows 11 VM using LXD
</a>
</div>
</div>
);
};

export default UploadIsoImageHint;
14 changes: 14 additions & 0 deletions src/sass/_progress_bar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.p-progress-bar {
border: 1px solid #00000026;
border-radius: 2px;
height: 30px;
margin-top: 5px;
padding: 2px;
width: 100%;

div {
background-color: #0e8420;
border-radius: 2px;
height: 100%;
}
}
1 change: 1 addition & 0 deletions src/sass/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ $border-thin: 1px solid $color-mid-light !default;
@import "profile_detail_panel";
@import "profile_list";
@import "profile_used_by_default_project";
@import "progress_bar";
@import "project_select";
@import "rename_header";
@import "scrollable_table";
Expand Down
6 changes: 6 additions & 0 deletions src/types/storage.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ export interface LxdStoragePoolResources {
total: number;
};
}

export interface UploadState {
percentage: number;
loaded: number;
total?: number;
}
25 changes: 24 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3125,6 +3125,15 @@ axe-core@^4.6.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece"
integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==

[email protected]:
version "1.3.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3"
integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"

axobject-query@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1"
Expand Down Expand Up @@ -5376,7 +5385,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==

follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
Expand Down Expand Up @@ -5416,6 +5425,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"

form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"

[email protected]:
version "2.2.9"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
Expand Down Expand Up @@ -8680,6 +8698,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"

proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==

psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
Expand Down

0 comments on commit 23846ba

Please sign in to comment.