1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/networks/templates/network.html
catborise 0ee03c0971 feat(networks): the network pages on Bootstrap's cards, tables and dialogs
As the storage pages; reviewed with Codex.

- The list is a card per network: its name with Active or Stopped and
  Autostart badges, its type (network_mode: Isolated, NAT, Routed,
  Open, Bridge, Macvtap, SR-IOV), its device, and each address with
  its DHCP range; a bridge or macvtap network's addresses come from
  the outside network.
- A network's page: its heading starts or stops it, the menu sets
  autostart and deletes a stopped one. A Network card tells its state,
  type, bridge or interface, MAC and what the type does; a card per
  address (every <ip>, each with its DHCP range dialog, which libvirt
  takes on a stopped network); its fixed addresses of every subnet in
  one table, searched, each with Edit and Delete, one dialog adding
  and editing them; the VMs whose definition names it, connected or
  configured, linked (wvmNetwork.get_vms, one domain listing, a VM
  undefined meanwhile left out); its
  bandwidth limits where libvirt sets them (not on macvtap or SR-IOV
  networks), in KiB/s, from the saved definition with a note that a
  running network takes them when restarted; its XML in an accordion.
  The page reads the running and the saved definition once each, and
  only to render itself: its actions, which all redirect, read nothing
  of it first.
- The New network dialog is one form, as the New pool one: the type
  as radio cards (the radio_card.html include, now shared by both),
  the chosen type's fields only, IPv6 with its box, a bridge or
  interface suggested from the host's (get_host_interfaces, not the
  bridges libvirt made for its own networks); a refused network or
  one libvirt refuses opens the dialog again with what was entered,
  the reasons beside the fields and focus on the first.
- What the New pool and New network dialogs share is one script,
  static/js/choice_dialog.js: the radio cards show their fieldsets
  (data-kinds) and a refused form opens again focused on its first
  refused field; each dialog keeps only its own changes.
- The fixed-address dialogs per family go to the page's own dialog.
2026-10-29 10:09:27 +03:00

