1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

feat(instances): the VM page's add dialogs in the panel's dialog look

The Add disk, Add network device and bandwidth dialogs of the VM page
set their labels in a narrow column beside the fields and closed with
a grey Close: Add disk split "GB" over two lines and offered an
unexplained Metadata box, its existing-volume pool was a dropdown of
links that wrote a hidden field; the network list prefixed every
choice with "Network" or "Interface"; the bandwidth fields asked for
"kilobytes" where libvirt counts KiB/s and KiB.

Now their labels sit above the fields, tied to them, in two columns.
Add disk is one form whose source, New disk or Existing volume, is a
radio card as in the new VM form, the chosen source's fields shown and
sent and its action taken; the size has a GiB suffix and
"Preallocate metadata (qcow2)", the existing volume's pool is a select
that loads its volumes, the bus and the cache are shared. Add network
device picks its
source from Networks and Interfaces groups and says how its MAC is
filled; the bandwidth limit names its units and when a running VM gets
it. Move says when the host has no other pool and is disabled then.
Every dialog closes with Cancel and its action; the posted fields are
unchanged.
This commit is contained in:
catborise 2026-10-31 17:44:20 +03:00
parent fd080438c3
commit 0fd3e69c8e
8 changed files with 220 additions and 248 deletions

View file

@ -122,6 +122,38 @@ class PendingEditsTestCase(TestCase):
(switch,) = doc.xpath("//div[@id='editvol0']//input[@name=$n]", n=name)
self.assertEqual(doc.xpath("//div[@id='editvol0']//label[@for=$i]", i=switch.get("id"))[0].tag, "label")
def test_the_add_dialogs_label_their_fields_and_units(self):
disks = [disk("vda", "default", "")]
nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
"ipv4": None, "ipv6": None, "filterref": "", "target": "", "inbound": [], "outbound": []}
doc = self.page(live=disks, config=disks, nics=([nic], [nic]), get_networks=(["default"],) * 2,
get_ifaces=(["br0"],) * 2, get_storages=(["default"],) * 2)
# every field of the Add disk, Add network device and bandwidth dialogs has its label
for dialog in ("addvol", "addInstanceNetwork", "AddQos0"):
# a radio card's label wraps its radio
fields = doc.xpath("//div[@id=$d]//*[self::input or self::select][not(@type='hidden' or @type='radio')]",
d=dialog)
self.assertTrue(fields, dialog)
for field in fields:
self.assertTrue(doc.xpath("//label[@for=$i]", i=field.get("id") or "-"), (dialog, field.get("name")))
self.assertEqual(doc.xpath("//div[@id='addvol']//span[@class='input-group-text']/text()"), ["GiB"])
# Add disk: the source is a radio card, each source's fields a fieldset; the script sets the form's action
self.assertEqual(doc.xpath("//div[@id='addvol']//input[@name='disk_source']/@value"), ["new", "existing"])
self.assertEqual(doc.xpath("//div[@id='addvol']//fieldset/@data-kinds"), ["new", "existing"])
(form,) = doc.xpath("//form[@id='addvol-form']")
self.assertEqual((form.get("data-new"), form.get("data-existing")),
(reverse("instances:add_new_vol", args=[self.instance.id]),
reverse("instances:add_existing_vol", args=[self.instance.id])))
self.assertEqual(doc.xpath("//select[@name='add-net-network']//option/@value"), ["net:default", "iface:br0"])
self.assertEqual(doc.xpath("//div[@id='AddQos0']//label/text()"),
["Direction", "Average (KiB/s)", "Peak (KiB/s)", "Burst (KiB)"])
# Move needs a pool other than the disk's own
for pools, disabled in (([], ["disabled"]), (["default"], ["disabled"]), (["other"], []),
(["default", "other"], [])):
with self.subTest(pools=pools):
doc = self.page(live=disks, config=disks, get_storages=(pools,) * 2)
self.assertEqual(doc.xpath("//div[@id='movedisk0']//button[@type='submit']/@disabled"), disabled)
def test_the_clone_form_lists_the_persistent_devices(self):
nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
"ipv4": None, "ipv6": None, "filterref": "", "target": "", "inbound": [], "outbound": []}

View file

@ -136,7 +136,6 @@ HTML_SINK = re.compile(r"""\.html\(|innerHTML\s*=|append\(\s*['"]<""")
ALLOWED_HTML_SINKS = {
("accounts/templates/login.html", "$btn.html("): "static spinner markup",
("accounts/templates/accounts/otp_login.html", "$btn.html("): "static spinner markup",
("instances/templates/instance.html", "//sto_input.innerHTML = pool;"): "comment",
("instances/templates/instance.html", "hostDevices.innerHTML = html;"): "the panel's own fragment, host_devices_form.html, escaped by Django",
("instances/templates/instance.html", "migratePlan.innerHTML = html;"): "the panel's own fragment, migrate_plan.html, escaped by Django",
}