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

fix(templates): each confirmation says what its action does

Twenty-five prompts asked only "Are you sure?", and on the storage page
one handler asked the same before every button, Start and Enable
included. Each destructive button now names its object and says what
happens: a stopped network cuts running VMs off, a deleted pool keeps
its files, a deleted volume loses its data, a reverted snapshot loses
the changes made since. Saves no longer ask, and disabled buttons lose
prompts they could not show. The VM QoS messages said to restart the
network where the VM has to be powered off and on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
catborise 2026-10-21 21:13:35 +03:00
parent bf6b736aef
commit 2144b3fe20
12 changed files with 70 additions and 49 deletions

View file

@ -323,7 +323,7 @@
<td style="width:5px;">
<form action="{% url 'instances:flavor_delete' flavor.id %}" method="post" role="form" aria-label="Delete flavor form">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-secondary" name="delete_flavor" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<button type="submit" class="btn btn-sm btn-secondary" name="delete_flavor" title="{% trans "Delete" %}" data-confirm="{% blocktrans with name=flavor.label %}Delete flavor {{ name }}? Only the size template goes; VMs made from it keep their size.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>

View file

@ -109,7 +109,7 @@
<div class="clearfix"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-success" name="edit_volume" onclick="return confirm('{% trans "Are you sure to change volume properties?" %}')">{% trans "Save" %}</button>
<button type="submit" class="btn btn-success" name="edit_volume">{% trans "Save" %}</button>
</div><!-- /.modal-footer -->
</div> <!-- /.modal-content -->

View file

@ -312,11 +312,11 @@
<input type="hidden" name="storage" value="{{ disk.storage }}">
<input type="hidden" name="name" value="{{ disk.image }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-secondary" value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure to detach volume?" %}')">
<button type="submit" class="btn btn-sm btn-secondary" value="{{ disk.dev }}" title="{% trans "Detach" %}" data-confirm="{% blocktrans with dev=disk.dev %}Detach {{ dev }}? Its file is kept.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'eject' %}
</button>
{% else %}
<button class="btn btn-sm btn-secondary disabled" disabled value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
<button class="btn btn-sm btn-secondary disabled" disabled value="{{ disk.dev }}" title="{% trans "Detach" %}">
{% bs_icon 'eject' %}
</button>
{% endif %}
@ -328,11 +328,11 @@
<input type="hidden" name="storage" value="{{ disk.storage }}">
<input type="hidden" name="name" value="{{ disk.image }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-secondary" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure to delete volume?" %}')">
<button type="submit" class="btn btn-sm btn-secondary" title="{% trans "Delete" %}" data-confirm="{% blocktrans with dev=disk.dev %}Delete {{ dev }}? It is detached and its file and data are removed.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
{% else %}
<button class="btn btn-sm btn-secondary disabled" disabled title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
<button class="btn btn-sm btn-secondary disabled" disabled title="{% trans "Delete" %}">
{% bs_icon 'trash' %}
</button>
{% endif %}
@ -478,7 +478,7 @@
<td>
<form action="{% url 'instances:delete_network' instance.id %}" method="post" name="delete_network" role="form">{% csrf_token %}
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
data-confirm="{% blocktrans with mac=network.mac %}Remove network card {{ mac }}? A running VM loses its connection through it at once.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>
@ -556,7 +556,7 @@
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary"
name="set_qos" data-bs-toggle="modal"
title="{% trans "Edit QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Edit QoS" %}">
<span>{% bs_icon 'save' %} </span>
</button>
</form>
@ -566,7 +566,7 @@
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-danger"
name="unset_qos"
title="{% trans "Delete QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Delete QoS" %}" data-confirm="{% blocktrans with direction=att.direction mac=q %}Remove the {{ direction }} limit of {{ mac }}? A running VM keeps it until it is powered off and on.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>

View file

@ -107,7 +107,7 @@
{% csrf_token %}{% submission_key %}
<input type="hidden" name="name" value="{{ snap.name }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-primary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')" {% if busy or held %}disabled{% endif %}>
<button type="submit" class="btn btn-sm btn-primary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" data-confirm="{% blocktrans with name=snap.name %}Revert to snapshot {{ name }}? Disk changes made since it are lost.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)" {% if busy or held %}disabled{% endif %}>
<span>{% bs_icon 'download' %} </span>
</button>
{% else %}
@ -121,7 +121,7 @@
<td style="width:30px;">
<form action="{% url 'instances:delete_snapshot' instance.id %}" method="post" role="form" aria-label="Delete snapshot form">{% csrf_token %}
<input type="hidden" name="name" value="{{ snap.name }}">
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')" {% if busy or held %}disabled{% endif %}>
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" data-confirm="{% blocktrans with name=snap.name %}Delete snapshot {{ name }}? The VM's current disks are not changed.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)" {% if busy or held %}disabled{% endif %}>
{% bs_icon 'trash' %}
</button>
</form>

View file

