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

fix(templates): pages parse without markup errors, element IDs are unique

Rendered through every URL on libvirt's test driver, the pages had
duplicate IDs and markup browsers repaired silently:

- Each flavor modal's "Advanced" button toggled #flav-add-collapse, the
  first modal's section; every modal now has its own. The flavor and
  template copies of the custom tab's control IDs, and the template
  tab's unused images input, are gone.
- The VM page's action icons and the XML editors were styled by
  repeated IDs; they use classes.
- Modal forms opened inside modal-body and closed after modal-footer;
  they wrap both. Stray end tags and duplicate attributes are removed,
  paragraphs no longer hold modals or rows, table heads have a row, and
  the create page closes its row and column.
- html lang is the active language instead of "en".

A test walks every URL as a superuser and fails on a repeated ID. The
console page still has no doctype: its full-height layout relies on
quirks mode and needs its own check.
This commit is contained in:
catborise 2026-10-18 23:29:00 +03:00
parent 2f58e18a22
commit 3a4488d34f
24 changed files with 157 additions and 97 deletions

View file

@ -53,7 +53,7 @@
</td>
<td>{{ user.last_login }}</td>
<td>{% if user.is_staff %}{% bs_icon 'check' %}{% endif %}</td>
<td>{% if user.is_superuser %}{% bs_icon 'check' %}</span>{% endif %}</td>
<td>{% if user.is_superuser %}{% bs_icon 'check' %}{% endif %}</td>
<td>{% if can_clone %}{% bs_icon 'check' %}{% endif %}</td>
<td>
<div class="float-end btn-group">

View file

@ -113,7 +113,6 @@
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static "js/chart.umd.min.js" %}"></script>

View file

@ -1,6 +1,6 @@
{% load static %}
{% load i18n %}
<html>
{% get_current_language as LANGUAGE_CODE %}<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta charset="utf-8">

View file

