code cleanup on custom dialogs

Signed-off-by: Arthur Lu <learthurgo@gmail.com>
This commit is contained in:
Arthur Lu 2023-05-05 21:43:15 +00:00
parent a0fe199ccb
commit 822c89adda
6 changed files with 149 additions and 226 deletions

View File

@ -8,6 +8,7 @@
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/nav.css">
<link rel="stylesheet" href="css/form.css">
<script src="scripts/index.js" type="module"></script>
<script src="scripts/instance.js" type="module"></script>
</head>

View File

@ -1,5 +1,5 @@
import {requestPVE, requestAPI, goToPage, getURIData, resources} from "./utils.js";
import {Dialog} from "./dialog.js";
import {dialog} from "./dialog.js";
window.addEventListener("DOMContentLoaded", init); // do the dumb thing where the disk config refreshes every second
@ -174,13 +174,9 @@ function addDiskLine (fieldset, busPrefix, busName, device, diskDetails) {
}
async function handleDiskDetach () {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = `Detach ${this.dataset.disk}`;
dialog.formBody = `<p>Are you sure you want to detach disk</p><p>${this.dataset.disk}</p>`;
dialog.callback = async (result, form) => {
let header = `Detach ${this.dataset.disk}`;
let body = `<p>Are you sure you want to detach disk</p><p>${this.dataset.disk}</p>`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
let body = {
@ -200,21 +196,16 @@ async function handleDiskDetach () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleDiskAttach () {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
let diskImage = config.data[this.dataset.disk];
dialog.header = `Attach ${diskImage}`;
dialog.formBody = `<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>`;
let header = `Attach ${diskImage}`;
let 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>`;
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
let device = form.get("device");
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
@ -236,19 +227,14 @@ async function handleDiskAttach () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleDiskResize () {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
let header = `Resize ${this.dataset.disk}`;
let 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>`;
dialog.header = `Resize ${this.dataset.disk}`;
dialog.formBody = `<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>`;
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
let body = {
@ -269,18 +255,14 @@ async function handleDiskResize () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleDiskMove () {
let content = type === "qemu" ? "images" : "rootdir";
let storage = await requestPVE(`/nodes/${node}/storage`, "GET", null);
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = `Move ${this.dataset.disk}`;
let header = `Move ${this.dataset.disk}`;
let options = "";
storage.data.forEach((element) => {
@ -288,16 +270,14 @@ async function handleDiskMove () {
options += `<option value="${element.storage}">${element.storage}</option>"`;
}
});
let select = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select">${options}</select>`;
let 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>`;
dialog.formBody = `
let 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>
`;
dialog.shadowRoot.querySelector("#storage-select").selectedIndex = -1;
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
let body = {
@ -319,19 +299,14 @@ async function handleDiskMove () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleDiskDelete () {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
let header = `Delete ${this.dataset.disk}`;
let body = `<p>Are you sure you want to <strong>delete</strong> disk</p><p>${this.dataset.disk}</p>`;
dialog.header = `Delete ${this.dataset.disk}`;
dialog.formBody = `<p>Are you sure you want to <strong>delete</strong> disk</p><p>${this.dataset.disk}</p>`;
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
let body = {
@ -351,18 +326,14 @@ async function handleDiskDelete () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleDiskAdd () {
let content = type === "qemu" ? "images" : "rootdir";
let storage = await requestPVE(`/nodes/${node}/storage`, "GET", null);
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = "Create New Disk";
let header = "Create New Disk";
let options = "";
storage.data.forEach((element) => {
@ -370,17 +341,15 @@ async function handleDiskAdd () {
options += `<option value="${element.storage}">${element.storage}</option>"`;
}
});
let select = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select" required>${options}</select>`;
let 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>`;
dialog.formBody = `
let 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>
`;
dialog.shadowRoot.querySelector("#storage-select").selectedIndex = -1;
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
let body = {
node: node,
@ -401,18 +370,14 @@ async function handleDiskAdd () {
populateDisk();
}
}
};
dialog.show();
});
}
async function handleCDAdd () {
let content = "iso";
let storage = await requestPVE(`/nodes/${node}/storage`, "GET", null);
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = `Add a CDROM`;
let header = `Add a CDROM`;
let storageOptions = "";
storage.data.forEach((element) => {
@ -420,29 +385,15 @@ async function handleCDAdd () {
storageOptions += `<option value="${element.storage}">${element.storage}</option>"`;
}
});
let storageSelect = `<label for="storage-select">Storage</label><select class="w3-select w3-border" name="storage-select" id="storage-select" required>${storageOptions}</select>`;
let 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>`;
dialog.formBody = `
let 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>
`;
dialog.shadowRoot.querySelector("#storage-select").selectedIndex = -1;
dialog.shadowRoot.querySelector("#storage-select").addEventListener("change", async () => {
let storage = dialog.shadowRoot.querySelector("#storage-select").value;
let ISOSelect = dialog.shadowRoot.querySelector("#iso-select");
let isos = await requestPVE(`/nodes/${node}/storage/${storage}/content`, "GET", {content: content});
isos.data.forEach((element) => {
if (element.content.includes(content)) {
ISOSelect.append(new Option(element.volid.replace(`${storage}:${content}/`, ""), element.volid));
}
});
ISOSelect.selectedIndex = -1;
});
dialog.callback = async (result, form) => {
let d = dialog(header, body, async (result, form) => {
if (result === "confirm") {
let body = {
node: node,
@ -462,9 +413,19 @@ async function handleCDAdd () {
populateDisk();
}
}
};
});
dialog.show();
d.querySelector("#storage-select").addEventListener("change", async () => {
let storage = document.querySelector("#storage-select").value;
let ISOSelect = document.querySelector("#iso-select");
ISOSelect.innerHTML = `<option hidden disabled selected value></option>`;
let isos = await requestPVE(`/nodes/${node}/storage/${storage}/content`, "GET", {content: content});
isos.data.forEach((element) => {
if (element.content.includes(content)) {
ISOSelect.append(new Option(element.volid.replace(`${storage}:${content}/`, ""), element.volid));
}
});
});
}
async function handleFormExit () {

View File

@ -1,60 +1,39 @@
export class Dialog extends HTMLElement {
constructor () {
super();
let shadowRoot = this.attachShadow({mode: "open"});
export function dialog (header, body, callback = async (result, form) => {}) {
let 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 class="w3-center w3-container">
<button value="cancel" form="form" class="w3-button w3-margin" style="background-color: #f00;" formnovalidate>CANCEL</button>
<button value="confirm" form="form" class="w3-button w3-margin" style="background-color: #0f0;">CONFIRM</button>
</div>
`;
dialog.className = "w3-container w3-card w3-border-0";
dialog.querySelector("#prompt").innerText = header;
dialog.querySelector("form").innerHTML = body;
shadowRoot.innerHTML = `
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="css/style.css" type="text/css">
<link rel="stylesheet" href="css/form.css" type="text/css">
<dialog class="w3-container w3-card-4 w3-border-0">
<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 class="w3-center w3-container">
<button value="cancel" form="form" class="w3-button w3-margin" style="background-color: #f00;" formnovalidate>CANCEL</button>
<button value="confirm" form="form" class="w3-button w3-margin" style="background-color: #0f0;">CONFIRM</button>
</div>
</dialog>
`;
document.body.append(dialog);
dialog.showModal();
this.shadowElement = shadowRoot;
this.dialog = shadowRoot.querySelector("dialog");
this.form = shadowRoot.querySelector("form");
}
dialog.addEventListener("close", async () => {
await callback(dialog.returnValue, new FormData(dialog.querySelector("form")));
dialog.parentElement.removeChild(dialog);
});
set header (header) {
this.shadowElement.querySelector("#prompt").innerText = header;
}
set formBody (formBody) {
this.form.innerHTML = formBody;
}
set callback (callback) {
this.dialog.addEventListener("close", async () => {
await callback(this.dialog.returnValue, new FormData(this.form));
document.querySelector("dialog-form").remove();
});
}
show () {
this.dialog.showModal();
}
return dialog;
}
export function alert (message) {
let form = document.createElement("form");
form.method = "dialog";
form.innerHTML = `
<p class="w3-center" style="margin-bottom: 0px;">${message}</p>
<div class="w3-center">
<button class="w3-button w3-margin" id="submit">OK</button>
</div>
`;
let dialog = document.createElement("dialog");
dialog.classList.add("w3-card");
dialog.classList.add("w3-container");
dialog.append(form);
dialog.innerHTML = `
<form method="dialog">
<p class="w3-center" style="margin-bottom: 0px;">${message}</p>
<div class="w3-center">
<button class="w3-button w3-margin" id="submit">OK</button>
</div>
</form>
`;
dialog.className = "w3-container w3-card w3-border-0";
document.body.append(dialog);
dialog.showModal();
@ -62,6 +41,6 @@ export function alert (message) {
dialog.addEventListener("close", () => {
dialog.parentElement.removeChild(dialog);
})
}
customElements.define("dialog-form", Dialog);
return dialog;
}

