From 68fafc1e37bef9153c1238db5bf74b28ef3f187f Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Thu, 7 Nov 2024 07:37:16 +0000 Subject: [PATCH] simplify addResourceLine interface, add update modules script to npm scripts, add value to DraggableItem --- config.html => instance.html | 2 +- package.json | 3 +- scripts/draggable.js | 9 ++ scripts/{config.js => instance.js} | 8 +- scripts/user.js | 243 +++++++++++++++++++++++++---- scripts/utils.js | 3 +- user.html | 2 +- 7 files changed, 232 insertions(+), 38 deletions(-) rename config.html => instance.html (96%) rename scripts/{config.js => instance.js} (98%) diff --git a/config.html b/instance.html similarity index 96% rename from config.html rename to instance.html index 98089bd..87f12a1 100644 --- a/config.html +++ b/instance.html @@ -10,7 +10,7 @@ - + diff --git a/package.json b/package.json index 9d0a2e6..3b93f72 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Front-end for ProxmoxAAS", "type": "module", "scripts": { - "lint": "html-validator --continue; stylelint --formatter verbose --fix css/*.css; DEBUG=eslint:cli-engine eslint --fix scripts/" + "lint": "html-validator --continue; stylelint --formatter verbose --fix css/*.css; DEBUG=eslint:cli-engine eslint --fix scripts/", + "update-modules": "rm -rf modules/wfa.js modules/wfa.wasm; curl https://git.tronnet.net/alu/WFA-JS/releases/download/latest/wfa.js -o modules/wfa.js; curl https://git.tronnet.net/alu/WFA-JS/releases/download/latest/wfa.wasm -o modules/wfa.wasm" }, "devDependencies": { "eslint": "^8.43.0", diff --git a/scripts/draggable.js b/scripts/draggable.js index ed6e07d..5dd3c52 100644 --- a/scripts/draggable.js +++ b/scripts/draggable.js @@ -74,6 +74,7 @@ class DraggableContainer extends HTMLElement { } class DraggableItem extends HTMLElement { + #value = null; uuid = null; constructor () { super(); @@ -103,6 +104,14 @@ class DraggableItem extends HTMLElement { set innerHTML (innerHTML) { this.content.innerHTML = innerHTML; } + + get value () { + return this.#value; + } + + set value (value) { + this.#value = value; + } } customElements.define("draggable-container", DraggableContainer); diff --git a/scripts/config.js b/scripts/instance.js similarity index 98% rename from scripts/config.js rename to scripts/instance.js index b2e75e3..d8150ee 100644 --- a/scripts/config.js +++ b/scripts/instance.js @@ -105,12 +105,12 @@ async function populateResources () { return a.localeCompare(b); }); } - addResourceLine(resourcesConfigPage, field, "cpu", { value: config.data.cpu, options }); + addResourceLine(resourcesConfigPage.cpu, field, { value: config.data.cpu, options }); } - addResourceLine(resourcesConfigPage, field, "cores", { value: config.data.cores, min: 1, max: 8192 }); - addResourceLine(resourcesConfigPage, field, "memory", { value: config.data.memory, min: 16, step: 1 }); + addResourceLine(resourcesConfigPage.cores, field, { value: config.data.cores, min: 1, max: 8192 }); + addResourceLine(resourcesConfigPage.memory, field, { value: config.data.memory, min: 16, step: 1 }); if (type === "lxc") { - addResourceLine(resourcesConfigPage, field, "swap", { value: config.data.swap, min: 0, step: 1 }); + addResourceLine(resourcesConfigPage.swap, field, { value: config.data.swap, min: 0, step: 1 }); } } diff --git a/scripts/user.js b/scripts/user.js index 9585b61..e6f31ef 100644 --- a/scripts/user.js +++ b/scripts/user.js @@ -10,36 +10,53 @@ let allNodes; let allPools; let clusterResourceConfig; +const resourceRulesLines = {}; // list of all resource rules fieldsets on the page + const resourceInputTypes = { // input types for each resource for config page cpu: { + type: "list", element: "interactive-list", align: "start" }, cores: { + type: "numeric", element: "input", attributes: { type: "number" } }, memory: { + type: "numeric", element: "input", attributes: { type: "number" } }, swap: { + type: "numeric", element: "input", attributes: { type: "number" } }, network: { + type: "numeric", element: "input", attributes: { type: "number" } }, + storage: { + type: "numeric", + icon: "images/resources/disk.svg", + element: "input", + unitText: "B", + attributes: { + type: "number" + } + }, pci: { + type: "list", element: "interactive-list", align: "start" } @@ -91,10 +108,15 @@ class InteractiveList extends HTMLElement { } get value () { - + const ret = []; + for (const elem of this.container.childNodes) { + ret.push(elem.value); + } + return ret; } set value (value) { + this.container.innerHTML = ""; for (const item of value) { this.#addItem(item); } @@ -151,29 +173,57 @@ class InteractiveListMatchItem extends HTMLElement { + -
-

-

match=""

-

max=

- +
+

+

+

+
+ + +
`; this.#nameElem = this.shadowRoot.querySelector("#name"); this.#matchElem = this.shadowRoot.querySelector("#match"); this.#maxElem = this.shadowRoot.querySelector("#max"); + this.configBtn = this.shadowRoot.querySelector("#config-btn"); + this.configBtn.onclick = this.#handleConfig.bind(this); + setSVGSrc(this.configBtn, "images/common/config.svg"); + setSVGAlt(this.configBtn, "Config Item"); + this.deleteBtn = this.shadowRoot.querySelector("#delete-btn"); this.deleteBtn.onclick = this.#handleDelete.bind(this); setSVGSrc(this.deleteBtn, "images/actions/delete-active.svg"); @@ -182,8 +232,8 @@ class InteractiveListMatchItem extends HTMLElement { #update () { this.#nameElem.innerText = this.#name; - this.#matchElem.innerText = this.#match; - this.#maxElem.innerText = this.#max; + this.#matchElem.innerText = `match="${this.#match}"`; + this.#maxElem.innerText = `max=${this.#max}`; } get name () { @@ -213,6 +263,51 @@ class InteractiveListMatchItem extends HTMLElement { this.#update(); } + get value () { + return { + name: this.#name, + match: this.#match, + max: this.#max + }; + } + + set value (value) { + this.#name = value.name; + this.#match = value.match; + this.#max = value.max; + this.#update(); + } + + #handleConfig () { + const header = `Edit ${this.#name} Rule`; + + const body = ` +
+ + + + + + +
+ `; + + const d = dialog(header, body, async (result, form) => { + if (result === "confirm") { + const newItem = { + name: form.get("name"), + match: form.get("match"), + max: form.get("max") + }; + this.value = newItem; + } + }); + + d.querySelector("#name").value = this.#name; + d.querySelector("#match").value = this.#match; + d.querySelector("#max").value = this.#max; + } + #handleDelete () { const header = `Delete ${this.name}`; const body = `

Are you sure you want to delete ${this.name}

`; @@ -269,7 +364,7 @@ async function populateGroups () { for (const groupName of Object.keys(allGroups)) { const group = allGroups[groupName]; const item = document.createElement("draggable-item"); - item.data = group; + item.value = group; item.innerHTML = `
drag icon @@ -291,30 +386,48 @@ async function populateResources () { const field = document.querySelector("#resources"); for (const resourceName of Object.keys(userData.resources)) { const resource = userData.resources[resourceName]; - if (resourcesConfigPage[resourceName]) { - const resourceConfig = resourcesConfigPage[resourceName]; - let resourceLine; + let resourceLine; + let resourceConfig; - if (resourceName === "cpu" || resourceName === "pci") { - resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.global }, "(Global)"); + if (resourcesConfigPage[resourceName]) { + resourceConfig = resourcesConfigPage[resourceName]; + resourceConfig.id = `${resourceName}-global`; + + if (resourceConfig.type === "list") { + resourceLine = addResourceLine(resourceConfig, field, { value: resource.global }, "(Global)"); } else { - resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.global.max }, "(Global)"); + resourceLine = addResourceLine(resourceConfig, field, { value: resource.global.max }, "(Global)"); } postPopulateResourceLine(field, resourceName, "global", resourceConfig, resourceLine); for (const nodeSpecificName of Object.keys(resource.nodes)) { // for each node specific, add a line with the node name as a prefix - if (resourceName === "cpu" || resourceName === "pci") { - resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.nodes[nodeSpecificName] }, `(${nodeSpecificName})`); + resourceConfig.id = `${resourceName}-${nodeSpecificName}`; + if (resourceConfig.type === "list") { + resourceLine = addResourceLine(resourceConfig, field, { value: resource.nodes[nodeSpecificName] }, `(${nodeSpecificName})`); } else { - resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.nodes[nodeSpecificName].max }, `(${nodeSpecificName})`); + resourceLine = addResourceLine(resourceConfig, field, { value: resource.nodes[nodeSpecificName].max }, `(${nodeSpecificName})`); } postPopulateResourceLine(field, resourceName, nodeSpecificName, resourceConfig, resourceLine); } } + else { + resourceConfig = resourcesConfigPage.storage; + resourceConfig.id = `${resourceName}-global`; + resourceConfig.name = resourceName; + resourceLine = addResourceLine(resourceConfig, field, { value: resource.global.max }, "(Global)"); + + postPopulateResourceLine(field, resourceName, "global", resourceConfig, resourceLine); + + for (const nodeSpecificName of Object.keys(resource.nodes)) { // for each node specific, add a line with the node name as a prefix + resourceConfig.id = `${resourceName}-${nodeSpecificName}`; + resourceLine = addResourceLine(resourceConfig, field, { value: resource.nodes[nodeSpecificName].max }, `(${nodeSpecificName})`); + postPopulateResourceLine(field, resourceName, nodeSpecificName, resourceConfig, resourceLine); + } + } } document.querySelector("#resource-add").addEventListener("click", handleResourceAdd); } @@ -338,6 +451,9 @@ function postPopulateResourceLine (field, resourceName, resourceScope, resourceC resourceLine.resourceName = resourceName; resourceLine.resourceScope = resourceScope; + resourceLine.resourceType = resourceConfig.type; + + resourceRulesLines[resourceLine.element.id] = resourceLine; } async function handleResourceAdd () { @@ -351,7 +467,7 @@ async function handleResourceAdd () { - `; + `; const d = dialog(header, body, async (result, form) => { if (result === "confirm") { @@ -359,8 +475,6 @@ async function handleResourceAdd () { const type = clusterResourceConfig[name].type; const scope = form.get("scope"); - console.log(name, type, scope); - // check if the resource name is not in the cluster config resources if (!clusterResourceConfig[name]) { alert(`${name} is not an allowed resource name`); @@ -384,23 +498,24 @@ async function handleResourceAdd () { } const field = document.querySelector("#resources"); + const resourceConfig = resourcesConfigPage[name]; let resourceLine; if (scope === "global" && type === "numeric") { userData.resources[name].global = { max: 0 }; - resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].global.max }, "(Global)"); + resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].global.max }, "(Global)"); } else if (scope === "global" && type === "list") { userData.resources[name].global = []; - resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].global }, "(Global)"); + resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].global }, "(Global)"); } else if (scope !== "global" && type === "numeric") { userData.resources[name].nodes[scope] = { max: 0 }; - resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].nodes[scope].max }, `(${scope})`); + resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].nodes[scope].max }, `(${scope})`); } else if (scope !== "global" && type === "list") { userData.resources[name].nodes[scope] = []; - resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].nodes[scope] }, `(${scope})`); + resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].nodes[scope] }, `(${scope})`); } postPopulateResourceLine(field, name, scope, resourcesConfigPage[name], resourceLine); @@ -437,6 +552,8 @@ async function handleResourceDelete () { else { userData.resources[this.resourceName].nodes[this.resourceScope] = false; } + + delete resourceRulesLines[this.element.id]; } }); } @@ -449,7 +566,7 @@ async function populateCluster () { for (const node of allNodes) { // for each node of all cluster nodes const item = document.createElement("draggable-item"); - item.data = node; + item.value = node; item.innerHTML = `
drag icon @@ -466,7 +583,7 @@ async function populateCluster () { for (const pool of allPools) { // for each pool of all cluster pools const item = document.createElement("draggable-item"); - item.data = pool; + item.value = pool; item.innerHTML = `
drag icon @@ -492,5 +609,73 @@ async function populateCluster () { } async function handleFormExit () { - // TODO + const body = { + attributes: { + memberOf: [] + }, + resources: {}, + cluster: { + admin: document.querySelector("#admin").checked, + nodes: {}, + pools: {}, + vmid: { + min: document.querySelector("#vmid-min").value, + max: document.querySelector("#vmid-max").value + } + } + }; + + for (const group of document.querySelector("#groups-enabled").value) { + body.attributes.memberOf.push(group.dn); + } + + // populate resources + for (const key of Object.keys(resourceRulesLines)) { + const resourceLine = resourceRulesLines[key]; + // if type is numeric + if (resourceLine.resourceType === "numeric") { + if (body.resources[resourceLine.resourceName] === undefined) { + body.resources[resourceLine.resourceName] = { + global: { + max: 0 + }, + nodes: {} + }; + } + if (resourceLine.resourceScope === "global") { + body.resources[resourceLine.resourceName].global.max = resourceLine.element.value; + } + else { + body.resources[resourceLine.resourceName].nodes[resourceLine.resourceScope].max = resourceLine.element.value; + } + } + else { + if (body.resources[resourceLine.resourceName] === undefined) { + body.resources[resourceLine.resourceName] = { + global: [], + nodes: {} + }; + } + if (resourceLine.resourceScope === "global") { + body.resources[resourceLine.resourceName].global = resourceLine.element.value; + } + else { + body.resources[resourceLine.resourceName].nodes[resourceLine.resourceScope] = resourceLine.element.value; + } + } + } + + // populate nodes + for (const node of document.querySelector("#nodes-enabled").value) { + body.cluster.nodes[node] = true; + } + + // populate pools + for (const pool of document.querySelector("#pools-enabled").value) { + body.cluster.pools[pool] = true; + } + + // TODO post to api + + console.log(body); } diff --git a/scripts/utils.js b/scripts/utils.js index 61f46ee..9d5ac28 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -427,8 +427,7 @@ export function isEmpty (obj) { } } -export function addResourceLine (config, field, resourceType, attributesOverride, labelPrefix = null) { - const resourceConfig = config[resourceType]; +export function addResourceLine (resourceConfig, field, attributesOverride, labelPrefix = null) { const iconHref = resourceConfig.icon; const elementType = resourceConfig.element; const labelText = labelPrefix ? `${labelPrefix} ${resourceConfig.name}` : resourceConfig.name; diff --git a/user.html b/user.html index e263201..63c58fb 100644 --- a/user.html +++ b/user.html @@ -68,7 +68,7 @@ Cluster
- +