@ -13,8 +13,8 @@
<h5 class="modal-title">{% trans "Add Instance Network" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="{% url 'instances:add_network' instance.id %}" method="post" action="" role="form" aria-label="Add instance network form">{% csrf_token %}
<form action="{% url 'instances:add_network' instance.id %}" method="post" role="form" aria-label="Add instance network form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "MAC" %}</label>
<div class="col-sm-6">

View file

@ -90,13 +90,13 @@
<input type="checkbox" name="meta_prealloc" value="true">
</div>
</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="add_new_vol">{% trans "Add Volume" %}</button>
</div>
</form>
</div> <!-- /.modal-body -->
<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="add_new_vol">{% trans "Add Volume" %}</button>
</div>
</div> <!-- /.modal-body -->
</form>
</div>
<div class="tab-pane" id="ExistingDisk">
<form action="{% url 'instances:add_existing_vol' instance.id %}" method="post" role="form" aria-label="Add existing volume to instance form">{% csrf_token %}
<div class="modal-body">

View file

@ -77,7 +77,7 @@
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromXML">
<div>
<form method="post" role="form" aria-label="Create instance with XML form">{% csrf_token %}
<div class="col-sm-12" id="xmlheight">
<div class="col-sm-12 xmlheight">
<input type="hidden" name="dom_xml"/>
<textarea id="editor"></textarea>
</div>

View file

@ -101,8 +101,8 @@
<h5 class="modal-title">{% trans "Create Virtual Machine" %} ({{ flavor.label }})</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" role="form" aria-label="Create instance from flavor form">{% csrf_token %}{% submission_key %}
<form method="post" role="form" aria-label="Create instance from flavor form">{% csrf_token %}{% submission_key %}
<div class="modal-body">
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-7">
@ -118,7 +118,7 @@
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Firmware" %}</label>
<div class="col-sm-7">
<select class="form-select" id="select_firmware" name="firmware">
<select class="form-select" name="firmware">
{% for frm in firmwares %}
<option value="{{ frm }}" {% if frm in default_firmware %}selected{% endif %}>{{ frm }}</option>
{% endfor %}
@ -177,12 +177,12 @@
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Advanced" %}</label>
<div class="d-grid col-sm-7">
<button class="btn btn-outline-light" type="button" data-bs-toggle="collapse" data-bs-target="#flav-add-collapse" aria-expanded="false" aria-controls="flav-add-collapse">
<button class="btn btn-outline-light" type="button" data-bs-toggle="collapse" data-bs-target="#flav-add-collapse-{{ flavor.id }}" aria-expanded="false" aria-controls="flav-add-collapse-{{ flavor.id }}">
<span>{% bs_icon 'caret-down'%} </span>
</button>
</div>
</div>
<div class="collapse" id="flav-add-collapse">
<div class="collapse" id="flav-add-collapse-{{ flavor.id }}">
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "MAC" %}</label>
<div class="col-sm-7">
@ -213,7 +213,7 @@
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "HDD cache mode" %}</label>
<div class="col-sm-7">
<select class="form-select" id="cache_mode" name="cache_mode">
<select class="form-select" name="cache_mode">
{% for mode, name in cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected {% endif %}>
{% trans name %}</option>
@ -248,7 +248,7 @@
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Add CD-Rom" %}</label>
<div class="col-sm-7">
<select class="form-select" id="add_cdrom" name="add_cdrom">
<select class="form-select" name="add_cdrom">
<option value="None" {% if bus == default_cdrom %}selected{% endif %}>--{% trans "False" %}--</option>
{% for bus in cdrom_buses %}
<option value="{{ bus }}" {% if bus == default_cdrom %}selected {% endif %}>
@ -261,7 +261,7 @@
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Add Input Device" %}</label>
<div class="col-sm-7">
<select class="form-select" id="add_input" name="add_input">
<select class="form-select" name="add_input">
<option value="None" {% if bus == default_input_device_bus %}selected{% endif %}>--{% trans "False" %}--</option>
{% for bus in input_device_buses %}
<option value="{{ bus }}" {% if bus == default_input_device_bus %}selected{% endif %}>
@ -564,7 +564,7 @@
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Firmware" %}</label>
<div class="col-sm-7">
<select class="form-select" id="select_firmware" name="firmware">
<select class="form-select" name="firmware">
{% for frm in firmwares %}
<option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option>
{% endfor %}
@ -608,7 +608,6 @@
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Template Disk" %}</label>
<input id="images" name="images" type="hidden" value=""/>
<div class="col-sm-3">
<select class="form-select" onchange="get_template_vols({{ compute_id }}, value);">
{% if storages %}
@ -673,7 +672,7 @@
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "HDD cache mode" %}</label>
<div class="col-sm-7">
<select class="form-select" id="cache_mode" name="cache_mode">
<select class="form-select" name="cache_mode">
{% for mode, name in cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>
{% trans name %}</option>
@ -729,7 +728,7 @@
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Add CD-Rom" %}</label>
<div class="col-sm-7">
<select class="form-select" id="add_cdrom" name="add_cdrom">
<select class="form-select" name="add_cdrom">
<option value="None" {% if bus == default_cdrom %}selected{% endif %}>--{% trans "False" %}--</option>
{% for bus in cdrom_buses %}
<option value="{{ bus }}" {% if bus == default_cdrom %}selected {% endif %}>
@ -742,7 +741,7 @@
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Add Input Device" %}</label>
<div class="col-sm-7">
<select class="form-select" id="add_input" name="add_input">
<select class="form-select" name="add_input">
<option value="None" {% if bus == default_input_device_bus %}selected{% endif %}>--{% trans "False" %}--</option>
{% for bus in input_device_buses %}
<option value="{{ bus }}" {% if bus == default_input_device_bus %}selected{% endif %}>
@ -786,6 +785,8 @@
</div>
</div><!-- /Tab panes -->
</div>
</div>
</div>
{% endblock %}
{% block script %}

View file

@ -71,26 +71,26 @@
<ul class="nav nav-pills nav-fill" id="navbtn" role="tablist" aria-label="Instance actions">
<li class="nav-item" role="presentation">
<button class="nav-link action-button active" id="power-tab" aria-controls="power" data-bs-toggle="pill" data-bs-target="#power" type="button" role="tab" aria-selected="true">
<span id="action-block" aria-hidden="true">{% bs_icon 'power' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'power' size='2em'%} </span>
{% trans "Power" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="access-tab" aria-controls="access" data-bs-toggle="pill" data-bs-target="#access" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'lock' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'lock' size='2em'%} </span>
{% trans "Access" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="resize-tab" aria-controls="resize" data-bs-toggle="pill" data-bs-target="#resize" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'arrows-angle-expand' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'arrows-angle-expand' size='2em'%} </span>
{% trans "Resize" %}
</button>
</li>
{% if can_snapshot %}
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="snapshots-tab" aria-controls="snapshots" data-bs-toggle="pill" data-bs-target="#snapshots" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'camera' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'camera' size='2em'%} </span>
{% trans "Snapshot" %}
</button>
</li>
@ -98,20 +98,20 @@
{% if show_settings %}
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="settings-tab" aria-controls="settings" data-bs-toggle="pill" data-bs-target="#settings" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'gear-wide-connected' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'gear-wide-connected' size='2em'%} </span>
{% trans "Settings" %}
</button>
</li>
{% endif %}
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="graphics-tab" aria-controls="graphics" data-bs-toggle="pill" data-bs-target="#graphics" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'bar-chart-line' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'bar-chart-line' size='2em'%} </span>
{% trans "Stats" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link action-button" id="undefine-tab" aria-controls="undefine" data-bs-toggle="pill" data-bs-target="#undefine" type="button" role="tab" aria-selected="false">
<span id="action-block" aria-hidden="true">{% bs_icon 'trash' size='2em'%} </span>
<span class="action-block" aria-hidden="true">{% bs_icon 'trash' size='2em'%} </span>
{% trans "Destroy" %}
</button>
</li>

View file

@ -85,7 +85,7 @@
<p class="fw-bold">{% trans 'Autostart' %}</p>
<div class="row">
<div class="col-sm-12 text-center">
<p>{% trans "Autostart your instance when host server is power on " %}
<p>{% trans "Autostart your instance when host server is power on " %}</p>
{% if instance.autostart == 0 %}
<form action="{% url 'instances:set_autostart' instance.id %}" method="post" class="d-inline">{% csrf_token %}
<button type="submit" class="btn btn-success">{% trans "Enable" %}</button>
@ -95,7 +95,6 @@
<button type="submit" class="btn btn-danger">{% trans "Disable" %}</button>
</form>
{% endif %}
</p>
</div>
</div>
<p class="fw-bold">{% trans 'Boot Order' %}</p>
@ -165,7 +164,7 @@
<p class="fw-bold">
{% trans "Instance Media" %}
<button class="btn btn-success float-end"
type="submit" type="button"
type="submit"
title="{% trans 'Add CD-ROM' %}"
{% if instance.status != 5 %} disabled {% endif %}>
<span>{% bs_icon 'plus' %} </span>
@ -245,10 +244,10 @@
</div>
{% endfor %}
<div class="clearfix"></div>
<p class="fw-bold">
{% trans "Instance Volume" %}
<div class="mb-3">
<span class="fw-bold">{% trans "Instance Volume" %}</span>
{% include 'add_instance_volume.html' %}
</p>
</div>
<table class="table table-hover">
<thead>
<tr>
@ -345,10 +344,10 @@
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="network">
<p>
<div class="mb-3">
{% trans "Add a network device" %}
{% include 'add_instance_network_block.html' %}
</p>
</div>
<div class="row mt-3">
<div class="col-lg-12 mt-3">
@ -655,7 +654,7 @@
{% endif %}
<form action="{% url 'instances:change_xml' instance.id %}" method="post" role="form" aria-label="Edit instance XML form">
{% csrf_token %}
<div class="col-sm-12" id="xmlheight">
<div class="col-sm-12 xmlheight">
<textarea id="editor">{{ instance.inst_xml }}</textarea>
</div>
{% if instance.status == 5 %}
@ -673,10 +672,10 @@
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
<div>
<p class="fw-bold">
{% trans "Instance owners" %}
<div class="mb-3">
<span class="fw-bold">{% trans "Instance owners" %}</span>
{% include 'add_instance_owner_block.html' %}
</p>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap mt-3" data-sortable>
@ -749,7 +748,7 @@
{% if request.user.is_superuser %}
<label>{% trans "Network devices" %}:</label>
{% for network in instance.config_networks %}
<p>
<div class="mb-3">
<div class="row">
<label class="col-sm-2 col-form-label offset-1">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-6">
@ -762,7 +761,7 @@
</div>
</div>
</div>
</p>
</div>
{% endfor %}
{% else %}
{% for network in instance.config_networks %}

View file

@ -89,10 +89,12 @@
<div class="table-responsive">
<table class="table" id="internal-snapshots">
<thead>
<th scope="col">{% trans "Date" %}</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Description" %}</th>
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
<tr>
<th scope="col">{% trans "Date" %}</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Description" %}</th>
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for snap in instance.internal_snapshots %}
@ -135,10 +137,12 @@
<div class="table-responsive">
<table class="table" id="external-snapshots">
<thead>
<th scope="col">{% trans "Date" %}</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Description" %}</th>
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
<tr>
<th scope="col">{% trans "Date" %}</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Description" %}</th>
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for ext_snap in instance.external_snapshots %}

