- The Host devices tab gives a shut-off VM a PCI device of its host and
takes it back; libvirt binds the device to vfio-pci when the VM starts
and gives it back when it stops (managed). The VM's PCI devices are
listed with whether the running VM and its next start have them.
- A device is offered, and accepted on the server under the compute's
claim, only when it is no bridge of any kind (bridge header, PCIe port,
or of the bridge class: host bridge, LPC), the host has none of its own
network interfaces, disks or graphics card on it (else: bind it to
vfio-pci on the host first), no other VM names it, and every other
endpoint of its IOMMU group is the VM's too or has no driver or
vfio-pci and no other VM; bridges in the group stay with pcieport, and
a member that cannot be read keeps the group. Nothing in the group is
taken implicitly.
- A snapshot revert checks the PCI devices of the snapshot with the same
rules; migration and clones already leave host devices out.
- The add forms of the tab load together, when the tab opens.
- A Host devices tab on the VM page lists the VM's USB host devices, each
once: how it is chosen (its address, or any device of its model), the
device the running VM holds and whether its next start has it; its other
host devices (PCI, SCSI, mediated) are listed for the XML editor.
- Superusers add a USB device of the host and take it back, on a running
VM too. The add form is loaded when the tab opens, as its choices read
every VM of the host; a hub, a device the host has its own disks or
network interfaces on, or one another VM names is not offered, with the
reason, and refused on the server under the compute's claim. A device is
chosen by model when the host has one of it, else by its address.
- A VM without a USB controller is refused; a device is taken from each
definition as that definition describes it.
- A clone gets none of the VM's host devices (the clone form says so), and
a VM with host devices is not migrated. A snapshot revert is refused when
it would give the VM a device another VM names or the host uses.
- A NIC that is a host device is no longer edited or removed from the
Network tab: the XML editor changes it.
- A named CPU model is written without check='none' and with
fallback='forbid': libvirt checks before the start that the host runs
it, and it is not swapped for another model.
- Mode maximum is written as that mode; it was written as a model named
maximum.
- Model names up to 64 characters: the -noTSX and -IBRS models, the ones a
mixed set of hosts needs, were refused at 20.
- The mode or model is checked against the host's capabilities when the VM
is defined, from the page and the API alike; no CPU element (QEMU's
oldest default CPU) is no longer offered.
- The default CPU mode setting offers the modes the new VM dialog has;
no-model and custom, which matched none of them, become host-model.
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.
- The pool list is a card per pool, two to a row, as the host
Overview's Hardware and Software cards: its name with Active or
Stopped and Autostart badges in the header, then its type, its path
(an RBD pool's Ceph pool), its volume count and its space as the
Overview shows it (computes/pool_bar.html, without the name again);
a stopped pool's space is unavailable, not zero.
wvmStorages.get_storages_info gives the path, autostart and percent
used, which computes.views.host_pools computed for itself.
- A pool's page has a Space card (percent, used of size, of what: the
filesystem of its path, its volume group or the Ceph cluster, which
other pools may share; free) and a Pool card (state, autostart,
type, path) instead of a two-column list mixing facts and forms.
Start or Stop is its heading's button; autostart and deleting a
stopped pool are in its menu; the confirmations stay.
- The volume table drops the row number; a volume's capacity and what
it has allocated share a column hidden on a phone; its format is a
badge; its actions are one group, Clone and a menu with Delete,
named for the volume.
- "New pool", "New volume" and "Upload ISO" are labelled buttons
instead of bare green pluses; their dialogs' titles name them.
- The New pool dialog is one form (design reviewed with Codex): the
pool's type as radio cards, as the VM create page chooses its disk
source (their style moves to webvirtcloud.css), one name, and only
the chosen type's fields, the others hidden and disabled.
- A directory's or a share's mount path follows the name
(/var/lib/libvirt/<name>) until it is edited: no default path of
another pool any more.
- ISO images is a directory preset named iso; when the host has an
iso pool, a card links to it instead.
- Ceph offers the host's Ceph secrets by their names, not every
secret by UUID; without one it says so and links the Secrets page.
- LVM asks to agree that the device is erased, the server too.
- AddStgPool checks each type's fields, a name or a path another
pool has, an RBD secret that is not one of the host's Ceph secrets,
and the names "." and "..". A refused pool, or one libvirt refuses,
opens the dialog again with what was entered, the reasons beside
the fields and focus on the first; the audit row for a taken name
stays.
- The state filter is a row of pills, All and each state, counting the
VMs the search finds (the chosen state aside); a state without any is
hidden unless chosen (All never), and a status line tells screen
readers how many VMs match. Every state keeps its pill.
- A labelled "New VM" button replaces the bare green plus; "Find IP"
says "Find IP / MAC", which it finds.
- States are badges; a shut-off VM is grey, not red: it is not a fault.
- A VM's cell holds its name, its title, and for those who see owners
its first owner as a badge, "+N" for the others (named for screen
readers and on hover); the separate user column goes.
- Each row has the same button group for a superuser or the VM's
owners, from the actions the user may use (states.allowed_actions),
all of it disabled on a VM an administrator paused; view-only users
get none. It holds Console, the power action
the state calls for (Start, Resume, Shut down, or Clone for a
template) and a menu of the others (Suspend, Force off, Power cycle),
what does not apply now disabled. The power button is an icon,
named on hover and for screen readers, so every group is as wide and
they line up from row to row; the power cut confirmations stay. On a
phone Console shows its icon only too.
- A grouped host's row has its name, a Connected or Not connected
badge, its VM count and a labelled host memory bar; an unreachable
host's row is table-warning. Its collapse control and the one for
all hosts are buttons, with aria-expanded kept.
- vCPUs and memory hide on a phone in both layouts, and table cells
keep Bootstrap's own padding there: Zephyr doubles it and a list's
last column no longer fit the screen.
- The search read a row's whole text, so a word on a button or in a
state label matched every row. VM rows now carry data-search (the
name, title and owners, and the host in the flat list), and
table_filter.js searches that text when a row has it; the state has
its own filter.
- Nothing matching left an empty table. table_filter.js shows the
page's #no-match while no row does; the VM list's says so and offers
"Clear the filters". A user with no VMs at all is told so instead of
an empty table.
- The grouped list's "#" toggled each host on its own: a host
collapsed by hand opened while the others closed. It now collapses
every host, or opens them all once all are collapsed.
- One alert names every host the panel cannot reach, with why, in
place of one dismissible alert per host; users who see no hosts keep
their own message.
- A host's VM list showed a VM's maximum memory, the Instances page
its current memory: both show the current memory now.
- The owners' users are prefetched with the VMs; the flat list's
Bootstrap 4 font-italic is fst-italic.
- The VM list's power forms (style="display:inline") are d-inline.
- The fields scripts show and hide are d-none, and the scripts add and
remove that class instead of calling jQuery's show() and hide(),
which could not undo it: the custom memory boxes of the Resize tab,
the ISO upload's error message and the clone dialog's metadata row.
- The please-wait dialog's full bar is w-100.
- The create-VM pages' own 56rem width (.create-vm) goes: pages stop
at 960px now, about the same.
- static/css/font-awesome.min.css goes: no page loaded it, no element
used its classes and its fonts were never in the repository.
Three upstream rules in webvirtcloud.css set every page's layout
against Bootstrap 5.3:
- .container { max-width: 900px } held each page to 900px at any
screen width, below Bootstrap's own widths. Pages now take
Bootstrap's container widths up to its lg one, 960px, and stop
there on wider screens: its xl and xxl widths (1140px, 1320px) leave
the panel's forms and lists too wide;
- .page-header (margins of 5px 5px 20px 20px) put headings 20px right
of the content under them; its 14 uses are mb-3, flush with the
content (one carried Bootstrap 4's mr-auto, now me-auto);
- p { margin: 10px } indented every paragraph 10px from the content
beside it; paragraphs take Bootstrap's own bottom margin.
webvirtcloud.css keeps only what Bootstrap has no utility for: the
page width cap, the XML editor's place and height, the Tom Select
colour fix, the sortable table headers' arrows, the grouped VM list's
collapsed hosts and the charts' height.
webvirtcloud.css still carried the upstream panel's rules for what
Bootstrap 5.3 has as utilities and components:
- the page's bottom space (body padding) is pb-5 on base.html's body,
and the console page no longer has to undo it;
- the VM page's action tabs (.action-button, .action-block with a
fixed 65px width) are text-center with a d-block icon; its tab panes
(#power ... #undefine, one rule each) mt-3;
- the bordered sub-tab panes (.tab-pane-bordered, a fixed #eee border
that dark themes could not change) border border-top-0 p-3;
- the search box and header buttons (.search, .btn-header) m-1 and
mt-1; the SSH key select (.keyselect) a form-select;
- the boot order list (.multipleselect, fixed size, #c0c0c0 border) a
list-group of form-check-input items; its unused .multipleselect-on
goes;
- table cells centred by a global rule are align-middle on each table.
Two rules against Bootstrap's behaviour go: menus opened on hover on
wide screens (Bootstrap opens them on a click, and keyboard and touch
users had no hover), and "a { outline: 0 }", which hid the keyboard
focus ring.
The paragraph and page-header margins and the 900px container stay
for a separate change: they set every page's layout.
The fork's own rules in webvirtcloud.css redid what Bootstrap 5.3 has:
- the capacity bars (.cap-*) are Bootstrap progress bars: the memory a
.progress-stacked of running VMs' memory and shut-off VMs' (at half
opacity), each segment a progressbar with its amount as its value
text; the pool bars a thin .progress. The line where the host's
memory ends goes: the share above the bar says it, red when over;
- the status dot (.compute-dot) is a rounded-circle, the big figures
(.compute-ratio) fs-4, the Computes table's cell padding py-3;
- the steps of creating a VM (.create-steps) are a breadcrumb with "›"
as its divider;
- the XML and Advanced disclosures (.disclosure with a turning chevron)
are Bootstrap accordions;
- the chosen volumes and networks (.row-list) are list-unstyled, their
names text-break;
- a chart's container is position-relative by utility; its height stays.
Dead rules go too: .netIN, .netOUT, .diskRD, .diskWR, .input-xs and
.bottom-bar-margin had no users.
The Instances page (grouped and not) and a host's Instances page read
the VMs without an order, so they showed in whatever order the database
returned them: insertion order on SQLite, any order on PostgreSQL,
where it changed between page loads and made a state test fail at
random in CI. They are now ordered by name.
42a1783 refuses a vCPU change on a running VM, also on a direct
request: the VM would get them only at its next start, past the quota
counted now. The live data-loss test still posted vCPUs to a running VM
and expected the change to be pending, so it failed on every connection
type. It now expects the refusal, with its message, and the definition's
vCPUs unchanged; the pending options part is as before.
The VM page offered a lite and a full VNC console. Lite lacked a clipboard,
display settings and a way to reconnect; full put noVNC's own side bar under
the panel's bar, with a Fullscreen item that called a function noVNC does
not have, status messages hidden behind the bar, saved noVNC settings that
overrode the Access tab's, connection settings that cannot apply, and a
Connect button that reused the page's two-minute token and failed.
console-vnc.html and static/js/console_vnc.js replace both, on noVNC's RFB
with Bootstrap controls, and keep what the full view did:
- Status in the bar (Connecting, Connected, Disconnected, Connection lost,
Could not connect, Authentication failed with the server's reason on
hover); Disconnect, then Reconnect, which loads the page again for a new
token.
- The VNC password in a dialog (with a username when the server asks for
one), not a browser prompt; without passwordless console it is not in
the page.
- Send key(s) gains Hold Ctrl / Alt / Windows (noVNC's extra keys).
- Clipboard: what the VM copies, sending text to the VM's clipboard, and
typing it key by key (new: works in firmware, installers and login
prompts; Shift for the characters a US keyboard shifts, 25 ms a key as
firmware drops faster keys, verified in a UEFI shell).
- View: scaling (none, fit, resize the VM's screen), clip, drag a clipped
screen, view only, dot cursor, quality, compression; it starts as
Settings > Console says (a touch screen starts fitted).
- A Keyboard item on touch devices (noVNC's on-screen keyboard input,
key events and Android's text-only input), Fullscreen with keyboard
lock, the bell.
Dropped with the full view: noVNC's websocket host/port/path, repeater ID
and log level (the panel fixes them), automatic reconnect (the token is
single use in time), XVP power buttons (QEMU has no XVP) and shared-mode off.
The bar is compact (about 45 px in the 850x600 window). The Access tab has
one Console button instead of the Lite/Full split and four switches, and
it and the VM list open one window per VM (opening it again reloads it).
- The VM's console settings have no "Clear keymap" box: it wrote an empty
keymap attribute into the VM, and choosing Auto already removes the
keymap; that change is logged as "remove console keymap".
- The "Clip viewport" switch was labelled "View Clipboard".
- The full console page no longer fills the password field with "None"
for a VM without a console password.
The console page is /console/<compute>/<uuid>/ with the view and display
options in the query. It was /console/?token=<compute>-<uuid>, a "token"
that was only the VM's address, parsed alongside a signed token that no
link ever sent. The list's console button takes the VM's UUID from the
database instead of asking libvirt for each row, and no longer defines a
global function per row.
- A VM delete reads delete_disk and delete_nvram as booleans from a JSON
or form body. The view read them as HTML checkboxes from the form body
only: JSON {"delete_disk": true} kept the disks while reporting success,
and a form "delete_disk=false" deleted them. Refused options stop
nothing; the API schema names both options.
- Power on and power cycle of a template answer 409 instead of reporting a
start that did not happen.
- The VM details leave out the host's ISO images for non-superusers, as
the VM page does; reading them also refreshed every pool of the host.
- Flavors take values of at least 1, as the web form does, and show their
id for later changes; a migration needs neither live nor offline; a VM
create on an unknown compute answers 404 instead of 500.
- On a narrow screen the grouped list hid the User, VCPU and Memory
headings but not the VMs' cells, so values sat under the wrong headings;
the cells now hide with their headings. Memory says MiB.
- A user with the view_instances permission sees each VM's host in the
list, as the host alerts already name them; the host page link stays a
superuser's.
- A flavor's memory and disk are labelled MiB and GiB, and each value is at
least 1, so "4" is no longer saved as a 4 MiB preset by mistake.
A boot device moved up or down was rebuilt without its click handler, so
unticking it afterwards left it in the boot order that was applied; the
moved label now keeps its checkbox. An empty root password did nothing
and said nothing; it is refused with a message. Changing a NIC of a
shut-off VM gave no confirmation; it now says the device changed.
The resource pattern was not anchored and took any letters after the VM's
name, so VM "web" also counted "webserver" and "myweb"; a VM with no
resource of its own got "Secondary/OK", stored on every page view. A
VM's resources are now its name or name_SUFFIX at the start of a line,
and a VM without one is "None DRBD".
When the guest agent failed, the Guest Info section stayed empty with no
word: the request's error answer was not handled. An agent without the
time zone or host name command lost the OS details it had answered too.
The section now shows the agent's error, and a missing command leaves out
only its own details.
Only external snapshots had their name checked. An internal one could be
taken with an empty name: libvirt named it, the job and its log row named
nothing, and a recovery looked for "" and marked the job failed though the
snapshot existed. Both kinds now pass the same check when queued, and the
internal form asks for a name as the external one does.
Snapshot dates showed no year, so last year's snapshot looked like this
year's in the list one reverts from; they now read 2026-10-13 23:04:00
under "Date (time zone)", as on the Jobs page.
The saved console form had no initial values, so every posted field
counted as changed: saving only a password also logged the keymap and the
listen address again, each time. A listen address the form does not list
(set in the XML editor) was not a choice, so the select showed localhost
and a save moved the console there. The form now takes its initial values
from the VM, on the page and on the save, and offers the VM's own address.
Only administrators suspend and resume, and the page tells an owner of a
paused VM "Administrator blocked your instance", but the power views let
the owner force it off or cycle it, on the page and the API, and start it
again: the block ended. The power permission now refuses a paused VM to
everyone but administrators.
Disk sizes were pre-filled rounded to whole GiB, so a 3.5 GiB disk showed
4 and was grown, for good, whenever another disk was resized; an empty
field or a disk outside a pool was a 500. A disk's field is now empty with
its size as the placeholder: an empty field leaves the disk alone, a
larger whole number grows it, anything else is refused with a message.
vCPUs change only on a shut-off VM, also on a direct request: a running
VM got them at its next start, past the quota counted against its live
count. Current vCPUs or memory above the maximum are refused instead of
failing (and dropping vCPU hot-plug) or being capped by libvirt. A running
VM's maximum memory select is disabled, since its maximum is kept, and a
custom memory value is sent only while its box is ticked.
A tombstone kept the VM's name and UUID but not its compute, so the page
could not say where the VM had been. It now stores the compute's name (as
text: the compute may be the one deleted) and the list shows it; records
made before this have no host. Deleting a record says so, and the admin
tables use scope="col" instead of the invalid span="col".
Closes#217
- A failed login writes the name it was tried with, as typed: a control
character in it made every download holding that row fail, as the
workbook cannot hold one. Workbooks now write it as \x01; the VM
report shares the code.
- A download held every matching row in memory: it now takes the newest
100000, and its About sheet says when the search should be narrowed.
The list pages' search (filter-table.js) read what was typed as a
regular expression: a "(" or "[" stopped it with an error. It also
needed jQuery, while the instance list had its own script for the same
search. Both are now static/js/table_filter.js: plain text in any case,
the instance list's state list and host groups where a page has them,
remembered for a day as before. A group's header row is marked as one
(data-group-header) instead of being any row without a state.
- An administrator could block, delete or demote (unset Active or
Superuser on) their own account, and with it the last one, leaving the
panel without anyone to undo it. These are now refused.
- The user form showed "No password set." for users who have one: its
password field never read the user's password. Its "Set password"
link went to ../password/, a page this panel does not have. The form
now says whether a password is set and links the page that sets it.
- "Staff status" said "can log into this admin site"; here it lets a
user change and delete the template VMs given to them, and is now
called Template editor.
- A quota of 0 counted as no limit, like -1, so a user's quota could not
be taken away. Now -1 has no limit and 0 allows none; the help says
so, and the labels give the units: memory in MiB, disk in GiB.
- A user's page for a user that does not exist answered with a server
error instead of not found.
- The user list said "None" for a user who never logged in; it says
"never", and dates fit their column.
- The password form's labels no longer break mid-word.
The Settings page saved whatever was posted, and some lists offered
values the panel cannot use:
- Quota Debug could not be turned off: "False" is a non-empty text and
counted as on.
- VM Clone Name Prefix showed as a True/False list, so no prefix could
be typed, and touching it set the prefix to True.
- Disk SCSI Controller listed " lsilogic" with a space, never shown as
chosen, and virtio-blk, which is no SCSI controller.
- VM Machine Type listed x86_64, an architecture; VM CD-ROM Device
listed virtio, a bus a CD-ROM cannot use.
- Logs per Page and Disk Owner UID/GID are read with int(): a letter
broke the logs page and VM create.
A migration fixes the lists and moves a value they no longer offer to
the default (the clone prefix keeps its text). A listed setting now
takes one of its choices, and every number setting a whole number in
its range, checked before it is saved.
The dialog filled its texts with String.replace, which reads $& and $'
in the replacement as patterns: a host named c$&1 showed as
c%(hosts)s1. The values now go in through a function, which inserts
them as they are.
Cloud images read their first-boot settings with cloud-init. The panel
served them only over HTTP, which a VM reaches only with the network
and a reverse DNS name for its address. A new VM can now carry them
itself: Cloud-init seed (Advanced) on the create form, or cloud_init in
the API.
The job writes a NoCloud seed, an ISO labelled cidata, into the disk's
directory pool as cidata-<uuid>.iso and puts it in the VM's CD-ROM
drive. Its meta-data holds the VM's UUID as instance-id and its name as
hostname, written as JSON so a VM named 123 or yes keeps a string
hostname; its user-data is what the HTTP datasource serves, with the
creating user's SSH keys. The ISO is built with pycdlib and written
through a libvirt stream, as the NVRAM copy is, now one helper for both.
A seed needs a CD-ROM bus and a directory pool, else the create is
refused. A failed create deletes the seed with the new disk, recovery
names one left behind, and deleting the VM with its disks deletes its
seed, found in the pools by name even when ejected. A clone's drive is
left empty: the seed is the source VM's identity.
When the guest agent does not answer a root password or SSH key change
in time, the command was already sent: the agent runs it when it
answers again. Measured: with the agent frozen the change failed with
"guest agent command timed out", and after it resumed, root's hash was
the one sent. The error now adds "The change may still be made when the
agent answers again." A failed synchronization sends no command and
gets no note.
The IP search and the VM report read each running VM's addresses and
OS from its guest agent with interfaceAddresses() and guestInfo(),
which wait for a hung agent indefinitely. The search answered by its
deadline, but the thread stayed blocked, counted against the search
thread limit and held up the worker's exit.
They now use the 10 s agent command the VM page uses, shared as
vrtManager.instance.agent_command() and agent_interfaces(). With the
agent frozen, a search answers in 5 s from the leases and the ARP
table, counts the VM unread, and its thread ends about 5 s later.
The VM page read the guest's addresses with interfaceAddresses() from
the agent, and the OS details with agent commands without a timeout.
A connected agent that hangs (a frozen guest) kept the page from
loading at all and held a worker: seen still loading after 120 s.
Both now go through the 10 s guest agent command the root password and
SSH key use. The addresses come from guest-network-get-interfaces, in
the form the page's address code already reads; an agent that does not
answer leaves the ARP address. With the agent frozen the page loads in
about 10 s and the OS details answer with the agent's error (400) in
5 s, instead of a server error.
The Access tab's Root Password and SSH Keys worked only on a shut-off
VM, through gstfsd on the compute. A running VM whose QEMU guest agent
is connected now gets them through the agent: guest-set-user-password
with the SHA-512 crypt hash, and guest-ssh-add-authorized-keys adding
the key to root's keys. A shut-off VM still goes through gstfsd; any
other VM is refused with "Shut the VM down, or start its QEMU guest
agent, and try again", and the buttons are disabled.
Each is one agent command with a 10 s timeout. libvirt's own calls for
them (setUserPassword, authorizedSSHKeysSet) wait for a hung agent
indefinitely, holding a worker and the VM's lock, and the domain's
agent timeout would change every other agent call of the VM too.
The agent's or libvirt's error is shown as is through error_text, so
users who do not see the hosts get no connection details, and the
attempt is logged as failed. The README names the SELinux boolean a
RHEL-family guest needs before its agent may add SSH keys.
Under each host's CPU and memory load, the dialog shows one line:
Given to VMs: 24 vCPUs (1.5:1), 48.0 GB memory.
Pool default: 120.0 GB free.
The line is read when the dialog opens, from computes/<id>/capacity/,
as plain text, so the VM list itself reads no VM definitions. The CPU
bars and the lines share one queue, host by host and two reads at a
time, as each read holds a worker. A host that cannot be read says so.
Compute.capacity now also gives the vCPUs per CPU and its pools with
the default one first, so the Computes page and the dialog share them.
A paused guest cannot answer its agent, so the address search and the VM
report asked it nothing, but counted the VM neither as without an agent
nor as not read: the coverage line read as if every address had been
seen. It now counts among the agents not read, and the line names the
paused case.
The "Find IP" dialog takes a MAC address (aa:bb:..., aa-bb-... or
aabb...) as well as an IP address or a network. A MAC finds the VMs with
a network card of that MAC, shut-off ones too (from their definitions),
with that card's addresses when the VM runs: for a MAC seen on a switch
port or in an external DHCP server's log. A MAC on more than one VM is
marked as a possible conflict. Only the VMs with that MAC are read, and
the card's link-local address, which comes from the MAC, is left out.
The Administration menu gets "VM report (.xlsx)", a workbook for
superusers. The VMs sheet has one row per VM on every compute, shut-off
ones and those the panel has not registered too: name,
state, host, OS and OS family, vCPUs, memory, IP addresses and where they
came from, MAC addresses, owners, and whether the panel has registered
it. A Coverage sheet says when it was made and what could not be read.
The addresses come from the same live reading as the address search,
now shared by both. The OS is the guest agent's (libvirt's guest info)
or, without one, the libosinfo id a tool such as virt-install wrote into
the definition. Link-local IPv6 addresses are left out of the report.
Text cells stay text: a VM name starting with "=" is not a formula.
openpyxl 3.1.5 writes the workbook.
The Instances page's filters and the address search become one control:
the state list, the search box and a "Find IP" button. "Find IP" opens a
dialog (with the address typed in the search box, if any). An IP address
or a network (CIDR) finds the running VMs whose network cards use it,
read live from libvirt within 5 seconds: libvirt's DHCP leases, the host's
ARP table and the guest agent. Only addresses on the VM's own NICs count,
so interfaces inside the guest such as docker0 never match. Each match
shows the VM, its MAC, where the address came from, and a possible-
conflict mark when two VMs report one address. A coverage line says how
many running VMs were checked, how many had no connected guest agent (a
static address on them cannot be seen), which agents could not be read,
which hosts were not reached or not finished in time, and when it was
read.
The hosts are read in parallel: leases and ARP first, then each guest
agent on its own thread, so an agent that never answers holds back only
its own VM; libvirt waits for an agent without limit, so a search starts
only while its threads fit in 16 search threads in the worker.
Administrators search every compute, VMs the panel has not registered
yet included, and see the hosts; other users find only their own VMs and
see no hosts or host errors.
Found by review (Codex and agy) of the XML editor's dialog:
- A busy or moved VM made the page reload and the edit was lost. The
dialog's requests now get these answers as JSON (X-Requested-With, as the
create page's flavor forms) and show them; any other failure says the
edit is still in the editor.
- libvirt finds the VM to define by the XML's UUID: another VM's UUID
overwrote that VM past its lock, a new or missing one added a VM. The
XML must keep this VM's UUID; a malformed one is refused with the
parser's error, as on the create page.
- Save sends the XML whose diff the dialog shows, also when two previews
answer out of order; a failed save keeps the diff and Save.
- The edited XML is defined with the line breaks libvirt gave, not the
form's CRLF, and the VM's state is read before the definition, so a
failed read cannot turn a saved change into an error.
The XML editor of a running or paused VM only showed its XML. It now
shows the persistent definition, the one the VM starts with next (with
secrets, like the other edit forms), and Change saves it through the same
review dialog; the running VM keeps its live configuration until it
restarts. The tab and the saved message say so. The check against a
definition changed since the editor was opened covers this case too.
Change used to define the edited XML at once. It now opens a dialog with
the lines the edit changes in the VM's definition, and Save there defines
it. A definition libvirt refuses comes back to the dialog with the reason,
and the edit stays in the editor; before, the page reloaded with the
current XML and the edit was lost. A definition that changed since the
editor was opened (another tab, a disk added) is not overwritten: the
dialog asks to reload. An unchanged XML has nothing to save.
change_xml answers the dialog with JSON; the editor's script moves to
static/js/xml_editor.js.
sortable.js 0.8.0 (abandoned since 2014) sorted only a table's first
body: in the grouped VM list the first host's row moved among its VMs
and the other hosts never sorted, and memory sorted as text ("1024 MB"
before "512 MB"). tablesort 5.7.1 sorts each table body on its own and
keeps a row marked data-sort-method="none" in place, so hosts stay in
name order with their row on top and their VMs sort below it. Memory,
vCPUs and volume sizes sort by their numbers (data-sort). Headers are
reachable by keyboard and carry aria-sort.
base.html loads tablesort for every table marked data-sortable; the
per-page sortable includes and sortable-theme-bootstrap.css go, so these
tables take the theme's table style like the others. The grouped list's
row numbers go (meaningless once sorted). The VM owners and VM logs
tables were marked sortable but never loaded the script; they stay
unsorted.
The list gets a state selector beside the search box. The search now
matches plain text in any case (a '(' no longer breaks it), and in the
grouped list a host's name shows all of its VMs while a host row shows
only while one of its VMs does. The filter moves to
static/js/instance_filter.js with a Node test; the other lists keep
filter-table.js. Collapsing a host now sets a class on its table body
instead of each row's display, so a collapsed host and the filter no
longer undo each other.
From the Codex and agy review of the VM audit log before v3.2.0:
- Resolving an uncertain migration wrote its row without the VM's UUID,
because the migrate job writes no entries of its own; the resolution now
takes the UUID from the job's intent, so the VM's Logs tab shows it.
- A console keymap or listen address the VM could not take (no graphics
device, or a console on a socket) was logged as done although nothing
changed; it now writes no row, as an edit that changes nothing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Operations on a VM now write their rows the way the admin audit does: one
row per operation, done or failed with a safe reason, with the user, IP and
host. Each row names the VM ("VM web-1: delete snapshot snap1") and carries
its UUID in the new Logs.instance_uuid column.
- audit_vm and failed_vm wrap audit() and failed() for a VM. A failure the
view turns into a message is caught outside the block, so its failed row
is written; busy claims, permission and quota refusals and invalid input
write nothing. No password, key or XML goes into a row.
- All VM page operations move from addlogmsg to audit_vm, and addlogmsg
goes. QoS changes and defining a VM from XML are logged now; a gstfsd
error reply (root password, SSH key) is a failed row, not a done one.
- Partial results are rows of their own: the VM delete, then each disk
delete; the disk detach, then the volume delete; the migration, then a
failed autostart restore, NVRAM removal or record on the target.
- Migration rows use the compute name as the host column, as every other
row does.
- VM job rows (create, clone, snapshots) and their resolutions carry the
VM's UUID; snapshot job intents now record it. The owner rows of the VM
page and the accounts pages, and the ownership system rows, carry it too.
- The Logs tab loads vm_logs/<pk>/ and shows the rows with the VM's UUID,
so a rename keeps the history, two VMs with one name are told apart and
a VM on two computes is one VM. It gains a Result column. Rows from
before this change have no UUID and stay on the Logs page only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The machine types, volumes, templates and disk buses load when their
parent changes. A slow answer for an earlier choice could arrive last and
fill the list for the newer one, and while a list loaded it still held
the previous choice's items, which could be submitted.
Each list now empties when its load starts, and a newer load aborts the
older request, so only the answer for the current choice fills it. Chosen
volumes stay: they are found by name in any pool.
Fixes#177
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The dialog looped over every compute and skipped the unreachable ones,
so its "No compute is reachable." message showed only when there were no
computes at all. With hosts that were all offline it was an empty list.
The index view now gives the dialog the reachable computes, and the
template lists those, so the message covers that case.
Fixes#176
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>