diff --git a/index.html b/index.html index f71ad3d..694a004 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,6 @@ -
diff --git a/scripts/config.js b/scripts/config.js index 9018076..b654576 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -1,4 +1,4 @@ -import {requestPVE, requestAPI, goToPage, getURIData, resources} from "./utils.js"; +import {requestPVE, requestAPI, goToPage, getURIData, resources_config} from "./utils.js"; import {alert, dialog} from "./dialog.js"; window.addEventListener("DOMContentLoaded", init); // do the dumb thing where the disk config refreshes every second diff --git a/scripts/index.js b/scripts/index.js index 33e4e3b..069d89e 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -1,4 +1,4 @@ -import {requestPVE, requestAPI, goToPage} from "./utils.js"; +import {requestPVE, requestAPI, goToPage, instances_config, nodes_config} from "./utils.js"; import {alert, dialog} from "./dialog.js"; window.addEventListener("DOMContentLoaded", init); @@ -57,9 +57,9 @@ async function populateInstances () { `; for(let i = 0; i < instances.length; i++) { - let newInstance = document.createElement("instance-obj"); + let newInstance = new Instance(); newInstance.data = instances[i]; - instanceContainer.append(newInstance); + instanceContainer.append(newInstance.shadowElement); } } @@ -193,4 +193,212 @@ async function handleInstanceAdd () { d.querySelector("#memory").max = userResources.avail.memory; d.querySelector("#vmid").min = userInstances.vmid.min; d.querySelector("#vmid").max = userInstances.vmid.max; +} + +export class Instance { + constructor () { + let shadowRoot = document.createElement("div"); + + shadowRoot.innerHTML = ` +
+
+

+
+
+

+
+
+

+
+
+ +

+
+
+

+
+
+ +

