From 912cee33c7c395f7089925a0060888169d74ed54 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Sat, 31 Aug 2024 12:31:29 +0000 Subject: [PATCH] move addResourceLine to utils, localize page specific resource line values in page script --- scripts/config.js | 118 +++++++++++++++----------------------- scripts/utils.js | 142 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 73 deletions(-) diff --git a/scripts/config.js b/scripts/config.js index ab099fd..0d54984 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -1,11 +1,11 @@ -import { requestPVE, requestAPI, goToPage, getURIData, resourcesConfig, setTitleAndHeader, bootConfig, setAppearance, setSVGSrc, setSVGAlt } from "./utils.js"; +import { requestPVE, requestAPI, goToPage, getURIData, resourcesConfig, setTitleAndHeader, bootConfig, setAppearance, setSVGSrc, setSVGAlt, mergeDeep, addResourceLine } 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; +const pcieMetaData = resourcesConfig.pci; const bootMetaData = bootConfig; let node; @@ -13,6 +13,33 @@ let type; let vmid; let config; +const resourceInputTypes = { // input types for each resource for config page + cpu: { + element: "select", + attributes: {} + }, + cores: { + element: "input", + attributes: { + type: "number" + } + }, + memory: { + element: "input", + attributes: { + type: "number" + } + }, + swap: { + element: "input", + attributes: { + type: "number" + } + } +}; + +const resourcesConfigPage = mergeDeep({}, resourcesConfig, resourceInputTypes); + async function init () { setAppearance(); setTitleAndHeader(); @@ -28,6 +55,9 @@ async function init () { await getConfig(); + const name = type === "qemu" ? "name" : "hostname"; + document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]); + populateResources(); populateDisk(); populateNetworks(); @@ -49,8 +79,7 @@ async function getConfig () { } async function populateResources () { - const name = type === "qemu" ? "name" : "hostname"; - document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]); + const field = document.querySelector("#resources"); if (type === "qemu") { const global = await requestAPI("/global/config/resources"); const user = await requestAPI("/user/config/resources"); @@ -76,63 +105,12 @@ async function populateResources () { return a.localeCompare(b); }); } - addResourceLine("resources", "images/resources/cpu.svg", "select", "CPU Type", "proctype", { value: config.data.cpu, options }); + addResourceLine(resourcesConfigPage, field, "cpu", { 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"); + addResourceLine(resourcesConfigPage, field, "cores", { value: config.data.cores, min: 1, max: 8192 }); + addResourceLine(resourcesConfigPage, field, "memory", { value: config.data.memory, min: 16, step: 1 }); 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.createElementNS("http://www.w3.org/2000/svg", "svg"); - setSVGSrc(icon, iconHref); - setSVGAlt(icon, labelText); - field.append(icon); - - const label = document.createElement("label"); - label.innerText = labelText; - label.htmlFor = id; - 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); + addResourceLine(resourcesConfigPage, field, "swap", { value: config.data.swap, min: 0, step: 1 }); } } @@ -256,7 +234,7 @@ async function handleDiskAttach () { const body = `
- +
`; @@ -285,7 +263,7 @@ async function handleDiskResize () { const body = `
- +
`; @@ -383,9 +361,9 @@ async function handleDiskAdd () { const body = `
- + ${select} - +
`; @@ -416,7 +394,7 @@ async function handleCDAdd () { const body = `
- +
`; @@ -466,7 +444,7 @@ function addNetworkLine (fieldset, prefix, netID, netDetails) { const field = document.querySelector(`#${fieldset}`); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - setSVGSrc(icon, "images/resources/network.svg"); + setSVGSrc(icon, networkMetaData.icon); setSVGAlt(icon, `${prefix}${netID}`); icon.dataset.network = netID; icon.dataset.values = netDetails; @@ -566,7 +544,7 @@ async function handleNetworkAdd () { `; if (type === "lxc") { - body += ""; + body += ""; } body += ""; @@ -616,7 +594,7 @@ function addDeviceLine (fieldset, prefix, deviceID, deviceDetails, deviceName) { const field = document.querySelector(`#${fieldset}`); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - setSVGSrc(icon, "images/resources/device.svg"); + setSVGSrc(icon, pcieMetaData.icon); setSVGAlt(icon, `${prefix}${deviceID}`); icon.dataset.device = deviceID; icon.dataset.values = deviceDetails; @@ -783,8 +761,8 @@ function addBootLine (container, data, before = null) { item.data = data; item.innerHTML = `
- drag icon - ${bootMetaData[data.prefix].alt} + drag icon + ${bootMetaData[data.prefix].alt}

${data.id}

${data.detail}

@@ -850,8 +828,6 @@ async function handleFormExit () { } const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/resources`, "POST", body); if (result.status === 200) { - await getConfig(); - populateDisk(); goToPage("index.html"); } else { diff --git a/scripts/utils.js b/scripts/utils.js index 1cbb41e..bee37d9 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,6 +1,30 @@ import { API, organization } from "../vars.js"; export const resourcesConfig = { + cpu: { + name: "CPU Type", + icon: "images/resources/cpu.svg", + id: "proctype", + unitText: null + }, + cores: { + name: "CPU Amount", + icon: "images/resources/cpu.svg", + id: "cores", + unitText: "Cores" + }, + memory: { + name: "Memory", + icon: "images/resources/ram.svg", + id: "ram", + unitText: "MiB" + }, + swap: { + name: "Swap", + icon: "images/resources/swap.svg", + id: "swap", + unitText: "MiB" + }, disk: { actionBarOrder: ["move", "resize", "detach_attach", "delete"], lxc: { @@ -28,9 +52,17 @@ export const resourcesConfig = { } }, network: { + name: "Network", + icon: "images/resources/network.svg", + id: "network", + unitText: "MB/s", prefix: "net" }, - pcie: { + pci: { + name: "Devices", + icon: "images/resources/device.svg", + id: "devices", + unitText: null, prefix: "hostpci" } }; @@ -335,10 +367,116 @@ export function setSVGSrc (svgElem, href) { if (!useElem) { useElem = document.createElementNS("http://www.w3.org/2000/svg", "use"); } - useElem.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", `${href}#symb`); + useElem.setAttribute("href", `${href}#symb`); svgElem.append(useElem); } export function setSVGAlt (svgElem, alt) { svgElem.setAttribute("aria-label", alt); } + +/** + * Simple object check. + * @param item + * @returns {boolean} + */ +export function isObject (item) { + return (item && typeof item === "object" && !Array.isArray(item)); +} + +/** + * Deep merge two objects. + * @param target + * @param ...sources + */ +export function mergeDeep (target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } + else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} + +export function addResourceLine (config, field, resourceType, attributesOverride, labelPrefix = null) { + const resourceConfig = config[resourceType]; + const iconHref = resourceConfig.icon; + const elementType = resourceConfig.element; + const labelText = labelPrefix ? `${labelPrefix} ${resourceConfig.name}` : resourceConfig.name; + const id = resourceConfig.id; + const unitText = resourceConfig.unitText; + const attributes = { ...(resourceConfig.attributes), ...(attributesOverride) }; + + const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + setSVGSrc(icon, iconHref); + setSVGAlt(icon, labelText); + field.append(icon); + + const label = document.createElement("label"); + label.innerText = labelText; + label.htmlFor = id; + field.append(label); + + if (elementType === "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 (elementType === "select" || elementType === "multi-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"); + if (elementType === "multi-select") { + select.setAttribute("multiple", true); + } + field.append(select); + } + else if (customElements.get(elementType)) { + const elem = document.createElement(elementType); + if (attributes.options) { + for (const option of attributes.options) { + elem.append(new Option(option)); + } + } + elem.value = attributes.value; + elem.id = id; + elem.name = id; + elem.required = true; + field.append(elem); + } + + 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); + } +}