1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

feat(instances): the VM page's add dialogs in the panel's dialog look

The Add disk, Add network device and bandwidth dialogs of the VM page
set their labels in a narrow column beside the fields and closed with
a grey Close: Add disk split "GB" over two lines and offered an
unexplained Metadata box, its existing-volume pool was a dropdown of
links that wrote a hidden field; the network list prefixed every
choice with "Network" or "Interface"; the bandwidth fields asked for
"kilobytes" where libvirt counts KiB/s and KiB.

Now their labels sit above the fields, tied to them, in two columns.
Add disk is one form whose source, New disk or Existing volume, is a
radio card as in the new VM form, the chosen source's fields shown and
sent and its action taken; the size has a GiB suffix and
"Preallocate metadata (qcow2)", the existing volume's pool is a select
that loads its volumes, the bus and the cache are shared. Add network
device picks its
source from Networks and Interfaces groups and says how its MAC is
filled; the bandwidth limit names its units and when a running VM gets
it. Move says when the host has no other pool and is disabled then.
Every dialog closes with Cancel and its action; the posted fields are
unchanged.
This commit is contained in:
catborise 2026-10-31 17:44:20 +03:00
parent fd080438c3
commit 0fd3e69c8e
8 changed files with 220 additions and 248 deletions

View file

