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

fix(templates): forms and detail lists fit a phone screen

Column offsets without a breakpoint applied below it, where the column
is already full width, so the change password, compute, user, VM
create and VM settings forms ran past the right edge on a phone. Each
offset now starts at its column's breakpoint. The interface and network
detail lists were rows directly inside rows and sit in a column now;
the compute form loses a Bootstrap 3 thumbnail padding that broke its
gutters, and the add-rule dialog's hint is no longer cut off. The page
test checks both grid rules on every page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
catborise 2026-10-21 13:13:55 +03:00
parent e324e79fcb
commit 676bcfeb09
11 changed files with 91 additions and 79 deletions

View file

@ -8,7 +8,7 @@
{% block content %}
<div class="row">
<div class="offset-2 col-lg-8">
<div class="offset-lg-2 col-lg-8">
<div class="card">
<div class="card-header">
<h4 class="card-title">{%trans "Change Password" %}: {{ user }}</h4>

View file

@ -8,7 +8,7 @@
{% block page_heading %}{{ title }}{% endblock page_heading %}
{% block content %}
<div class="card col-sm-10 offset-1">
<div class="card col-sm-10 offset-sm-1">
<div class="card-body">
<form id="create-update" action="" method="post">
{% csrf_token %}

View file

@ -12,7 +12,7 @@
</div>
</div>
<div class="row">
<div class="thumbnail col-sm-10 offset-1">
<div class="col-sm-10 offset-sm-1">
<form id="create-update" action="" method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}

View file

@ -65,7 +65,7 @@
</div>
</div>
<div class="row">
<div class="d-grid col-sm-6 offset-3 mt-3">
<div class="d-grid col-sm-6 offset-sm-3 mt-3">
<button class="btn btn-primary" type="button" name="create_instance" onclick="goto_create()">
{% trans "Next" %}
</button>

View file

@ -600,7 +600,7 @@
{% endif %}
</div>
<div class="row">
<div class="d-grid col-sm-7 offset-3">
<div class="d-grid col-sm-7 offset-sm-3">
{% if storages %}
<button type="submit" class="btn btn-primary" name="create" formnovalidate value="1">
{% trans "Create" %}
@ -876,7 +876,7 @@
{% endif %}
</div>
<div class="row">
<div class="d-grid col-sm-7 offset-3">
<div class="d-grid col-sm-7 offset-sm-3">
{% if storages %}
<button type="submit" class="btn btn-primary" name="create" value="1" formnovalidate>
{% trans "Create" %}

View file

@ -239,7 +239,7 @@
</div>
</div>
{% empty %}
<div class="offset-3 col-sm-6">
<div class="offset-sm-3 col-sm-6">
<div class="bg-warning rounded shadow-sm">{% trans 'There is not any CD-ROM device.' %}</div>
</div>
{% endfor %}
@ -607,7 +607,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-3">
<div class="col-sm-6 offset-sm-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% if instance.status != 5 %}checked{% else %}disabled{% endif %}>
<label class="form-check-label" for="vm_live_migrate">{% trans "Live migration" %}</label>
@ -615,7 +615,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-3">
<div class="col-sm-6 offset-sm-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="unsafe_migrate" value="true" id="vm_unsafe_migrate">
<label class="form-check-label" for="vm_unsafe_migrate">{% trans "Unsafe migration" %}</label>
@ -623,7 +623,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-3">
<div class="col-sm-6 offset-sm-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% if instance.status == 5 %}checked{% else %}disabled{% endif %}>
<label class="form-check-label" for="offline_migrate">{% trans "Offline migration" %}</label>
@ -631,7 +631,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-3">
<div class="col-sm-6 offset-sm-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="autoconverge" value="true" id="autoconverge" {% if instance.status != 1 %}disabled{% else %}checked{% endif %}>
<label class="form-check-label" for="autoconverge" title="{% trans 'Forces CPU convergence during live migration' %}">{% trans "Auto converge" %}</label>
@ -639,7 +639,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-3">
<div class="col-sm-6 offset-sm-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="compress" value="true" id="compress" {% if instance.status != 1 %}disabled{% endif %}>
<label class="form-check-label" for="compress" title="{% trans 'Compress instance memory for fast migration' %}">{% trans "Compressed" %}</label>
@ -756,7 +756,7 @@
{% for network in instance.config_networks %}
<div class="mb-3">
<div class="row">
<label class="col-sm-2 col-form-label offset-1">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<label class="col-sm-2 col-form-label offset-sm-1">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-6">
<div class="input-group">
<input type="text" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
@ -778,7 +778,7 @@
<label>{% trans "Storage devices" %}:</label>
{% for disk in instance.config_disks %}
<div class="row">
<label class="col-sm-2 col-form-label offset-1">{{ disk.dev }} ({{ disk.storage }})</label>
<label class="col-sm-2 col-form-label offset-sm-1">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-6">
<div class="input-group mb-3">
<input id="disk_name-{{ disk.dev }}" type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
@ -847,7 +847,7 @@
</div>
</div>
<div class="row">
<div class="d-grid offset-3 col-sm-6">
<div class="d-grid offset-sm-3 col-sm-6">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-success" name="change_options">{% trans "Change" %}</button>
{% else %}

