fix bugs in network config change,
generalize dialog layout, add form validation callback, add password change form
This commit is contained in:
parent
4806d7f18f
commit
e97f8d5bbc
15
account.html
15
account.html
@ -64,17 +64,10 @@
|
||||
<p id="nodes">Nodes:</p>
|
||||
</div>
|
||||
<div class="w3-card w3-padding">
|
||||
<h3>Password</h3>
|
||||
<form id="password-form">
|
||||
<div class="input-grid" style="grid-template-columns: auto auto 1fr;">
|
||||
<label for="new-password">New Password</label>
|
||||
<input class="w3-input w3-border" type="password" id="new-password">
|
||||
<span></span>
|
||||
<label for="confirm-password">Confirm Password</label>
|
||||
<input class="w3-input w3-border" type="password" id="confirm-password">
|
||||
<span></span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex row nowrap">
|
||||
<h3>Password</h3>
|
||||
<button class="w3-button w3-margin" id="change-password">Change Password</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w3-card w3-padding">
|
||||
<h3>Cluster Resources</h3>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { dialog } from "./dialog.js";
|
||||
import { requestAPI, goToPage, getCookie, setTitleAndHeader } from "./utils.js";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
@ -42,6 +43,8 @@ async function init () {
|
||||
document.querySelector("#nodes").innerText = `Nodes: ${nodes.toString()}`;
|
||||
|
||||
populateResources("#resource-container", meta, resources);
|
||||
|
||||
document.querySelector("#change-password").addEventListener("click", handlePasswordChangeForm);
|
||||
}
|
||||
|
||||
function populateResources (containerID, meta, resources) {
|
||||
@ -117,3 +120,33 @@ function parseNumber (value, unitData) {
|
||||
return `${value} ${unit}`;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePasswordChangeForm () {
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="new-password">New Password</label>
|
||||
<input class="w3-input w3-border" type="password" id="new-password" name="new-password">
|
||||
<label for="confirm-password">Confirm Password</label>
|
||||
<input class="w3-input w3-border" type="password" id="confirm-password" name="confirm-password">
|
||||
</form>
|
||||
<p class="w3-large" id="error-message" style="text-align: center; color: var(--negative-color); margin-top: 0.5em; margin-bottom: 0;"></p>
|
||||
`;
|
||||
dialog("Change Password", body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
const result = await requestAPI("/auth/password", "POST", {password: form.get("new-password")});
|
||||
if (result.status !== 200) {
|
||||
alert(result.error);
|
||||
}
|
||||
}
|
||||
}, (dialog, form) => {
|
||||
const pass = form.get("new-password");
|
||||
const conf = form.get("confirm-password");
|
||||
if (pass !== conf) {
|
||||
dialog.querySelector("#error-message").innerText = "Passwords must match";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ function addDiskLine (fieldset, busPrefix, busName, device, diskDetails) {
|
||||
async function handleDiskDetach () {
|
||||
const disk = this.dataset.disk;
|
||||
const header = `Detach ${disk}`;
|
||||
const body = `<p>Are you sure you want to detach disk</p><p>${disk}</p>`;
|
||||
const body = `<p>Are you sure you want to detach disk ${disk}</p>`;
|
||||
dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
|
||||
@ -250,7 +250,12 @@ async function handleDiskDetach () {
|
||||
|
||||
async function handleDiskAttach () {
|
||||
const header = `Attach ${this.dataset.disk}`;
|
||||
const body = `<label for="device">${type === "qemu" ? "SATA" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" required></input>`;
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="device">${type === "qemu" ? "SATA" : "MP"}</label>
|
||||
<input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" required></input>
|
||||
</form>
|
||||
`;
|
||||
|
||||
dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -274,7 +279,12 @@ async function handleDiskAttach () {
|
||||
|
||||
async function handleDiskResize () {
|
||||
const header = `Resize ${this.dataset.disk}`;
|
||||
const body = "<label for=\"size-increment\">Size Increment (GiB)</label><input class=\"w3-input w3-border\" name=\"size-increment\" id=\"size-increment\" type=\"number\" min=\"0\" max=\"131072\"></input>";
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="size-increment">Size Increment (GiB)</label>
|
||||
<input class="w3-input w3-border" name="size-increment" id="size-increment" type="number" min="0" max="131072"></input>
|
||||
</form>
|
||||
`;
|
||||
|
||||
dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -310,8 +320,10 @@ async function handleDiskMove () {
|
||||
const select = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select"><option hidden disabled selected value></option>${options}</select>`;
|
||||
|
||||
const body = `
|
||||
${select}
|
||||
<label for="delete-check">Delete Source</label><input class="w3-input w3-border" name="delete-check" id="delete-check" type="checkbox" checked required>
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
${select}
|
||||
<label for="delete-check">Delete Source</label><input class="w3-input w3-border" name="delete-check" id="delete-check" type="checkbox" checked required>
|
||||
</form>
|
||||
`;
|
||||
|
||||
dialog(header, body, async (result, form) => {
|
||||
@ -337,7 +349,7 @@ async function handleDiskMove () {
|
||||
async function handleDiskDelete () {
|
||||
const disk = this.dataset.disk;
|
||||
const header = `Delete ${disk}`;
|
||||
const body = `<p>Are you sure you want to <strong>delete</strong> disk</p><p>${disk}</p>`;
|
||||
const body = `<p>Are you sure you want to <strong>delete</strong> disk${disk}</p>`;
|
||||
dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
|
||||
@ -367,9 +379,11 @@ async function handleDiskAdd () {
|
||||
const select = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select" required><option hidden disabled selected value></option>${options}</select>`;
|
||||
|
||||
const body = `
|
||||
<label for="device">${type === "qemu" ? "SATA" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" value="0" required></input>
|
||||
${select}
|
||||
<label for="size">Size (GiB)</label><input class="w3-input w3-border" name="size" id="size" type="number" min="0" max="131072" required></input>
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="device">${type === "qemu" ? "SATA" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" value="0" required></input>
|
||||
${select}
|
||||
<label for="size">Size (GiB)</label><input class="w3-input w3-border" name="size" id="size" type="number" min="0" max="131072" required></input>
|
||||
</form>
|
||||
`;
|
||||
|
||||
dialog(header, body, async (result, form) => {
|
||||
@ -407,9 +421,11 @@ async function handleCDAdd () {
|
||||
const storageSelect = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select" required><option hidden disabled selected value></option>${storageOptions}</select>`;
|
||||
|
||||
const body = `
|
||||
<label for="device">IDE</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="3" required></input>
|
||||
${storageSelect}
|
||||
<label for="iso-select">Image</label><select class="w3-select w3-border" name="iso-select" id="iso-select" required></select>
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="device">IDE</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="3" required></input>
|
||||
${storageSelect}
|
||||
<label for="iso-select">Image</label><select class="w3-select w3-border" name="iso-select" id="iso-select" required></select>
|
||||
</form>
|
||||
`;
|
||||
|
||||
const d = dialog(header, body, async (result, form) => {
|
||||
@ -509,7 +525,11 @@ async function handleNetworkConfig () {
|
||||
const netID = this.dataset.network;
|
||||
const netDetails = this.dataset.values;
|
||||
const header = `Edit net${netID}`;
|
||||
const body = "<label for=\"rate\">Rate Limit (MB/s)</label><input type=\"number\" id=\"rate\" name=\"rate\" class=\"w3-input w3-border\">";
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="rate">Rate Limit (MB/s)</label><input type="number" id="rate" name="rate" class="w3-input w3-border">
|
||||
</form>
|
||||
`;
|
||||
|
||||
const d = dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -523,7 +543,8 @@ async function handleNetworkConfig () {
|
||||
}
|
||||
await getConfig();
|
||||
populateNetworks();
|
||||
updateBootLine(`boot-net${netID}`, { id: `net${netID}`, prefix: "net", value: config.data[`net${netID}`] });
|
||||
const id = `net${netID}`;
|
||||
updateBootLine(`boot-net${netID}`, { id, prefix: "net", value: id, detail: config.data[`net${netID}`] });
|
||||
}
|
||||
});
|
||||
|
||||
@ -551,10 +572,15 @@ async function handleNetworkDelete () {
|
||||
|
||||
async function handleNetworkAdd () {
|
||||
const header = "Create Network Interface";
|
||||
let body = "<label for=\"netid\">Interface ID</label><input type=\"number\" id=\"netid\" name=\"netid\" class=\"w3-input w3-border\"><label for=\"rate\">Rate Limit (MB/s)</label><input type=\"number\" id=\"rate\" name=\"rate\" class=\"w3-input w3-border\">";
|
||||
let body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="netid">Interface ID</label><input type="number" id="netid" name="netid" class="w3-input w3-border">
|
||||
<label for="rate">Rate Limit (MB/s)</label><input type="number" id="rate" name="rate" class="w3-input w3-border">
|
||||
`;
|
||||
if (type === "lxc") {
|
||||
body += "<label for=\"name\">Interface Name</label><input type=\"text\" id=\"name\" name=\"name\" class=\"w3-input w3-border\"></input>";
|
||||
}
|
||||
body += "</form>";
|
||||
|
||||
dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -648,7 +674,11 @@ async function handleDeviceConfig () {
|
||||
const deviceDetails = this.dataset.values;
|
||||
const deviceName = this.dataset.name;
|
||||
const header = `Edit Expansion Card ${deviceID}`;
|
||||
const body = "<label for=\"device\">Device</label><select id=\"device\" name=\"device\" required></select><label for=\"pcie\">PCI-Express</label><input type=\"checkbox\" id=\"pcie\" name=\"pcie\" class=\"w3-input w3-border\">";
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="device">Device</label><select id="device" name="device" required></select><label for="pcie">PCI-Express</label><input type="checkbox" id="pcie" name="pcie" class="w3-input w3-border">
|
||||
</form>
|
||||
`;
|
||||
|
||||
const d = dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -694,7 +724,11 @@ async function handleDeviceDelete () {
|
||||
|
||||
async function handleDeviceAdd () {
|
||||
const header = "Add Expansion Card";
|
||||
const body = "<label for=\"device\">Device</label><select id=\"device\" name=\"device\" required></select><label for=\"pcie\">PCI-Express</label><input type=\"checkbox\" id=\"pcie\" name=\"pcie\" class=\"w3-input w3-border\">";
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="device">Device</label><select id="device" name="device" required></select><label for="pcie">PCI-Express</label><input type="checkbox" id="pcie" name="pcie" class="w3-input w3-border">
|
||||
</form>
|
||||
`;
|
||||
|
||||
const d = dialog(header, body, async (result, form) => {
|
||||
if (result === "confirm") {
|
||||
@ -798,11 +832,11 @@ function updateBootLine (id, newData) {
|
||||
const enabled = document.querySelector("#enabled");
|
||||
const disabled = document.querySelector("#disabled");
|
||||
let element = null;
|
||||
if (enabled.getItemByID(id)) {
|
||||
element = enabled.getItemByID(id);
|
||||
if (enabled.querySelector(`#${id}`)) {
|
||||
element = enabled.querySelector(`#${id}`);
|
||||
}
|
||||
if (disabled.getItemByID(id)) {
|
||||
element = disabled.getItemByID(id);
|
||||
if (disabled.querySelector(`#${id}`)) {
|
||||
element = disabled.querySelector(`#${id}`);
|
||||
}
|
||||
if (element) {
|
||||
const container = element.container;
|
||||
|
@ -1,20 +1,43 @@
|
||||
export function dialog (header, body, callback = async (result, form) => { }) {
|
||||
export function dialog (header, body, onclose = async (result, form) => { }, validate = async (dialog, form) => {
|
||||
return true;
|
||||
}) {
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.innerHTML = `
|
||||
<p class="w3-large" id="prompt" style="text-align: center;"></p>
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form"></form>
|
||||
<div id="body"></div>
|
||||
<div class="w3-center w3-container">
|
||||
<button value="cancel" form="form" class="w3-button w3-margin" style="background-color: var(--negative-color, #f00); color: var(--lightbg-text-color, black);" formnovalidate>CANCEL</button>
|
||||
<button value="confirm" form="form" class="w3-button w3-margin" style="background-color: var(--positive-color, #0f0); color: var(--lightbg-text-color, black);">CONFIRM</button>
|
||||
<button id="cancel" value="cancel" form="form" class="w3-button w3-margin" style="background-color: var(--negative-color, #f00); color: var(--lightbg-text-color, black);" formnovalidate>CANCEL</button>
|
||||
<button id="confirm" value="confirm" form="form" class="w3-button w3-margin" style="background-color: var(--positive-color, #0f0); color: var(--lightbg-text-color, black);">CONFIRM</button>
|
||||
</div>
|
||||
`;
|
||||
dialog.className = "w3-container w3-card w3-border-0";
|
||||
dialog.querySelector("#prompt").innerText = header;
|
||||
dialog.querySelector("form").innerHTML = body;
|
||||
dialog.querySelector("#body").innerHTML = body;
|
||||
dialog.addEventListener("close", async () => {
|
||||
await callback(dialog.returnValue, new FormData(dialog.querySelector("form")));
|
||||
const formElem = dialog.querySelector("form");
|
||||
let formData = null;
|
||||
if (formElem) {
|
||||
formData = new FormData(formElem);
|
||||
}
|
||||
await onclose(dialog.returnValue, formData);
|
||||
dialog.parentElement.removeChild(dialog);
|
||||
});
|
||||
dialog.querySelector("#confirm").addEventListener("click", async (e) => {
|
||||
e.preventDefault();
|
||||
let valid = true;
|
||||
const formElem = dialog.querySelector("form");
|
||||
if (formElem) {
|
||||
const form = new FormData(formElem);
|
||||
valid = await validate(dialog, form);
|
||||
}
|
||||
if (valid) {
|
||||
dialog.close(e.target.value);
|
||||
}
|
||||
});
|
||||
dialog.querySelector("#cancel").addEventListener("click", async (e) => {
|
||||
e.preventDefault();
|
||||
dialog.close(e.target.value);
|
||||
});
|
||||
document.body.append(dialog);
|
||||
dialog.showModal();
|
||||
return dialog;
|
||||
|
@ -78,34 +78,36 @@ async function handleInstanceAdd () {
|
||||
const header = "Create New Instance";
|
||||
|
||||
const body = `
|
||||
<label for="type">Instance Type</label>
|
||||
<select class="w3-select w3-border" name="type" id="type" required>
|
||||
<option value="lxc">Container</option>
|
||||
<option value="qemu">Virtual Machine</option>
|
||||
</select>
|
||||
<label for="node">Node</label>
|
||||
<select class="w3-select w3-border" name="node" id="node" required></select>
|
||||
<label for="name">Name</label>
|
||||
<input class="w3-input w3-border" name="name" id="name" required></input>
|
||||
<label for="vmid">ID</label>
|
||||
<input class="w3-input w3-border" name="vmid" id="vmid" type="number" required></input>
|
||||
<label for="cores">Cores (Threads)</label>
|
||||
<input class="w3-input w3-border" name="cores" id="cores" type="number" min="1" max="8192" required></input>
|
||||
<label for="memory">Memory (MiB)</label>
|
||||
<input class="w3-input w3-border" name="memory" id="memory" type="number" min="16", step="1" required></input>
|
||||
<p class="container-specific none" style="grid-column: 1 / span 2; text-align: center;">Container Options</p>
|
||||
<label class="container-specific none" for="swap">Swap (MiB)</label>
|
||||
<input class="w3-input w3-border container-specific none" name="swap" id="swap" type="number" min="0" step="1" required disabled></input>
|
||||
<label class="container-specific none" for="template-storage">Template Storage</label>
|
||||
<select class="w3-select w3-border container-specific none" name="template-storage" id="template-storage" required disabled></select>
|
||||
<label class="container-specific none" for="template-image">Template Image</label>
|
||||
<select class="w3-select w3-border container-specific none" name="template-image" id="template-image" required disabled></select>
|
||||
<label class="container-specific none" for="rootfs-storage">ROOTFS Storage</label>
|
||||
<select class="w3-select w3-border container-specific none" name="rootfs-storage" id="rootfs-storage" required disabled></select>
|
||||
<label class="container-specific none" for="rootfs-size">ROOTFS Size (GiB)</label>
|
||||
<input class="w3-input w3-border container-specific none" name="rootfs-size" id="rootfs-size" type="number" min="0" max="131072" required disabled></input>
|
||||
<label class="container-specific none" for="password">Password</label>
|
||||
<input class="w3-input w3-border container-specific none" name="password" id="password" type="password" required disabled></input>
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
<label for="type">Instance Type</label>
|
||||
<select class="w3-select w3-border" name="type" id="type" required>
|
||||
<option value="lxc">Container</option>
|
||||
<option value="qemu">Virtual Machine</option>
|
||||
</select>
|
||||
<label for="node">Node</label>
|
||||
<select class="w3-select w3-border" name="node" id="node" required></select>
|
||||
<label for="name">Name</label>
|
||||
<input class="w3-input w3-border" name="name" id="name" required></input>
|
||||
<label for="vmid">ID</label>
|
||||
<input class="w3-input w3-border" name="vmid" id="vmid" type="number" required></input>
|
||||
<label for="cores">Cores (Threads)</label>
|
||||
<input class="w3-input w3-border" name="cores" id="cores" type="number" min="1" max="8192" required></input>
|
||||
<label for="memory">Memory (MiB)</label>
|
||||
<input class="w3-input w3-border" name="memory" id="memory" type="number" min="16", step="1" required></input>
|
||||
<p class="container-specific none" style="grid-column: 1 / span 2; text-align: center;">Container Options</p>
|
||||
<label class="container-specific none" for="swap">Swap (MiB)</label>
|
||||
<input class="w3-input w3-border container-specific none" name="swap" id="swap" type="number" min="0" step="1" required disabled></input>
|
||||
<label class="container-specific none" for="template-storage">Template Storage</label>
|
||||
<select class="w3-select w3-border container-specific none" name="template-storage" id="template-storage" required disabled></select>
|
||||
<label class="container-specific none" for="template-image">Template Image</label>
|
||||
<select class="w3-select w3-border container-specific none" name="template-image" id="template-image" required disabled></select>
|
||||
<label class="container-specific none" for="rootfs-storage">ROOTFS Storage</label>
|
||||
<select class="w3-select w3-border container-specific none" name="rootfs-storage" id="rootfs-storage" required disabled></select>
|
||||
<label class="container-specific none" for="rootfs-size">ROOTFS Size (GiB)</label>
|
||||
<input class="w3-input w3-border container-specific none" name="rootfs-size" id="rootfs-size" type="number" min="0" max="131072" required disabled></input>
|
||||
<label class="container-specific none" for="password">Password</label>
|
||||
<input class="w3-input w3-border container-specific none" name="password" id="password" type="password" required disabled></input>
|
||||
</form>
|
||||
`;
|
||||
|
||||
const d = dialog(header, body, async (result, form) => {
|
||||
|
Loading…
Reference in New Issue
Block a user