View file

@ -13,8 +13,8 @@
<h5 class="modal-title">{% trans "Create New Interface" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create New Interface form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create New Interface form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">

View file

@ -13,8 +13,8 @@
<h5 class="modal-title">{% trans "Add New Network" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Add New Network form" novalidate>{% csrf_token %}
<form method="post" action="" role="form" aria-label="Add New Network form" novalidate>{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">

View file

@ -75,7 +75,7 @@
<div class="collapse" id="collapseXML">
<div class="card card-body">
<form method="post" role="form" aria-label="Network edit with XML form">{% csrf_token %}
<div id="xmlheight">
<div class="xmlheight">
<input type="hidden" name="edit_xml"/>
<textarea id="edit_editor">{{ xml }}</textarea>
</div>

View file

@ -16,7 +16,7 @@
<div class="modal-body">
<form method="post" role="form" aria-label="Add new NWFilter rule form">{% csrf_token %}
<div class="row">
<div class="col-12" id="xmlheight">
<div class="col-12 xmlheight">
<input type="hidden" name="nwfilterrule_xml"/>
<textarea id="rule_editor"></textarea>
</div>

View file

@ -16,7 +16,7 @@
<form method="post" role="form" aria-label="Create new NWFilter form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<div class="col-12" id="xmlheight">
<div class="col-12 xmlheight">
<input type="hidden" name="nwfilter_xml"/>
<textarea id="editor" name="from_xml"></textarea>
</div>

View file

@ -52,7 +52,7 @@
<div class="collapse" id="collapseXML">
<div class="card card-body">
<form class="form" method="post" role="form">{% csrf_token %}
<div class="col-12" id="xmlheight">
<div class="col-12 xmlheight">
<input type="hidden" name="edit_xml"/>
<textarea id="edit_editor">{{ xml }}</textarea>
</div>
@ -89,9 +89,11 @@
<div class="col-12 col-sm-12">
<table class="table table-hover mb-3">
<thead>
<th scope="col">#</th>
<th scope="col">{% trans "Reference" %}</th>
<th scope="col">{% trans "Action" %}</th>
<tr>
<th scope="col">#</th>
<th scope="col">{% trans "Reference" %}</th>
<th scope="col">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for ref in refs %}
@ -119,13 +121,15 @@
<table class="table table-hover">
<thead>
<th scope="col">{% trans "Rule" %}</th>
<th scope="col" class="text-nowrap">{% trans "Action Type" %}</th>
<th scope="col">{% trans "Direction" %}</th>
<th scope="col">{% trans "Priority" %}</th>
<th scope="col">{% trans "Statematch" %}</th>
<th scope="col">{% trans "Directives" %}</th>
<th scope="col" style="width:30px;">{% trans "Action" %}</th>
<tr>
<th scope="col">{% trans "Rule" %}</th>
<th scope="col" class="text-nowrap">{% trans "Action Type" %}</th>
<th scope="col">{% trans "Direction" %}</th>
<th scope="col">{% trans "Priority" %}</th>
<th scope="col">{% trans "Statematch" %}</th>
<th scope="col">{% trans "Directives" %}</th>
<th scope="col" style="width:30px;">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for rule in rules %}
@ -150,8 +154,6 @@
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block script %}

