1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/interfaces/views.py
catborise 050d071ff9 feat(interfaces): the interface pages on Bootstrap's cards, tables and dialogs
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.
2026-10-29 10:09:52 +03:00

116 lines
5.6 KiB
Python

from django.contrib import messages
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from libvirt import libvirtError
from computes.models import Compute
from instances.models import Instance
from interfaces.forms import AddInterface
from logs.audit import audit
from vrtManager.interface import networks_by_device, wvmInterface, wvmInterfaces
from admin.decorators import superuser_only
@superuser_only
def interfaces(request, compute_id):
"""The host's interfaces, and a new one where its libvirt can define them."""
compute = get_object_or_404(Compute, pk=compute_id)
try:
conn = wvmInterfaces(compute.hostname, compute.login, compute.password, compute.type)
ifaces_all = conn.get_ifaces_info()
defined_names = [iface["name"] for iface in ifaces_all]
changeable = conn.can_change_interfaces()
used_by = networks_by_device(conn.wvm)
for iface in ifaces_all:
iface["networks"] = used_by.get(iface["name"], [])
# the list by type, in libvirt's four, a type the host has none of left out
iface_groups = [(title, [iface for iface in ifaces_all if iface["type"] == kind])
for kind, title in (("bridge", _("Bridges")), ("bond", _("Bonds")),
("vlan", _("VLANs")), ("ethernet", _("Ethernet")))]
form = AddInterface()
# the new interface dialog's type: the one sent back after a refusal
if_kind = request.POST.get("itype", "bridge")
# the host's devices are only the create form's choices
netdevs, members, netdev_error = [], [], None
if changeable:
try:
netdevs = conn.get_net_devices()
members = conn.get_member_candidates(netdevs)
ethernet_devices = [dev for dev in members if dev not in defined_names]
except Exception as err: # a host without the node device driver too
netdev_error = err
messages.error(request, _("The network devices of this host could not be read: %(error)s") % {"error": err})
# the page says why nothing is created on a host that cannot change
# interfaces; without the host's devices nothing can be checked
if request.method == "POST" and "create" in request.POST and changeable and netdev_error is None:
form = AddInterface(request.POST, netdevs=netdevs, members=members,
defined=defined_names)
if form.is_valid():
data = form.cleaned_data
try:
with audit(request, compute, f"Interface {data['name']}", "add"):
conn.create_iface(
data["name"], data["itype"], data["start_mode"], data["netdev"],
data["ipv4_type"], data["ipv4_addr"], data["ipv4_gw"],
data["ipv6_type"], data["ipv6_addr"], data["ipv6_gw"],
data["stp"], data["delay"],
)
except libvirtError as err: # shown in the dialog, which opens again
form.add_error(None, str(err))
else:
return HttpResponseRedirect(reverse("interface", args=[compute_id, data["name"]]))
conn.close()
except libvirtError as lib_err:
messages.error(request, lib_err)
return render(request, "interfaces.html", locals())
@superuser_only
def interface(request, compute_id, iface):
"""An interface of the host; started, stopped or deleted where the host's libvirt can."""
compute = get_object_or_404(Compute, pk=compute_id)
try:
conn = wvmInterface(compute.hostname, compute.login, compute.password, compute.type, iface)
changeable = conn.can_change_interfaces()
# the page says why nothing is done on a host that cannot change interfaces
if request.method == "POST" and changeable:
here = reverse("interface", args=[compute_id, iface])
subject = f"Interface {iface}"
try:
if "stop" in request.POST:
with audit(request, compute, subject, "stop"):
conn.stop_iface()
return HttpResponseRedirect(here)
if "start" in request.POST:
with audit(request, compute, subject, "start"):
conn.start_iface()
return HttpResponseRedirect(here)
if "delete" in request.POST:
with audit(request, compute, subject, "delete"):
conn.delete_iface()
return HttpResponseRedirect(reverse("interfaces", args=[compute_id]))
except libvirtError as err: # OperationError too: an active one, a bridge whose addresses go nowhere
messages.error(request, err)
return HttpResponseRedirect(here)
info = conn.get_info()
info["addressing"] = [("IPv4", info["ipv4"]), ("IPv6", info["ipv6"])]
state = conn.is_active()
networks = networks_by_device(conn.wvm).get(iface, [])
if info["type"] == "bridge":
vms = conn.get_vms(networks)
vm_ids = dict(Instance.objects.filter(compute=compute, name__in=[vm["vm"] for vm in vms]).values_list("name", "id"))
for vm in vms:
vm["id"] = vm_ids.get(vm["vm"])
conn.close()
except libvirtError as lib_err:
messages.error(request, lib_err)
return render(request, "interface.html", locals())