From 68d0631e33fea220cba9d9930cdb69f8426d6a08 Mon Sep 17 00:00:00 2001 From: catborise Date: Sat, 31 Oct 2026 15:53:54 +0300 Subject: [PATCH] feat(instances): a VM's disks as rows of one list The Disks tab gave each disk a card: its capacity in large type, its pool, its path and its buttons stacked, two cards to a row, so a VM with a few disks filled the screen. Each disk is now a row of one list: the device with its bus and format, the capacity and the space used with a thin bar, the pool and the path, and Edit, Move, Detach and Delete at the row's end; the device details still fold out below it. A qcow2 file whose metadata takes it past its capacity shows a full bar. --- .../templates/instances/settings_tab.html | 68 ++++++++++--------- instances/tests/test_pending_edits.py | 14 ++-- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index 093af3d9..b9ee4c64 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -145,7 +145,7 @@
- {# its CD-ROM drives, then its disks as cards; the forms are those of the actions #} + {# its CD-ROM drives, then its disks as list rows; the forms are those of the actions #} {% if instance.status != 5 %}

{% bs_icon 'lock' %} {% trans "The VM is running: CD-ROM drives added now use the SCSI bus. Shut it down to detach other CD-ROM drives, or to detach or delete disks." %}

{% endif %} @@ -203,41 +203,34 @@

{% trans "Disks" %} · {{ instance.config_disks|length }}

{% include 'add_instance_volume.html' %}
-
+
+
{% for disk in instance.config_disks %} -
-
-
-
-

{% bs_icon 'device-hdd' %} {{ disk.dev }}

- {{ disk.bus }} · {{ disk.format }} -
+
+
+
+

{% bs_icon 'device-hdd' %} {{ disk.dev }}

+
{{ disk.bus }} · {{ disk.format }}
+
+
{% if disk.storage is None %}
{% trans "Error getting disk info" %}
{% else %} -
{% trans "Capacity" %}
-

{{ disk.size|filesizeformat }}

-

{% blocktrans with used=disk.used|filesizeformat %}{{ used }} used{% endblocktrans %}

-
-
{% trans "Storage pool" %} {{ disk.storage }}
-
{% trans "Source" %}
-

{{ disk.path }}

+ {% widthratio disk.used disk.size 100 as used_percent %} + {# a qcow2 file's metadata can take it past its capacity: the bar stops full #} + {% if used_percent|add:0 > 100 %}{% widthratio 100 1 1 as used_percent %}{% endif %} +
+ {% trans "Capacity" %} {{ disk.size|filesizeformat }} + {% blocktrans with used=disk.used|filesizeformat %}{{ used }} used{% endblocktrans %}
+
+
+
+
{% trans "Storage pool" %} {{ disk.storage }}
+
{% trans "Source" %}: {{ disk.path }}
{% endif %} -
- {% trans "Device details" %} -
-
{% trans "Cache" %}
{{ disk.cache }}
-
{% trans "IO mode" %}
{{ disk.io }}
-
{% trans "Discard" %}
{{ disk.discard }}
-
{% trans "Detect zeroes" %}
{{ disk.detect_zeroes }}
-
{% trans "Serial" %}
{{ disk.serial|default:"—" }}
-
{% trans "Read-only" %}
{{ disk.readonly|yesno }}
-
{% trans "Shareable" %}
{{ disk.shareable|yesno }}
-
-
-
-
+
+
+ {% trans "Device details" %} +
+
{% trans "Cache" %}
{{ disk.cache }}
+
{% trans "IO mode" %}
{{ disk.io }}
+
{% trans "Discard" %}
{{ disk.discard }}
+
{% trans "Detect zeroes" %}
{{ disk.detect_zeroes }}
+
{% trans "Serial" %}
{{ disk.serial|default:"—" }}
+
{% trans "Read-only" %}
{{ disk.readonly|yesno }}
+
{% trans "Shareable" %}
{{ disk.shareable|yesno }}
+
+
+ {% endfor %}
+
diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py index 36f29b29..fcf16c25 100644 --- a/instances/tests/test_pending_edits.py +++ b/instances/tests/test_pending_edits.py @@ -90,13 +90,13 @@ class PendingEditsTestCase(TestCase): doc = self.page(nics=(nics, nics)) self.assertEqual(doc.xpath("//div[@id='network']//div[article]/@class"), wide) - def test_one_disk_card_spans_the_row_and_more_sit_side_by_side(self): - for disks, wide in (([disk("vda", "default", "")], ["col-12"]), - ([disk("vda", "default", ""), disk("vdb", "default", "")], ["col-12 col-md-6"] * 2)): - with self.subTest(count=len(disks)): - doc = self.page(live=disks, config=disks) - self.assertEqual(doc.xpath("//div[@id='disks']//div[article[starts-with(@aria-labelledby, 'disk-')]]/@class"), - wide) + def test_the_disks_are_rows_of_one_list(self): + disks = [disk("vda", "default", "") | {"used": 1 << 29}, disk("vdb", "default", "") | {"used": (1 << 30) + (1 << 20)}] + doc = self.page(live=disks, config=disks) + rows = doc.xpath("//div[@id='disks']//div[contains(@class, 'list-group')]/article[starts-with(@aria-labelledby, 'disk-')]") + self.assertEqual([row.get("aria-labelledby") for row in rows], ["disk-vda", "disk-vdb"]) + # the used share as a bar, full when a qcow2 file outgrows its capacity + self.assertEqual(doc.xpath("//div[@id='disks']//div[@role='progressbar']/@aria-valuenow"), ["50", "100"]) 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",