diff --git a/computes/migrations/0006_login_password_length.py b/computes/migrations/0006_login_password_length.py
new file mode 100644
index 00000000..e32339ba
--- /dev/null
+++ b/computes/migrations/0006_login_password_length.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.2.18 on 2026-10-26 11:12
+
+import computes.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('computes', '0005_alter_compute_hostname_alter_compute_login'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='compute',
+ name='login',
+ field=models.CharField(blank=True, max_length=32, validators=[computes.validators.validate_login], verbose_name='login'),
+ ),
+ migrations.AlterField(
+ model_name='compute',
+ name='password',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='password'),
+ ),
+ ]
diff --git a/computes/models.py b/computes/models.py
index 4f48941a..407f63ec 100644
--- a/computes/models.py
+++ b/computes/models.py
@@ -15,8 +15,8 @@ from vrtManager.storage import wvmStorages
class Compute(Model):
name = CharField(_("name"), max_length=64, unique=True)
hostname = CharField(_("hostname"), max_length=64, validators=[validate_hostname])
- login = CharField(_("login"), max_length=20, blank=True, validators=[validate_login])
- password = CharField(_("password"), max_length=14, blank=True, null=True)
+ login = CharField(_("login"), max_length=32, blank=True, validators=[validate_login])
+ password = CharField(_("password"), max_length=255, blank=True, null=True)
details = CharField(_("details"), max_length=64, null=True, blank=True)
type = IntegerField()
migration_address = CharField(
diff --git a/computes/templates/computes/form.html b/computes/templates/computes/form.html
index 2f08c60c..b6375c95 100644
--- a/computes/templates/computes/form.html
+++ b/computes/templates/computes/form.html
@@ -3,12 +3,12 @@
{% load bootstrap_icons %}
{% load i18n %}
-{% block title %}{% trans "Add Compute" %}{% endblock %}
+{% block title %}{% if form.instance.pk %}{% trans "Edit Compute" %}{% else %}{% trans "Add Compute" %}{% endif %}{% endblock %}
{% block content %}
diff --git a/computes/templates/computes/instances.html b/computes/templates/computes/instances.html
index f4bfb2cf..0a83eee4 100644
--- a/computes/templates/computes/instances.html
+++ b/computes/templates/computes/instances.html
@@ -61,7 +61,6 @@
{% trans "Hypervisor doesn't have any Instances" %}
{% endif %}
-
{% else %}
diff --git a/computes/templates/overview.html b/computes/templates/overview.html
index f3ccece8..5160d075 100644
--- a/computes/templates/overview.html
+++ b/computes/templates/overview.html
@@ -130,7 +130,7 @@
return span ? text + ' ({{ average_over|escapejs }} ' + span + ' s)' : text;
},
});
- var memChart = lineChart(document.getElementById("memChart"), "MB", ["Usage"]);
+ var memChart = lineChart(document.getElementById("memChart"), "MiB", ["Usage"]);
if (Boolean({{ status }}) === true) {
// the CPU sample of the previous answer: usage is taken since then
var cpuSample = null;
diff --git a/computes/tests/test_compute_password.py b/computes/tests/test_compute_password.py
index c0e7e5d6..a6ac2614 100644
--- a/computes/tests/test_compute_password.py
+++ b/computes/tests/test_compute_password.py
@@ -26,3 +26,8 @@ class ComputePasswordTests(TestCase):
password="secret", type=conn_type)
self.assertEqual(self.edit(compute, ""), "secret")
self.assertEqual(self.edit(compute, "other"), "other")
+
+ def test_a_long_password_is_kept_and_the_page_says_edit(self):
+ compute = Compute.objects.create(name="cp-long", hostname="192.0.2.7", login="admin", password="", type=1)
+ self.assertContains(self.client.get(reverse("compute_update", args=[compute.id])), "Edit Compute")
+ self.assertEqual(self.edit(compute, "p" * 40), "p" * 40)
diff --git a/computes/tests/test_cpu_graph.py b/computes/tests/test_cpu_graph.py
index f51f7d3b..bbb509f1 100644
--- a/computes/tests/test_cpu_graph.py
+++ b/computes/tests/test_cpu_graph.py
@@ -20,6 +20,7 @@ from lxml import html
from computes.models import Compute
from computes.views import CPU_SAMPLE_MAX_AGE, CPU_SAMPLE_SIGNER
from vrtManager.hostdetails import cpu_percent, wvmHostDetails
+from vrtManager.util import ConnectionFailed
# the page script runs after the shared chart helpers
CHARTS_JS = settings.BASE_DIR / "static" / "js" / "charts.js"
@@ -77,7 +78,7 @@ class GraphTestCase(TestCase):
self.other = Compute.objects.create(name="other", hostname="192.0.2.2", login="u", password="p", type=1)
admin = get_user_model().objects.create_superuser("graph-admin", "graph@example.com", "pw")
self.client.force_login(admin)
- for target in ("computes.views.wvmConnect", "computes.views.wvmHostDetails"):
+ for target in ("computes.models.wvmHostDetails", "computes.views.wvmHostDetails"):
patcher = patch(target)
self.addCleanup(patcher.stop)
self.conn = patcher.start().return_value # the graph reads through wvmHostDetails
@@ -117,6 +118,16 @@ class GraphTestCase(TestCase):
self.assertEqual((data["cpudata"], data["memdata"]["total"]), (None, None))
self.assertNotIn("cpu_sample", data)
+ def test_a_host_that_cannot_be_connected_to_answers_without_values(self):
+ down = ConnectionFailed("192.0.2.1 is down")
+ with patch("computes.views.wvmHostDetails", side_effect=down), patch(
+ "computes.models.wvmHostDetails", side_effect=down
+ ):
+ self.assertIsNone(self.graph()["cpudata"])
+ for url in (reverse("machines", args=[self.compute.id, "x86_64"]),
+ reverse("buses", args=[self.compute.id, "x86_64", "q35", "disk"])):
+ self.assertEqual(json.loads(self.client.get(url).content), {})
+
@unittest.skipUnless(shutil.which("node"), "node is not installed")
class GraphScriptTestCase(TestCase):
diff --git a/computes/tests/test_views.py b/computes/tests/test_views.py
index 009dbd19..c14efcf7 100644
--- a/computes/tests/test_views.py
+++ b/computes/tests/test_views.py
@@ -180,7 +180,7 @@ class ComputesTestCase(TestCase):
self.assertEqual(response.status_code, 200)
def test_a_disk_is_offered_the_hosts_disk_buses(self):
- with patch("computes.views.wvmConnect") as conn:
+ with patch("computes.models.wvmHostDetails") as conn:
conn.return_value.get_disk_device_types.return_value = ["disk", "cdrom", "lun"]
conn.return_value.get_disk_bus_types.return_value = ["virtio", "sata", "scsi"]
response = self.client.get(reverse("buses", args=[self.compute.id, "x86_64", "q35", "disk"]))
diff --git a/computes/views.py b/computes/views.py
index e608d069..62e246ef 100644
--- a/computes/views.py
+++ b/computes/views.py
@@ -12,7 +12,6 @@ from instances.models import Instance
from libvirt import libvirtError
from logs.audit import audit, changed_fields, snapshot
from operations import claims
-from vrtManager.connection import wvmConnect
from vrtManager.hostdetails import wvmHostDetails
from computes.forms import (
@@ -206,17 +205,11 @@ def get_compute_machine_types(request, compute_id, arch):
class ComputeManager:
def __init__(self, compute_id):
self.compute = get_object_or_404(Compute, pk=compute_id)
- self.conn = wvmConnect(
- self.compute.hostname,
- self.compute.login,
- self.compute.password,
- self.compute.type,
- )
def get_machine_types(self, arch):
data = dict()
try:
- data["machines"] = self.conn.get_machine_types(arch)
+ data["machines"] = self.compute.proxy.get_machine_types(arch)
except libvirtError:
pass
@@ -225,11 +218,11 @@ class ComputeManager:
def get_disk_buses(self, arch, machine, disk):
data = dict()
try:
- disk_device_types = self.conn.get_disk_device_types(arch, machine)
+ disk_device_types = self.compute.proxy.get_disk_device_types(arch, machine)
if disk in disk_device_types:
if disk == "disk":
- data["bus"] = sorted(self.conn.get_disk_bus_types(arch, machine))
+ data["bus"] = sorted(self.compute.proxy.get_disk_bus_types(arch, machine))
elif disk == "cdrom":
data["bus"] = ["ide", "sata", "scsi"]
elif disk == "floppy":
diff --git a/virtsecrets/templates/secrets.html b/virtsecrets/templates/secrets.html
index 62cd0f36..6bc6be4e 100644
--- a/virtsecrets/templates/secrets.html
+++ b/virtsecrets/templates/secrets.html
@@ -66,7 +66,7 @@
| {{ secret.uuid }} |
{{ secret.usageType }} |
- {{ secret.usage }} |
+ {{ secret.usage|default_if_none:"" }} |
|