@ -1,157 +1,123 @@
{% load i18n %}
{% load static %}
{% load bootstrap_icons %}
{% if request.user.is_superuser %}
<a href="#addvol" type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal">{% bs_icon 'plus' %} {% trans "Add disk" %}</a>
<!-- Modal pool -->
{# one form: a card per disk source, the fields of the chosen one (the others hidden and disabled, so not sent); the source chooses the form's action #}
<div class="modal fade" id="addvol" tabindex="-1" role="dialog" aria-labelledby="addInstanceVolumeLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form class="modal-content" id="addvol-form" method="post" action="{% url 'instances:add_new_vol' instance.id %}"
data-new="{% url 'instances:add_new_vol' instance.id %}" data-existing="{% url 'instances:add_existing_vol' instance.id %}"
aria-label="{% trans "Add disk form" %}">{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title">{% trans "Add Instance Volume" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title" id="addInstanceVolumeLabel">{% trans "Add disk" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
</div>
<ul class="nav nav-tabs" aria-label="Disk attach type">
<li class="nav-item" role="presentation">
<button class="nav-link active" aria-controls="NewDisk" data-bs-toggle="tab" data-bs-target="#NewDisk" type="button" role="tab" aria-selected="true">
{% trans 'New Disk' %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" aria-controls="ExistingDisk" data-bs-toggle="tab" data-bs-target="#ExistingDisk" type="button" role="tab" aria-selected="true">
{% trans 'Existing Disk' %}
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="NewDisk">
<form action="{% url 'instances:add_new_vol' instance.id %}" method="post" role="form" aria-label="Add new volume to disk form">{% csrf_token %}
<div class="modal-body">
<p class="fw-bold">{% trans "Volume parameters" %}</p>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Storage" %}</label>
<div class="col-sm-6">
<select name="storage" class="form-select image-format">
{% for storage in storages_host %}
<option value="{{ storage }}">{{ storage }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" required pattern="[a-zA-Z0-9\.\-_]+">
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Format" %}</label>
<div class="col-sm-6">
<select name="format" class="form-select image-format">
{% for format in instance.formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Size" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="size" value="10" maxlength="5" required pattern="[0-9]+">
</div>
<label class="col-sm-1 col-form-label">{% trans "GB" %}</label>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Bus" %}</label>
<div class="col-sm-6">
<select name="bus" class="form-select image-format">
{% for bus in bus_host %}
<option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
<div class="col-sm-6">
<select name="cache" class="form-select image-format">
{% for mode, name in instance.cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row meta-prealloc">
<label class="col-sm-3 col-form-label">{% trans "Metadata" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="meta_prealloc" value="true">
</div>
</div>
</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-primary" name="add_new_vol">{% trans "Add Volume" %}</button>
<div class="modal-body">
<fieldset class="mb-3">
<legend class="form-label fs-6">{% trans "Disk source" %}</legend>
<div class="row row-cols-1 row-cols-sm-2 g-2">
{% trans "New disk" as title %}{% trans "An empty disk of the size you set, made in a pool." as help %}
{% include "radio_card.html" with name="disk_source" value="new" chosen="new" %}
{% trans "Existing volume" as title %}{% trans "A volume already in a pool of the host." as help %}
{% include "radio_card.html" with name="disk_source" value="existing" chosen="new" %}
</div>
</fieldset>
<fieldset data-kinds="new">
<div class="row g-3">
<div class="col-sm-6">
<label class="form-label" for="addvol-storage">{% trans "Storage pool" %}</label>
<select id="addvol-storage" name="storage" class="form-select">
{% for storage in storages_host %}
<option value="{{ storage }}">{{ storage }}</option>
{% endfor %}
</select>
</div>
</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">
<p class="fw-bold">{% trans "Volume parameters" %}</p>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Storage" %}</label>
<div class="col-sm-6">
<div class="dropdown">
<button id="select_storage" class="btn btn-secondary dropdown-toggle form-control" type="button" data-bs-toggle="dropdown">{% trans 'Select Pool' %}...</button>
<div class="dropdown-menu">
{% for storage in storages_host %}
<a class="dropdown-item" href="#" onclick="get_volumes({{ instance.compute.id }}, '{{ storage }}')">{{ storage }}</a>
{% endfor %}
</div>
<input id="selected_storage" name="selected_storage" hidden/>
</div>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label" >{% trans "Volume" %}</label>
<div class="col-sm-6">
<select id="vols" name="vols" class="form-select" disabled>
<option value="" selected>{% trans 'None' %}</option>
<!-- populate with javascript -->
</select>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Bus" %}</label>
<div class="col-sm-6">
<select name="bus" class="form-select image-format">
{% for bus in bus_host %}
<option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
<div class="col-sm-6">
<select name="cache" class="form-select image-format">
{% for mode, name in instance.cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="addvol-name">{% trans "Name" %}</label>
<input id="addvol-name" type="text" class="form-control" name="name" required pattern="[a-zA-Z0-9\.\-_]+" aria-describedby="addvol-name-help">
<div class="form-text" id="addvol-name-help">{% trans "Letters, digits, dot, dash and underscore." %}</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="addvol-size">{% trans "Size" %}</label>
<div class="input-group">
<input id="addvol-size" type="text" class="form-control" name="size" value="10" maxlength="5" required pattern="[0-9]+" inputmode="numeric" aria-describedby="addvol-size-unit">
<span class="input-group-text" id="addvol-size-unit">GiB</span>
</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-primary">{% trans "Add Volume" %}</button>
</div>
<div class="col-sm-6">
<label class="form-label" for="addvol-format">{% trans "Format" %}</label>
<select id="addvol-format" name="format" class="form-select">
{% for format in instance.formats %}
<option value="{{ format }}"{% if format == default_format %} selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="addvol-prealloc" name="meta_prealloc" value="true">
<label class="form-check-label" for="addvol-prealloc">{% trans "Preallocate metadata (qcow2)" %}</label>
</div>
</form>
</div>
</div>
</fieldset>
<fieldset data-kinds="existing">
<div class="row g-3">
<div class="col-sm-6">
<label class="form-label" for="selected_storage">{% trans "Storage pool" %}</label>
<select id="selected_storage" name="selected_storage" class="form-select" required
onchange="get_volumes({{ instance.compute.id }}, this.value)">
<option value="" selected disabled>{% trans "Choose a pool" %}</option>
{% for storage in storages_host %}
<option value="{{ storage }}">{{ storage }}</option>
{% endfor %}
</select>
</div>
<div class="col-sm-6">
<label class="form-label" for="vols">{% trans "Volume" %}</label>
<select id="vols" name="vols" class="form-select" required>
<option value="" selected disabled>{% trans "Choose a pool first" %}</option>
</select>
</div>
</div>
</fieldset>
<div class="row g-3 mt-0">
<div class="col-sm-6">
<label class="form-label" for="addvol-bus">{% trans "Bus" %}</label>
<select id="addvol-bus" name="bus" class="form-select">
{% for bus in bus_host %}
<option value="{{ bus }}"{% if bus == default_bus %} selected{% endif %}>{% trans bus %}</option>
{% endfor %}
</select>
</div>
<div class="col-sm-6">
<label class="form-label" for="addvol-cache">{% trans "Cache mode" %}</label>
<select id="addvol-cache" name="cache" class="form-select">
{% for mode, name in instance.cache_modes %}
<option value="{{ mode }}"{% if mode == default_cache %} selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
</div>
</div>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Add disk" %}</button>
</div>
</form>
</div>
</div>
<script src="{% static 'js/choice_dialog.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var form = document.getElementById('addvol-form');
choiceDialog(document.getElementById('addvol'), 'disk_source', function (source) {
form.action = form.dataset[source];
});
});
</script>
{% endif %}