diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a328d17 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,42 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": "standard", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "no-tabs": [ + "error", + { + "allowIndentationTabs": true + } + ], + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "brace-style": [ + "error", + "stroustrup", + { + "allowSingleLine": false + } + ] + } +} diff --git a/.gitignore b/.gitignore index 55a914d..8c9746f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -vars.js \ No newline at end of file +vars.js +**/package-lock.json +**/node_modules \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3725374 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "proxmoxaas-client", + "version": "0.0.1", + "description": "Front-end for ProxmoxAAS", + "type": "module", + "scripts": { + "lint": "eslint --fix ." + }, + "devDependencies": { + "eslint": "^8.43.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^16.0.1", + "eslint-plugin-promise": "^6.1.1" + } +} diff --git a/scripts/account.js b/scripts/account.js index 131b845..de64aa5 100644 --- a/scripts/account.js +++ b/scripts/account.js @@ -2,7 +2,7 @@ import { requestAPI, goToPage, getCookie, setTitleAndHeader } from "./utils.js"; window.addEventListener("DOMContentLoaded", init); -let prefixes = { +const prefixes = { 1024: [ "", "Ki", @@ -17,17 +17,17 @@ let prefixes = { "G", "T" ] -} +}; -async function init() { +async function init () { setTitleAndHeader(); - let cookie = document.cookie; + const cookie = document.cookie; if (cookie === "") { goToPage("login.html"); } - let resources = await requestAPI("/user/resources"); - let instances = await requestAPI("/user/config/cluster"); - let nodes = await requestAPI("/user/config/nodes"); + const resources = await requestAPI("/user/resources"); + const instances = await requestAPI("/user/config/cluster"); + const nodes = await requestAPI("/user/config/nodes"); document.querySelector("#username").innerText = `Username: ${getCookie("username")}`; document.querySelector("#pool").innerText = `Pool: ${instances.pool}`; document.querySelector("#vmid").innerText = `VMID Range: ${instances.vmid.min} - ${instances.vmid.max}`; @@ -35,24 +35,25 @@ async function init() { buildResourceTable(resources, "#resource-table"); } -function buildResourceTable(resources, tableid) { +function buildResourceTable (resources, tableid) { if (resources instanceof Object) { - let table = document.querySelector(tableid); - let tbody = table.querySelector("tbody"); + const table = document.querySelector(tableid); + const tbody = table.querySelector("tbody"); Object.keys(resources.resources).forEach((element) => { if (resources.resources[element].display) { if (resources.resources[element].type === "list") { - + // TODO + console.error("Unimplemented"); } else { - let row = tbody.insertRow(); - let key = row.insertCell(); + const row = tbody.insertRow(); + const key = row.insertCell(); key.innerText = `${element}`; - let used = row.insertCell(); + const used = row.insertCell(); used.innerText = `${parseNumber(resources.used[element], resources.resources[element])}`; - let val = row.insertCell(); + const val = row.insertCell(); val.innerText = `${parseNumber(resources.avail[element], resources.resources[element])}`; - let total = row.insertCell(); + const total = row.insertCell(); total.innerText = `${parseNumber(resources.max[element], resources.resources[element])}`; } } @@ -60,22 +61,22 @@ function buildResourceTable(resources, tableid) { } } -function parseNumber(value, unitData) { - let compact = unitData.compact; - let multiplier = unitData.multiplier; - let base = unitData.base; - let unit = unitData.unit; +function parseNumber (value, unitData) { + const compact = unitData.compact; + const multiplier = unitData.multiplier; + const base = unitData.base; + const unit = unitData.unit; value = multiplier * value; if (value <= 0) { return `0 ${unit}`; } else if (compact) { - let exponent = Math.floor(Math.log(value) / Math.log(base)); + const exponent = Math.floor(Math.log(value) / Math.log(base)); value = value / base ** exponent; - let unitPrefix = prefixes[base][exponent]; - return `${value} ${unitPrefix}${unit}` + const unitPrefix = prefixes[base][exponent]; + return `${value} ${unitPrefix}${unit}`; } else { return `${value} ${unit}`; } -} \ No newline at end of file +} diff --git a/scripts/config.js b/scripts/config.js index b4303e7..d4bebc4 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -1,824 +1,830 @@ -import { requestPVE, requestAPI, goToPage, getURIData, resources_config, setTitleAndHeader } from "./utils.js"; -import { alert, dialog } from "./dialog.js"; - -window.addEventListener("DOMContentLoaded", init); // do the dumb thing where the disk config refreshes every second - -let diskMetaData = resources_config.disk; -let networkMetaData = resources_config.network; -let pcieMetaData = resources_config.pcie; - -let node; -let type; -let vmid; -let config; - -async function init() { - setTitleAndHeader(); - let cookie = document.cookie; - if (cookie === "") { - goToPage("login.html"); - } - - let uriData = getURIData(); - node = uriData.node; - type = uriData.type; - vmid = uriData.vmid; - - await getConfig(); - - populateResources(); - populateDisk(); - populateNetworks(); - populateDevices(); - - document.querySelector("#exit").addEventListener("click", handleFormExit); -} - -function getOrdered(keys) { - let ordered_keys = Object.keys(keys).sort((a, b) => { parseInt(a) - parseInt(b) }); // ordered integer list - return ordered_keys; -} - -async function getConfig() { - config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET"); -} - -async function populateResources() { - let name = type === "qemu" ? "name" : "hostname"; - document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]); - if (type === "qemu") { - let global = await requestAPI("/global/config/resources"); - let user = await requestAPI("/user/config/resources"); - let options = []; - if (global.cpu.whitelist) { - options = user.max.cpu.sort((a, b) => { return a.localeCompare(b) }); - } - else { - let supported = await requestPVE(`/nodes/${node}/capabilities/qemu/cpu`); - supported.data.forEach((element) => { - if (!user.max.cpu.includes(element.name)) { - options.push(element.name); - } - }); - options = options.sort((a, b) => { return a.localeCompare(b) }) - console.log(options); - console.log("blacklist not yet supported") - } - addResourceLine("resources", "images/resources/cpu.svg", "select", "CPU Type", "proctype", { value: config.data.cpu, options: options }); - } - addResourceLine("resources", "images/resources/cpu.svg", "input", "CPU Amount", "cores", { type: "number", value: config.data.cores, min: 1, max: 8192 }, "Cores"); - addResourceLine("resources", "images/resources/ram.svg", "input", "Memory", "ram", { type: "number", value: config.data.memory, min: 16, step: 1 }, "MiB"); - if (type === "lxc") { - addResourceLine("resources", "images/resources/swap.svg", "input", "Swap", "swap", { type: "number", value: config.data.swap, min: 0, step: 1 }, "MiB"); - } -} - -function addResourceLine(fieldset, iconHref, type, labelText, id, attributes, unitText = null) { - let field = document.querySelector(`#${fieldset}`); - - let icon = document.createElement("img"); - icon.src = iconHref; - icon.alt = labelText; - field.append(icon); - - let label = document.createElement("label"); - label.innerText = labelText; - label.htmlFor = labelText; - field.append(label); - - if (type === "input") { - let input = document.createElement("input"); - for (let k in attributes) { - input.setAttribute(k, attributes[k]) - } - input.id = id; - input.name = id; - input.required = true; - input.classList.add("w3-input"); - input.classList.add("w3-border"); - field.append(input); - } - else if (type === "select") { - let select = document.createElement("select"); - for (let option of attributes.options) { - select.append(new Option(option)); - } - select.value = attributes.value; - select.id = id; - select.name = id; - select.required = true; - select.classList.add("w3-select"); - select.classList.add("w3-border"); - field.append(select); - } - - if (unitText) { - let unit = document.createElement("p"); - unit.innerText = unitText; - field.append(unit); - } - else { - let unit = document.createElement("div"); - unit.classList.add("hidden"); - field.append(unit); - } -} - -async function populateDisk() { - document.querySelector("#disks").innerHTML = ""; - for (let i = 0; i < diskMetaData[type].prefixOrder.length; i++) { - let prefix = diskMetaData[type].prefixOrder[i]; - let busName = diskMetaData[type][prefix].name; - let disks = {}; - Object.keys(config.data).forEach((element) => { - if (element.startsWith(prefix)) { - disks[element.replace(prefix, "")] = config.data[element]; - } - }); - let ordered_keys = getOrdered(disks); - ordered_keys.forEach((element) => { - let disk = disks[element]; - addDiskLine("disks", prefix, busName, element, disk); - }); - } - document.querySelector("#disk-add").addEventListener("click", handleDiskAdd); - - if (type === "qemu") { - document.querySelector("#cd-add").classList.remove("none"); - document.querySelector("#cd-add").addEventListener("click", handleCDAdd); - } -} - -function addDiskLine(fieldset, busPrefix, busName, device, diskDetails) { - let field = document.querySelector(`#${fieldset}`); - - let diskName = `${busName} ${device}`; - let diskID = `${busPrefix}${device}`; - - // Set the disk icon, either drive.svg or disk.svg - let icon = document.createElement("img"); - icon.src = diskMetaData[type][busPrefix].icon; - icon.alt = diskName; - icon.dataset.disk = diskID; - field.append(icon); - - // Add a label for the disk bus and device number - let diskLabel = document.createElement("label"); - diskLabel.innerText = diskName; - diskLabel.dataset.disk = diskID; - field.append(diskLabel); - - // Add text of the disk configuration - let diskDesc = document.createElement("p"); - diskDesc.innerText = diskDetails; - diskDesc.dataset.disk = diskID; - diskDesc.style.overflowX = "hidden"; - diskDesc.style.whiteSpace = "nowrap"; - field.append(diskDesc); - - let actionDiv = document.createElement("div"); - diskMetaData.actionBarOrder.forEach((element) => { - let action = document.createElement("img"); - if (element === "detach_attach" && diskMetaData[type][busPrefix].actions.includes("attach")) { // attach - action.src = "images/actions/disk/attach.svg"; - action.title = "Attach Disk"; - action.addEventListener("click", handleDiskAttach); - action.classList.add("clickable"); - } - else if (element === "detach_attach" && diskMetaData[type][busPrefix].actions.includes("detach")) { // detach - action.src = "images/actions/disk/detach.svg"; - action.title = "Detach Disk"; - action.addEventListener("click", handleDiskDetach); - action.classList.add("clickable"); - } - else if (element === "delete") { - let active = diskMetaData[type][busPrefix].actions.includes(element) ? "active" : "inactive"; // resize - action.src = `images/actions/delete-${active}.svg`; - action.title = "Delete Disk"; - if (active === "active") { - action.addEventListener("click", handleDiskDelete); - action.classList.add("clickable"); - } - } - else { - let active = diskMetaData[type][busPrefix].actions.includes(element) ? "active" : "inactive"; // resize - action.src = `images/actions/disk/${element}-${active}.svg`; - if (active === "active") { - action.title = `${element.charAt(0).toUpperCase()}${element.slice(1)} Disk`; - if (element === "move") { - action.addEventListener("click", handleDiskMove); - } - else if (element === "resize") { - action.addEventListener("click", handleDiskResize); - } - action.classList.add("clickable"); - } - } - action.dataset.disk = diskID; - action.alt = action.title; - actionDiv.append(action); - }); - field.append(actionDiv); -} - -async function handleDiskDetach() { - 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 = { - node: node, - type: type, - vmid: vmid, - disk: this.dataset.disk - }; - let result = await requestAPI("/instance/disk/detach", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleDiskAttach() { - let header = `Attach ${this.dataset.disk}`; - let body = ``; - - 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"; - let body = { - node: node, - type: type, - vmid: vmid, - disk: `${type === "qemu" ? "sata" : "mp"}${device}`, - source: this.dataset.disk.replace("unused", "") - } - let result = await requestAPI("/instance/disk/attach", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleDiskResize() { - let header = `Resize ${this.dataset.disk}`; - let body = ``; - - dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - disk: this.dataset.disk, - size: form.get("size-increment") - } - let result = await requestAPI("/instance/disk/resize", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleDiskMove() { - let content = type === "qemu" ? "images" : "rootdir"; - let storage = await requestPVE(`/nodes/${node}/storage`, "GET"); - - let header = `Move ${this.dataset.disk}`; - - let options = ""; - storage.data.forEach((element) => { - if (element.content.includes(content)) { - options += `"`; - } - }); - let select = ``; - - let body = ` - ${select} - - `; - - dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - disk: this.dataset.disk, - storage: form.get("storage-select"), - delete: form.get("delete-check") === "on" ? "1" : "0" - } - let result = await requestAPI("/instance/disk/move", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleDiskDelete() { - let header = `Delete ${this.dataset.disk}`; - let body = `Are you sure you want to delete 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 = { - node: node, - type: type, - vmid: vmid, - disk: this.dataset.disk - }; - let result = await requestAPI("/instance/disk/delete", "DELETE", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleDiskAdd() { - let content = type === "qemu" ? "images" : "rootdir"; - let storage = await requestPVE(`/nodes/${node}/storage`, "GET"); - - let header = "Create New Disk"; - - let options = ""; - storage.data.forEach((element) => { - if (element.content.includes(content)) { - options += `"`; - } - }); - let select = ``; - - let body = ` - - ${select} - - `; - - dialog(header, body, async (result, form) => { - if (result === "confirm") { - let body = { - node: node, - type: type, - vmid: vmid, - disk: `${type === "qemu" ? "sata" : "mp"}${form.get("device")}`, - storage: form.get("storage-select"), - size: form.get("size") - }; - let result = await requestAPI("/instance/disk/create", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); -} - -async function handleCDAdd() { - let content = "iso"; - let storage = await requestPVE(`/nodes/${node}/storage`, "GET"); - - let header = `Add a CDROM`; - - let storageOptions = ""; - storage.data.forEach((element) => { - if (element.content.includes(content)) { - storageOptions += `"`; - } - }); - let storageSelect = ``; - - let body = ` - - ${storageSelect} - - `; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - let body = { - node: node, - type: type, - vmid: vmid, - disk: `ide${form.get("device")}`, - iso: form.get("iso-select") - }; - let result = await requestAPI("/instance/disk/create", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - } - else { - alert(result.error); - await getConfig(); - populateDisk(); - } - } - }); - - 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 populateNetworks() { - document.querySelector("#networks").innerHTML = ""; - let networks = {}; - let prefix = networkMetaData.prefix; - Object.keys(config.data).forEach((element) => { - if (element.startsWith(prefix)) { - networks[element.replace(prefix, "")] = config.data[element]; - } - }); - let ordered_keys = getOrdered(networks); - ordered_keys.forEach((element) => { - addNetworkLine("networks", prefix, element, networks[element]); - }); - - document.querySelector("#network-add").addEventListener("click", handleNetworkAdd) -} - -function addNetworkLine(fieldset, prefix, netID, netDetails) { - let field = document.querySelector(`#${fieldset}`); - - let icon = document.createElement("img"); - icon.src = "images/resources/network.svg"; - icon.alt = `${prefix}${netID}`; - icon.dataset.network = netID; - icon.dataset.values = netDetails; - field.appendChild(icon); - - let netLabel = document.createElement("label"); - netLabel.innerText = `${prefix}${netID}`; - netLabel.dataset.network = netID; - netLabel.dataset.values = netDetails; - field.append(netLabel); - - let netDesc = document.createElement("p"); - netDesc.innerText = netDetails; - netDesc.dataset.network = netID; - netDesc.dataset.values = netDetails; - netDesc.style.overflowX = "hidden"; - netDesc.style.whiteSpace = "nowrap"; - field.append(netDesc); - - let actionDiv = document.createElement("div"); - - let configBtn = document.createElement("img"); - configBtn.classList.add("clickable"); - configBtn.src = `images/actions/network/config.svg`; - configBtn.title = "Config Interface"; - configBtn.addEventListener("click", handleNetworkConfig); - configBtn.dataset.network = netID; - configBtn.dataset.values = netDetails; - actionDiv.appendChild(configBtn); - - let deleteBtn = document.createElement("img"); - deleteBtn.classList.add("clickable"); - deleteBtn.src = `images/actions/delete-active.svg`; - deleteBtn.title = "Delete Interface"; - deleteBtn.addEventListener("click", handleNetworkDelete); - deleteBtn.dataset.network = netID; - deleteBtn.dataset.values = netDetails; - actionDiv.appendChild(deleteBtn); - - field.append(actionDiv); -} - -async function handleNetworkConfig() { - let netID = this.dataset.network; - let netDetails = this.dataset.values; - let header = `Edit net${netID}`; - let body = ``; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-network="${netID}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - netid: netID, - rate: form.get("rate") - } - let result = await requestAPI("/instance/network/modify", "POST", body); - if (result.status === 200) { - await getConfig(); - populateNetworks(); - } - else { - alert(result.error); - await getConfig(); - populateNetworks(); - } - } - }); - - d.querySelector("#rate").value = netDetails.split("rate=")[1].split(",")[0]; -} - -async function handleNetworkDelete() { - let netID = this.dataset.network; - let header = `Delete net${netID}`; - let body = ``; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-network="${netID}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - netid: netID - } - let result = await requestAPI("/instance/network/delete", "DELETE", body); - if (result.status === 200) { - await getConfig(); - populateNetworks(); - } - else { - alert(result.error); - await getConfig(); - populateNetworks(); - } - } - }); -} - -async function handleNetworkAdd() { - let header = `Create Network Interface`; - let body = ``; - if (type === "lxc") { - body += ``; - } - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - let body = { - node: node, - type: type, - vmid: vmid, - netid: form.get("netid"), - rate: form.get("rate") - } - if (type === "lxc") { - body.name = form.get("name") - } - let result = await requestAPI("/instance/network/create", "POST", body); - if (result.status === 200) { - await getConfig(); - populateNetworks(); - } - else { - alert(result.error); - await getConfig(); - populateNetworks(); - } - } - }); -} - -async function populateDevices() { - if (type === "qemu") { - document.querySelector("#devices-card").classList.remove("none"); - document.querySelector("#devices").innerHTML = ""; - let devices = {}; - let prefix = pcieMetaData.prefix; - Object.keys(config.data).forEach((element) => { - if (element.startsWith(prefix)) { - devices[element.replace(prefix, "")] = config.data[element]; - } - }); - let ordered_keys = getOrdered(devices); - ordered_keys.forEach(async (element) => { - let deviceData = await requestAPI(`/instance/pci?node=${node}&type=${type}&vmid=${vmid}&hostpci=${element}`, "GET"); - addDeviceLine("devices", prefix, element, devices[element], deviceData.device_name); - }); - - document.querySelector("#device-add").addEventListener("click", handleDeviceAdd) - } -} - -function addDeviceLine(fieldset, prefix, deviceID, deviceDetails, deviceName) { - let field = document.querySelector(`#${fieldset}`); - - let icon = document.createElement("img"); - icon.src = "images/resources/device.svg"; - icon.alt = `${prefix}${deviceID}`; - icon.dataset.device = deviceID; - icon.dataset.values = deviceDetails; - icon.dataset.name = deviceName; - field.appendChild(icon); - - let deviceLabel = document.createElement("p"); - - deviceLabel.innerText = deviceName; - deviceLabel.dataset.device = deviceID; - deviceLabel.dataset.values = deviceDetails; - deviceLabel.dataset.name = deviceName; - deviceLabel.style.overflowX = "hidden"; - deviceLabel.style.whiteSpace = "nowrap"; - field.append(deviceLabel); - - let actionDiv = document.createElement("div"); - - let configBtn = document.createElement("img"); - configBtn.classList.add("clickable"); - configBtn.src = `images/actions/device/config.svg`; - configBtn.title = "Config Device"; - configBtn.addEventListener("click", handleDeviceConfig); - configBtn.dataset.device = deviceID; - configBtn.dataset.values = deviceDetails; - configBtn.dataset.name = deviceName; - actionDiv.appendChild(configBtn); - - let deleteBtn = document.createElement("img"); - deleteBtn.classList.add("clickable"); - deleteBtn.src = `images/actions/delete-active.svg`; - deleteBtn.title = "Delete Device"; - deleteBtn.addEventListener("click", handleDeviceDelete); - deleteBtn.dataset.device = deviceID; - deleteBtn.dataset.values = deviceDetails; - deleteBtn.dataset.name = deviceName; - actionDiv.appendChild(deleteBtn); - - field.append(actionDiv); -} - -async function handleDeviceConfig() { - let deviceID = this.dataset.device; - let deviceDetails = this.dataset.values; - let deviceName = this.dataset.name; - let header = `Edit Expansion Card ${deviceID}`; - let body = ``; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-device="${deviceID}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - hostpci: deviceID, - device: form.get("device"), - pcie: form.get("pcie") ? 1 : 0 - } - let result = await requestAPI("/instance/pci/modify", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDevices(); - } - else { - alert(result.error); - await getConfig(); - populateDevices(); - } - } - }); - - let availDevices = await requestAPI(`/nodes/pci?node=${node}`, "GET"); - d.querySelector("#device").append(new Option(deviceName, deviceDetails.split(",")[0])); - for (let availDevice of availDevices) { - d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.id)); - } - d.querySelector("#pcie").checked = deviceDetails.includes("pcie=1"); -} - -async function handleDeviceDelete() { - let deviceID = this.dataset.device; - let header = `Remove Expansion Card ${deviceID}`; - let body = ``; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - document.querySelector(`img[data-device="${deviceID}"]`).src = "images/status/loading.svg"; - let body = { - node: node, - type: type, - vmid: vmid, - hostpci: deviceID - } - let result = await requestAPI("/instance/pci/delete", "DELETE", body); - if (result.status === 200) { - await getConfig(); - populateDevices(); - } - else { - alert(result.error); - await getConfig(); - populateDevices(); - } - } - }); -} - -async function handleDeviceAdd() { - let header = `Add Expansion Card`; - let body = ``; - - let d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - let body = { - node: node, - type: type, - vmid: vmid, - device: form.get("device"), - pcie: form.get("pcie") ? 1 : 0 - } - let result = await requestAPI("/instance/pci/create", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDevices(); - } - else { - alert(result.error); - await getConfig(); - populateDevices(); - } - } - }); - - let availDevices = await requestAPI(`/nodes/pci?node=${node}`, "GET"); - for (let availDevice of availDevices) { - d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.id)); - } - d.querySelector("#pcie").checked = true; -} - -async function handleFormExit() { - let body = { - node: node, - type: type, - vmid: vmid, - cores: document.querySelector("#cores").value, - memory: document.querySelector("#ram").value - } - if (type === "lxc") { - body.swap = document.querySelector("#swap").value; - } - else if (type === "qemu") { - body.proctype = document.querySelector("#proctype").value; - } - let result = await requestAPI("/instance/resources", "POST", body); - if (result.status === 200) { - await getConfig(); - populateDisk(); - goToPage("index.html"); - } - else { - alert(result.error); - } -} \ No newline at end of file +import { requestPVE, requestAPI, goToPage, getURIData, resourcesConfig, setTitleAndHeader } from "./utils.js"; +import { alert, dialog } from "./dialog.js"; + +window.addEventListener("DOMContentLoaded", init); // do the dumb thing where the disk config refreshes every second + +const diskMetaData = resourcesConfig.disk; +const networkMetaData = resourcesConfig.network; +const pcieMetaData = resourcesConfig.pcie; + +let node; +let type; +let vmid; +let config; + +async function init () { + setTitleAndHeader(); + const cookie = document.cookie; + if (cookie === "") { + goToPage("login.html"); + } + + const uriData = getURIData(); + node = uriData.node; + type = uriData.type; + vmid = uriData.vmid; + + await getConfig(); + + populateResources(); + populateDisk(); + populateNetworks(); + populateDevices(); + + document.querySelector("#exit").addEventListener("click", handleFormExit); +} + +function getOrdered (keys) { + const orderedKeys = Object.keys(keys).sort((a, b) => { + return parseInt(a) - parseInt(b); + }); // ordered integer list + return orderedKeys; +} + +async function getConfig () { + config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET"); +} + +async function populateResources () { + const name = type === "qemu" ? "name" : "hostname"; + document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]); + if (type === "qemu") { + const global = await requestAPI("/global/config/resources"); + const user = await requestAPI("/user/config/resources"); + let options = []; + if (global.cpu.whitelist) { + options = user.max.cpu.sort((a, b) => { + return a.localeCompare(b); + }); + } + else { + const supported = await requestPVE(`/nodes/${node}/capabilities/qemu/cpu`); + supported.data.forEach((element) => { + if (!user.max.cpu.includes(element.name)) { + options.push(element.name); + } + }); + options = options.sort((a, b) => { + return a.localeCompare(b); + }); + console.log(options); + console.log("blacklist not yet supported"); + } + addResourceLine("resources", "images/resources/cpu.svg", "select", "CPU Type", "proctype", { value: config.data.cpu, options }); + } + addResourceLine("resources", "images/resources/cpu.svg", "input", "CPU Amount", "cores", { type: "number", value: config.data.cores, min: 1, max: 8192 }, "Cores"); + addResourceLine("resources", "images/resources/ram.svg", "input", "Memory", "ram", { type: "number", value: config.data.memory, min: 16, step: 1 }, "MiB"); + if (type === "lxc") { + addResourceLine("resources", "images/resources/swap.svg", "input", "Swap", "swap", { type: "number", value: config.data.swap, min: 0, step: 1 }, "MiB"); + } +} + +function addResourceLine (fieldset, iconHref, type, labelText, id, attributes, unitText = null) { + const field = document.querySelector(`#${fieldset}`); + + const icon = document.createElement("img"); + icon.src = iconHref; + icon.alt = labelText; + field.append(icon); + + const label = document.createElement("label"); + label.innerText = labelText; + label.htmlFor = labelText; + field.append(label); + + if (type === "input") { + const input = document.createElement("input"); + for (const k in attributes) { + input.setAttribute(k, attributes[k]); + } + input.id = id; + input.name = id; + input.required = true; + input.classList.add("w3-input"); + input.classList.add("w3-border"); + field.append(input); + } + else if (type === "select") { + const select = document.createElement("select"); + for (const option of attributes.options) { + select.append(new Option(option)); + } + select.value = attributes.value; + select.id = id; + select.name = id; + select.required = true; + select.classList.add("w3-select"); + select.classList.add("w3-border"); + field.append(select); + } + + if (unitText) { + const unit = document.createElement("p"); + unit.innerText = unitText; + field.append(unit); + } + else { + const unit = document.createElement("div"); + unit.classList.add("hidden"); + field.append(unit); + } +} + +async function populateDisk () { + document.querySelector("#disks").innerHTML = ""; + for (let i = 0; i < diskMetaData[type].prefixOrder.length; i++) { + const prefix = diskMetaData[type].prefixOrder[i]; + const busName = diskMetaData[type][prefix].name; + const disks = {}; + Object.keys(config.data).forEach((element) => { + if (element.startsWith(prefix)) { + disks[element.replace(prefix, "")] = config.data[element]; + } + }); + const orderedKeys = getOrdered(disks); + orderedKeys.forEach((element) => { + const disk = disks[element]; + addDiskLine("disks", prefix, busName, element, disk); + }); + } + document.querySelector("#disk-add").addEventListener("click", handleDiskAdd); + + if (type === "qemu") { + document.querySelector("#cd-add").classList.remove("none"); + document.querySelector("#cd-add").addEventListener("click", handleCDAdd); + } +} + +function addDiskLine (fieldset, busPrefix, busName, device, diskDetails) { + const field = document.querySelector(`#${fieldset}`); + + const diskName = `${busName} ${device}`; + const diskID = `${busPrefix}${device}`; + + // Set the disk icon, either drive.svg or disk.svg + const icon = document.createElement("img"); + icon.src = diskMetaData[type][busPrefix].icon; + icon.alt = diskName; + icon.dataset.disk = diskID; + field.append(icon); + + // Add a label for the disk bus and device number + const diskLabel = document.createElement("label"); + diskLabel.innerText = diskName; + diskLabel.dataset.disk = diskID; + field.append(diskLabel); + + // Add text of the disk configuration + const diskDesc = document.createElement("p"); + diskDesc.innerText = diskDetails; + diskDesc.dataset.disk = diskID; + diskDesc.style.overflowX = "hidden"; + diskDesc.style.whiteSpace = "nowrap"; + field.append(diskDesc); + + const actionDiv = document.createElement("div"); + diskMetaData.actionBarOrder.forEach((element) => { + const action = document.createElement("img"); + if (element === "detach_attach" && diskMetaData[type][busPrefix].actions.includes("attach")) { // attach + action.src = "images/actions/disk/attach.svg"; + action.title = "Attach Disk"; + action.addEventListener("click", handleDiskAttach); + action.classList.add("clickable"); + } + else if (element === "detach_attach" && diskMetaData[type][busPrefix].actions.includes("detach")) { // detach + action.src = "images/actions/disk/detach.svg"; + action.title = "Detach Disk"; + action.addEventListener("click", handleDiskDetach); + action.classList.add("clickable"); + } + else if (element === "delete") { + const active = diskMetaData[type][busPrefix].actions.includes(element) ? "active" : "inactive"; // resize + action.src = `images/actions/delete-${active}.svg`; + action.title = "Delete Disk"; + if (active === "active") { + action.addEventListener("click", handleDiskDelete); + action.classList.add("clickable"); + } + } + else { + const active = diskMetaData[type][busPrefix].actions.includes(element) ? "active" : "inactive"; // resize + action.src = `images/actions/disk/${element}-${active}.svg`; + if (active === "active") { + action.title = `${element.charAt(0).toUpperCase()}${element.slice(1)} Disk`; + if (element === "move") { + action.addEventListener("click", handleDiskMove); + } + else if (element === "resize") { + action.addEventListener("click", handleDiskResize); + } + action.classList.add("clickable"); + } + } + action.dataset.disk = diskID; + action.alt = action.title; + actionDiv.append(action); + }); + field.append(actionDiv); +} + +async function handleDiskDetach () { + const header = `Detach ${this.dataset.disk}`; + const 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"; + const body = { + node, + type, + vmid, + disk: this.dataset.disk + }; + const result = await requestAPI("/instance/disk/detach", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleDiskAttach () { + const header = `Attach ${this.dataset.disk}`; + const body = ``; + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + const device = form.get("device"); + document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + disk: `${type === "qemu" ? "sata" : "mp"}${device}`, + source: this.dataset.disk.replace("unused", "") + }; + const result = await requestAPI("/instance/disk/attach", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleDiskResize () { + const header = `Resize ${this.dataset.disk}`; + const body = ""; + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + disk: this.dataset.disk, + size: form.get("size-increment") + }; + const result = await requestAPI("/instance/disk/resize", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleDiskMove () { + const content = type === "qemu" ? "images" : "rootdir"; + const storage = await requestPVE(`/nodes/${node}/storage`, "GET"); + + const header = `Move ${this.dataset.disk}`; + + let options = ""; + storage.data.forEach((element) => { + if (element.content.includes(content)) { + options += `"`; + } + }); + const select = ``; + + const body = ` + ${select} + + `; + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + disk: this.dataset.disk, + storage: form.get("storage-select"), + delete: form.get("delete-check") === "on" ? "1" : "0" + }; + const result = await requestAPI("/instance/disk/move", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleDiskDelete () { + const header = `Delete ${this.dataset.disk}`; + const body = `Are you sure you want to delete 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"; + const body = { + node, + type, + vmid, + disk: this.dataset.disk + }; + const result = await requestAPI("/instance/disk/delete", "DELETE", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleDiskAdd () { + const content = type === "qemu" ? "images" : "rootdir"; + const storage = await requestPVE(`/nodes/${node}/storage`, "GET"); + + const header = "Create New Disk"; + + let options = ""; + storage.data.forEach((element) => { + if (element.content.includes(content)) { + options += `"`; + } + }); + const select = ``; + + const body = ` + + ${select} + + `; + + dialog(header, body, async (result, form) => { + if (result === "confirm") { + const body = { + node, + type, + vmid, + disk: `${type === "qemu" ? "sata" : "mp"}${form.get("device")}`, + storage: form.get("storage-select"), + size: form.get("size") + }; + const result = await requestAPI("/instance/disk/create", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); +} + +async function handleCDAdd () { + const content = "iso"; + const storage = await requestPVE(`/nodes/${node}/storage`, "GET"); + + const header = "Add a CDROM"; + + let storageOptions = ""; + storage.data.forEach((element) => { + if (element.content.includes(content)) { + storageOptions += `"`; + } + }); + const storageSelect = ``; + + const body = ` + + ${storageSelect} + + `; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + const body = { + node, + type, + vmid, + disk: `ide${form.get("device")}`, + iso: form.get("iso-select") + }; + const result = await requestAPI("/instance/disk/create", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + } + else { + alert(result.error); + await getConfig(); + populateDisk(); + } + } + }); + + d.querySelector("#storage-select").addEventListener("change", async () => { + const storage = document.querySelector("#storage-select").value; + const ISOSelect = document.querySelector("#iso-select"); + ISOSelect.innerHTML = ""; + const isos = await requestPVE(`/nodes/${node}/storage/${storage}/content`, "GET", { content }); + isos.data.forEach((element) => { + if (element.content.includes(content)) { + ISOSelect.append(new Option(element.volid.replace(`${storage}:${content}/`, ""), element.volid)); + } + }); + }); +} + +async function populateNetworks () { + document.querySelector("#networks").innerHTML = ""; + const networks = {}; + const prefix = networkMetaData.prefix; + Object.keys(config.data).forEach((element) => { + if (element.startsWith(prefix)) { + networks[element.replace(prefix, "")] = config.data[element]; + } + }); + const orderedKeys = getOrdered(networks); + orderedKeys.forEach((element) => { + addNetworkLine("networks", prefix, element, networks[element]); + }); + + document.querySelector("#network-add").addEventListener("click", handleNetworkAdd); +} + +function addNetworkLine (fieldset, prefix, netID, netDetails) { + const field = document.querySelector(`#${fieldset}`); + + const icon = document.createElement("img"); + icon.src = "images/resources/network.svg"; + icon.alt = `${prefix}${netID}`; + icon.dataset.network = netID; + icon.dataset.values = netDetails; + field.appendChild(icon); + + const netLabel = document.createElement("label"); + netLabel.innerText = `${prefix}${netID}`; + netLabel.dataset.network = netID; + netLabel.dataset.values = netDetails; + field.append(netLabel); + + const netDesc = document.createElement("p"); + netDesc.innerText = netDetails; + netDesc.dataset.network = netID; + netDesc.dataset.values = netDetails; + netDesc.style.overflowX = "hidden"; + netDesc.style.whiteSpace = "nowrap"; + field.append(netDesc); + + const actionDiv = document.createElement("div"); + + const configBtn = document.createElement("img"); + configBtn.classList.add("clickable"); + configBtn.src = "images/actions/network/config.svg"; + configBtn.title = "Config Interface"; + configBtn.addEventListener("click", handleNetworkConfig); + configBtn.dataset.network = netID; + configBtn.dataset.values = netDetails; + actionDiv.appendChild(configBtn); + + const deleteBtn = document.createElement("img"); + deleteBtn.classList.add("clickable"); + deleteBtn.src = "images/actions/delete-active.svg"; + deleteBtn.title = "Delete Interface"; + deleteBtn.addEventListener("click", handleNetworkDelete); + deleteBtn.dataset.network = netID; + deleteBtn.dataset.values = netDetails; + actionDiv.appendChild(deleteBtn); + + field.append(actionDiv); +} + +async function handleNetworkConfig () { + const netID = this.dataset.network; + const netDetails = this.dataset.values; + const header = `Edit net${netID}`; + const body = ""; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-network="${netID}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + netid: netID, + rate: form.get("rate") + }; + const result = await requestAPI("/instance/network/modify", "POST", body); + if (result.status === 200) { + await getConfig(); + populateNetworks(); + } + else { + alert(result.error); + await getConfig(); + populateNetworks(); + } + } + }); + + d.querySelector("#rate").value = netDetails.split("rate=")[1].split(",")[0]; +} + +async function handleNetworkDelete () { + const netID = this.dataset.network; + const header = `Delete net${netID}`; + const body = ""; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-network="${netID}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + netid: netID + }; + const result = await requestAPI("/instance/network/delete", "DELETE", body); + if (result.status === 200) { + await getConfig(); + populateNetworks(); + } + else { + alert(result.error); + await getConfig(); + populateNetworks(); + } + } + }); +} + +async function handleNetworkAdd () { + const header = "Create Network Interface"; + let body = ""; + if (type === "lxc") { + body += ""; + } + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + const body = { + node, + type, + vmid, + netid: form.get("netid"), + rate: form.get("rate") + }; + if (type === "lxc") { + body.name = form.get("name"); + } + const result = await requestAPI("/instance/network/create", "POST", body); + if (result.status === 200) { + await getConfig(); + populateNetworks(); + } + else { + alert(result.error); + await getConfig(); + populateNetworks(); + } + } + }); +} + +async function populateDevices () { + if (type === "qemu") { + document.querySelector("#devices-card").classList.remove("none"); + document.querySelector("#devices").innerHTML = ""; + const devices = {}; + const prefix = pcieMetaData.prefix; + Object.keys(config.data).forEach((element) => { + if (element.startsWith(prefix)) { + devices[element.replace(prefix, "")] = config.data[element]; + } + }); + const orderedKeys = getOrdered(devices); + orderedKeys.forEach(async (element) => { + const deviceData = await requestAPI(`/instance/pci?node=${node}&type=${type}&vmid=${vmid}&hostpci=${element}`, "GET"); + addDeviceLine("devices", prefix, element, devices[element], deviceData.device_name); + }); + + document.querySelector("#device-add").addEventListener("click", handleDeviceAdd); + } +} + +function addDeviceLine (fieldset, prefix, deviceID, deviceDetails, deviceName) { + const field = document.querySelector(`#${fieldset}`); + + const icon = document.createElement("img"); + icon.src = "images/resources/device.svg"; + icon.alt = `${prefix}${deviceID}`; + icon.dataset.device = deviceID; + icon.dataset.values = deviceDetails; + icon.dataset.name = deviceName; + field.appendChild(icon); + + const deviceLabel = document.createElement("p"); + + deviceLabel.innerText = deviceName; + deviceLabel.dataset.device = deviceID; + deviceLabel.dataset.values = deviceDetails; + deviceLabel.dataset.name = deviceName; + deviceLabel.style.overflowX = "hidden"; + deviceLabel.style.whiteSpace = "nowrap"; + field.append(deviceLabel); + + const actionDiv = document.createElement("div"); + + const configBtn = document.createElement("img"); + configBtn.classList.add("clickable"); + configBtn.src = "images/actions/device/config.svg"; + configBtn.title = "Config Device"; + configBtn.addEventListener("click", handleDeviceConfig); + configBtn.dataset.device = deviceID; + configBtn.dataset.values = deviceDetails; + configBtn.dataset.name = deviceName; + actionDiv.appendChild(configBtn); + + const deleteBtn = document.createElement("img"); + deleteBtn.classList.add("clickable"); + deleteBtn.src = "images/actions/delete-active.svg"; + deleteBtn.title = "Delete Device"; + deleteBtn.addEventListener("click", handleDeviceDelete); + deleteBtn.dataset.device = deviceID; + deleteBtn.dataset.values = deviceDetails; + deleteBtn.dataset.name = deviceName; + actionDiv.appendChild(deleteBtn); + + field.append(actionDiv); +} + +async function handleDeviceConfig () { + const deviceID = this.dataset.device; + const deviceDetails = this.dataset.values; + const deviceName = this.dataset.name; + const header = `Edit Expansion Card ${deviceID}`; + const body = ""; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-device="${deviceID}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + hostpci: deviceID, + device: form.get("device"), + pcie: form.get("pcie") ? 1 : 0 + }; + const result = await requestAPI("/instance/pci/modify", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDevices(); + } + else { + alert(result.error); + await getConfig(); + populateDevices(); + } + } + }); + + const availDevices = await requestAPI(`/nodes/pci?node=${node}`, "GET"); + d.querySelector("#device").append(new Option(deviceName, deviceDetails.split(",")[0])); + for (const availDevice of availDevices) { + d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.id)); + } + d.querySelector("#pcie").checked = deviceDetails.includes("pcie=1"); +} + +async function handleDeviceDelete () { + const deviceID = this.dataset.device; + const header = `Remove Expansion Card ${deviceID}`; + const body = ""; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + document.querySelector(`img[data-device="${deviceID}"]`).src = "images/status/loading.svg"; + const body = { + node, + type, + vmid, + hostpci: deviceID + }; + const result = await requestAPI("/instance/pci/delete", "DELETE", body); + if (result.status === 200) { + await getConfig(); + populateDevices(); + } + else { + alert(result.error); + await getConfig(); + populateDevices(); + } + } + }); +} + +async function handleDeviceAdd () { + const header = "Add Expansion Card"; + const body = ""; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + const body = { + node, + type, + vmid, + device: form.get("device"), + pcie: form.get("pcie") ? 1 : 0 + }; + const result = await requestAPI("/instance/pci/create", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDevices(); + } + else { + alert(result.error); + await getConfig(); + populateDevices(); + } + } + }); + + const availDevices = await requestAPI(`/nodes/pci?node=${node}`, "GET"); + for (const availDevice of availDevices) { + d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.id)); + } + d.querySelector("#pcie").checked = true; +} + +async function handleFormExit () { + const body = { + node, + type, + vmid, + cores: document.querySelector("#cores").value, + memory: document.querySelector("#ram").value + }; + if (type === "lxc") { + body.swap = document.querySelector("#swap").value; + } + else if (type === "qemu") { + body.proctype = document.querySelector("#proctype").value; + } + const result = await requestAPI("/instance/resources", "POST", body); + if (result.status === 200) { + await getConfig(); + populateDisk(); + goToPage("index.html"); + } + else { + alert(result.error); + } +} diff --git a/scripts/dialog.js b/scripts/dialog.js index 7d8e21e..9abb2c3 100644 --- a/scripts/dialog.js +++ b/scripts/dialog.js @@ -1,5 +1,5 @@ -export function dialog(header, body, callback = async (result, form) => { }) { - let dialog = document.createElement("dialog"); +export function dialog (header, body, callback = async (result, form) => { }) { + const dialog = document.createElement("dialog"); dialog.innerHTML = ` @@ -23,8 +23,8 @@ export function dialog(header, body, callback = async (result, form) => { }) { return dialog; } -export function alert(message) { - let dialog = document.createElement("dialog"); +export function alert (message) { + const dialog = document.createElement("dialog"); dialog.innerHTML = `