diff --git a/index.html b/index.html index 0519b37..70cbdf0 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@ + diff --git a/scripts/config.js b/scripts/config.js index 9492694..c0bc5ed 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -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 = `

Are you sure you want to detach disk

${this.dataset.disk}

`; - - dialog.callback = async (result, form) => { + let header = `Detach ${this.dataset.disk}`; + let body = `

Are you sure you want to detach disk

${this.dataset.disk}

`; + 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 = ``; + let header = `Attach ${diskImage}`; + let body = ``; - 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 = ``; - dialog.header = `Resize ${this.dataset.disk}`; - dialog.formBody = ``; - - 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 += `"`; } }); - let select = ``; + let select = ``; - dialog.formBody = ` + let body = ` ${select} `; - 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 = `

Are you sure you want to delete disk

${this.dataset.disk}

`; - dialog.header = `Delete ${this.dataset.disk}`; - dialog.formBody = `

Are you sure you want to delete disk

${this.dataset.disk}

`; - - 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 += `"`; } }); - let select = ``; + let select = ``; - dialog.formBody = ` + let body = ` ${select} `; - 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 += `"`; } }); - let storageSelect = ``; + let storageSelect = ``; - dialog.formBody = ` + let body = ` ${storageSelect} `; - 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 = ``; + 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 () { diff --git a/scripts/dialog.js b/scripts/dialog.js index 250b440..0600ea6 100644 --- a/scripts/dialog.js +++ b/scripts/dialog.js @@ -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 = ` +

+
+
+ + +
+ `; + dialog.className = "w3-container w3-card w3-border-0"; + dialog.querySelector("#prompt").innerText = header; + dialog.querySelector("form").innerHTML = body; - shadowRoot.innerHTML = ` - - - - -

-
-
- - -
-
- `; + 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 = ` -

${message}

-
- -
- `; - let dialog = document.createElement("dialog"); - dialog.classList.add("w3-card"); - dialog.classList.add("w3-container"); - dialog.append(form); + dialog.innerHTML = ` +
+

${message}

+
+ +
+
+ `; + 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); \ No newline at end of file + return dialog; +} \ No newline at end of file diff --git a/scripts/index.js b/scripts/index.js index 30ae543..8b038f6 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -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); - - dialog.header = "Create New Instance"; + let header = "Create New Instance"; - dialog.formBody = ` + let body = ` `; - 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; + }); } \ No newline at end of file diff --git a/scripts/instance.js b/scripts/instance.js index 92fc6d7..ae48b18 100644 --- a/scripts/instance.js +++ b/scripts/instance.js @@ -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 = `

Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM

${this.vmid}

` - let dialog = document.createElement("dialog-form"); - document.body.append(dialog); - - dialog.header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`; - dialog.formBody = `

Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM

${this.vmid}

` - - 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 = `

Are you sure you want to delete VM

${this.vmid}

` + let header = `Delete VM ${this.vmid}`; + let body = `

Are you sure you want to delete VM

${this.vmid}

` - 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(); + }); } } } diff --git a/scripts/utils.js b/scripts/utils.js index 0129b02..40cc5ec 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -206,8 +206,4 @@ 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(); } \ No newline at end of file