update account page, instance creation form, and config page,

fix issue with instance delete confirm popup,
fix issues with dialog exit handling without form element
This commit is contained in:
Arthur Lu 2023-11-15 20:18:01 +00:00
parent 7f4bdacef0
commit 230fe2c9e4
5 changed files with 55 additions and 51 deletions

View File

@ -53,12 +53,12 @@ function populateResources (containerID, meta, resources) {
Object.keys(meta).forEach((resourceType) => {
if (meta[resourceType].display) {
if (meta[resourceType].type === "list") {
resources[resourceType].forEach((listResource) => {
resources[resourceType].total.forEach((listResource) => {
createResourceUsageChart(container, listResource.name, listResource.avail, listResource.used, listResource.max, null);
});
}
else {
createResourceUsageChart(container, meta[resourceType].name, resources[resourceType].avail, resources[resourceType].used, resources[resourceType].max, meta[resourceType]);
createResourceUsageChart(container, meta[resourceType].name, resources[resourceType].total.avail, resources[resourceType].total.used, resources[resourceType].total.max, meta[resourceType]);
}
}
});
@ -124,29 +124,29 @@ function parseNumber (value, unitData) {
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">
<label for="new-password">New Password</label>
<input class="w3-input w3-border" id="new-password" name="new-password" type="password"required>
<label for="confirm-password">Confirm Password</label>
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
</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) => {
const d = 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;
}
});
var password = d.querySelector("#new-password")
var confirm_password = d.querySelector("#confirm-password");
function validatePassword(){
confirm_password.setCustomValidity( password.value !=
confirm_password.value ? "Passwords Don't Match" : '');
}
password.addEventListener("change", validatePassword);
confirm_password.addEventListener("keyup", validatePassword);
}

View File

@ -54,8 +54,10 @@ async function populateResources () {
const global = await requestAPI("/global/config/resources");
const user = await requestAPI("/user/config/resources");
let options = [];
if (global.cpu.whitelist) {
user.cpu.forEach((userType) => {
const globalCPU = global.cpu;
const userCPU = node in user.cpu.nodes ? user.cpu.nodes[node] : user.cpu.global;
if (globalCPU.whitelist) {
userCPU.forEach((userType) => {
options.push(userType.name);
});
options = options.sort((a, b) => {
@ -65,7 +67,7 @@ async function populateResources () {
else {
const supported = await requestPVE(`/nodes/${node}/capabilities/qemu/cpu`);
supported.data.forEach((supportedType) => {
if (!user.cpu.some((userType) => supportedType.name === userType.name)) {
if (!userCPU.some((userType) => supportedType.name === userType.name)) {
options.push(supportedType.name);
}
});

View File

@ -1,6 +1,4 @@
export function dialog (header, body, onclose = async (result, form) => { }, validate = async (dialog, form) => {
return true;
}) {
export function dialog (header, body, onclose = async (result, form) => { }) {
const dialog = document.createElement("dialog");
dialog.innerHTML = `
<p class="w3-large" id="prompt" style="text-align: center;"></p>
@ -15,29 +13,20 @@ export function dialog (header, body, onclose = async (result, form) => { }, val
dialog.querySelector("#body").innerHTML = body;
dialog.addEventListener("close", async () => {
const formElem = dialog.querySelector("form");
let formData = null;
if (formElem) {
formData = new FormData(formElem);
}
const formData = formElem ? new FormData(formElem) : null;
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) {
if (!dialog.querySelector("form")) {
dialog.querySelector("#confirm").addEventListener("click", async (e) => {
e.preventDefault();
dialog.close(e.target.value);
}
});
dialog.querySelector("#cancel").addEventListener("click", async (e) => {
e.preventDefault();
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;

View File

@ -163,6 +163,9 @@ async function handleInstanceAdd () {
const rootfsStorage = d.querySelector("#rootfs-storage");
rootfsStorage.selectedIndex = -1;
const userResources = await requestAPI("/user/dynamic/resources", "GET");
const userCluster = await requestAPI("/user/config/cluster", "GET");
const nodeSelect = d.querySelector("#node");
const clusterNodes = await requestPVE("/nodes", "GET");
const allowedNodes = await requestAPI("/user/config/nodes", "GET");
@ -185,6 +188,23 @@ async function handleInstanceAdd () {
});
templateStorage.selectedIndex = -1;
rootfsStorage.selectedIndex = -1;
if (node in userResources.cores.nodes) {
d.querySelector("#cores").max = userResources.cores.nodes[node].avail;
}
else {
d.querySelector("#cores").max = userResources.cores.global.avail;
}
if (node in userResources.memory.nodes) {
d.querySelector("#memory").max = userResources.memory.nodes[node].avail;
}
else {
d.querySelector("#memory").max = userResources.memory.global.avail;
}
d.querySelector("#vmid").min = userCluster.vmid.min;
d.querySelector("#vmid").max = userCluster.vmid.max;
});
const templateImage = d.querySelector("#template-image"); // populate templateImage depending on selected image storage
@ -199,11 +219,4 @@ async function handleInstanceAdd () {
});
templateImage.selectedIndex = -1;
});
const userResources = await requestAPI("/user/dynamic/resources", "GET");
const userCluster = await requestAPI("/user/config/cluster", "GET");
d.querySelector("#cores").max = userResources.cores.avail;
d.querySelector("#memory").max = userResources.memory.avail;
d.querySelector("#vmid").min = userCluster.vmid.min;
d.querySelector("#vmid").max = userCluster.vmid.max;
}

View File

@ -219,7 +219,7 @@ class InstanceCard extends HTMLElement {
handleDeleteButton () {
if (!this.actionLock && this.status === "stopped") {
const header = `Delete VM ${this.vmid}`;
const body = `<p>Are you sure you want to <strong>delete</strong> VM </p><p>${this.vmid}</p>`;
const body = `<p>Are you sure you want to <strong>delete</strong> VM ${this.vmid}</p>`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {