From 1284b93705c8f40a7d955dc745ea279da3c1cc51 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 27 Oct 2026 15:16:18 +0300 Subject: [PATCH] feat(computes): memory given as a share; the overview in Bootstrap cards and lists The memory given read "24.0 GB given of 32.0 GB": long, and the share of the host's memory, what one looks for, had to be worked out. It is now told as the vCPUs are: the share large ("75 %", amber over 90 %, red over 100 %), then "24.0 GB / 32.0 GB", the bar and "12.0 GB running". The Computes list's memory column shares it. Every card of the overview has a Bootstrap card-header with an icon: VMs, vCPUs, Memory, Storage, CPU in use and Memory in use (with the current reading at its right), Hardware, Software and connection. The last two were label and value lists under indented headings; they are list-group-flush rows now, the label at the left, the value at the right: counts in semibold, the hypervisor's and libvirt's versions as badges, the guest architectures as light badges, the emulator and the connection as code. The section headings, indented by the panel's page-header margin unlike the cards, are gone: each card names itself. The status line and notes are not paragraphs, which the panel indents. --- .../templates/computes/capacity_memory.html | 5 +- computes/templates/overview.html | 172 +++++++++++------- computes/tests/test_capacity.py | 4 +- computes/tests/test_overview.py | 27 ++- computes/views.py | 3 +- static/icon_cache/cpu_None_None_None.svg | 8 + static/icon_cache/layers_None_None_None.svg | 8 + static/icon_cache/memory_None_None_None.svg | 8 + .../speedometer2_None_None_None.svg | 11 ++ 9 files changed, 162 insertions(+), 84 deletions(-) create mode 100644 static/icon_cache/cpu_None_None_None.svg create mode 100644 static/icon_cache/layers_None_None_None.svg create mode 100644 static/icon_cache/memory_None_None_None.svg create mode 100644 static/icon_cache/speedometer2_None_None_None.svg diff --git a/computes/templates/computes/capacity_memory.html b/computes/templates/computes/capacity_memory.html index 08d65ad9..263a8aea 100644 --- a/computes/templates/computes/capacity_memory.html +++ b/computes/templates/computes/capacity_memory.html @@ -1,10 +1,11 @@ {% load i18n %} {# the memory a host has given its VMs: given, from views.shown_capacity #} -
{% blocktrans with all=given.assigned.all|filesizeformat memory=given.memory|filesizeformat %}{{ all }} given of {{ memory }}{% endblocktrans %}
+
{{ given.memory_percent }} %
+
{{ given.assigned.all|filesizeformat }} / {{ given.memory|filesizeformat }}
-
{% blocktrans with running=given.assigned.running|filesizeformat %}{{ running }} of it running{% endblocktrans %}
+
{% blocktrans with running=given.assigned.running|filesizeformat %}{{ running }} running{% endblocktrans %}
diff --git a/computes/templates/overview.html b/computes/templates/overview.html index fda5fcb3..31b27aff 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -13,7 +13,7 @@ {% block page_heading %}{{ compute.name }}{% endblock page_heading %} {% block content %} -

+

{% if connected %} {% trans "Connected" %} @@ -23,13 +23,13 @@ {{ compute.hostname }} {% if compute.details %}{{ compute.details }}{% endif %} -

+
{% include "computes/host_nav.html" with section="overview" %} {% if not connected %} @@ -39,108 +39,150 @@ {% endif %} - {% if given %}
-
-
{% trans "VMs" %}
+
+
{% bs_icon 'server' %} {% trans "VMs" %}
+
{{ given.vm_count }}
{% blocktrans with running=given.vms.running shutoff=given.vms.shutoff %}{{ running }} running, {{ shutoff }} shut off{% endblocktrans %}{% if given.vms.paused %}, {% blocktrans with paused=given.vms.paused %}{{ paused }} paused{% endblocktrans %}{% endif %}{% if given.vms.other %}, {% blocktrans with other=given.vms.other %}{{ other }} other{% endblocktrans %}{% endif %}
{% trans "Instances" %} → -
+
+
-
-
{% trans "vCPUs" %}
+
+
{% bs_icon 'cpu' %} {% trans "vCPUs" %}
+
{% include "computes/capacity_vcpus.html" %} -
+
+
-
-
{% trans "Memory" %}
+
+
{% bs_icon 'memory' %} {% trans "Memory" %}
+
{% include "computes/capacity_memory.html" %} -
+
+
-
-
{% trans "Storage" %}
+
+
{% bs_icon 'device-hdd' %} {% trans "Storage" %}
+
{% include "computes/capacity_storage.html" %} {% trans "Storages" %} → -
+
+
{% else %} -

{% trans "The capacity could not be read." %}

