mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
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.
436 lines
27 KiB
HTML
436 lines
27 KiB
HTML
{% 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 %}
|