View File

@ -1,5 +1,5 @@
import {requestPVE, requestAPI, goToPage} from "./utils.js";
import {Dialog} from "./dialog.js";
import {dialog} from "./dialog.js";
window.addEventListener("DOMContentLoaded", init);
@ -64,12 +64,9 @@ async function populateInstances () {
}
async function handleInstanceAdd () {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
let header = "Create New Instance";
dialog.header = "Create New Instance";
dialog.formBody = `
let body = `
<label for="type">Instance Type</label>
<select class="w3-select w3-border" name="type" id="type" required>
<option value="lxc">Container</option>
@ -100,67 +97,7 @@ async function handleInstanceAdd () {
<input class="w3-input w3-border container-specific none" name="password" id="password" type="password" required disabled></input>
`;
let typeSelect = dialog.shadowRoot.querySelector("#type");
typeSelect.selectedIndex = -1;
typeSelect.addEventListener("change", () => {
if(typeSelect.value === "qemu") {
dialog.shadowRoot.querySelectorAll(".container-specific").forEach((element) => {
element.classList.add("none");
element.disabled = true;
});
}
else {
dialog.shadowRoot.querySelectorAll(".container-specific").forEach((element) => {
element.classList.remove("none");
element.disabled = false;
});
}
});
let templateContent = "iso";
let templateStorage = dialog.shadowRoot.querySelector("#template-storage");
templateStorage.selectedIndex = -1;
let rootfsContent = "rootdir";
let rootfsStorage = dialog.shadowRoot.querySelector("#rootfs-storage");
rootfsStorage.selectedIndex = -1;
let nodeSelect = dialog.shadowRoot.querySelector("#node");
let nodes = await requestPVE("/nodes", "GET");
nodes.data.forEach((element) => {
if (element.status === "online") {
nodeSelect.add(new Option(element.node));
}
});
nodeSelect.selectedIndex = -1;
nodeSelect.addEventListener("change", async () => { // change template and rootfs storage based on node
let node = nodeSelect.value;
let storage = await requestPVE(`/nodes/${node}/storage`, "GET");
storage.data.forEach((element) => {
if (element.content.includes(templateContent)) {
templateStorage.add(new Option(element.storage));
}
if (element.content.includes(rootfsContent)) {
rootfsStorage.add(new Option(element.storage));
}
});
templateStorage.selectedIndex = -1;
rootfsStorage.selectedIndex = -1;
});
let templateImage = dialog.shadowRoot.querySelector("#template-image"); // populate templateImage by
templateStorage.addEventListener("change", async () => {
let content = "vztmpl";
let images = await requestPVE(`/nodes/${nodeSelect.value}/storage/${templateStorage.value}/content`, "GET");
images.data.forEach((element) => {
if (element.content.includes(content)) {
templateImage.append(new Option(element.volid.replace(`${templateStorage.value}:${content}/`, ""), element.volid));
}
});
templateImage.selectedIndex = -1;
});
dialog.callback = async (result, form) => {
let d = dialog(header, body, async (result, form) => {
if (result === "confirm") {
let body = {
node: form.get("node"),
@ -186,7 +123,66 @@ async function handleInstanceAdd () {
populateInstances();
}
}
}
});
dialog.show();
let typeSelect = d.querySelector("#type");
typeSelect.selectedIndex = -1;
typeSelect.addEventListener("change", () => {
if(typeSelect.value === "qemu") {
d.querySelectorAll(".container-specific").forEach((element) => {
element.classList.add("none");
element.disabled = true;
});
}
else {
d.querySelectorAll(".container-specific").forEach((element) => {
element.classList.remove("none");
element.disabled = false;
});
}
});
let templateContent = "iso";
let templateStorage = d.querySelector("#template-storage");
templateStorage.selectedIndex = -1;
let rootfsContent = "rootdir";
let rootfsStorage = d.querySelector("#rootfs-storage");
rootfsStorage.selectedIndex = -1;
let nodeSelect = d.querySelector("#node");
let nodes = await requestPVE("/nodes", "GET");
nodes.data.forEach((element) => {
if (element.status === "online") {
nodeSelect.add(new Option(element.node));
}
});
nodeSelect.selectedIndex = -1;
nodeSelect.addEventListener("change", async () => { // change template and rootfs storage based on node
let node = nodeSelect.value;
let storage = await requestPVE(`/nodes/${node}/storage`, "GET");
storage.data.forEach((element) => {
if (element.content.includes(templateContent)) {
templateStorage.add(new Option(element.storage));
}
if (element.content.includes(rootfsContent)) {
rootfsStorage.add(new Option(element.storage));
}
});
templateStorage.selectedIndex = -1;
rootfsStorage.selectedIndex = -1;
});
let templateImage = d.querySelector("#template-image"); // populate templateImage depending on selected image storage
templateStorage.addEventListener("change", async () => {
templateImage.innerHTML = ``;
let content = "vztmpl";
let images = await requestPVE(`/nodes/${nodeSelect.value}/storage/${templateStorage.value}/content`, "GET");
images.data.forEach((element) => {
if (element.content.includes(content)) {
templateImage.append(new Option(element.volid.replace(`${templateStorage.value}:${content}/`, ""), element.volid));
}
});
templateImage.selectedIndex = -1;
});
}

View File

@ -1,5 +1,5 @@
import {requestPVE, requestAPI, goToPage, goToURL, instances, nodes} from "./utils.js";
import {Dialog} from "./dialog.js";
import {dialog} from "./dialog.js";
export class Instance extends HTMLElement {
constructor () {
@ -117,14 +117,10 @@ export class Instance extends HTMLElement {
async handlePowerButton () {
if(!this.actionLock) {
let header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
let body = `<p>Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM</p><p>${this.vmid}</p>`
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
dialog.formBody = `<p>Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM</p><p>${this.vmid}</p>`
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
this.actionLock = true;
let targetAction = this.status === "running" ? "stop" : "start";
@ -158,9 +154,7 @@ export class Instance extends HTMLElement {
}
}
}
}
dialog.show();
});
}
}
@ -180,13 +174,11 @@ export class Instance extends HTMLElement {
handleDeleteButton () {
if (!this.actionLock && this.status === "stopped") {
let dialog = document.createElement("dialog-form");
document.body.append(dialog);
dialog.header = `Delete VM ${this.vmid}`;
dialog.formBody = `<p>Are you sure you want to <strong>delete</strong> VM </p><p>${this.vmid}</p>`
let header = `Delete VM ${this.vmid}`;
let body = `<p>Are you sure you want to <strong>delete</strong> VM </p><p>${this.vmid}</p>`
dialog.callback = async (result, form) => {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
this.actionLock = true;
let prevStatus = this.status;
@ -215,9 +207,7 @@ export class Instance extends HTMLElement {
this.actionLock = false;
}
}
}
dialog.show();
});
}
}
}

View File

@ -207,7 +207,3 @@ export function getURIData () {
export function deleteAllCookies () {
document.cookie.split(";").forEach(function(c) { document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/;domain=.tronnet.net;"); });
}
export function reload () {
window.location.reload();
}