1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00

fix(instances): VM list columns, hosts for viewers, flavor units

- On a narrow screen the grouped list hid the User, VCPU and Memory
  headings but not the VMs' cells, so values sat under the wrong headings;
  the cells now hide with their headings. Memory says MiB.
- A user with the view_instances permission sees each VM's host in the
  list, as the host alerts already name them; the host page link stays a
  superuser's.
- A flavor's memory and disk are labelled MiB and GiB, and each value is at
  least 1, so "4" is no longer saved as a 4 MiB preset by mistake.
This commit is contained in:
catborise 2026-10-26 15:50:54 +03:00
parent 52d00e818b
commit b89f280076
6 changed files with 31 additions and 9 deletions

View file

@ -10,6 +10,11 @@ from .utils import nic_macs
class FlavorForm(forms.ModelForm):
# the units the create form fills in from a flavor
memory = forms.IntegerField(label=_("Memory (MiB)"), min_value=1)
vcpu = forms.IntegerField(label=_("vCPUs"), min_value=1)
disk = forms.IntegerField(label=_("Disk (GiB)"), min_value=1)
class Meta:
model = Flavor
fields = "__all__"

View file

@ -36,7 +36,7 @@
{% endif %}
</td>
{% if app_settings.VM_DRBD_STATUS == 'True' %}
<td class="d-none d-sm-table-cell"></td>
<td></td>
{% endif %}
{% if compute.status is True %}
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
@ -62,7 +62,7 @@
<br>
{% if instance.info %}<p class="m-0 small fst-italic">{{ instance.title }}</p>{% endif %}
</td>
<td>
<td class="d-none d-sm-table-cell">
<em>
{% if instance.userinstance_set.all.count > 0 %}
{{ instance.userinstance_set.all.0.user }}
@ -80,8 +80,8 @@
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="badge bg-success-subtle text-success-emphasis">{% else %}<span class="badge bg-danger-subtle text-danger-emphasis">{% endif %}{{ instance.drbd }}</span>
</td>
{% endif %}
<td data-sort="{{ instance.info.3 }}">{{ instance.info.3 }}</td>
<td data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
<td class="d-none d-sm-table-cell" data-sort="{{ instance.info.3 }}">{{ instance.info.3 }}</td>
<td class="d-none d-sm-table-cell" data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MiB{% endif %}</td>
<td class="text-nowrap">
{% include 'instance_actions.html' %}
</td>

View file

@ -4,7 +4,7 @@
<thead>
<tr>
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
{% if request.user.is_superuser %}
{% if show_hosts %}
<th scope="col">{% trans 'Host' %}<br>{% trans 'User' %}</th>
{% endif %}
<th scope="col">{% trans 'Status' %}</th>
@ -26,9 +26,10 @@
<br>
{% if instance.info %}<p class="m-0 small font-italic">{{ instance.title }}</p>{% endif %}
</td>
{% if request.user.is_superuser %}
{% if show_hosts %}
<td>
<a href="{% url 'overview' instance.compute.id %}">{{ instance.compute.name }}</a><br>
{# the host pages are a superuser's #}
{% if request.user.is_superuser %}<a href="{% url 'overview' instance.compute.id %}">{{ instance.compute.name }}</a>{% else %}{{ instance.compute.name }}{% endif %}<br>
<small><em>
{% if instance.userinstance_set.all.count > 0 %}
{{ instance.userinstance_set.all.0.user }}
@ -48,7 +49,7 @@
</td>
{% endif %}
<td data-sort="{{ instance.info.3 }}">{{ instance.info.3 }}</td>
<td data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
<td data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MiB{% endif %}</td>
<td class="text-nowrap">
{% include 'instance_actions.html' %}
</td>

View file

@ -307,3 +307,12 @@ class XmlRefusalTests(TestCase):
def test_a_definition_libvirt_refuses(self):
self.assertIn("libvirt", self.post("<domain type='nope'><name>new</name><memory>1024</memory></domain>"))
class FlavorFormTests(TestCase):
def test_a_flavor_is_at_least_one_of_each_in_named_units(self):
from instances.forms import FlavorForm
form = FlavorForm({"label": "tiny", "memory": "0", "vcpu": "-1", "disk": "0"})
self.assertEqual(set(form.errors), {"memory", "vcpu", "disk"})
self.assertEqual((form["memory"].label, form["disk"].label), ("Memory (MiB)", "Disk (GiB)"))

View file

@ -61,3 +61,10 @@ class IndexHostAlertTests(TestCase):
viewer.user_permissions.add(Permission.objects.get(codename="view_instances"))
response = self.get(viewer, down={"other-host"})
self.assertContains(response, "Connection Failed: no route to 10.0.0.2")
def test_global_viewer_sees_each_vms_host_without_the_host_page(self):
viewer = get_user_model().objects.create_user(username="viewer2", password="pwd")
viewer.user_permissions.add(Permission.objects.get(codename="view_instances"))
response = self.get(viewer, down=set())
self.assertContains(response, "mine-host")
self.assertNotContains(response, reverse("overview", args=[self.mine.id]))

View file

@ -65,7 +65,7 @@ class ListInfoCallTests(TestCase):
self.assertEqual(response.status_code, 200)
return response
def assert_one_info_per_vm(self, response, memory="2048 MB"):
def assert_one_info_per_vm(self, response, memory="2048 MiB"):
self.assertEqual(len(self.domains), 2, "one proxy per VM row")
for domain in self.domains:
self.assertEqual(domain.info_calls, 1)