1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 20:26:48 +00:00

feat(instances): the Boot tab shows the boot order as the chain the VM tries

A checkbox marked a device as booting and also chose what Up and Down
moved, which moved every checked device at once: to move one device the
others had to be unchecked and checked again. The buttons sat apart
from the rows, and a VM booting by device type got a note that did not
say what to do.

Now the devices the VM tries are a numbered chain, each with its own
up, down and Remove, and the others are listed below it with Add; the
posted order is unchanged. Devices read by kind first (CD-ROM sda,
Disk vda) with their file or network below; a VM booting by device
type says so and how to choose an order. Autostart and the boot menu
sit below, lighter. Focus stays on the device that moved, and on a
narrow screen a device's buttons wrap below its name.

The Turkish catalog says "Ön yükleme" and "sanal makine" throughout,
also in the Power tab texts.
This commit is contained in:
catborise 2026-11-03 19:41:19 +03:00
parent c247a7a3f0
commit 4565221c5c
12 changed files with 740 additions and 688 deletions

View file

@ -120,7 +120,6 @@ th[aria-sort="ascending"]:not([data-sort-method="none"])::after {
padding-right: var(--bs-breadcrumb-item-padding-x);
}
/* the boot order editor numbers its checked devices, top to bottom */
/* the power tab's states: the current one filled, the others a ring */
.power-dot {
width: .8rem;
@ -131,26 +130,39 @@ th[aria-sort="ascending"]:not([data-sort-method="none"])::after {
}
.power-dot.bg-success, .power-dot.bg-warning, .power-dot.bg-secondary { border-color: transparent; }
.boot-sequence {
counter-reset: boot;
/* the boot order: the devices the VM tries in turn, numbered and joined by a line */
.boot-chain { counter-reset: boot; }
.boot-node { position: relative; padding: .5rem 0; }
.boot-chain > .boot-node { counter-increment: boot; }
.boot-step {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 1.75rem;
height: 1.75rem;
border-radius: 50%;
border: 2px solid var(--bs-primary);
color: var(--bs-primary);
background: var(--bs-body-bg);
font-size: .8rem;
font-weight: 600;
position: relative;
z-index: 1;
}
.boot-sequence > label:has(> input:checked) {
counter-increment: boot;
}
.boot-rank {
width: 1rem;
text-align: center;
}
.boot-rank::before {
content: "—";
}
.boot-sequence > label:has(> input:checked) .boot-rank::before {
content: counter(boot);
.boot-chain .boot-step::before { content: counter(boot); }
.boot-chain > .boot-node:not(:last-child)::after {
content: "";
position: absolute;
left: calc(.875rem - 1px);
top: calc(50% + .875rem);
height: calc(100% - 1.75rem);
border-left: 2px solid var(--bs-border-color);
}
.boot-spare .boot-step { visibility: hidden; }
/* on a narrow screen a device's buttons go below its name, not over it */
.boot-text { min-width: 10rem; flex-basis: 10rem; }
.boot-chain .boot-spare-only, .boot-spare .boot-chain-only { display: none !important; }
/* a log message keeps a readable width when its table scrolls on a phone */
.log-message {

View file

@ -154,35 +154,37 @@
// every NIC of the clone a new address: one the VM has would clash with it
document.querySelectorAll("[data-new-mac]").forEach(function (input) { random_mac(input.name); });
// the boot order: the checked devices, top to bottom, as "disk:vda,cdrom:sda"
function set_orderlist() {
var order = $('#b_order input:checked').map(function () { return this.value; }).get();
$('#bootorder').val(order.join(','));
// the boot order: the devices of #boot-chain in turn, as "disk:vda,cdrom:sda"; each moves on its own
var chain = document.getElementById("boot-chain");
if (chain) {
var spare = document.getElementById("boot-spare");
var refresh = function () {
var nodes = Array.from(chain.children);
document.getElementById("bootorder").value = nodes.map(function (node) { return node.dataset.bootValue; }).join(",");
document.getElementById("boot-chain-empty").hidden = nodes.length > 0;
nodes.forEach(function (node, i) {
node.querySelector('[data-boot="up"]').disabled = i === 0;
node.querySelector('[data-boot="down"]').disabled = i === nodes.length - 1;
});
};
document.getElementById("boot_opt").addEventListener("click", function (event) {
var button = event.target.closest("[data-boot]");
if (!button) return;
var node = button.closest(".boot-node");
var action = button.dataset.boot;
if (action === "up") chain.insertBefore(node, node.previousElementSibling);
if (action === "down") chain.insertBefore(node.nextElementSibling, node);
if (action === "remove") spare.prepend(node);
if (action === "add") chain.append(node);
refresh();
// the keyboard stays on the device that moved
var same = { up: "up", down: "down", remove: "add", add: "remove" }[action];
var target = node.querySelector('[data-boot="' + same + '"]');
if (target.disabled) target = node.querySelector('.boot-chain-only [data-boot]:not([disabled])');
target.focus();
});
refresh();
}
set_orderlist();
$('#b_order').on('change', 'input', set_orderlist);
$('#boot_order_up').on('click', function () {
$('#b_order label input:checked').each(function () {
var label = $(this).parent();
var newPos = label.index() - 1;
if (newPos > -1) {
$('#b_order label').eq(newPos).before(label);
}
});
set_orderlist();
});
$('#boot_order_down').on('click', function () {
var countOptions = $('#b_order label').length;
var countSelected = $('#b_order label input:checked').length;
$('#b_order label input:checked').each(function () {
var label = $(this).parent();
var newPos = label.index() + countSelected;
if (newPos < countOptions) {
$('#b_order label').eq(newPos).after(label);
}
});
set_orderlist();
});
// the Host devices tab's add forms: loaded once, when the tab first opens
var hostDevices = document.getElementById("host-devices");