From 4f0ae3b30d07e63c38734310411102b080c46cc5 Mon Sep 17 00:00:00 2001 From: catborise Date: Sat, 24 Oct 2026 11:29:38 +0300 Subject: [PATCH] fix(instances): the new-VM host dialog says when no compute is reachable The dialog looped over every compute and skipped the unreachable ones, so its "No compute is reachable." message showed only when there were no computes at all. With hosts that were all offline it was an empty list. The index view now gives the dialog the reachable computes, and the template lists those, so the message covers that case. Fixes #176 Co-Authored-By: Claude Opus 5.5 (1M context) --- instances/templates/create_inst_block.html | 52 +++++++++++----------- instances/tests/test_create_dialog_cpu.py | 31 +++++++++++++ instances/views.py | 3 +- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/instances/templates/create_inst_block.html b/instances/templates/create_inst_block.html index a5a17f8b..4729f458 100644 --- a/instances/templates/create_inst_block.html +++ b/instances/templates/create_inst_block.html @@ -15,37 +15,35 @@ {% include "create_steps.html" with step=1 %}
{% trans "Choose the host the VM runs on." %}
- {% for compute in computes %} - {% if compute.status is True %} - -
-
{{ compute.name }}
-
-
{% trans "CPU" %} · {{ compute.cpu_count }} vCPU
-
-
-
-
- + {% for compute in reachable_computes %} +
+
+
{{ compute.name }}
+
+
{% trans "CPU" %} · {{ compute.cpu_count }} vCPU
+
+
+
+
-
-
{% trans "Memory" %} · {{ compute.ram_size|filesizeformat }}
-
-
-
-
- {{ compute.ram_usage }}% -
-
-
-
- {% endif %} +
+
{% trans "Memory" %} · {{ compute.ram_size|filesizeformat }}
+
+
+
+
+ {{ compute.ram_usage }}% +
+
+ +
+ {% empty %} -

{% trans "No compute is reachable." %}

+

{% trans "No compute is reachable." %}

{% endfor %}
diff --git a/instances/tests/test_create_dialog_cpu.py b/instances/tests/test_create_dialog_cpu.py index 2e627fae..1b294216 100644 --- a/instances/tests/test_create_dialog_cpu.py +++ b/instances/tests/test_create_dialog_cpu.py @@ -112,3 +112,34 @@ class CreateDialogCpuTestCase(TestCase): # the earlier opening's answer (99) is ignored; a failed read is a dash self.assertEqual(result["final"], [["42%", "42", "42%"], ["0%", None, "—"], ["0%", None, "…"]]) self.assertEqual(result["all"], urls[:2] + urls + urls) + + +class CreateDialogHostsTestCase(TestCase): + """The dialog lists the reachable computes; without one it says so.""" + + def setUp(self): + self.client.force_login(get_user_model().objects.create_superuser("cd-admin", "cd@example.com", "pw")) + for i in (1, 2): + Compute.objects.create(name=f"cd{i}", hostname=f"192.0.2.{i}", login="u", password="p", type=1) + for name, value in (("cpu_count", 1), ("ram_size", 1024), ("ram_usage", 0)): + p = patch.object(Compute, name, new_callable=PropertyMock, return_value=value) + p.start() + self.addCleanup(p.stop) + p = patch("instances.views.utils.refr") + p.start() + self.addCleanup(p.stop) + + def dialog(self, reachable): + with patch.object(Compute, "status", property(lambda compute: compute.name in reachable)): + response = self.client.get(reverse("instances:index")) + return html.fromstring(response.content).get_element_by_id("AddInstance") + + def test_only_reachable_computes_are_listed(self): + dialog = self.dialog({"cd2"}) + self.assertEqual([e.text for e in dialog.xpath(".//a/div/div[1]")], ["cd2"]) + self.assertNotIn("No compute is reachable.", dialog.text_content()) + + def test_no_reachable_compute_is_said(self): + dialog = self.dialog(set()) + self.assertEqual(dialog.xpath(".//a"), []) + self.assertIn("No compute is reachable.", dialog.text_content()) diff --git a/instances/views.py b/instances/views.py index 3ba2816f..561ea37b 100755 --- a/instances/views.py +++ b/instances/views.py @@ -85,7 +85,8 @@ def index(request): return render( request, "allinstances.html", - {"computes": computes, "instances": instances, "show_hosts": show_hosts, "own_hosts_down": own_hosts_down}, + {"computes": computes, "instances": instances, "show_hosts": show_hosts, "own_hosts_down": own_hosts_down, + "reachable_computes": [c for c in computes if c.status is True]}, )