View file

@ -26,7 +26,7 @@ body {
left: 0;
}
#xmlheight {
.xmlheight {
height: 364px;
}
@ -42,7 +42,7 @@ body {
margin-top: 5px;
}
#action-block {
.action-block {
display: block;
padding: 15px 0;
font-weight: 400;

View file

@ -44,8 +44,8 @@
<div class="tab-content">
<div class="tab-pane active" id="dirtab">
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create storage with pool dir path form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create storage with pool dir path form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Type" %}</label>
<div class="col-sm-6">
@ -73,8 +73,8 @@
</form>
</div>
<div class="tab-pane" id="lvmtab">
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create storage pool with LVM pool">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create storage pool with LVM pool">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Type" %}</label>
<div class="col-sm-6">
@ -102,8 +102,8 @@
</form>
</div>
<div class="tab-pane" id="cephtab">
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create storage pool with ceph form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create storage pool with ceph form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Type" %}</label>
<div class="col-sm-6">
@ -167,8 +167,8 @@
</form>
</div>
<div class="tab-pane" id="netfstab">
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create storage pool with net filesystem form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create storage pool with net filesystem form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Type" %}</label>
<div class="col-sm-6">
@ -223,8 +223,8 @@
</form>
</div>
<div class="tab-pane" id="isotab">
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create iso pool form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create iso pool form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">

View file

@ -86,10 +86,10 @@
{% if state %}
<p>
<div>
{% include 'search_block.html' %}
<h5 class="page-header">{% trans "Volumes" %}</h5>
</p>
</div>
{% if volumes %}
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
<thead>
@ -190,7 +190,6 @@
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block script %}

