mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
As the storage and network pages; plan reviewed with Codex. - The list is grouped by type (bridges, bonds, VLANs, ethernet; a type the host has none of is left out), three cards a row, with a search of their names, MACs and networks (table_filter.js, which now takes any element with data-search as a row); a card per interface: its link as the kernel reports it (up green, down grey, another state or unknown told as such, no red card for a link that is merely down), MAC, MTU, speed when known, the members it reports and the libvirt networks that use it, linked. - An interface's page: an Interface card (type, libvirt's own state where it manages interfaces, link, start mode, MAC, MTU, speed, STP, networks using it); on a host whose libvirt changes interfaces, a card per family with how it is addressed and its addresses; the members of a bridge, bond or VLAN with their link and speed, or that none is reported; for a bridge, the running VMs' cards on it, directly or through a network using it, with their tap and MAC, linked (wvmInterface.get_vms: one domain listing). Not the bridge's delay: netcf tells it in seconds, the udev backend in hundredths. Start or Stop in its heading, Delete in its menu, disabled while it runs, on hosts that can change interfaces only. - A host whose libvirt only reads interfaces says so once, with that it does not tell their addresses, instead of "None" on every page. - The New interface dialog is one form, as the New pool and New network ones (choice_dialog.js, radio_card.html): Bridge or Ethernet, a bridge's member from the devices it can take, an ethernet interface's device from the ones the host does not define yet, a static address's fields with its choice; a refused interface, or one libvirt refuses, opens it again with what was entered; a made one opens its page. - networks_by_device maps the libvirt networks to their bridges or interfaces on the page's own connection.
168 lines
13 KiB
HTML
168 lines
13 KiB
HTML
{% load i18n %}
|
|
{% load static %}
|
|
{% load bootstrap_icons %}
|
|
{% load form_fields %}
|
|
{% if request.user.is_superuser %}
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#AddInterface">
|
|
<span aria-hidden="true">{% bs_icon 'plus-lg' %}</span> {% trans "New interface" %}
|
|
</button>
|
|
|
|
{# one form: a card per type, the fields of the chosen one (the others hidden and disabled, so not sent) #}
|
|
<div class="modal fade" id="AddInterface" tabindex="-1" aria-labelledby="AddInterfaceLabel" aria-hidden="true"
|
|
{% if form.errors %}data-open{% endif %}>
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable modal-fullscreen-sm-down">
|
|
<form method="post" class="modal-content" id="interface-form">{% csrf_token %}
|
|
<div class="modal-header">
|
|
<div>
|
|
<h5 class="modal-title" id="AddInterfaceLabel">{% trans "New interface" %}</h5>
|
|
<div class="small text-body-secondary">{% blocktrans with host=compute.name %}On {{ host }}{% endblocktrans %}</div>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
{% include "instances/form_errors.html" %}
|
|
|
|
<fieldset class="mb-3">
|
|
<legend class="form-label fs-6">{% trans "Type" %}</legend>
|
|
<div class="row row-cols-1 row-cols-sm-2 g-2">
|
|
{% trans "Bridge" as title %}{% trans "A new bridge over a device of the host; VMs can join it." as help %}
|
|
{% include "radio_card.html" with name="itype" value="bridge" chosen=if_kind %}
|
|
{% trans "Ethernet" as title %}{% trans "A device of the host, configured and started by libvirt." as help %}
|
|
{% include "radio_card.html" with name="itype" value="ethernet" chosen=if_kind %}
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset data-kinds="bridge">
|
|
<legend class="visually-hidden">{% trans "Bridge" %}</legend>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-sm-6">
|
|
<label for="iface_name" class="form-label">{% trans "Bridge name" %}</label>
|
|
<input id="iface_name" type="text" name="name" class="form-control font-monospace{% if if_kind == "bridge" %}{{ form.name|invalid }}{% endif %}"
|
|
value="{% if if_kind == "bridge" %}{{ form.name.value|default:"" }}{% endif %}" placeholder="br0" maxlength="15" required pattern="[A-Za-z0-9_.\-]+"
|
|
{% if if_kind == "bridge" %}{% error_attrs form.name "iface_name" %}{% endif %}>
|
|
{% if if_kind == "bridge" %}{% field_error form.name "iface_name" %}{% endif %}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label for="iface_netdev" class="form-label">{% trans "Member device" %}</label>
|
|
<select id="iface_netdev" name="netdev" class="form-select{{ form.netdev|invalid }}" required {% error_attrs form.netdev "iface_netdev" "iface_netdev_help" %}>
|
|
{# a device taken meanwhile is chosen again, not swapped for the first one #}
|
|
{% if form.netdev.value and form.netdev.value not in members %}<option value="" selected>{% trans "Choose a device" %}</option>{% endif %}
|
|
{% for dev in members %}<option value="{{ dev }}"{% if dev == form.netdev.value %} selected{% endif %}>{{ dev }}</option>{% endfor %}
|
|
</select>
|
|
<div class="form-text" id="iface_netdev_help">{% trans "Devices in no bridge, bond or VLAN." %}</div>
|
|
{% field_error form.netdev "iface_netdev" %}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label for="iface_stp" class="form-label">{% trans "Spanning tree (STP)" %}</label>
|
|
<select id="iface_stp" name="stp" class="form-select">
|
|
<option value="off"{% if form.stp.value == "off" %} selected{% endif %}>{% trans "Off" %}</option>
|
|
<option value="on"{% if form.stp.value == "on" %} selected{% endif %}>{% trans "On" %}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label for="iface_delay" class="form-label">{% trans "Forward delay (s)" %}</label>
|
|
<input id="iface_delay" type="number" name="delay" min="0" class="form-control{{ form.delay|invalid }}" value="{{ form.delay.value|default:"0" }}" required
|
|
{% error_attrs form.delay "iface_delay" %}>
|
|
{% field_error form.delay "iface_delay" %}
|
|
</div>
|
|
</div>
|
|
<div class="alert alert-warning small">
|
|
{% bs_icon 'exclamation-triangle' %} {% trans "The device's own addresses go: the bridge takes the ones below, and connections through the device drop while it is rebuilt." %}
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset data-kinds="ethernet">
|
|
<legend class="visually-hidden">{% trans "Ethernet" %}</legend>
|
|
{% if ethernet_devices %}
|
|
<div class="mb-3">
|
|
<label for="iface_device" class="form-label">{% trans "Device" %}</label>
|
|
<select id="iface_device" name="name" class="form-select{% if if_kind == "ethernet" %}{{ form.name|invalid }}{% endif %}" required
|
|
{% if if_kind == "ethernet" %}{% error_attrs form.name "iface_device" %}{% endif %}>
|
|
{% if if_kind == "ethernet" and form.name.value and form.name.value not in ethernet_devices %}<option value="" selected>{% trans "Choose a device" %}</option>{% endif %}
|
|
{% for dev in ethernet_devices %}<option value="{{ dev }}"{% if dev == form.name.value %} selected{% endif %}>{{ dev }}</option>{% endfor %}
|
|
</select>
|
|
{% if if_kind == "ethernet" %}{% field_error form.name "iface_device" %}{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-secondary small">{% trans "The host has every device defined already; a device's page shows it." %}</div>
|
|
{% endif %}
|
|
</fieldset>
|
|
|
|
<div class="mb-3">
|
|
<label for="iface_ipv4" class="form-label">IPv4</label>
|
|
<select id="iface_ipv4" name="ipv4_type" class="form-select" data-static="#iface_ipv4_static">
|
|
<option value="none"{% if form.ipv4_type.value == "none" %} selected{% endif %}>{% trans "None" %}</option>
|
|
<option value="dhcp"{% if form.ipv4_type.value == "dhcp" or not form.is_bound and "dhcp" == "dhcp" %} selected{% endif %}>DHCP</option>
|
|
<option value="static"{% if form.ipv4_type.value == "static" %} selected{% endif %}>{% trans "Static" %}</option>
|
|
</select>
|
|
</div>
|
|
<fieldset class="row g-3 mb-3" id="iface_ipv4_static">
|
|
<legend class="visually-hidden">IPv4</legend>
|
|
<div class="col-sm-6">
|
|
<label for="iface_ipv4_addr" class="form-label">{% trans "Address/prefix" %}</label>
|
|
<input id="iface_ipv4_addr" name="ipv4_addr" class="form-control font-monospace{{ form.ipv4_addr|invalid }}" value="{{ form.ipv4_addr.value|default:"" }}"
|
|
placeholder="192.0.2.10/24" required {% error_attrs form.ipv4_addr "iface_ipv4_addr" %}>
|
|
{% field_error form.ipv4_addr "iface_ipv4_addr" %}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label for="iface_ipv4_gw" class="form-label">{% trans "Gateway" %}</label>
|
|
<input id="iface_ipv4_gw" name="ipv4_gw" class="form-control font-monospace{{ form.ipv4_gw|invalid }}" value="{{ form.ipv4_gw.value|default:"" }}"
|
|
placeholder="192.0.2.1" {% error_attrs form.ipv4_gw "iface_ipv4_gw" %}>
|
|
{% field_error form.ipv4_gw "iface_ipv4_gw" %}
|
|
</div>
|
|
</fieldset>
|
|
<div class="mb-3">
|
|
<label for="iface_ipv6" class="form-label">IPv6</label>
|
|
<select id="iface_ipv6" name="ipv6_type" class="form-select" data-static="#iface_ipv6_static">
|
|
<option value="none"{% if form.ipv6_type.value == "none" %} selected{% endif %}>{% trans "None" %}</option>
|
|
<option value="dhcp"{% if form.ipv6_type.value == "dhcp" or not form.is_bound and "none" == "dhcp" %} selected{% endif %}>DHCP</option>
|
|
<option value="static"{% if form.ipv6_type.value == "static" %} selected{% endif %}>{% trans "Static" %}</option>
|
|
</select>
|
|
</div>
|
|
<fieldset class="row g-3 mb-3" id="iface_ipv6_static">
|
|
<legend class="visually-hidden">IPv6</legend>
|
|
<div class="col-sm-6">
|
|
<label for="iface_ipv6_addr" class="form-label">{% trans "Address/prefix" %}</label>
|
|
<input id="iface_ipv6_addr" name="ipv6_addr" class="form-control font-monospace{{ form.ipv6_addr|invalid }}" value="{{ form.ipv6_addr.value|default:"" }}"
|
|
placeholder="2001:db8::10/64" required {% error_attrs form.ipv6_addr "iface_ipv6_addr" %}>
|
|
{% field_error form.ipv6_addr "iface_ipv6_addr" %}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label for="iface_ipv6_gw" class="form-label">{% trans "Gateway" %}</label>
|
|
<input id="iface_ipv6_gw" name="ipv6_gw" class="form-control font-monospace{{ form.ipv6_gw|invalid }}" value="{{ form.ipv6_gw.value|default:"" }}"
|
|
placeholder="2001:db8::1" {% error_attrs form.ipv6_gw "iface_ipv6_gw" %}>
|
|
{% field_error form.ipv6_gw "iface_ipv6_gw" %}
|
|
</div>
|
|
</fieldset>
|
|
<div class="mb-3">
|
|
<label for="iface_start_mode" class="form-label">{% trans "Starts" %}</label>
|
|
<select id="iface_start_mode" name="start_mode" class="form-select">
|
|
<option value="onboot"{% if form.start_mode.value == "onboot" %} selected{% endif %}>{% trans "On boot" %}</option>
|
|
<option value="hotplug"{% if form.start_mode.value == "hotplug" %} selected{% endif %}>{% trans "When plugged in" %}</option>
|
|
<option value="none"{% if form.start_mode.value == "none" %} selected{% endif %}>{% trans "Only by hand" %}</option>
|
|
</select>
|
|
</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="create">{% trans "Create interface" %}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script src="{% static 'js/choice_dialog.js' %}"></script>
|
|
<script>
|
|
// a static address's fields show with its choice (after the page's
|
|
// scripts, Bootstrap's among them)
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var form = document.getElementById('interface-form');
|
|
form.querySelectorAll('select[data-static]').forEach(function (method) {
|
|
var fields = form.querySelector(method.dataset.static);
|
|
function show() { fields.hidden = fields.disabled = method.value !== 'static'; }
|
|
method.addEventListener('change', show);
|
|
show();
|
|
});
|
|
choiceDialog(document.getElementById('AddInterface'), 'itype', function () {});
|
|
});
|
|
</script>
|
|
{% endif %}
|