mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The top of a VM's page was one line of values between pipes, an icon for the guest agent and a hidden snapshot count, then a row of big action icons; the host links sat in a bar fixed to the bottom of the window, over the content. One card now holds it all (design chosen by the owner from four): - the name with the state badge, the title under it, and where the VM is: its host and the host's VM list for administrators, the VM list for owners; - OS details (with a connected guest agent) and Refresh; - the guest agent's state in words and the snapshot count; - CPU, memory, each disk with its device name and size, and the IPv4 addresses, labelled; - the page's sections as small labelled pills in the card's footer: one row from 992 px, wrapping below. The page has no other heading: base.html's heading area is a block it leaves out. Each section below it (Power, Access, Resize, Snapshot, Settings, Stats, Destroy) is a card of the same kind, so the page reads as one design (owner's choice of two): its sub-tabs as a small underlined nav in the card's header, its panes in the card's body without their own borders. Buttons are of normal size in the theme's primary colour, and those that cannot be undone (Power Cycle, Force Off, Destroy) in the danger colour. The Stats charts lose their coloured borders. instance_tabs.js finds the Settings tabs by their tablist role. The bottom bar and its setting (VIEW_INSTANCE_DETAIL_BOTTOM_BAR) are removed, and with it the CSS that kept the old Settings tabs on one line.
118 lines
5.7 KiB
Python
118 lines
5.7 KiB
Python
"""The default settings rows of the first migrations carry fixed ids, which a
|
|
PostgreSQL id sequence does not see. A later migration that inserts without
|
|
an id must not collide with them."""
|
|
|
|
import importlib
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
|
|
from django.apps import apps
|
|
from django.db import connection
|
|
from django.test import TestCase, TransactionTestCase
|
|
|
|
from appsettings.models import AppSettings
|
|
|
|
login_lockout = importlib.import_module("appsettings.migrations.0014_login_lockout_settings")
|
|
|
|
|
|
@unittest.skipUnless(connection.vendor == "postgresql", "id sequences are a PostgreSQL concern")
|
|
class SequenceAfterFixedIdsTestCase(TransactionTestCase):
|
|
serialized_rollback = True
|
|
|
|
def test_login_lockout_settings_insert_after_fixed_ids(self):
|
|
with connection.schema_editor() as schema_editor:
|
|
login_lockout.del_settings(apps, schema_editor)
|
|
# the state after the fixed-id inserts: rows exist, the sequence starts at 1
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("SELECT setval(pg_get_serial_sequence('appsettings_appsettings', 'id'), 1, false)")
|
|
login_lockout.add_settings(apps, schema_editor)
|
|
self.assertEqual(AppSettings.objects.filter(key__startswith="LOGIN_").count(), 2)
|
|
|
|
|
|
nic_type = importlib.import_module("appsettings.migrations.0016_nic_type_rtl8139")
|
|
|
|
|
|
class NicTypeTestCase(TestCase):
|
|
def test_the_misspelt_nic_type_becomes_rtl8139(self):
|
|
setting = AppSettings.objects.get(key="INSTANCE_NIC_DEFAULT_TYPE")
|
|
self.assertEqual(setting.choices, "default,e1000,e1000e,rtl8139,virtio")
|
|
AppSettings.objects.filter(pk=setting.pk).update(value="rt18139", choices="default,e1000,e1000e,rt18139,virtio")
|
|
nic_type.fix_rtl8139(apps, SimpleNamespace(connection=connection))
|
|
setting.refresh_from_db()
|
|
self.assertEqual((setting.value, setting.choices), ("rtl8139", "default,e1000,e1000e,rtl8139,virtio"))
|
|
|
|
|
|
class SnapshotFreeSpaceSettingsTestCase(TestCase):
|
|
def test_the_rows_fit_their_columns(self):
|
|
# PostgreSQL refuses a value longer than the column, sqlite does not
|
|
for key in ("SNAPSHOT_FREE_SPACE_PERCENT", "SNAPSHOT_FREE_SPACE_MIN_GIB"):
|
|
setting = AppSettings.objects.get(key=key)
|
|
for field in ("name", "key", "value", "description"):
|
|
limit = AppSettings._meta.get_field(field).max_length
|
|
if limit:
|
|
self.assertLessEqual(len(getattr(setting, field)), limit, f"{key} {field}")
|
|
|
|
|
|
class WorkerSettingsTestCase(TestCase):
|
|
def test_the_rows_fit_their_columns(self):
|
|
for key in ("WORKER_CONCURRENCY", "JOB_RETENTION_DAYS"):
|
|
setting = AppSettings.objects.get(key=key)
|
|
for field in ("name", "key", "value", "description"):
|
|
limit = AppSettings._meta.get_field(field).max_length
|
|
if limit:
|
|
self.assertLessEqual(len(getattr(setting, field)), limit, f"{key} {field}")
|
|
|
|
|
|
choices_fix = importlib.import_module("appsettings.migrations.0020_fix_setting_choices")
|
|
|
|
|
|
class ChoicesFixTestCase(TestCase):
|
|
"""Choices that offered values the panel cannot use, and a text setting
|
|
that showed as a True/False list."""
|
|
|
|
def test_the_broken_choices_are_fixed_and_their_values_moved(self):
|
|
broken = {
|
|
"CLONE_INSTANCE_DEFAULT_PREFIX": ("instance", "True,False"),
|
|
"INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER": ("virtio-blk", "virtio-scsi, lsilogic, virtio-blk"),
|
|
"INSTANCE_MACHINE_DEFAULT_TYPE": ("x86_64", "q35,x86_64,virt"),
|
|
"INSTANCE_CDROM_ADD": ("virtio", "None,ide,sata,scsi,virtio"),
|
|
}
|
|
for key, (value, choices) in broken.items():
|
|
AppSettings.objects.filter(key=key).update(value=value, choices=choices)
|
|
choices_fix.fix_choices(apps, SimpleNamespace(connection=connection))
|
|
fixed = {s.key: (s.value, s.choices) for s in AppSettings.objects.filter(key__in=broken)}
|
|
self.assertEqual(fixed, {
|
|
"CLONE_INSTANCE_DEFAULT_PREFIX": ("instance", ""),
|
|
"INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER": ("virtio-scsi", "virtio-scsi,lsilogic"),
|
|
"INSTANCE_MACHINE_DEFAULT_TYPE": ("q35", "q35,pc,virt"),
|
|
"INSTANCE_CDROM_ADD": ("sata", "None,ide,sata,scsi"),
|
|
})
|
|
|
|
|
|
descriptions = importlib.import_module("appsettings.migrations.0021_setting_descriptions")
|
|
|
|
|
|
class DescriptionsTestCase(TestCase):
|
|
def test_every_setting_says_what_it_does_within_its_column(self):
|
|
limit = AppSettings._meta.get_field("description").max_length
|
|
removed = {"VIEW_INSTANCE_DETAIL_BOTTOM_BAR"} # by 0023
|
|
for key, text in descriptions.DESCRIPTIONS.items():
|
|
with self.subTest(key=key):
|
|
self.assertLessEqual(len(text), limit)
|
|
if key not in removed:
|
|
self.assertEqual(AppSettings.objects.get(key=key).description, text)
|
|
self.assertFalse(AppSettings.objects.filter(key__in=removed).exists())
|
|
|
|
|
|
cpu_mode = importlib.import_module("appsettings.migrations.0022_cpu_mode_choices")
|
|
|
|
|
|
class CpuModeChoicesTestCase(TestCase):
|
|
def test_the_default_cpu_mode_is_one_the_new_vm_dialog_offers(self):
|
|
for old, new in (("no-model", "host-model"), ("custom", "host-model"), ("host-passthrough", "host-passthrough")):
|
|
with self.subTest(old=old):
|
|
AppSettings.objects.filter(key="INSTANCE_CPU_DEFAULT_MODE").update(
|
|
value=old, choices="no-model,host-model,host-passthrough,custom")
|
|
cpu_mode.fix_cpu_mode(apps, SimpleNamespace(connection=connection))
|
|
setting = AppSettings.objects.get(key="INSTANCE_CPU_DEFAULT_MODE")
|
|
self.assertEqual((setting.value, setting.choices), (new, "host-model,host-passthrough,maximum"))
|