View file

@ -1,7 +1,8 @@
{% load static %}
{% load django_bootstrap5 %}
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
{% get_current_language as LANGUAGE_CODE %}<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -40,7 +41,7 @@
{% endif %}
<div role="main" class="container">
<div class"row">
<div>
<div>
<div class="float-end">
{% block page_heading_extra %}

View file

@ -33,7 +33,7 @@
<a class="dropdown-item {% view_active request 'admin:group_list' %}" href="{% url 'admin:group_list' %}">{% bs_icon 'people-fill' %} {% trans "Groups" %}</a>
<a class="dropdown-item {% view_active request 'admin:logs' %}" href="{% url 'admin:logs' %}">{% bs_icon 'card-list' %} {% trans "Logs" %}</a>
<a class="dropdown-item {% view_active request 'admin:tombstone_list' %}" href="{% url 'admin:tombstone_list' %}">{% bs_icon 'trash' %} {% trans "Removed VMs" %}</a>
<a class="dropdown-item {% view_active request 'appsettings' %}" href="{% url 'appsettings' %}">{% bs_icon 'gear-wide-connected' %} {% trans "Settings" %}</i></a>
<a class="dropdown-item {% view_active request 'appsettings' %}" href="{% url 'appsettings' %}">{% bs_icon 'gear-wide-connected' %} {% trans "Settings" %}</a>
</div>
</li>
{% endif %}