+
{% trans "The capacity could not be read." %}
{% endif %} {# the load is read on its own: shown also when the details could not be #} -
-
-
{% trans "CPU in use" %} –
+
+
+ {% bs_icon 'speedometer2' %} {% trans "CPU in use" %} – +
+
-
+
+
-
-
{% trans "Memory in use" %} –
+
+
+ {% bs_icon 'memory' %} {% trans "Memory in use" %} – +
+
-
+
+
-

+

{% trans "The last 10 readings, one every 5 seconds. Memory in use leaves out the host's buffers and cache." %} {% trans "Waiting for the first reading." %} -

+
{% if host %} -
+
- -
-
{% trans "Hostname" %}
-
{{ host.hostname }}
-
{% trans "Architecture" %}
-
{{ host.arch }}
-
{% trans "Processor" %}
-
{{ host.processor|default:"–" }}
-
{% trans "Logical CPUs" %}
-
{{ host.topology.cpus }}
-
{% trans "Physical cores" %}
-
{{ host.topology.cores|default:"–" }}
-
{% trans "Sockets" %}
-
{{ host.topology.sockets|default:"–" }}
-
{% trans "NUMA nodes" %}
-
{{ host.topology.nodes|default:"–" }}
-
{% trans "Clock" %}
-
{{ host.topology.mhz }} MHz
-
{% trans "Memory" %}
-
{{ host.memory|filesizeformat }}
-
+
+
{% bs_icon 'cpu' %} {% trans "Hardware" %}
+
    +
  • + {% trans "Processor" %} + {{ host.processor|default:"–" }} +
  • +
  • + {% trans "Architecture" %} + {{ host.arch }} +
  • +
  • + {% trans "Logical CPUs" %} + {{ host.topology.cpus }} +
  • +
  • + {% trans "Physical cores" %} + {{ host.topology.cores|default:"–" }} +
  • +
  • + {% trans "Sockets" %} + {{ host.topology.sockets|default:"–" }} +
  • +
  • + {% trans "NUMA nodes" %} + {{ host.topology.nodes|default:"–" }} +
  • +
  • + {% trans "Clock" %} + {{ host.ghz }} GHz +
  • +
  • + {% trans "Memory" %} + {{ host.memory|filesizeformat }} +
  • +
+
- -
-
{% trans "Hypervisor" %}
-
{{ host.hypervisor }} {{ host.hypervisor_version }}
-
libvirt
-
{{ host.libvirt_version }}
-
{% trans "Emulator" %}
-
{{ host.emulator|default:"–" }}
-
{% trans "Guest architectures" %}
-
- {% for arch, types in host.guest_archs.items %} -
{{ arch }} ({{ types|join:", " }})
- {% endfor %} -
-
{% trans "Connection" %}
-
{{ host.uri }}
-
+
+
{% bs_icon 'layers' %} {% trans "Software and connection" %}
+
    +
  • + {% trans "Hostname" %} + {{ host.hostname }} +
  • +
  • + {{ host.hypervisor }} + {{ host.hypervisor_version }} +
  • +
  • + libvirt + {{ host.libvirt_version }} +
  • +
  • + {% trans "Guest architectures" %} + {% for arch, types in host.guest_archs.items %}{{ arch }} {{ types|join:" · " }}{% endfor %} +
  • +
  • + {% trans "Emulator" %} + {{ host.emulator|default:"–" }} +
  • +
  • + {% trans "Connection" %} + {{ host.uri }} +
  • +
+
{% endif %} diff --git a/computes/tests/test_capacity.py b/computes/tests/test_capacity.py index d7cb6107..5e88203a 100644 --- a/computes/tests/test_capacity.py +++ b/computes/tests/test_capacity.py @@ -74,8 +74,8 @@ class ComputesPageTests(TestCase): self.assertIn("host-one", cells[0]) self.assertIn("2.5:1", cells[1]) self.assertIn("40 vCPUs on 16 CPUs 24 running", cells[1]) - self.assertIn("48.0 GB given of 64.0 GB", cells[2]) - self.assertIn("32.0 GB of it running", cells[2]) + self.assertIn("75 % 48.0 GB / 64.0 GB", cells[2]) + self.assertIn("32.0 GB running", cells[2]) self.assertIn("120.0 GB free of 500.0 GB", cells[3]) # host-two could not be read: one cell says so self.assertIn("capacity could not be read", " ".join(two.text_content().split())) diff --git a/computes/tests/test_overview.py b/computes/tests/test_overview.py index e482efc8..2e33db30 100644 --- a/computes/tests/test_overview.py +++ b/computes/tests/test_overview.py @@ -49,27 +49,26 @@ class OverviewTestCase(TestCase): conn.get_emulator.return_value = "/usr/libexec/qemu-kvm" conn.get_hypervisors_domain_types.return_value = {"i686": ["kvm", "qemu"], "x86_64": ["kvm", "qemu"]} page = self.page() - self.assertIn("Connected", text(page, "//p[span]")) - self.assertIn("rack 4", text(page, "//p[span]")) - cards = [" ".join(" ".join(card.itertext()).split()) for card in page.xpath("//div[@class='card-body' or contains(@class,'card-body ')]")] + self.assertIn("Connected", text(page, "//div[@id='host_status']")) + self.assertIn("rack 4", text(page, "//div[@id='host_status']")) + cards = [" ".join(" ".join(card.itertext()).split()) for card in page.xpath("//div[@class='card h-100']")] self.assertIn("VMs 6 3 running, 2 shut off, 1 paused Instances →", cards) self.assertTrue(any(card.startswith("vCPUs 1.5:1 12 vCPUs on 8 CPUs 6 running") for card in cards), cards) - self.assertTrue(any("24.0 GB given of 32.0 GB" in card for card in cards), cards) + self.assertTrue(any(card.startswith("Memory 75 % 24.0 GB / 32.0 GB") for card in cards), cards) self.assertTrue(any("120.0 GB free of 500.0 GB" in card for card in cards), cards) - hardware = text(page, "//h4[.='Hardware']/following-sibling::dl") - self.assertIn("Logical CPUs 8 Physical cores 4 Sockets 2", hardware) - self.assertIn("NUMA nodes 2", hardware) - software = text(page, "//h4[.='Software and connection']/following-sibling::dl") - self.assertIn("Hypervisor QEMU 10.1.0", software) - self.assertIn("x86_64 (kvm, qemu)", software) - # the charts poll only for a host that was read + hardware = text(page, "//div[@class='card-header'][normalize-space()='Hardware']/..") + self.assertIn("Processor Xeon Architecture x86_64 Logical CPUs 8 Physical cores 4 Sockets 2 NUMA nodes 2 " + "Clock 2.4 GHz Memory 32.0 GB", hardware) + software = text(page, "//div[@class='card-header'][normalize-space()='Software and connection']/..") + self.assertIn("QEMU 10.1.0 libvirt 11.10.0", software) + self.assertIn("x86_64 kvm · qemu", software) self.assertTrue(page.xpath("//canvas[@id='cpuChart']")) @patch("computes.views.wvmHostDetails") def test_a_host_the_panel_cannot_reach_keeps_its_page_with_the_reason(self, details): page = self.page(connected=False, given=None) details.assert_not_called() - self.assertIn("Not connected", text(page, "//p[span]")) + self.assertIn("Not connected", text(page, "//div[@id='host_status']")) self.assertIn("did not answer within 5 s", text(page, "//div[@role='alert']")) # the way back: its sections, its connection and its removal self.assertTrue(page.xpath("//nav[@aria-label='Host sections']")) @@ -83,10 +82,10 @@ class OverviewTestCase(TestCase): details.return_value.get_lib_version.side_effect = libvirtError("cannot read the version") page = self.page() self.assertIn("cannot read the version", text(page, "//div[@role='alert']")) - self.assertTrue(page.xpath("//h5[.='VMs']")) + self.assertTrue(page.xpath("//div[@class='card-header'][normalize-space()='VMs']")) # the load is read on its own, by the charts self.assertTrue(page.xpath("//canvas[@id='cpuChart']")) - self.assertFalse(page.xpath("//h4[.='Hardware']")) + self.assertFalse(page.xpath("//div[@class='card-header'][normalize-space()='Hardware']")) def test_the_list_links_the_overview_of_a_host_it_cannot_reach(self): with patch.object(Compute, "status", new_callable=PropertyMock, return_value=False): diff --git a/computes/views.py b/computes/views.py index a941875a..f25b5953 100644 --- a/computes/views.py +++ b/computes/views.py @@ -84,9 +84,10 @@ def overview(request, compute_id): try: conn = wvmHostDetails(compute.hostname, compute.login, compute.password, compute.type) hostname, arch, memory, _cpus, processor, uri = conn.get_node_info() + topology = conn.get_cpu_topology() context["host"] = { "hostname": hostname, "arch": arch, "memory": memory, "processor": processor, "uri": uri, - "topology": conn.get_cpu_topology(), + "topology": topology, "ghz": round(topology["mhz"] / 1000, 1), "hypervisor": conn.wvm.getType(), "hypervisor_version": conn.get_version(), "libvirt_version": conn.get_lib_version(), "emulator": conn.get_emulator(arch), "guest_archs": conn.get_hypervisors_domain_types(), diff --git a/static/icon_cache/cpu_None_None_None.svg b/static/icon_cache/cpu_None_None_None.svg new file mode 100644 index 00000000..63769638 --- /dev/null +++ b/static/icon_cache/cpu_None_None_None.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/icon_cache/layers_None_None_None.svg b/static/icon_cache/layers_None_None_None.svg new file mode 100644 index 00000000..cc0ac302 --- /dev/null +++ b/static/icon_cache/layers_None_None_None.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/icon_cache/memory_None_None_None.svg b/static/icon_cache/memory_None_None_None.svg new file mode 100644 index 00000000..bbec3765 --- /dev/null +++ b/static/icon_cache/memory_None_None_None.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/icon_cache/speedometer2_None_None_None.svg b/static/icon_cache/speedometer2_None_None_None.svg new file mode 100644 index 00000000..6d246824 --- /dev/null +++ b/static/icon_cache/speedometer2_None_None_None.svg @@ -0,0 +1,11 @@ + + + + + + + + + + +