View file

@ -37,39 +37,41 @@
{% if changeable is False %}{% include 'interfaces_unchangeable.html' %}{% endif %}
<div class="row">
<dl class="ms-3 row">
<dt class="col-4">{% trans "Interface" %}</dt>
<dd class="col-8"><strong>{{ iface }}</strong></dd>
<dt class="col-4">{% trans "IPv4" %} ({% if ipv4 == None %}{% trans 'None' %}{% else %}{{ ipv4_type }}{% endif %})</dt>
<dd class="col-8">{{ ipv4 }}</dd>
<dt class="col-4">{% trans "IPv6" %} ({% if ipv6 == None %}{% trans 'None' %}{% else %}{{ ipv6_type }}{% endif %})</dt>
<dd class="col-8">{{ ipv6 }}</dd>
<dt class="col-4">{% trans "MAC" %}</dt>
<dd class="col-8">{{ mac }}</dd>
<dt class="col-4">{% trans "Interface Type" %}</dt>
<dd class="col-8">{{ itype }}</dd>
{% if itype == 'bridge' %}
<dt class="col-4">{% trans "Bridge Device" %}</dt>
<dd class="col-8">{{ bridge }}</dd>
{% endif %}
<dt class="col-4">{% trans "Boot Mode" %}</dt>
<dd class="col-8">{{ start_mode }}</dd>
<dt class="col-4">{% trans "State" %}</dt>
<dd class="col-8">
{% if changeable is False %}
{% if state == 0 %}{% trans "Inactive" %}{% else %}{% trans "Active" %}{% endif %}
{% else %}
<form action="" method="post" role="form" aria-label="Interface start/stop/delete form">{% csrf_token %}
{% if state == 0 %}
<input type="submit" class="btn btn-sm btn-primary" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% endif %}
</form>
<div class="col-12">
<dl class="ms-3 row">
<dt class="col-4">{% trans "Interface" %}</dt>
<dd class="col-8"><strong>{{ iface }}</strong></dd>
<dt class="col-4">{% trans "IPv4" %} ({% if ipv4 == None %}{% trans 'None' %}{% else %}{{ ipv4_type }}{% endif %})</dt>
<dd class="col-8">{{ ipv4 }}</dd>
<dt class="col-4">{% trans "IPv6" %} ({% if ipv6 == None %}{% trans 'None' %}{% else %}{{ ipv6_type }}{% endif %})</dt>
<dd class="col-8">{{ ipv6 }}</dd>
<dt class="col-4">{% trans "MAC" %}</dt>
<dd class="col-8">{{ mac }}</dd>
<dt class="col-4">{% trans "Interface Type" %}</dt>
<dd class="col-8">{{ itype }}</dd>
{% if itype == 'bridge' %}
<dt class="col-4">{% trans "Bridge Device" %}</dt>
<dd class="col-8">{{ bridge }}</dd>
{% endif %}
</dd>
</dl>
<dt class="col-4">{% trans "Boot Mode" %}</dt>
<dd class="col-8">{{ start_mode }}</dd>
<dt class="col-4">{% trans "State" %}</dt>
<dd class="col-8">
{% if changeable is False %}
{% if state == 0 %}{% trans "Inactive" %}{% else %}{% trans "Active" %}{% endif %}
{% else %}
<form action="" method="post" role="form" aria-label="Interface start/stop/delete form">{% csrf_token %}
{% if state == 0 %}
<input type="submit" class="btn btn-sm btn-primary" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% endif %}
</form>
{% endif %}
</dd>
</dl>
</div>
<div class="col-sm-12">
<h5>{% trans 'Slaves' %}</h5>