View file

@ -11,10 +11,10 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Create New Secret" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Create Secret form">{% csrf_token %}
<form method="post" action="" role="form" aria-label="Create Secret form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Ephemeral" %}</label>
<div class="col-sm-6">

View file

@ -90,8 +90,8 @@
<h5 class="modal-title">{% trans "Set secret value" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" role="form" aria-label="Edit secret form">{% csrf_token %}
<form method="post" role="form" aria-label="Edit secret form">{% csrf_token %}
<div class="modal-body">
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Value" %}</label>
<input type="hidden" name="uuid" value="{{ secret.uuid }}">

View file

@ -0,0 +1,55 @@
"""Every HTML page a superuser can open has unique element IDs: a repeated
ID makes getElementById, labels and Bootstrap targets reach the first one
only. Pages are rendered on libvirt's test driver."""
from collections import Counter
from unittest.mock import patch
import libvirt
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import NoReverseMatch, URLPattern, URLResolver, get_resolver, reverse
from instances.models import Flavor, Instance
from lxml import html
def named_urls(patterns, prefix="", keys=()):
for p in patterns:
if isinstance(p, URLResolver):
yield from named_urls(p.url_patterns, f"{prefix}{p.namespace}:" if p.namespace else prefix,
keys + tuple(p.pattern.regex.groupindex))
elif isinstance(p, URLPattern) and p.name:
yield prefix + p.name, keys + tuple(p.pattern.regex.groupindex)
class UniqueIdTests(TestCase):
def test_no_page_repeats_an_id(self):
conn = libvirt.open("test:///default")
self.addCleanup(conn.close)
compute = Compute.objects.create(name="testdrv", hostname="localhost", login="", password="", type=4)
vm = Instance.objects.create(compute=compute, name="test", uuid=conn.lookupByName("test").UUIDString())
Flavor.objects.create(label="second", memory=512, vcpu=1, disk=10) # the flavor modals repeat per flavor
self.client.force_login(get_user_model().objects.create_superuser("ids", "ids@example.com", "pw"))
self.client.raise_request_exception = False
values = {"pk": vm.id, "compute_id": compute.id, "pool": "default-pool", "arch": "i686", "machine": "pc"}
checked = []
with patch("vrtManager.connection.connection_manager.get_connection", return_value=conn), \
patch("vrtManager.connection.wvmConnect.get_machine_types", return_value=["pc"]), \
patch("vrtManager.connection.wvmConnect.get_nwfilters", return_value=[]), \
patch.object(libvirt.virConnect, "getMemoryStats", return_value={"total": 8 << 20, "free": 4 << 20, "buffers": 0, "cached": 0}), \
patch.object(libvirt.virConnect, "getCPUStats", return_value={"kernel": 1, "idle": 1, "user": 1, "iowait": 0}):
for name, keys in named_urls(get_resolver().url_patterns):
try:
url = reverse(name, kwargs={k: values.get(k, 1) for k in keys})
except NoReverseMatch:
continue
response = self.client.get(url)
if response.status_code != 200 or not response["Content-Type"].startswith("text/html"):
continue
checked.append(name)
ids = Counter(html.fromstring(response.content).xpath("//@id"))
with self.subTest(page=name):
self.assertEqual({i: n for i, n in ids.items() if n > 1}, {})
self.assertTrue({"index", "overview", "instances:instance", "nwfilter"} <= set(checked), checked)
self.assertIn("instances:create_instance", checked)