@ -1685,10 +1685,7 @@ def set_qos(request, pk):
else:
messages.success(
request,
_(
"%(qos_dir)s QoS is set. Network XML is changed. \
Stop and start network to activate new config."
)
_("%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on.")
% {"qos_dir": qos_dir.capitalize()},
)
@ -1714,10 +1711,7 @@ def unset_qos(request, pk):
else:
messages.success(
request,
_(
"%(qos_dir)s QoS is deleted. Network XML is changed. \
Stop and start network to activate new config."
)
_("%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is powered off and on.")
% {"qos_dir": qos_dir.capitalize()},
)
return _back(request, pk, "network")

View file

@ -63,9 +63,9 @@
<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?" %}')">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" data-confirm="{% blocktrans with name=iface %}Delete interface {{ name }}? Its definition is removed from the host.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" data-confirm="{% blocktrans with name=iface %}Stop interface {{ name }}? The host and its VMs lose their connections through it at once; if you reach the host through it, you lose the host.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% endif %}
</form>
{% endif %}

View file

@ -49,9 +49,9 @@
<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?" %}')">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}" data-confirm="{% blocktrans with name=pool %}Delete network {{ name }}? VMs that use it no longer start.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}" data-confirm="{% blocktrans with name=pool %}Stop network {{ name }}? Running VMs on it lose their connection through it at once.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% endif %}
</form>
</dd>
@ -61,7 +61,7 @@
{% 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" %}">
<input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart" data-confirm="{% blocktrans with name=pool %}Disable autostart of network {{ name }}? After the host restarts it stays stopped, and VMs that use it do not start.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)" value="{% trans "Disable" %}">
{% endif %}
</form>
</dd>
@ -136,7 +136,7 @@
value="Apply"
name="modify_dhcp_range"
title="{% trans "Edit DHCP Range" %}"
onclick="return confirm('{% trans "Are you sure?" %}')"/>
/>
</dd>
<input hidden name="family" value="ipv4"/>
</form>
@ -190,12 +190,12 @@
<input hidden name="family" value="ipv4"/>
<button type="submit" class="btn btn-sm btn-primary"
name="modify_fixed_address"
title="{% trans "Edit entry" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Edit entry" %}">
{% bs_icon 'save' %}
</button>
<button type="submit" class="btn btn-sm btn-danger"
name="delete_fixed_address"
title="{% trans "Delete entry" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Delete entry" %}" data-confirm="{% blocktrans with mac=fix4.mac %}Delete the fixed address of {{ mac }}? The VM may get another address when its lease ends.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</td>
@ -251,7 +251,7 @@
value="Apply"
name="modify_dhcp_range"
title="{% trans "Edit DHCP Range" %}"
onclick="return confirm('{% trans "Are you sure?" %}')"/>
/>
</dd>
<input hidden name="family" value="ipv6"/>
</form>
@ -301,12 +301,12 @@
<input hidden name="family" value="ipv6"/>
<button type="submit" class="btn btn-sm btn-primary"
name="modify_fixed_address"
title="{% trans "Edit entry" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Edit entry" %}">
<span>{% bs_icon 'save' %} </span>
</button>
<button type="submit" class="btn btn-sm btn-danger"
name="delete_fixed_address"
title="{% trans "Delete entry" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Delete entry" %}" data-confirm="{% blocktrans with id=fix6.id %}Delete the fixed address of {{ id }}? The VM may get another address when its lease ends.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
<span>{% bs_icon 'trash' %} </span>
</button>
</td>
@ -358,12 +358,12 @@
<input name="qos_direction" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary"
name="set_qos" data-bs-toggle="modal"
title="{% trans "Edit QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Edit QoS" %}">
{% bs_icon 'save' %}
</button>
<button type="submit" class="btn btn-sm btn-danger"
name="unset_qos"
title="{% trans "Delete QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
title="{% trans "Delete QoS" %}" data-confirm="{% blocktrans with direction=q name=pool %}Remove the {{ direction }} limit of network {{ name }}? It stays in force until the network is stopped and started.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</td>

View file

@ -104,7 +104,7 @@
<td style="width:30px;">
<form action="" method="post" role="form" aria-label="Delete NWFilter form">{% csrf_token %}
<input type="hidden" name="ref" value="{{ ref }}">
<button type="submit" class="btn btn-sm btn-danger" name="del_nwfilter_ref" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}');">
<button type="submit" class="btn btn-sm btn-danger" name="del_nwfilter_ref" title="{% trans "Delete" %}" data-confirm="{% blocktrans with ref=ref %}Remove the reference to {{ ref }}? Its rules no longer apply through this filter.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>
@ -148,7 +148,7 @@
<input type="hidden" name="action" value="{{ rule.action }}">
<input type="hidden" name="direction" value="{{ rule.direction }}">
<input type="hidden" name="priority" value="{{ rule.priority }}">
<button type="submit" class="btn btn-sm btn-danger" name="del_nwfilter_rule" title="{% trans 'Delete' %}" onclick="return confirm('{% trans "Are you sure?" %}');">
<button type="submit" class="btn btn-sm btn-danger" name="del_nwfilter_rule" title="{% trans 'Delete' %}" data-confirm="{% trans "Delete this rule? Interfaces that use the filter no longer get it." %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>

View file