View file

@ -38,33 +38,35 @@
</div>
<div class="row">
<dl class="ms-3 row">
<dt class="col-6">{% trans "Network name" %}</dt>
<dd class="col-6">{{ pool }}</dd>
<dt class="col-6">{% trans "Device" %}</dt>
<dd class="col-6">{{ device }}</dd>
<dt class="col-6">{% trans "State" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Network start/stop/delete form">{% csrf_token %}
{% if state == 0 %}
<input type="submit" class="btn btn-sm btn-success" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% endif %}
</form>
</dd>
<dt class="col-6">{% trans "Autostart" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Network enable/disable autostart form">{% csrf_token %}
{% if autostart == 0 %}
<input type="submit" class="btn btn-sm btn-success" name="set_autostart" value="{% trans "Enable" %}">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart" onclick="return confirm('{% trans "Are you sure?" %}')" value="{% trans "Disable" %}">
{% endif %}
</form>
</dd>
</dl>
<div class="col-12">
<dl class="ms-3 row">
<dt class="col-6">{% trans "Network name" %}</dt>
<dd class="col-6">{{ pool }}</dd>
<dt class="col-6">{% trans "Device" %}</dt>
<dd class="col-6">{{ device }}</dd>
<dt class="col-6">{% trans "State" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Network start/stop/delete form">{% csrf_token %}
{% if state == 0 %}
<input type="submit" class="btn btn-sm btn-success" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% endif %}
</form>
</dd>
<dt class="col-6">{% trans "Autostart" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Network enable/disable autostart form">{% csrf_token %}
{% if autostart == 0 %}
<input type="submit" class="btn btn-sm btn-success" name="set_autostart" value="{% trans "Enable" %}">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart" onclick="return confirm('{% trans "Are you sure?" %}')" value="{% trans "Disable" %}">
{% endif %}
</form>
</dd>
</dl>
</div>
</div>
<div class="d-grid col-lg-12">

View file

@ -20,8 +20,8 @@
<input type="hidden" name="nwfilterrule_xml"/>
<textarea id="rule_editor"></textarea>
</div>
<p class="text-muted">{% trans 'If there is a rule which has same attributes it replaces that rule' %}...</p>
</div>
<p class="text-muted">{% trans 'If there is a rule which has same attributes it replaces that rule' %}...</p>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary" name="add_nwfilter_rule">{% trans "Add" %}</button>

View file

@ -157,10 +157,6 @@ p {
margin-top: 65px;
}
.thumbnail {
padding: 10px;
}
/* make dropdowns show on hover */
@media only screen and (min-width: 768px) {
.dropdown:hover .dropdown-menu {

View file

@ -1,5 +1,6 @@
"""Every HTML page a superuser can open, rendered on libvirt's test driver."""
import re
from collections import Counter
from unittest.mock import patch
@ -70,3 +71,14 @@ class PageTests(TestCase):
bare = [t.get("class") for t in page.xpath("//table[not(parent::div[contains(@class, 'table-responsive')])]")]
with self.subTest(page=name):
self.assertEqual(bare, [])
def test_grid_columns_fit_a_phone(self):
# below a column's breakpoint it is full width, so an offset without one
# pushes it past the screen; a row needs columns inside, not another row
row = "contains(concat(' ', normalize-space(@class), ' '), ' row ')"
for name, page in self.pages():
offsets = [cls for cls in page.xpath("//@class")
if re.search(r"\boffset-\d+\b", cls) and not re.search(r"\bcol-\d+\b", cls)]
with self.subTest(page=name):
self.assertEqual(offsets, [])
self.assertEqual(page.xpath(f"//*[{row}]/*[{row}]/@class"), [])