Username: {{.account.Username}}
-Pools: {{MapKeys .account.Pools ", "}}
-VMID Range: {{.account.VMID.Min}} - {{.account.VMID.Max}}
-Nodes: {{MapKeys .account.Nodes ", "}}
Notice: There is a known regression in login time. Please be patient.
diff --git a/web/scripts/backups.js b/web/scripts/backups.js index 1fc805b..0d71c07 100644 --- a/web/scripts/backups.js +++ b/web/scripts/backups.js @@ -83,7 +83,7 @@ class BackupCard extends HTMLElement { async handleDeleteButton () { const template = this.shadowRoot.querySelector("#delete-dialog"); - dialog(template, async (result, form) => { + dialog(template, async (result, _form) => { if (result === "confirm") { const body = { volid: this.volid @@ -99,7 +99,7 @@ class BackupCard extends HTMLElement { async handleRestoreButton () { const template = this.shadowRoot.querySelector("#restore-dialog"); - dialog(template, async (result, form) => { + dialog(template, async (result, _form) => { if (result === "confirm") { const body = { volid: this.volid diff --git a/web/scripts/clientsync.js b/web/scripts/clientsync.js index 58d39a4..5e10409 100644 --- a/web/scripts/clientsync.js +++ b/web/scripts/clientsync.js @@ -19,7 +19,7 @@ export async function setupClientSync (callback) { } else if (scheme === "interrupt") { const socket = new WebSocket(`wss://${window.API.replace("https://", "")}/sync/interrupt`); - socket.addEventListener("open", (event) => { + socket.addEventListener("open", (_event) => { socket.send(`rate ${rate}`); }); socket.addEventListener("message", (event) => { diff --git a/web/scripts/config.js b/web/scripts/config.js index 53be948..50e8a13 100644 --- a/web/scripts/config.js +++ b/web/scripts/config.js @@ -54,7 +54,7 @@ class VolumeAction extends HTMLElement { async handleDiskDetach () { const disk = this.dataset.volume; - dialog(this.template, async (result, form) => { + dialog(this.template, async (result, _form) => { if (result === "confirm") { this.setStatusLoading(); const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/detach`, "POST"); @@ -136,7 +136,7 @@ class VolumeAction extends HTMLElement { async handleDiskDelete () { const disk = this.dataset.volume; - dialog(this.template, async (result, form) => { + dialog(this.template, async (result, _form) => { if (result === "confirm") { this.setStatusLoading(); const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/delete`, "DELETE"); @@ -224,7 +224,7 @@ async function handleCDAdd () { const isos = await requestAPI("/user/vm-isos", "GET"); const select = d.querySelector("#iso-select"); - for (const iso of isos) { + for (const iso of isos.data) { select.add(new Option(iso.name, iso.volid)); } select.selectedIndex = -1; @@ -275,7 +275,7 @@ class NetworkAction extends HTMLElement { async handleNetworkDelete () { const netID = this.dataset.network; - dialog(this.template, async (result, form) => { + dialog(this.template, async (result, _form) => { if (result === "confirm") { setIconSrc(document.querySelector(`svg[data-network="${netID}"]`), "images/status/loading.svg"); const net = `${netID}`; @@ -375,7 +375,7 @@ class DeviceAction extends HTMLElement { const availDevices = await requestAPI(`/cluster/${node}/pci`, "GET"); d.querySelector("#device").append(new Option(deviceName, deviceDetails.split(",")[0])); - for (const availDevice of availDevices) { + for (const availDevice of availDevices.data) { d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.device_bus)); } d.querySelector("#pcie").checked = deviceDetails.includes("pcie=1"); @@ -383,7 +383,7 @@ class DeviceAction extends HTMLElement { async handleDeviceDelete () { const deviceID = this.dataset.device; - dialog(this.template, async (result, form) => { + dialog(this.template, async (result, _form) => { if (result === "confirm") { this.setStatusLoading(); const device = `${deviceID}`; @@ -437,8 +437,8 @@ async function handleDeviceAdd () { } }); - const availDevices = await requestAPI(`/cluster/${node}/pci`, "GET"); - for (const availDevice of availDevices) { + const availDevices = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci`, "GET"); + for (const availDevice of availDevices.data) { d.querySelector("#device").append(new Option(availDevice.device_name, availDevice.device_bus)); } d.querySelector("#pcie").checked = true; diff --git a/web/scripts/dialog.js b/web/scripts/dialog.js index 24660c5..5d3cc60 100644 --- a/web/scripts/dialog.js +++ b/web/scripts/dialog.js @@ -17,7 +17,7 @@ * body contains an optional form or other information, * and controls contains a series of buttons which controls the form */ -export function dialog (template, onclose = async (result, form) => { }) { +export function dialog (template, onclose = async (_result, _form) => { }) { const dialog = template.content.querySelector("dialog").cloneNode(true); document.body.append(dialog); dialog.addEventListener("close", async () => { diff --git a/web/scripts/draggable.js b/web/scripts/draggable.js index bf21541..b0fcf10 100644 --- a/web/scripts/draggable.js +++ b/web/scripts/draggable.js @@ -13,7 +13,7 @@ class DraggableContainer extends HTMLElement { window.Sortable.create(this.content, { group: this.dataset.group, ghostClass: "ghost", - setData: function (dataTransfer, dragEl) { + setData: function (dataTransfer, _dragEl) { dataTransfer.setDragImage(blank, 0, 0); } }); diff --git a/web/scripts/index.js b/web/scripts/index.js index 0de2e33..6b395a8 100644 --- a/web/scripts/index.js +++ b/web/scripts/index.js @@ -159,7 +159,7 @@ class InstanceCard extends HTMLElement { async handlePowerButton () { if (!this.actionLock) { const template = this.shadowRoot.querySelector("#power-dialog"); - dialog(template, async (result, form) => { + dialog(template, async (result, _form) => { if (result === "confirm") { this.actionLock = true; const targetAction = this.status === "running" ? "stop" : "start"; @@ -193,7 +193,7 @@ class InstanceCard extends HTMLElement { handleDeleteButton () { if (!this.actionLock && this.status === "stopped") { const template = this.shadowRoot.querySelector("#delete-dialog"); - dialog(template, async (result, form) => { + dialog(template, async (result, _form) => { if (result === "confirm") { this.actionLock = true; @@ -247,7 +247,7 @@ function sortInstances () { const searchQuery = document.querySelector("#search").value || null; let criteria; if (!searchQuery) { - criteria = (item, query = null) => { + criteria = (item, _query = null) => { return { score: item.vmid, alignment: null }; }; } @@ -342,11 +342,11 @@ async function handleInstanceAddButton () { } } }); - - const templates = await requestAPI("/user/ct-templates", "GET"); - + + // setup type select const typeSelect = d.querySelector("#type"); typeSelect.selectedIndex = -1; + // on type change, reveal or hide the container specific section typeSelect.addEventListener("change", () => { if (typeSelect.value === "qemu") { d.querySelectorAll(".container-specific").forEach((element) => { @@ -366,66 +366,62 @@ async function handleInstanceAddButton () { element.disabled = true; }); - const rootfsContent = "rootdir"; - const rootfsStorage = d.querySelector("#rootfs-storage"); - rootfsStorage.selectedIndex = -1; - - const userResources = await requestAPI("/user/dynamic/resources", "GET"); - const userCluster = await requestAPI("/user/config/cluster", "GET"); - - const nodeSelect = d.querySelector("#node"); - nodeSelect.innerHTML = ""; - const clusterNodes = await requestPVE("/nodes", "GET"); - const allowedNodes = Object.keys(userCluster.nodes); - clusterNodes.data.forEach((element) => { - if (element.status === "online" && allowedNodes.includes(element.node)) { - nodeSelect.add(new Option(element.node)); - } + // setup pool select + const poolSelect = d.querySelector("#pool"); + poolSelect.innerHTML = ""; + // add user pools to selector + const userPools = Object.keys((await requestAPI("/access/pools", "GET")).data.pools); + userPools.forEach((element) => { + poolSelect.add(new Option(element)); }); + poolSelect.selectedIndex = -1; + // on pool change, get the allowed nodes for that pool, then repopulate the node selector + poolSelect.addEventListener("change", async () => { + const pool = (await requestAPI(`/access/pools/${poolSelect.value}`, "GET")).data.pool; + + const nodeSelect = d.querySelector("#node"); + nodeSelect.innerHTML = ""; + const clusterNodes = (await requestPVE("/nodes", "GET")).data; + const allowedNodes = Object.keys(pool["nodes-allowed"]); + clusterNodes.forEach((element) => { + if (element.status === "online" && allowedNodes.includes(element.node)) { + nodeSelect.add(new Option(element.node)); + } + }); + nodeSelect.selectedIndex = -1; + + // set vmid min/max + d.querySelector("#vmid").min = pool["vmid-allowed"].min; + d.querySelector("#vmid").max = pool["vmid-allowed"].max; + }); + + // setup node select + const nodeSelect = d.querySelector("#node"); nodeSelect.selectedIndex = -1; + // on node change, get the available storages and repopulate the storage selector nodeSelect.addEventListener("change", async () => { // change rootfs storage based on node const node = nodeSelect.value; - const storage = await requestPVE(`/nodes/${node}/storage`, "GET"); + const storage = (await requestPVE(`/nodes/${node}/storage`, "GET")).data; rootfsStorage.innerHTML = ""; - storage.data.forEach((element) => { + storage.forEach((element) => { if (element.content.includes(rootfsContent)) { rootfsStorage.add(new Option(element.storage)); } }); rootfsStorage.selectedIndex = -1; - - // set core and memory min/max depending on node selected - if (node in userResources.cores.nodes) { - d.querySelector("#cores").max = userResources.cores.nodes[node].avail; - } - else { - d.querySelector("#cores").max = userResources.cores.global.avail; - } - - if (node in userResources.memory.nodes) { - d.querySelector("#memory").max = userResources.memory.nodes[node].avail; - } - else { - d.querySelector("#memory").max = userResources.memory.global.avail; - } }); - // set vmid min/max - d.querySelector("#vmid").min = userCluster.vmid.min; - d.querySelector("#vmid").max = userCluster.vmid.max; - - // add user pools to selector - const poolSelect = d.querySelector("#pool"); - poolSelect.innerHTML = ""; - const userPools = Object.keys(userCluster.pools); - userPools.forEach((element) => { - poolSelect.add(new Option(element)); - }); - poolSelect.selectedIndex = -1; + // setup root dir select + const rootfsStorage = d.querySelector("#rootfs-storage"); + rootfsStorage.selectedIndex = -1; + // set rootfs content type (rootdir) + const rootfsContent = "rootdir"; + // setup templateImage depending on selected image storage + const templateImage = d.querySelector("#template-image"); // add template images to selector - const templateImage = d.querySelector("#template-image"); // populate templateImage depending on selected image storage - for (const template of templates) { + const templates = await requestAPI("/user/ct-templates", "GET"); + for (const template of templates.data) { templateImage.append(new Option(template.name, template.volid)); } templateImage.selectedIndex = -1; diff --git a/web/scripts/utils.js b/web/scripts/utils.js index 41b102c..5685e8d 100644 --- a/web/scripts/utils.js +++ b/web/scripts/utils.js @@ -80,33 +80,34 @@ async function request (url, content) { try { const response = await fetch(url, content); const contentType = response.headers.get("Content-Type"); - let data = null; + const res = {}; if (contentType === null) { - data = {}; + res.data = null; + res.status = response.status; } else if (contentType.includes("application/json")) { - data = await response.json(); - data.status = response.status; + res.data = await response.json(); + res.status = response.status; } else if (contentType.includes("text/html")) { - data = { data: await response.text() }; - data.status = response.status; + res.data = await response.text(); + res.status = response.status; } else if (contentType.includes("text/plain")) { - data = { data: await response.text() }; - data.status = response.status; + res.data = await response.text(); + res.status = response.status; } else { - data = {}; + res.data = null; + res.status = response.status; } - + if (!response.ok) { - return { status: response.status, error: data ? data.error : response.status }; + return { status: response.status, error: res.data ? res.data.error : response.status }; } else { - data.status = response.status; - return data || response; + return res; } } catch (error) { diff --git a/web/templates/config.go.tmpl b/web/templates/config.go.tmpl index 6c46289..df94b1a 100644 --- a/web/templates/config.go.tmpl +++ b/web/templates/config.go.tmpl @@ -448,7 +448,7 @@{{.Device_ID}}
{{.Device_Name}}
VMID Range: {{.AllowedVMIDRange.Min}} - {{.AllowedVMIDRange.Max}}
+Nodes: {{MapKeys .AllowedNodes ", "}}
+Max Backups Per Instance: {{.AllowedBackups.MaxPerInstance}} Max Backups Total: {{.AllowedBackups.MaxTotal}}
+