fix draggable list responsiveness,

add event handlers for adding/removing boot options
This commit is contained in:
Arthur Lu 2023-08-16 23:14:42 +00:00
parent 2b75732e92
commit 8c328dd22c
2 changed files with 136 additions and 129 deletions

View File

@ -12,6 +12,7 @@ let node;
let type;
let vmid;
let config;
const bootEntries = {};
async function init () {
setTitleAndHeader();
@ -230,21 +231,19 @@ function addDiskLine (fieldset, busPrefix, busName, device, diskDetails) {
}
async function handleDiskDetach () {
const header = `Detach ${this.dataset.disk}`;
const body = `<p>Are you sure you want to detach disk</p><p>${this.dataset.disk}</p>`;
const disk = this.dataset.disk;
const header = `Detach ${disk}`;
const body = `<p>Are you sure you want to detach disk</p><p>${disk}</p>`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${this.dataset.disk}/detach`, "POST");
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/detach`, "POST");
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
deleteBootLine(`boot-${disk}`);
}
});
}
@ -260,17 +259,15 @@ async function handleDiskAttach () {
const body = {
source: this.dataset.disk.replace("unused", "")
};
const disk = `${type === "qemu" ? "sata" : "mp"}${device}`;
const prefix = type === "qemu" ? "sata" : "mp";
const disk = `${prefix}${device}`;
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/attach`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
addBootLine("disabled", { id: disk, prefix, value: config.data[disk] });
}
});
}
@ -281,20 +278,19 @@ async function handleDiskResize () {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
const disk = this.dataset.disk;
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
const body = {
size: form.get("size-increment")
};
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${this.dataset.disk}/resize`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/resize`, "POST", body);
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
const prefix = bootMetaData.eligiblePrefixes.find((pref) => disk.startsWith(pref));
updateBootLine(`boot-${disk}`, { id: disk, prefix, value: config.data[disk] });
}
});
}
@ -320,42 +316,38 @@ async function handleDiskMove () {
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
const disk = this.dataset.disk;
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
const body = {
storage: form.get("storage-select"),
delete: form.get("delete-check") === "on" ? "1" : "0"
};
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${this.dataset.disk}/move`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/move`, "POST", body);
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
const prefix = bootMetaData.eligiblePrefixes.find((pref) => disk.startsWith(pref));
updateBootLine(`boot-${disk}`, { id: disk, prefix, value: config.data[disk] });
}
});
}
async function handleDiskDelete () {
const header = `Delete ${this.dataset.disk}`;
const body = `<p>Are you sure you want to <strong>delete</strong> disk</p><p>${this.dataset.disk}</p>`;
const disk = this.dataset.disk;
const header = `Delete ${disk}`;
const body = `<p>Are you sure you want to <strong>delete</strong> disk</p><p>${disk}</p>`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {
document.querySelector(`img[data-disk="${this.dataset.disk}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${this.dataset.disk}/delete`, "DELETE");
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
document.querySelector(`img[data-disk="${disk}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/delete`, "DELETE");
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
deleteBootLine(`boot-${disk}`);
}
});
}
@ -386,17 +378,16 @@ async function handleDiskAdd () {
storage: form.get("storage-select"),
size: form.get("size")
};
const disk = `${type === "qemu" ? "sata" : "mp"}${form.get("device")}`;
const id = form.get("device");
const prefix = type === "qemu" ? "sata" : "mp";
const disk = `${prefix}${id}`;
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
addBootLine("disabled", { id: disk, prefix, value: config.data[disk] });
}
});
}
@ -428,15 +419,12 @@ async function handleCDAdd () {
};
const disk = `ide${form.get("device")}`;
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDisk();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDisk();
}
addBootLine("disabled", { id: disk, prefix: "ide", value: config.data[disk] });
}
});
@ -530,15 +518,12 @@ async function handleNetworkConfig () {
rate: form.get("rate")
};
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/net${netID}/modify`, "POST", body);
if (result.status === 200) {
await getConfig();
populateNetworks();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateNetworks();
}
updateBootLine(`boot-net${netID}`, { id: `net${netID}`, prefix: "net", value: config.data[`net${netID}`] });
}
});
@ -554,15 +539,12 @@ async function handleNetworkDelete () {
if (result === "confirm") {
document.querySelector(`img[data-network="${netID}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/net${netID}/delete`, "DELETE");
if (result.status === 200) {
await getConfig();
populateNetworks();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateNetworks();
}
deleteBootLine(`boot-net${netID}`);
}
});
}
@ -584,15 +566,12 @@ async function handleNetworkAdd () {
}
const netID = form.get("netid");
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/net${netID}/create`, "POST", body);
if (result.status === 200) {
await getConfig();
populateNetworks();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateNetworks();
}
addBootLine("disabled", { id: `net${netID}`, prefix: "net", value: config.data[`net${netID}`] });
}
});
}
@ -679,16 +658,12 @@ async function handleDeviceConfig () {
pcie: form.get("pcie") ? 1 : 0
};
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/hostpci${deviceID}/modify`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDevices();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDevices();
}
}
});
const availDevices = await requestAPI(`/cluster/${node}/pci`, "GET");
@ -708,16 +683,12 @@ async function handleDeviceDelete () {
if (result === "confirm") {
document.querySelector(`img[data-device="${deviceID}"]`).src = "images/status/loading.svg";
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/hostpci${deviceID}/delete`, "DELETE");
if (result.status === 200) {
await getConfig();
populateDevices();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDevices();
}
}
});
}
@ -732,16 +703,12 @@ async function handleDeviceAdd () {
pcie: form.get("pcie") ? 1 : 0
};
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/create`, "POST", body);
if (result.status === 200) {
await getConfig();
populateDevices();
}
else {
if (result.status !== 200) {
alert(result.error);
}
await getConfig();
populateDevices();
}
}
});
const availDevices = await requestAPI(`/cluster/${node}/pci`, "GET");
@ -756,17 +723,23 @@ async function populateBoot () {
document.querySelector("#boot-card").classList.remove("none");
document.querySelector("#enabled").title = "Enabled";
document.querySelector("#disabled").title = "Disabled";
const order = config.data.boot.replace("order=", "").split(";");
let order = [];
if (config.data.boot.startsWith("order=")) {
order = config.data.boot.replace("order=", "").split(";");
}
const bootable = { disabled: [] };
const eligible = bootMetaData.eligiblePrefixes;
for (let i = 0; i < order.length; i++) {
const element = order[i];
const prefix = eligible.find((pref) => order[i].startsWith(pref));
bootable[i] = { id: order[i], prefix };
const value = config.data[element];
bootable[i] = { id: element, prefix, value };
}
Object.keys(config.data).forEach((element) => {
const prefix = eligible.find((pref) => element.startsWith(pref));
const value = config.data[element];
if (prefix && !order.includes(element)) {
bootable.disabled.push({ id: element, prefix });
bootable.disabled.push({ id: element, prefix, value });
}
});
Object.keys(bootable).sort();
@ -783,20 +756,42 @@ async function populateBoot () {
}
}
function addBootLine (fieldset, bootable, before = null) {
function addBootLine (container, data, before = null) {
const item = document.createElement("draggable-item");
item.bootable = bootable;
item.data = data;
item.innerHTML = `
<img src="${bootMetaData[bootable.prefix].icon}">
<p style="margin: 0px;">${bootable.id}</p>
<img src="${bootMetaData[data.prefix].icon}">
<p style="margin: 0px;">${data.id}</p>
<p style="margin: 0px; overflow-x: hidden; white-space: nowrap;">${data.value}</p>
`;
item.draggable = true;
item.classList.add("drop-target");
item.id = `boot-${data.id}`;
if (before) {
document.querySelector(`#${fieldset}`).insertBefore(item, before);
document.querySelector(`#${container}`).insertBefore(item, before);
}
else {
document.querySelector(`#${fieldset}`).append(item);
document.querySelector(`#${container}`).append(item);
}
item.container = container;
bootEntries[item.id] = item;
}
function deleteBootLine (id) {
bootEntries[id].parentElement.removeChild(bootEntries[id]);
}
function updateBootLine (id, newData) {
const element = bootEntries[id];
if (element) {
const container = element.container;
const before = element.nextSibling;
deleteBootLine(id);
addBootLine(container, newData, before);
return true;
}
else {
return false;
}
}

View File

@ -3,7 +3,6 @@ class DraggableContainer extends HTMLElement {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<link rel="stylesheet" href="css/style.css">
<label id="title"></label>
<div id="wrapper">
<draggable-item id="bottom" class="drop-target"></draggable-item>
@ -29,20 +28,40 @@ class DraggableContainer extends HTMLElement {
insertBefore (newNode, referenceNode) {
this.content.insertBefore(newNode, referenceNode);
}
deleteItemByID (nodeid) {
const node = this.content.querySelector(`#${nodeid}`);
if (node) {
this.content.removeChild(node);
return true;
}
else {
return false;
}
}
}
class DraggableItem extends HTMLElement {
constructor () {
super();
this.attachShadow({ mode: "open" });
// for whatever reason, only grid layout seems to respect the parent's content bounds
this.shadowRoot.innerHTML = `
<link rel="stylesheet" href="css/style.css">
<style>
#wrapper {
min-height: 1.5em;
grid-template-columns: auto 8ch 1fr;
display: grid;
column-gap: 10px;
align-items: center;
}
img {
height: 1em;
width: 1em;
}
</style>
<div id="wrapper" class="flex row"></div>
<div id="wrapper">
<div style="min-height: 1.5em;"></div>
</div>
`;
this.content = this.shadowRoot.querySelector("#wrapper");
// add drag and drop listeners
@ -83,13 +102,14 @@ class DraggableItem extends HTMLElement {
event.target.borderTop = false;
}
if (event.target.classList.contains("drop-target")) {
const data = event.dataTransfer.getData("application/json");
const data = JSON.parse(event.dataTransfer.getData("application/json"));
const content = event.dataTransfer.getData("text/html");
const item = document.createElement("draggable-item");
item.bootable = data;
item.data = data;
item.innerHTML = content;
item.draggable = true;
item.classList.add("drop-target");
item.id = `boot-${data.id}`;
event.target.parentElement.insertBefore(item, event.target);
}
this.content.attributeStyleMap.clear();
@ -105,14 +125,6 @@ class DraggableItem extends HTMLElement {
this.content.innerHTML = innerHTML;
}
get bootable () {
return this.data;
}
set bootable (bootable) {
this.data = bootable;
}
get borderTop () {
return this.content.style.borderTop === "";
}