@ -116,7 +116,7 @@
<td>
<form action="" method="post" role="form" aria-label="Delete NWFilter form">{% csrf_token %}
<input type="hidden" name="nwfiltername" value="{{ nwfilter.name }}">
<button type="submit" class="btn btn-sm btn-secondary" name="del_nwfilter" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<button type="submit" class="btn btn-sm btn-secondary" name="del_nwfilter" title="{% trans "Delete" %}" data-confirm="{% blocktrans with name=nwfilter.name %}Delete filter {{ name }}? A filter a VM uses is not deleted.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>

View file

@ -59,24 +59,27 @@
<dd class="col-6">{{ used|filesizeformat }}</dd>
<dt class="col-6">{% trans "State" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Storage start/stop form" class="confirm-form">{% csrf_token %}
<form action="" method="post" role="form" aria-label="Storage start/stop form">{% csrf_token %}
{% if state == 0 %}
<input type="submit" class="btn btn-sm btn-secondary" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}"
data-confirm="{% blocktrans with name=pool %}Delete pool {{ name }}? Only its definition goes; its volumes and their files stay.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}">
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}"
data-confirm="{% blocktrans with name=pool %}Stop pool {{ name }}? Its files stay, but until it is started again its volumes cannot be managed and VMs with disks in it may not start.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% endif %}
</form>
</dd>
<dt class="col-6">{% trans "Autostart" %}</dt>
<dd class="col-6">
<form action="" method="post" role="form" aria-label="Storage disable/enable autostart form" class="confirm-form">{% csrf_token %}
<form action="" method="post" role="form" aria-label="Storage disable/enable autostart form">{% csrf_token %}
{% if autostart == 0 %}
<input type="submit" class="btn btn-sm btn-secondary" name="set_autostart"
value="{% trans "Enable" %}">
{% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart"
value="{% trans "Disable" %}">
value="{% trans "Disable" %}"
data-confirm="{% blocktrans with name=pool %}Disable autostart of pool {{ name }}? After the host restarts it stays stopped.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% endif %}
</form>
</dd>
@ -172,9 +175,10 @@
{% endif %}
</td>
<td>
<form action="" method="post" role="form" aria-label="Delete volume form" class="confirm-form">{% csrf_token %}
<form action="" method="post" role="form" aria-label="Delete volume form">{% csrf_token %}
<input type="hidden" name="volname" value="{{ volume.name }}">
<button type="submit" class="btn btn-sm btn-secondary" name="del_volume" title="{% trans "Delete" %}">
<button type="submit" class="btn btn-sm btn-secondary" name="del_volume" title="{% trans "Delete" %}"
data-confirm="{% blocktrans with name=volume.name %}Delete volume {{ name }}? Its file and data are removed; a volume a VM uses is not deleted.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
{% bs_icon 'trash' %}
</button>
</form>
@ -221,12 +225,6 @@
});
$(document).ready(function() {
$('.confirm-form').on('submit', function(e) {
if (!confirm('{% trans "Are you sure?"|escapejs %}')) {
e.preventDefault();
}
});
var pendingUpload = null;
var uploadInProgress = false;
// the browser sends the file: leaving the page stops the upload

View file

@ -76,7 +76,7 @@
<a data-bs-toggle="modal" href="#editSecret{{ secret.uuid }}" class="btn btn-sm btn-primary" title="{% trans "Edit" %}">
<span>{% bs_icon 'pencil' %} </span>
</a>
<button type="submit" class="btn btn-sm btn-danger" name="delete" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<button type="submit" class="btn btn-sm btn-danger" name="delete" title="{% trans "Delete" %}" data-confirm="{% blocktrans with uuid=secret.uuid %}Delete secret {{ uuid }}? Its value is gone; disks and pools that need it no longer start.{% endblocktrans %}" onclick="return confirm(this.dataset.confirm)">
<span>{% bs_icon 'trash' %} </span>
</button>
</form>

View file

@ -0,0 +1,29 @@
"""A confirmation says what its action does: every prompt in a template is the
button's own data-confirm text, shown by return confirm(this.dataset.confirm)."""
from pathlib import Path
from django.conf import settings
from django.test import SimpleTestCase
ONCLICK = 'onclick="return confirm(this.dataset.confirm)"'
class ConfirmPromptTests(SimpleTestCase):
def test_every_prompt_is_the_buttons_own_text(self):
base = Path(settings.BASE_DIR)
templates = [*base.glob("*/templates/**/*.html"), *base.glob("templates/**/*.html")]
self.assertTrue(templates)
for path in templates:
text = path.read_text()
at = text.find("confirm(")
while at != -1:
# the tag around it: prompt texts and template tags hold no < or >
tag = text[text.rfind("<", 0, at):text.find(">", at) + 1]
with self.subTest(template=str(path.relative_to(base)), tag=tag[:60]):
self.assertRegex(tag, r"^<(button|input|a)\b")
self.assertIn(ONCLICK, tag)
self.assertIn('data-confirm="', tag)
self.assertNotIn('data-confirm=""', tag)
self.assertNotIn("Are you sure", tag)
at = text.find("confirm(", at + 1)