+
+
+ + + + +
+
+ `; + + this.shadowElement = shadowRoot; + this.actionLock = false; + } + + set data (data) { + if (data.status === "unknown") { + data.status = "stopped"; + } + this.type = data.type; + this.status = data.status; + this.vmid = data.vmid; + this.name = data.name; + this.node = data.node; + this.update(); + } + + update () { + let vmidParagraph = this.shadowElement.querySelector("#instance-id"); + vmidParagraph.innerText = this.vmid; + + let nameParagraph = this.shadowElement.querySelector("#instance-name"); + nameParagraph.innerText = this.name ? this.name : ""; + + let typeParagraph = this.shadowElement.querySelector("#instance-type"); + typeParagraph.innerText = this.type; + + let statusParagraph = this.shadowElement.querySelector("#instance-status"); + statusParagraph.innerText = this.status; + + let statusIcon = this.shadowElement.querySelector("#instance-status-icon"); + statusIcon.src = instances_config[this.status].statusSrc; + statusIcon.alt = instances_config[this.status].statusAlt; + + let nodeNameParagraph = this.shadowElement.querySelector("#node-name"); + nodeNameParagraph.innerText = this.node.name; + + let nodeStatusParagraph = this.shadowElement.querySelector("#node-status"); + nodeStatusParagraph.innerText = this.node.status; + + let nodeStatusIcon = this.shadowElement.querySelector("#node-status-icon"); + nodeStatusIcon.src = nodes_config[this.node.status].statusSrc; + nodeStatusIcon.alt = nodes_config[this.node.status].statusAlt; + + let powerButton = this.shadowElement.querySelector("#power-btn"); + powerButton.src = instances_config[this.status].powerButtonSrc; + powerButton.alt = instances_config[this.status].powerButtonAlt; + powerButton.title = instances_config[this.status].powerButtonAlt; + powerButton.onclick = this.handlePowerButton.bind(this) + + let configButton = this.shadowElement.querySelector("#configure-btn"); + configButton.src = instances_config[this.status].configButtonSrc; + configButton.alt = instances_config[this.status].configButtonAlt; + configButton.title = instances_config[this.status].configButtonAlt; + configButton.onclick = this.handleConfigButton.bind(this); + + let consoleButton = this.shadowElement.querySelector("#console-btn"); + consoleButton.src = instances_config[this.status].consoleButtonSrc; + consoleButton.alt = instances_config[this.status].consoleButtonAlt; + consoleButton.title = instances_config[this.status].consoleButtonAlt; + consoleButton.onclick = this.handleConsoleButton.bind(this); + + let deleteButton = this.shadowElement.querySelector("#delete-btn"); + deleteButton.src = instances_config[this.status].deleteButtonSrc; + deleteButton.alt = instances_config[this.status].deleteButtonAlt; + deleteButton.title = instances_config[this.status].deleteButtonAlt; + deleteButton.onclick = this.handleDeleteButton.bind(this); + + if (this.node.status !== "online") { + powerButton.classList.add("hidden"); + configButton.classList.add("hidden"); + consoleButton.classList.add("hidden"); + deleteButton.classList.add("hidden"); + } + } + + 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}

` + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + this.actionLock = true; + let targetAction = this.status === "running" ? "stop" : "start"; + let targetStatus = this.status === "running" ? "stopped" : "running"; + let prevStatus = this.status; + this.status = "loading"; + + this.update(); + + let result = await requestPVE(`/nodes/${this.node.name}/${this.type}/${this.vmid}/status/${targetAction}`, "POST", {node: this.node.name, vmid: this.vmid}); + + const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); + + while (true) { + let taskStatus = await requestPVE(`/nodes/${this.node.name}/tasks/${result.data}/status`, "GET"); + if(taskStatus.data.status === "stopped" && taskStatus.data.exitstatus === "OK") { // task stopped and was successful + this.status = targetStatus; + this.update(); + this.actionLock = false; + break; + } + else if (taskStatus.data.status === "stopped") { // task stopped but was not successful + this.status = prevStatus; + alert(`attempted to ${targetAction} ${this.vmid} but process returned stopped:${result.data.exitstatus}`); + this.update(); + this.actionLock = false; + break; + } + else{ // task has not stopped + await waitFor(1000); + } + } + } + }); + } + } + + handleConfigButton () { + if (!this.actionLock && this.status === "stopped") { // if the action lock is false, and the node is stopped, then navigate to the conig page with the node infor in the search query + goToPage("config.html", {node: this.node.name, type: this.type, vmid: this.vmid}); + } + } + + handleConsoleButton () { + if (!this.actionLock && this.status === "running") { + let data = {console: `${this.type === "qemu" ? "kvm" : "lxc"}`, vmid: this.vmid, vmname: this.name, node: this.node.name, resize: "off", cmd: ""}; + data[`${this.type === "qemu" ? "novnc" : "xtermjs"}`] = 1; + goToURL(PVE, data, true); + } + } + + handleDeleteButton () { + if (!this.actionLock && this.status === "stopped") { + + let header = `Delete VM ${this.vmid}`; + let body = `

Are you sure you want to delete VM

${this.vmid}

` + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + this.actionLock = true; + let prevStatus = this.status; + this.status = "loading"; + this.update(); + + let action = {}; + action.purge = 1; + action["destroy-unreferenced-disks"] = 1; + + let body = { + node: this.node.name, + type: this.type, + vmid: this.vmid, + action: JSON.stringify(action) + }; + + let result = await requestAPI("/instance", "DELETE", body); + if (result.status === 200) { + this.parentNode.removeChild(this); + } + else { + alert(result.error); + this.status = this.prevStatus; + this.update(); + this.actionLock = false; + } + } + }); + } + } } \ No newline at end of file diff --git a/scripts/instance.js b/scripts/instance.js deleted file mode 100644 index a2ef802..0000000 --- a/scripts/instance.js +++ /dev/null @@ -1,216 +0,0 @@ -import {requestPVE, requestAPI, goToPage, goToURL, instances, nodes} from "./utils.js"; -import {alert, dialog} from "./dialog.js"; -import {PVE} from "../vars.js" - -export class Instance extends HTMLElement { - constructor () { - super(); - let shadowRoot = this.attachShadow({mode: "open"}); - - shadowRoot.innerHTML = ` - - -
-
-

-
-
-

-
-
-

-
-
- -

-
-
-

-
-
- -

-
-
- - - - -
-
- `; - - this.shadowElement = shadowRoot; - this.actionLock = false; - } - - set data (data) { - if (data.status === "unknown") { - data.status = "stopped"; - } - this.type = data.type; - this.status = data.status; - this.vmid = data.vmid; - this.name = data.name; - this.node = data.node; - this.update(); - } - - update () { - let vmidParagraph = this.shadowElement.querySelector("#instance-id"); - vmidParagraph.innerText = this.vmid; - - let nameParagraph = this.shadowElement.querySelector("#instance-name"); - nameParagraph.innerText = this.name ? this.name : ""; - - let typeParagraph = this.shadowElement.querySelector("#instance-type"); - typeParagraph.innerText = this.type; - - let statusParagraph = this.shadowElement.querySelector("#instance-status"); - statusParagraph.innerText = this.status; - - let statusIcon = this.shadowElement.querySelector("#instance-status-icon"); - statusIcon.src = instances[this.status].statusSrc; - statusIcon.alt = instances[this.status].statusAlt; - - let nodeNameParagraph = this.shadowElement.querySelector("#node-name"); - nodeNameParagraph.innerText = this.node.name; - - let nodeStatusParagraph = this.shadowElement.querySelector("#node-status"); - nodeStatusParagraph.innerText = this.node.status; - - let nodeStatusIcon = this.shadowElement.querySelector("#node-status-icon"); - nodeStatusIcon.src = nodes[this.node.status].statusSrc; - nodeStatusIcon.alt = nodes[this.node.status].statusAlt; - - let powerButton = this.shadowElement.querySelector("#power-btn"); - powerButton.src = instances[this.status].powerButtonSrc; - powerButton.alt = instances[this.status].powerButtonAlt; - powerButton.title = instances[this.status].powerButtonAlt; - powerButton.onclick = this.handlePowerButton.bind(this) - - let configButton = this.shadowElement.querySelector("#configure-btn"); - configButton.src = instances[this.status].configButtonSrc; - configButton.alt = instances[this.status].configButtonAlt; - configButton.title = instances[this.status].configButtonAlt; - configButton.onclick = this.handleConfigButton.bind(this); - - let consoleButton = this.shadowElement.querySelector("#console-btn"); - consoleButton.src = instances[this.status].consoleButtonSrc; - consoleButton.alt = instances[this.status].consoleButtonAlt; - consoleButton.title = instances[this.status].consoleButtonAlt; - consoleButton.onclick = this.handleConsoleButton.bind(this); - - let deleteButton = this.shadowElement.querySelector("#delete-btn"); - deleteButton.src = instances[this.status].deleteButtonSrc; - deleteButton.alt = instances[this.status].deleteButtonAlt; - deleteButton.title = instances[this.status].deleteButtonAlt; - deleteButton.onclick = this.handleDeleteButton.bind(this); - - if (this.node.status !== "online") { - powerButton.classList.add("hidden"); - configButton.classList.add("hidden"); - consoleButton.classList.add("hidden"); - deleteButton.classList.add("hidden"); - } - } - - 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}

` - - dialog(header, body, async (result, form) => { - if (result === "confirm") { - this.actionLock = true; - let targetAction = this.status === "running" ? "stop" : "start"; - let targetStatus = this.status === "running" ? "stopped" : "running"; - let prevStatus = this.status; - this.status = "loading"; - - this.update(); - - let result = await requestPVE(`/nodes/${this.node.name}/${this.type}/${this.vmid}/status/${targetAction}`, "POST", {node: this.node.name, vmid: this.vmid}); - - const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); - - while (true) { - let taskStatus = await requestPVE(`/nodes/${this.node.name}/tasks/${result.data}/status`, "GET"); - if(taskStatus.data.status === "stopped" && taskStatus.data.exitstatus === "OK") { // task stopped and was successful - this.status = targetStatus; - this.update(); - this.actionLock = false; - break; - } - else if (taskStatus.data.status === "stopped") { // task stopped but was not successful - this.status = prevStatus; - alert(`attempted to ${targetAction} ${this.vmid} but process returned stopped:${result.data.exitstatus}`); - this.update(); - this.actionLock = false; - break; - } - else{ // task has not stopped - await waitFor(1000); - } - } - } - }); - } - } - - handleConfigButton () { - if (!this.actionLock && this.status === "stopped") { // if the action lock is false, and the node is stopped, then navigate to the conig page with the node infor in the search query - goToPage("config.html", {node: this.node.name, type: this.type, vmid: this.vmid}); - } - } - - handleConsoleButton () { - if (!this.actionLock && this.status === "running") { - let data = {console: `${this.type === "qemu" ? "kvm" : "lxc"}`, vmid: this.vmid, vmname: this.name, node: this.node.name, resize: "off", cmd: ""}; - data[`${this.type === "qemu" ? "novnc" : "xtermjs"}`] = 1; - goToURL(PVE, data, true); - } - } - - handleDeleteButton () { - if (!this.actionLock && this.status === "stopped") { - - let header = `Delete VM ${this.vmid}`; - let body = `

Are you sure you want to delete VM

${this.vmid}

` - - dialog(header, body, async (result, form) => { - if (result === "confirm") { - this.actionLock = true; - let prevStatus = this.status; - this.status = "loading"; - this.update(); - - let action = {}; - action.purge = 1; - action["destroy-unreferenced-disks"] = 1; - - let body = { - node: this.node.name, - type: this.type, - vmid: this.vmid, - action: JSON.stringify(action) - }; - - let result = await requestAPI("/instance", "DELETE", body); - if (result.status === 200) { - this.parentNode.removeChild(this); - } - else { - alert(result.error); - this.status = this.prevStatus; - this.update(); - this.actionLock = false; - } - } - }); - } - } -} - -customElements.define("instance-obj", Instance); \ No newline at end of file diff --git a/scripts/utils.js b/scripts/utils.js index cb08be8..33e612c 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -7,7 +7,7 @@ export class NetworkError extends Error { } } -export const resources = { +export const resources_config = { disk: { actionBarOrder: ["move", "resize", "detach_attach", "delete"], lxc: { @@ -25,7 +25,7 @@ export const resources = { } } -export const instances = { +export const instances_config = { running: { statusSrc: "images/status/active.svg", statusAlt: "Instance is running", @@ -64,7 +64,7 @@ export const instances = { } } -export const nodes = { +export const nodes_config = { online: { statusSrc: "images/status/active.svg", statusAlt: "Node is online",