Skip to content

Commit

Permalink
Merge pull request #95 from robur-coop/updated_volumes
Browse files Browse the repository at this point in the history
Upload and download data from and to volumes
  • Loading branch information
hannesm authored Nov 20, 2024
2 parents eb3fe04 + 9e77c55 commit 7933ad7
Show file tree
Hide file tree
Showing 17 changed files with 650 additions and 447 deletions.
2 changes: 1 addition & 1 deletion albatross_json.ml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ let success = function
| `Block_devices bs -> block_infos bs
| `Old_unikernels _ -> `String "old unikernels not supported"
| `Unikernel_image _ -> `String "unikernel image not supported"
| `Block_device_image _ -> `String "block device image not supported"
| `Block_device_image (_, bd) -> `String bd

let console_data_to_json (ts, data) =
`Assoc
Expand Down
129 changes: 126 additions & 3 deletions assets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function postAlert(bg_color, content) {
alertContainer.classList.remove("block", `${bg_color}`)
alertContainer.classList.add("hidden")
alertContainer.removeChild(alert);
}, 2500);
}, 4000);
}

async function deployUnikernel() {
Expand Down Expand Up @@ -579,10 +579,32 @@ async function createVolume() {
const block_compressed = document.getElementById("block_compressed").checked;
const block_data = document.getElementById("block_data").files[0];

if (!isValidName(block_name)) {
if (!block_name || block_name === '') {
formAlert.classList.remove("hidden", "text-primary-500");
formAlert.classList.add("text-secondary-500");
formAlert.textContent = "Please enter a name for this volume"
formAlert.textContent = "Please enter a name"
buttonLoading(createButton, false, "Create volume")
return;
}
if (!isLengthValid(block_name)) {
formAlert.classList.remove("hidden", "text-primary-500");
formAlert.classList.add("text-secondary-500");
formAlert.textContent = "The name must have at least 1 character and must not exceed 63 characters."
buttonLoading(createButton, false, "Create volume")
return;
}
if (!isStartingCharacterValid(block_name)) {
formAlert.classList.remove("hidden", "text-primary-500");
formAlert.classList.add("text-secondary-500");
formAlert.textContent = "The name cannot start with a hyphen (-)."
buttonLoading(createButton, false, "Create volume")
return;
}
if (!areCharactersValid(block_name)) {
formAlert.classList.remove("hidden", "text-primary-500");
formAlert.classList.add("text-secondary-500");
formAlert.textContent = "Only letters (a-z, A-Z), digits (0-9), hyphens (-), and periods (.) are permitted.\
Special characters, spaces, and symbols other than the specified ones are not allowed"
buttonLoading(createButton, false, "Create volume")
return;
}
Expand Down Expand Up @@ -639,6 +661,89 @@ async function createVolume() {
}
}

async function downloadVolume(block_name) {
const downloadButton = document.getElementById(`download-block-button-${block_name}`);
const compression_level = document.getElementById("compression-level").innerText;
const molly_csrf = document.getElementById("molly-csrf").value;
try {
buttonLoading(downloadButton, true, "Downloading...")
const response = await fetch("/api/volume/download", {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(
{
"block_name": block_name,
"compression_level": Number(compression_level),
"molly_csrf": molly_csrf
})
})
if (!response.ok) {
const data = await response.json();
postAlert("bg-secondary-300", data.data);
buttonLoading(downloadButton, false, `Download ${block_name}`)
} else {
// trigger a download
const filename = response.headers.get('content-disposition')
.split(';')[1].split('=')[1].replace(/"/g, '');
const blob = await response.blob();
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = filename;
downloadLink.click();
URL.revokeObjectURL(downloadLink.href);
postAlert("bg-primary-300", "Download in progress");
buttonLoading(downloadButton, false, `Download ${block_name}`)
}
} catch (error) {
postAlert("bg-secondary-300", error);
buttonLoading(downloadButton, false, `Download ${block_name}`)
}
}

async function uploadToVolume(block_name) {
const uploadButton = document.getElementById(`upload-block-button-${block_name}`);
const block_compressed = document.getElementById("block_compressed").checked;
const block_data = document.getElementById("block_data").files[0];
const molly_csrf = document.getElementById("molly-csrf").value;

if (!block_data) {
postAlert("bg-secondary-300", "Please select a file to be uploaded");
buttonLoading(uploadButton, false, "Upload data")
return;
}

try {
buttonLoading(uploadButton, true, "Uploading...")
let formData = new FormData();
let json_data = JSON.stringify(
{
"block_name": block_name,
"block_compressed": block_compressed,
"molly_csrf": molly_csrf
})
formData.append("block_data", block_data)
formData.append("json_data", json_data)
const response = await fetch("/api/volume/upload", {
method: 'POST',
body: formData
})
const data = await response.json();
if (data.status === 200) {
postAlert("bg-primary-300", `Upload is succesful: ${data.data}`);
setTimeout(() => window.location.reload(), 1000);
buttonLoading(uploadButton, false, "Upload data")
} else {
postAlert("bg-secondary-300", data.data);
buttonLoading(uploadButton, false, "Upload data")
}
} catch (error) {
postAlert("bg-secondary-300", error);
buttonLoading(uploadButton, false, "Upload data")
}
}

function isValidName(s) {
const length = s.length;
if (length === 0 || length >= 64) return false;
Expand All @@ -652,3 +757,21 @@ function isValidName(s) {
return true;
}

function isLengthValid(s) {
const length = s.length;
return length > 0 && length < 64;
}

function isStartingCharacterValid(s) {
return s[0] !== '-';
}

function areCharactersValid(s) {
for (let i = 0; i < s.length; i++) {
const char = s[i];
if (!(/[a-zA-Z0-9.-]/).test(char)) {
return false;
}
}
return true;
}
2 changes: 1 addition & 1 deletion assets/style.css

Large diffs are not rendered by default.

20 changes: 6 additions & 14 deletions dashboard.ml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ let dashboard_layout ~csrf (user : User_model.user) ~icon
a_id "alert-container";
a_class
[
"absolute top-1/4 rounded-md right-4 z-50 w-fit \
"fixed top-1/4 rounded-md right-4 z-50 w-fit \
space-y-2 p-4 shadow text-wrap hidden";
];
]
Expand Down Expand Up @@ -545,19 +545,11 @@ let dashboard_layout ~csrf (user : User_model.user) ~icon
];
]
else div []);
button
~a:
[
a_id "logout-button";
a_onclick "logout()";
a_class
[
"my-3 py-3 rounded bg-secondary-500 \
hover:bg-secondary-800 w-full text-gray-50 \
font-semibold";
];
]
[ txt "Logout" ];
Utils.button_component
~attribs:
[ a_id "logout-button"; a_onclick "logout()" ]
~content:(txt "Logout") ~extra_css:"w-full my-2"
~btn_type:`Danger_full ();
];
];
section
Expand Down
73 changes: 73 additions & 0 deletions modal_dialog.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
let modal_dialog ~modal_title ~button_content ?(button_type = `Primary_full)
~content () =
Tyxml_html.(
section ~a:[]
[
div
~a:[ Unsafe.string_attrib "x-data" "{modalIsOpen: false}" ]
[
Utils.button_component ~content:button_content ~btn_type:button_type
~attribs:
[ Unsafe.string_attrib "x-on:click" "modalIsOpen = true" ]
();
div
~a:
[
Unsafe.string_attrib "x-cloak" "";
Unsafe.string_attrib "x-show" "modalIsOpen";
Unsafe.string_attrib "x-transition.opacity.duration.200ms" "";
Unsafe.string_attrib "x-trap.inert.noscroll" "modalIsOpen";
Unsafe.string_attrib "x-on:keydown.esc.window"
"modalIsOpen = false";
Unsafe.string_attrib "x-on:click.self" "modalIsOpen = false";
a_class
[
"fixed inset-0 z-30 flex items-end justify-center \
bg-black/20 p-4 backdrop-blur-md sm:items-center";
];
a_role [ "dialog" ];
a_aria "modal" [ "true" ];
]
[
div
~a:
[
Unsafe.string_attrib "x-show" "modalIsOpen";
Unsafe.string_attrib "x-transition:enter"
"transition ease-out duration-200 delay-100 \
motion-reduce:transition-opacity";
Unsafe.string_attrib "x-transition:enter-start"
"opacity-0 scale-50";
Unsafe.string_attrib "x-transition:enter-end"
"opacity-100 scale-100";
a_class
[
"flex max-w-xl flex-col gap-4 overflow-hidden \
rounded-md border border-neutral-300 bg-gray-50";
];
]
[
div
~a:
[
a_class
[ "flex items-center justify-between border-b p-4" ];
]
[
h3
~a:[ a_class [ "font-bold text-gray-700" ] ]
[ txt modal_title ];
i
~a:
[
a_class [ "fa-solid fa-x text-sm cursor-pointer" ];
Unsafe.string_attrib "x-on:click"
"modalIsOpen = false";
]
[];
];
div ~a:[ a_class [ "px-4" ] ] [ content ];
];
];
];
])
62 changes: 15 additions & 47 deletions settings_page.ml
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,10 @@ let settings_layout (configuration : Configuration.t) =
div
~a:[ a_id "add-config" ]
[
button
~a:
[
a_onclick "openConfigForm('','','','')";
a_class
[
"inline-flex items-center gap-x-2 text-sm \
font-semibold rounded-lg border border-1 py-1 px-2 \
border-primary-400 text-primary-600 \
hover:text-primary-500 focus:outline-none \
focus:text-primary-800 disabled:opacity-50 \
disabled:pointer-events-none";
];
]
[ i ~a:[ a_class [ "fa-solid fa-plus" ] ] [] ];
Utils.button_component
~attribs:[ a_onclick "openConfigForm('','','','')" ]
~content:(i ~a:[ a_class [ "fa-solid fa-plus" ] ] [])
~btn_type:`Primary_outlined ();
];
];
hr ~a:[ a_class [ "border border-primary-500 my-5" ] ] ();
Expand Down Expand Up @@ -199,18 +188,10 @@ let settings_layout (configuration : Configuration.t) =
div
~a:[ a_class [ "mx-auto my-4 flex justify-center" ] ]
[
button
~a:
[
a_onclick "saveConfig()";
a_id "config-button";
a_class
[
"py-3 px-3 rounded bg-primary-500 \
hover:bg-primary-800 text-gray-50 font-semibold";
];
]
[ txt "" ];
Utils.button_component
~attribs:
[ a_onclick "saveConfig()"; a_id "config-button" ]
~content:(txt "") ~btn_type:`Primary_full ();
];
];
];
Expand Down Expand Up @@ -373,33 +354,20 @@ let settings_layout (configuration : Configuration.t) =
];
]
[
button
~a:
Utils.button_component
~attribs:
[
a_onclick
("openConfigForm('" ^ ip ^ "','"
^ port ^ "','"
^ String.escaped certificate ^ "','"
^ String.escaped private_key ^ "')");
a_class
[
"inline-flex items-center \
gap-x-2 text-sm font-semibold \
rounded-lg border border-1 py-1 \
px-2 border-primary-400 \
text-primary-600 \
hover:text-primary-500 \
focus:outline-none \
focus:text-primary-800 \
disabled:opacity-50 \
disabled:pointer-events-none";
];
]
[
i
~a:[ a_class [ "fa-solid fa-pen" ] ]
[];
];
~content:
(i
~a:[ a_class [ "fa-solid fa-pen" ] ]
[])
~btn_type:`Primary_outlined ();
];
];
];
Expand Down
18 changes: 5 additions & 13 deletions sign_in.ml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ let login_page ~icon () =
a_id "alert-container";
a_class
[
"absolute top-1/4 rounded-md right-4 z-50 w-fit \
"fixed top-1/4 rounded-md right-4 z-50 w-fit \
space-y-2 p-4 shadow text-wrap hidden";
];
]
Expand Down Expand Up @@ -172,18 +172,10 @@ let login_page ~icon () =
];
div
[
button
~a:
[
a_id "login-button";
a_class
[
"py-3 rounded bg-primary-500 \
hover:bg-primary-800 w-full \
text-gray-50 font-semibold";
];
]
[ txt "Sign In" ];
Utils.button_component
~attribs:[ a_id "login-button" ]
~content:(txt "Sign In")
~btn_type:`Primary_full ();
];
];
];
Expand Down
Loading

0 comments on commit 7933ad7

Please sign in to comment.