436 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% load bootstrap_icons %}
{% block title %}{% trans "Network" %} - {{ pool }}{% endblock %}
{% block page_heading %}{{ compute.name }} - {% trans "Network" %}: {{ pool }}{% endblock page_heading %}
{% block page_heading_extra %}
{# the network's own actions: starting or stopping it, the others in a menu #}
<div class="btn-group" role="group" aria-label="{% blocktrans %}Actions for network {{ pool }}{% endblocktrans %}">
<form method="post" class="btn-group">{% csrf_token %}
{% if state %}
<button type="submit" class="btn btn-outline-secondary" name="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)">{% bs_icon 'stop-fill' %} {% trans "Stop" %}</button>
{% else %}
<button type="submit" class="btn btn-outline-secondary" name="start">{% bs_icon 'play-fill' %} {% trans "Start" %}</button>
{% endif %}
</form>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false" aria-label="{% trans "More actions" %}">{% bs_icon 'three-dots' %}</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><form method="post">{% csrf_token %}
{% if autostart %}
<button type="submit" class="dropdown-item" 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)">{% bs_icon 'toggle-off' %} {% trans "Disable autostart" %}</button>
{% else %}
<button type="submit" class="dropdown-item" name="set_autostart">{% bs_icon 'toggle-on' %} {% trans "Enable autostart" %}</button>
{% endif %}
</form></li>
{% if not state %}
<li><hr class="dropdown-divider"></li>
<li><form method="post">{% csrf_token %}
<button type="submit" class="dropdown-item text-danger" name="delete"
data-confirm="{% blocktrans with name=pool %}Delete network {{ name }}? VMs that use it no longer start.{% endblocktrans %}"
onclick="return confirm(this.dataset.confirm)">{% bs_icon 'trash' %} {% trans "Delete network" %}</button>
</form></li>
{% endif %}
</ul>
</div>
</div>
{% endblock page_heading_extra %}
{% block content %}
{% include "computes/host_nav.html" with section="networks" detail=True %}
<div class="card mb-3">
<div class="card-header">{% trans "Network" %}</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "State" %}</span>
<span class="d-flex flex-wrap justify-content-end gap-1">
{% if state %}<span class="badge text-bg-success">{% trans "Active" %}</span>{% else %}<span class="badge text-bg-secondary">{% trans "Stopped" %}</span>{% endif %}
{% if autostart %}<span class="badge text-bg-light border">{% trans "Autostart" %}</span>{% endif %}
</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "Type" %}</span>
<span class="badge text-bg-light border">{{ mode_label }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% if mode == "macvtap" %}{% trans "Host interface" %}{% elif mode == "bridge" %}{% trans "Host bridge" %}{% else %}{% trans "Bridge" %}{% endif %}</span>
<span class="font-monospace text-break text-end">{{ device|default:"–" }}</span>
</li>
{% if net_mac %}
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "MAC" %}</span>
<span class="font-monospace">{{ net_mac }}</span>
</li>
{% endif %}
</ul>
<div class="card-footer small text-body-secondary">{{ mode_help }}</div>
</div>
{% if external %}
<div class="alert alert-secondary">{% bs_icon 'info-circle' %} {% trans "The VMs on this network take their addresses from the outside network: libvirt gives none." %}</div>
{% else %}
<div class="row row-cols-1 row-cols-md-2 g-3 mb-3">
{% for ip in ip_networks %}
<div class="col">
<div class="card h-100">
<div class="card-header d-flex flex-wrap align-items-center gap-2">
<span class="me-auto">{% if ip.family == "ipv6" %}IPv6{% else %}IPv4{% endif %}</span>
{% if ip.range %}
{# a running network keeps its range: libvirt changes it while it is stopped #}
<span {% if state %}title="{% trans "Stop the network to change its range" %}"{% endif %}>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#DhcpRange"
data-ip-index="{{ ip.index }}" data-start="{{ ip.range.0 }}" data-end="{{ ip.range.1 }}" data-subnet="{{ ip.subnet }}"
{% if state %}disabled{% endif %}>{% bs_icon 'pencil' %} {% trans "DHCP range" %}</button>
</span>
{% endif %}
</div>
<ul class="list-group list-group-flush flex-grow-1">
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "Subnet" %}</span><span class="font-monospace text-break text-end">{{ ip.subnet|default:"–" }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "Host's address" %}</span><span class="font-monospace text-break text-end">{{ ip.address }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-baseline gap-3">
<span class="text-body-secondary">{% trans "DHCP" %}</span>
<span class="text-end">
{% if ip.range %}<span class="font-monospace text-break">{{ ip.range.0 }} – {{ ip.range.1 }}</span>
{% elif ip.dhcp %}{% trans "Fixed addresses only" %}
{% else %}<span class="badge text-bg-secondary">{% trans "Off" %}</span>{% endif %}
</span>
</li>
</ul>
</div>
</div>
{% empty %}
<div class="col">
<div class="card h-100"><div class="card-body small text-body-secondary">{% trans "The network has no address of the host: its VMs reach each other only." %}</div></div>
</div>
{% endfor %}
{% if ip_networks and "ipv6" not in families %}
<div class="col">
<div class="card h-100">
<div class="card-header">IPv6</div>
<div class="card-body small text-body-secondary">{% trans "The network has no IPv6 address; the XML definition below can give it one." %}</div>
</div>
</div>
{% endif %}
</div>
<div class="card mb-3">
<div class="card-header d-flex flex-wrap align-items-center gap-2">
<span class="me-auto">{% trans "Fixed addresses" %}</span>
{% if fixed_addresses %}
<input id="filter" class="form-control form-control-sm w-auto" type="search" placeholder="{% trans "Search" %}" aria-label="{% trans "Search fixed addresses" %}">
{% endif %}
{% if dhcp_networks %}
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#FixedAddress">
{% bs_icon 'plus-lg' %} {% trans "Add" %}
</button>
{% endif %}
</div>
{% if fixed_addresses %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0" data-sortable>
<thead>
<tr>
<th scope="col">{% trans "Address" %}</th>
<th scope="col">{% trans "MAC / DUID" %}</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col" data-sort-method="none"><span class="visually-hidden">{% trans "Actions" %}</span></th>
</tr>
</thead>
<tbody class="searchable">
{% for host in fixed_addresses %}
<tr data-search="{{ host.ip }} {{ host.mac|default:"" }}{{ host.id|default:"" }} {{ host.name }}">
<td class="font-monospace text-nowrap">{{ host.ip }}</td>
<td class="font-monospace text-nowrap">{{ host.mac|default:host.id|default:"–" }}</td>
<td class="text-break">{{ host.name|default:"–" }}</td>
<td class="text-end text-nowrap">
<div class="btn-group" role="group" aria-label="{% blocktrans with address=host.ip %}Actions for {{ address }}{% endblocktrans %}">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#FixedAddress"
data-ip-index="{{ host.ip_index }}" data-original="{{ host.ip }}" data-address="{{ host.ip }}"
data-mac="{{ host.mac|default:host.id|default:"" }}" data-name="{{ host.name }}" aria-label="{% trans "Edit" %}">{% bs_icon 'pencil' %} <span class="d-none d-sm-inline">{% trans "Edit" %}</span></button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false"
data-bs-popper-config='{"strategy": "fixed"}' aria-label="{% trans "More actions" %}">{% bs_icon 'three-dots' %}</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><form method="post">{% csrf_token %}
<input type="hidden" name="ip_index" value="{{ host.ip_index }}">
<input type="hidden" name="original" value="{{ host.ip }}">
<button type="submit" class="dropdown-item text-danger" name="delete_fixed_address"
data-confirm="{% blocktrans with address=host.ip %}Delete the fixed address {{ address }}? The VM may get another address when its lease ends.{% endblocktrans %}"
onclick="return confirm(this.dataset.confirm)">{% bs_icon 'trash' %} {% trans "Delete" %}</button>
</form></li>
</ul>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div role="status">
<div id="no-match" class="text-center text-body-secondary py-3" hidden>{% trans "No fixed address matches the search." %}</div>
</div>
{% elif dhcp_networks %}
<div class="card-body small text-body-secondary">{% trans "No fixed addresses: every VM gets one from the DHCP range." %}</div>
{% else %}
<div class="card-body small text-body-secondary">{% trans "The network serves no DHCP, so it has no fixed addresses." %}</div>
{% endif %}
</div>
{% endif %}
<div class="card mb-3">
<div class="card-header">{% trans "VMs on this network" %}</div>
{% if vms %}
<ul class="list-group list-group-flush">
{% for vm in vms %}
<li class="list-group-item d-flex justify-content-between align-items-center gap-3">
{% if vm.id %}<a href="{% url 'instances:instance' vm.id %}">{{ vm.name }}</a>{% else %}<span>{{ vm.name }}</span>{% endif %}
{% if vm.connected %}<span class="badge text-bg-success">{% trans "Connected" %}</span>{% else %}<span class="badge text-bg-light border">{% trans "Configured" %}</span>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div class="card-body small text-body-secondary">{% trans "No VM is defined on this network." %}</div>
{% endif %}
</div>
{% if qos_supported %}
<div class="card mb-3">
<div class="card-header d-flex flex-wrap align-items-center gap-2">
<span class="me-auto">{% trans "Bandwidth limits" %}</span>
{% if qos.items|length < 2 %}
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#QosLimit">{% bs_icon 'plus-lg' %} {% trans "Add limit" %}</button>
{% endif %}
</div>
{% if qos %}
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<tr>
<th scope="col">{% trans "Direction" %}</th>
<th scope="col">{% trans "Average" %}</th>
<th scope="col">{% trans "Peak" %}</th>
<th scope="col">{% trans "Burst" %}</th>
<th scope="col"><span class="visually-hidden">{% trans "Actions" %}</span></th>
</tr>
</thead>
<tbody>
{% for direction, limit in qos.items %}
<tr>
<td>{% if direction == "inbound" %}{% trans "Inbound" %}{% else %}{% trans "Outbound" %}{% endif %}</td>
<td>{% if limit.average %}{{ limit.average }} KiB/s{% else %}–{% endif %}</td>
<td>{% if limit.peak %}{{ limit.peak }} KiB/s{% else %}–{% endif %}</td>
<td>{% if limit.burst %}{{ limit.burst }} KiB{% else %}–{% endif %}</td>
<td class="text-end text-nowrap">
<div class="btn-group" role="group" aria-label="{% trans "Actions for the limit" %}">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#QosLimit"
data-direction="{{ direction }}" data-average="{{ limit.average|default:"" }}" data-peak="{{ limit.peak|default:"" }}"
data-burst="{{ limit.burst|default:"" }}" aria-label="{% trans "Edit" %}">{% bs_icon 'pencil' %} <span class="d-none d-sm-inline">{% trans "Edit" %}</span></button>
<form method="post" class="btn-group">{% csrf_token %}
<input type="hidden" name="qos_direction" value="{{ direction }}">
<button type="submit" class="btn btn-sm btn-outline-secondary" name="unset_qos" aria-label="{% trans "Remove" %}"
data-confirm="{% blocktrans with 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>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body small text-body-secondary">{% trans "No limits: the network's VMs share the host's bandwidth freely." %}</div>
{% endif %}
{% if state %}
<div class="card-footer small text-body-secondary">{% trans "A change takes effect when the network is stopped and started again." %}</div>
{% endif %}
</div>
{% endif %}
<section class="accordion mb-3" aria-labelledby="xml-heading">
<div class="accordion-item">
<h2 class="accordion-header" id="xml-heading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#network-xml" aria-expanded="false" aria-controls="network-xml">
{% trans "XML definition" %}
</button>
</h2>
<div class="accordion-collapse collapse" id="network-xml">
<form method="post" class="accordion-body" aria-label="{% trans "Change the network's XML" %}">{% csrf_token %}
<p class="small text-body-secondary">{% trans "The saved definition: a running network takes a change when it is stopped and started again." %}</p>
<input type="hidden" name="edit_xml">
<div class="xmlheight mb-3"><textarea id="edit_editor" aria-label="{% trans "Network XML" %}">{{ xml }}</textarea></div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary" name="edit_network">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
</section>
{# one dialog for every fixed address: Add opens it empty, a row's Edit with its entry, which stays on its subnet #}
{% if dhcp_networks %}
<div class="modal fade" id="FixedAddress" tabindex="-1" aria-labelledby="FixedAddressLabel" aria-hidden="true">
<div class="modal-dialog">
<form method="post" class="modal-content">{% csrf_token %}
<input type="hidden" name="original">
<div class="modal-header">
<h5 class="modal-title" id="FixedAddressLabel">{% trans "Fixed address" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
</div>
<div class="modal-body">
<div class="mb-3" id="fixed_subnet_row">
<label for="fixed_ip_index" class="form-label">{% trans "Subnet" %}</label>
<select id="fixed_ip_index" name="ip_index" class="form-select">
{% for ip in dhcp_networks %}<option value="{{ ip.index }}">{{ ip.subnet }}</option>{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="fixed_address" class="form-label">{% trans "Address" %}</label>
<input id="fixed_address" name="address" class="form-control font-monospace" required>
</div>
<div class="mb-3">
<label for="fixed_mac" class="form-label">{% trans "MAC (IPv4) or DUID (IPv6)" %}</label>
<input id="fixed_mac" name="mac" class="form-control font-monospace" pattern="[0-9a-fA-F:]+" aria-describedby="fixed_mac_help">
<div class="form-text" id="fixed_mac_help">{% trans "The VM's network card; without it, the VM is known by its name." %}</div>
</div>
<div class="mb-3">
<label for="fixed_name" class="form-label">{% trans "Name" %}</label>
<input id="fixed_name" name="name" class="form-control" pattern="[a-zA-Z0-9_.\-]+">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary" name="modify_fixed_address">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% endif %}
<div class="modal fade" id="DhcpRange" tabindex="-1" aria-labelledby="DhcpRangeLabel" aria-hidden="true">
<div class="modal-dialog">
<form method="post" class="modal-content">{% csrf_token %}
<input type="hidden" name="ip_index">
<div class="modal-header">
<h5 class="modal-title" id="DhcpRangeLabel">{% trans "DHCP range" %} <span class="text-body-secondary font-monospace" id="dhcp_subnet"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
</div>
<div class="modal-body row g-3">
<div class="col-sm-6">
<label for="range_start" class="form-label">{% trans "From" %}</label>
<input id="range_start" name="range_start" class="form-control font-monospace" required>
</div>
<div class="col-sm-6">
<label for="range_end" class="form-label">{% trans "To" %}</label>
<input id="range_end" name="range_end" class="form-control font-monospace" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary" name="modify_dhcp_range">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% if qos_supported %}
<div class="modal fade" id="QosLimit" tabindex="-1" aria-labelledby="QosLimitLabel" aria-hidden="true">
<div class="modal-dialog">
<form method="post" class="modal-content">{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="QosLimitLabel">{% trans "Bandwidth limit" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
</div>
<div class="modal-body row g-3">
<div class="col-12">
<label for="qos_direction" class="form-label">{% trans "Direction" %}</label>
<select id="qos_direction" name="qos_direction" class="form-select">
{# a direction with a limit is changed by its own Edit button #}
<option value="inbound"{% if "inbound" in qos %} data-taken{% endif %}>{% trans "Inbound: to the VMs" %}</option>
<option value="outbound"{% if "outbound" in qos %} data-taken{% endif %}>{% trans "Outbound: from the VMs" %}</option>
</select>
</div>
<div class="col-sm-4">
<label for="qos_average" class="form-label">{% trans "Average (KiB/s)" %}</label>
<input id="qos_average" name="qos_average" type="number" min="1" class="form-control" required>
</div>
<div class="col-sm-4">
<label for="qos_peak" class="form-label">{% trans "Peak (KiB/s)" %}</label>
<input id="qos_peak" name="qos_peak" type="number" min="0" class="form-control" aria-describedby="qos_peak_help">
</div>
<div class="col-sm-4">
<label for="qos_burst" class="form-label">{% trans "Burst (KiB)" %}</label>
<input id="qos_burst" name="qos_burst" type="number" min="0" class="form-control">
</div>
<div class="col-12 form-text mt-1" id="qos_peak_help">{% trans "Linux applies a peak to inbound traffic only." %}</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary" name="set_qos">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}
{% block script %}
{% if fixed_addresses %}<script src="{% static 'js/table_filter.js' %}"></script>{% endif %}
<script src="{% static "js/ace/ace.js" %}"></script>
<script>
var editor = ace.edit("edit_editor");
editor.getSession().setMode("ace/mode/xml");
var editInput = $('input[name="edit_xml"]');
editor.getSession().on("change", function () {
editInput.val(editor.getSession().getValue());
});
// the editor measures itself when its section is shown
document.getElementById("network-xml").addEventListener("shown.bs.collapse", () => editor.resize());
// a dialog opened by a row's button gets that row's values, one opened by an Add button starts empty
function fillFrom(dialogId, fill) {
var dialog = document.getElementById(dialogId);
if (dialog) dialog.addEventListener("show.bs.modal", function (event) {
fill(dialog.querySelector("form"), event.relatedTarget.dataset);
});
}
fillFrom("FixedAddress", function (form, data) {
form.elements.original.value = data.original || "";
form.elements.address.value = data.address || "";
form.elements.mac.value = data.mac || "";
form.elements.name.value = data.name || "";
if (data.ipIndex) form.elements.ip_index.value = data.ipIndex;
document.getElementById("fixed_subnet_row").hidden = Boolean(data.original);
});
fillFrom("DhcpRange", function (form, data) {
form.elements.ip_index.value = data.ipIndex;
form.elements.range_start.value = data.start;
form.elements.range_end.value = data.end;
document.getElementById("dhcp_subnet").textContent = data.subnet;
});
fillFrom("QosLimit", function (form, data) {
var direction = form.elements.qos_direction;
direction.querySelectorAll("option").forEach(function (option) {
option.disabled = data.direction ? option.value !== data.direction : option.hasAttribute("data-taken");
});
direction.value = data.direction || direction.querySelector("option:not(:disabled)").value;
form.elements.qos_average.value = data.average || "";
form.elements.qos_peak.value = data.peak || "";
form.elements.qos_burst.value = data.burst || "";
});
</script>
{% endblock %}