From 6e147ed8ccf3783011d4becbfb67b31d473b680a Mon Sep 17 00:00:00 2001 From: catborise Date: Sat, 31 Oct 2026 16:25:29 +0300 Subject: [PATCH] feat(instances): a VM's network devices as rows of one list The Network tab gave each network device a card with its buttons in a footer, two cards to a row, as the disks had. Each device is now a row of the same kind of list as the disks: eth N with its target, the source, the IPv4 addresses and the link switch, and Edit, Add QoS and Delete at the row's end; the device details, the QoS limits and the macvtap note still fold out below it. A row holds its Edit and Add QoS dialogs, so only rows sit in the list. --- .../templates/instances/settings_tab.html | 86 ++++++++++--------- instances/tests/test_pending_edits.py | 13 +-- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index b9ee4c64..c267f7b3 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -277,51 +277,34 @@
- {# a card per NIC, the only one across the row; its QoS limits fold out like its device details #} + {# a row per NIC in one list, as the disks; its QoS limits fold out like its device details #}

{% trans "Network devices" %} · {{ instance.config_networks|length }}

{% include 'add_instance_network_block.html' %}
-
+
+
{% for network in instance.config_networks %} -
-
-
-
-

{% bs_icon 'ethernet' %} eth{{ forloop.counter0 }} · {{ network.target|default:_("no target") }}

-
{% csrf_token %} - - -
- - -
-
-
-

{% trans "Source" %} {{ network.nic|default:"—" }}

-

{% trans "IPv4" %} {{ network.ipv4|join:", "|default:_("unknown") }}

-
- {% trans "Device details" %} -
-
{% trans "Type" %}
{{ network.type }}
-
{% trans "MAC" %}
{{ network.mac }}
-
{% trans "Model" %}
{{ network.model|default:"—" }}
-
{% trans "NWFilter" %}
{{ network.filterref|default:_("None") }}
-
{% trans "IPv6" %}
{{ network.ipv6|join:", "|default:_("unknown") }}
-
-
-
- {% trans "QoS limits" %} · {% if network.inbound and network.outbound %}{% trans "inbound, outbound" %}{% elif network.inbound %}{% trans "inbound" %}{% elif network.outbound %}{% trans "outbound" %}{% else %}{% trans "none" %}{% endif %} - {% if network.inbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="inbound" limit=network.inbound %}{% endif %} - {% if network.outbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="outbound" limit=network.outbound %}{% endif %} -

{% trans "Average and peak are in KiB/s, burst in KiB. A running VM gets a change after it is powered off and on." %}

-
- {% if network.type == 'direct' %} -

{% bs_icon 'info-circle' %} {% trans "In most configurations, macvtap does not work for host to guest network communication" %}

- {% endif %} +
+
+
+

{% bs_icon 'ethernet' %} eth{{ forloop.counter0 }}

+
{{ network.target|default:_("no target") }}
-
+
+
+ {% trans "Device details" %} +
+
{% trans "Type" %}
{{ network.type }}
+
{% trans "MAC" %}
{{ network.mac }}
+
{% trans "Model" %}
{{ network.model|default:"—" }}
+
{% trans "NWFilter" %}
{{ network.filterref|default:_("None") }}
+
{% trans "IPv6" %}
{{ network.ipv6|join:", "|default:_("unknown") }}
+
+
+
+ {% trans "QoS limits" %} · {% if network.inbound and network.outbound %}{% trans "inbound, outbound" %}{% elif network.inbound %}{% trans "inbound" %}{% elif network.outbound %}{% trans "outbound" %}{% else %}{% trans "none" %}{% endif %} + {% if network.inbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="inbound" limit=network.inbound %}{% endif %} + {% if network.outbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="outbound" limit=network.outbound %}{% endif %} +

{% trans "Average and peak are in KiB/s, burst in KiB. A running VM gets a change after it is powered off and on." %}

+
+ {% if network.type == 'direct' %} +

{% bs_icon 'info-circle' %} {% trans "In most configurations, macvtap does not work for host to guest network communication" %}

+ {% endif %} + {# the dialogs of Edit and Add QoS #} {% if network.type != "hostdev" %}
{% csrf_token %}
+
{% empty %} -

{% trans "The VM has no network device." %}

+
{% trans "The VM has no network device." %}
{% endfor %}
+
{% if show_hostdevs %} diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py index fcf16c25..0ba522df 100644 --- a/instances/tests/test_pending_edits.py +++ b/instances/tests/test_pending_edits.py @@ -81,14 +81,15 @@ class PendingEditsTestCase(TestCase): self.assertEqual(doc.xpath("//form[.//input[@name='qos_average']]//input[@name='qos_direction']/@value"), ["inbound", "outbound"]) - def test_one_nic_card_spans_the_row_and_more_sit_side_by_side(self): + def test_the_nics_are_rows_of_one_list_with_their_dialogs(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": []} - second = {**nic, "mac": "52:54:10:00:00:02"} - for nics, wide in (([nic], ["col-12"]), ([nic, second], ["col-12 col-md-6"] * 2)): - with self.subTest(count=len(nics)): - doc = self.page(nics=(nics, nics)) - self.assertEqual(doc.xpath("//div[@id='network']//div[article]/@class"), wide) + nics = [nic, {**nic, "mac": "52:54:10:00:00:02"}] + doc = self.page(nics=(nics, nics)) + rows = doc.xpath("//div[@id='network']//div[contains(@class, 'list-group')]/*") + # nothing but the rows sits in the list: each row holds its Edit and Add QoS dialogs + self.assertEqual([row.get("aria-labelledby") for row in rows], ["nic-0", "nic-1"]) + self.assertEqual([len(row.xpath(".//div[contains(@class, 'modal')][@id]")) for row in rows], [2, 2]) 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)}]