mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
A shut-off VM with TPM state, as every Windows 11 VM, was refused for migration: the state is a file of the source host, and an offline move would start it there with a new TPM. Such a VM now migrates as a shut-off VM with disks to copy does: its UEFI variables are copied, it is started paused, its CPUs never run, migrates live, which carries the TPM state, and is shut off on the destination. A shut-off VM with a saved state is refused before any of that, for every way it moves: started paused, it would resume and then lose the saved memory. The clone tab says that a clone gets a new, empty TPM, and the destroy page that the TPM state goes with the VM.
1020 lines
79 KiB
HTML
1020 lines
79 KiB
HTML
{% load i18n %}
|
||
{% load django_bootstrap5 %}
|
||
{% load bootstrap_icons %}
|
||
{% load operations %}
|
||
<div class="tab-pane" id="settings" role="tabpanel">
|
||
<!-- Nav tabs -->
|
||
<div class="card">
|
||
<div class="card-header bg-transparent pb-0">
|
||
<ul class="nav nav-underline small" role="tablist" aria-label="Instance settings">
|
||
{% if instance.guest_agent_ready %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="px-1 nav-link" data-bs-toggle="tab" id="osinfo-tab" data-bs-target="#osinfo" type="button" role="tab" aria-controls="osinfo" aria-selected="false" onclick="get_osinfo();">
|
||
<span>{% bs_icon 'info-circle' %} </span>
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#boot_opt" type="button" role="tab" aria-controls="boot_opt" aria-selected="true">
|
||
{% trans "Boot" %}
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#disks" type="button" role="tab" aria-controls="disks" aria-selected="false">
|
||
{% trans "Disk" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if can_manage_console %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#vncsettings" type="button" role="tab" aria-controls="vncsettings" aria-selected="false">
|
||
{% trans "Console" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#network" type="button" role="tab" aria-controls="network" aria-selected="false">
|
||
{% trans "Network" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if show_hostdevs %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#hostdevs" type="button" role="tab" aria-controls="hostdevs" aria-selected="false">
|
||
{% trans "Host devices" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if can_clone %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#clone" type="button" role="tab" aria-controls="clone" aria-selected="false">
|
||
{% trans "Clone" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#migrate" type="button" role="tab" aria-controls="migrate" aria-selected="false">
|
||
{% trans "Migrate" %}
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#xmledit" type="button" role="tab" aria-controls="xmledit" aria-selected="false">
|
||
{% trans "XML" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if can_change %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#options" type="button" role="tab" aria-controls="options" aria-selected="false">
|
||
{% trans "Options" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#users" type="button" role="tab" aria-controls="users" aria-selected="false">
|
||
{% trans "Users" %}
|
||
</button>
|
||
</li>
|
||
{% endif %}
|
||
</ul>
|
||
</div>
|
||
{% if instance.status != 5 %}
|
||
<p class="small text-body-secondary px-3 pt-3 mb-0" id="next-start-note">{% trans "While the VM runs, most settings here apply at its next start." %}</p>
|
||
{% endif %}
|
||
<!-- Tab panes -->
|
||
<div class="tab-content card-body">
|
||
{% if instance.guest_agent_ready %}
|
||
{% include 'instances/info_tab.html' %}
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
<div role="tabpanel" class="tab-pane active" id="boot_opt">
|
||
<section class="mb-4" aria-labelledby="startup-heading">
|
||
<h2 class="h6" id="startup-heading">{% trans "Startup settings" %}</h2>
|
||
<div class="list-group">
|
||
<div class="list-group-item py-3">
|
||
<form action="{% if instance.autostart %}{% url 'instances:unset_autostart' instance.id %}{% else %}{% url 'instances:set_autostart' instance.id %}{% endif %}" method="post">{% csrf_token %}
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" id="autostart" aria-describedby="autostart-help"{% if instance.autostart %} checked{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label fw-semibold" for="autostart">{% trans "Autostart" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mb-0 mt-1" id="autostart-help">{% trans "Start this VM when the host starts." %}</p>
|
||
</form>
|
||
</div>
|
||
<div class="list-group-item py-3">
|
||
<form action="{% if instance.config_bootmenu %}{% url 'instances:unset_bootmenu' instance.id %}{% else %}{% url 'instances:set_bootmenu' instance.id %}{% endif %}" method="post">{% csrf_token %}
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" id="bootmenu" aria-describedby="bootmenu-help"{% if instance.config_bootmenu %} checked{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label fw-semibold" for="bootmenu">{% trans "Boot menu" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mb-0 mt-1" id="bootmenu-help">{% trans "Show a device chooser when the VM starts." %}</p>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section aria-labelledby="boot-order-heading">
|
||
<h2 class="h6" id="boot-order-heading">{% trans "Boot order" %}</h2>
|
||
<p class="small text-body-secondary" id="boot-order-help">{% trans "Checked devices boot from top to bottom. Up and Down move all checked devices." %}</p>
|
||
{% if instance.config_boot_order and not instance.config_boot_devices.0.checked %}
|
||
<p class="small text-body-secondary" id="boot-by-type">
|
||
{% trans "The VM now boots by device type:" %}
|
||
{% for b in instance.config_boot_order.values %}{{ b.target }}{% if not forloop.last %}, {% endif %}{% endfor %}.
|
||
{% trans "Checking devices replaces this order." %}
|
||
</p>
|
||
{% endif %}
|
||
<form action="{% url 'instances:set_bootorder' instance.id %}" method="post" aria-labelledby="boot-order-heading" aria-describedby="boot-order-help">{% csrf_token %}
|
||
<input type="hidden" id="bootorder" name="bootorder">
|
||
<div id="b_order" class="list-group boot-sequence mb-3">
|
||
{% for device in instance.config_boot_devices %}
|
||
<label class="list-group-item d-flex align-items-center gap-3">
|
||
<input class="form-check-input flex-shrink-0 m-0" type="checkbox" value="{{ device.value }}"{% if device.checked %} checked{% endif %}>
|
||
<span class="boot-rank small text-body-secondary" aria-hidden="true"></span>
|
||
<span class="text-break"><strong>{{ device.name }}</strong><span class="d-block small text-body-secondary">{{ device.kind }} · {{ device.source|default:_("empty") }}</span></span>
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<button type="button" id="boot_order_up" class="btn btn-sm btn-secondary">{% bs_icon 'arrow-up' %} {% trans "Up" %}</button>
|
||
<button type="button" id="boot_order_down" class="btn btn-sm btn-secondary">{% bs_icon 'arrow-down' %} {% trans "Down" %}</button>
|
||
<button type="submit" class="btn btn-sm btn-primary ms-auto">{% trans "Apply order" %}</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
<div role="tabpanel" class="tab-pane" id="disks">
|
||
{# its CD-ROM drives, then its disks as list rows; the forms are those of the actions #}
|
||
{% if instance.status != 5 %}
|
||
<p class="small text-body-secondary">{% bs_icon 'lock' %} {% trans "The VM is running: CD-ROM drives added now use the USB or SCSI bus. Shut it down to detach a SATA or IDE CD-ROM drive, or to detach or delete disks." %}</p>
|
||
{% endif %}
|
||
<section class="mb-4" aria-labelledby="media-heading">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||
<h2 class="h6 mb-0" id="media-heading">{% trans "Removable media" %}</h2>
|
||
{% include 'add_instance_cdrom.html' %}
|
||
</div>
|
||
{% for cd in instance.media %}
|
||
<div class="card mb-2">
|
||
<form class="card-body" method="post" action="{% if cd.image %}{% url 'instances:unmount_iso' instance.id %}{% else %}{% url 'instances:mount_iso' instance.id %}{% endif %}">
|
||
{% csrf_token %}
|
||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-2">
|
||
<div class="d-flex align-items-center gap-3">
|
||
<span class="fs-4 text-body-secondary" aria-hidden="true">{% bs_icon 'disc' %}</span>
|
||
<div>
|
||
<div class="fw-semibold">{% trans "CD-ROM" %} {{ forloop.counter }}</div>
|
||
<div class="small text-body-secondary">{{ cd.dev }} · {{ cd.bus }} · {% if cd.image %}{{ cd.image }}{% else %}{% trans "No media" %}{% endif %}</div>
|
||
</div>
|
||
</div>
|
||
{% if not cd.image or cd.bus == "usb" %}
|
||
<button type="submit" class="btn btn-sm btn-secondary" formaction="{% url 'instances:detach_cdrom' instance.id cd.dev %}" formmethod="post"{% if instance.status != 5 and cd.bus != "scsi" and cd.bus != "usb" %} disabled{% endif %}>{% trans "Detach" %}</button>
|
||
{% endif %}
|
||
</div>
|
||
{% if cd.bus == "usb" %}
|
||
{# libvirt has no empty USB drive: it is detached, not ejected #}
|
||
{% elif cd.image %}
|
||
<input type="hidden" name="path" value="{{ cd.path }}">
|
||
<button type="submit" class="btn btn-sm btn-primary" value="{{ cd.dev }}" name="umount_iso">{% bs_icon 'eject' %} {% trans "Unmount" %}</button>
|
||
{% else %}
|
||
<label class="form-label small" for="media-{{ cd.dev }}">{% trans "ISO image" %}</label>
|
||
<div class="row g-2">
|
||
<div class="col-12 col-sm">
|
||
<select name="media" class="form-select form-select-sm" id="media-{{ cd.dev }}">
|
||
{% for iso in instance.media_iso %}
|
||
<option value="{{ iso }}">{{ iso }}</option>
|
||
{% empty %}
|
||
<option value="none">{% trans "None" %}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-sm-auto d-grid">
|
||
<button type="submit" class="btn btn-sm btn-primary" name="mount_iso" value="{{ cd.dev }}"{% if not instance.media_iso %} disabled{% endif %}>{% trans "Mount" %}</button>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</form>
|
||
</div>
|
||
{% empty %}
|
||
<p class="small text-body-secondary mb-0">{% trans "The VM has no CD-ROM drive." %}</p>
|
||
{% endfor %}
|
||
</section>
|
||
<section aria-labelledby="disks-heading">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||
<h2 class="h6 mb-0" id="disks-heading">{% trans "Disks" %} <span class="text-body-secondary fw-normal">· {{ instance.config_disks|length }}</span></h2>
|
||
{% include 'add_instance_volume.html' %}
|
||
</div>
|
||
<div class="card">
|
||
<div class="list-group list-group-flush">
|
||
{% for disk in instance.config_disks %}
|
||
<article class="list-group-item py-3" aria-labelledby="disk-{{ disk.dev }}">
|
||
<div class="row g-3 align-items-start small">
|
||
<div class="col-12 col-md-2">
|
||
<h3 class="h6 mb-1" id="disk-{{ disk.dev }}">{% bs_icon 'device-hdd' %} {{ disk.dev }}</h3>
|
||
<div class="text-body-secondary">{{ disk.bus }} · {{ disk.format }}</div>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
{% if disk.storage is None %}
|
||
<div class="alert alert-danger mb-0">{% trans "Error getting disk info" %}</div>
|
||
{% else %}
|
||
{% widthratio disk.used disk.size 100 as used_percent %}
|
||
{# a qcow2 file's metadata can take it past its capacity: the bar stops full #}
|
||
{% if used_percent|add:0 > 100 %}{% widthratio 100 1 1 as used_percent %}{% endif %}
|
||
<div class="d-flex flex-wrap justify-content-between gap-1">
|
||
<strong>{% trans "Capacity" %} {{ disk.size|filesizeformat }}</strong>
|
||
<span class="text-body-secondary">{% blocktrans with used=disk.used|filesizeformat %}{{ used }} used{% endblocktrans %}</span>
|
||
</div>
|
||
<div class="progress my-2" style="height: .25rem;" role="progressbar" aria-label="{% blocktrans with dev=disk.dev %}{{ dev }} used{% endblocktrans %}" aria-valuenow="{{ used_percent }}" aria-valuemin="0" aria-valuemax="100">
|
||
<div class="progress-bar" style="width: {{ used_percent }}%"></div>
|
||
</div>
|
||
<div><span class="text-body-secondary">{% trans "Storage pool" %}</span> <strong>{{ disk.storage }}</strong></div>
|
||
<div class="text-body-secondary text-break"><span class="visually-hidden">{% trans "Source" %}: </span>{{ disk.path }}</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="col-12 col-md-4 d-flex flex-wrap justify-content-md-end gap-2">
|
||
<form class="d-inline" action="{% url 'instances:edit_volume' instance.id %}" method="post" aria-label="{% trans "Edit instance volume form" %}">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||
{% include 'instances/edit_instance_volume.html' with id=forloop.counter0 %}
|
||
</form>
|
||
{% if request.user.is_superuser %}{% include 'instances/move_disk.html' with id=forloop.counter0 %}{% endif %}
|
||
<form class="d-inline" action="{% url 'instances:detach_vol' instance.id %}" method="post">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||
<button type="submit" class="btn btn-sm btn-secondary" value="{{ disk.dev }}"{% if instance.status != 5 %} disabled{% endif %} data-confirm="{% blocktrans with dev=disk.dev %}Detach {{ dev }}? Its file is kept.{% endblocktrans %}">{% trans "Detach" %}</button>
|
||
</form>
|
||
<form class="d-inline" action="{% url 'instances:delete_vol' instance.id %}" method="post">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||
<button type="submit" class="btn btn-sm btn-danger"{% if instance.status != 5 %} disabled{% endif %} data-confirm="{% blocktrans with dev=disk.dev %}Delete {{ dev }}? It is detached and its file and data are removed.{% endblocktrans %}">{% trans "Delete" %}</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<details class="small mt-2">
|
||
<summary class="text-body-secondary">{% trans "Device details" %}</summary>
|
||
<dl class="row mb-0 mt-2" style="max-width: 32rem">
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Cache" %}</dt><dd class="col-6 mb-1">{{ disk.cache }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "IO mode" %}</dt><dd class="col-6 mb-1">{{ disk.io }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Discard" %}</dt><dd class="col-6 mb-1">{{ disk.discard }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Detect zeroes" %}</dt><dd class="col-6 mb-1">{{ disk.detect_zeroes }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Serial" %}</dt><dd class="col-6 mb-1">{{ disk.serial|default:"—" }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Read-only" %}</dt><dd class="col-6 mb-1">{{ disk.readonly|yesno }}</dd>
|
||
<dt class="col-6 fw-normal text-body-secondary">{% trans "Shareable" %}</dt><dd class="col-6 mb-0">{{ disk.shareable|yesno }}</dd>
|
||
</dl>
|
||
</details>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% if instance.pcie_slots %}
|
||
{# a PCIe machine gives each virtio disk and network device a slot; a running VM gets no new one #}
|
||
{% with total=instance.pcie_slots.0 free=instance.pcie_slots.1 next=instance.config_pcie_slots %}
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 small mt-3">
|
||
<span class="{% if not free %}text-danger{% else %}text-body-secondary{% endif %}">
|
||
{% blocktrans %}PCIe slots: {{ free }} free of {{ total }}{% endblocktrans %}{% if next and next.1 != free %} · {% blocktrans with next_free=next.1 %}{{ next_free }} free from the next start{% endblocktrans %}{% endif %}
|
||
</span>
|
||
{% if request.user.is_superuser %}
|
||
<form action="{% url 'instances:add_pcie_slots' instance.id %}" method="post">{% csrf_token %}
|
||
<button type="submit" class="btn btn-sm btn-outline-secondary">{% trans "Add PCIe slots" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
{% endwith %}
|
||
{% endif %}
|
||
</section>
|
||
</div>
|
||
<div role="tabpanel" class="tab-pane" id="network">
|
||
{# a row per NIC in one list, as the disks; its QoS limits fold out like its device details #}
|
||
<section aria-labelledby="nics-heading">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||
<h2 class="h6 mb-0" id="nics-heading">{% trans "Network devices" %} <span class="text-body-secondary fw-normal">· {{ instance.config_networks|length }}</span></h2>
|
||
{% include 'add_instance_network_block.html' %}
|
||
</div>
|
||
<div class="card">
|
||
<div class="list-group list-group-flush">
|
||
{% for network in instance.config_networks %}
|
||
<article class="list-group-item py-3" aria-labelledby="nic-{{ forloop.counter0 }}">
|
||
<div class="row g-3 align-items-start small">
|
||
<div class="col-12 col-md-2">
|
||
<h3 class="h6 mb-1" id="nic-{{ forloop.counter0 }}">{% bs_icon 'ethernet' %} eth{{ forloop.counter0 }}</h3>
|
||
<div class="text-body-secondary text-break">{{ network.target|default:_("no target") }}</div>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<div class="text-break"><span class="text-body-secondary">{% trans "Source" %}</span> <strong class="ms-1">{{ network.nic|default:"—" }}</strong></div>
|
||
<div class="mb-2"><span class="text-body-secondary">{% trans "IPv4" %}</span> <span class="ms-1">{{ network.ipv4|join:", "|default:_("unknown") }}</span></div>
|
||
<form action="{% url 'instances:set_link_state' instance.id %}" method="post" aria-label="{% trans "Link state form" %}">{% csrf_token %}
|
||
<input type="hidden" name="mac" value="{{ network.mac }}">
|
||
<input type="hidden" name="set_link_state" value="{{ network.state }}">
|
||
<div class="form-check form-switch mb-0">
|
||
<input class="form-check-input" type="checkbox" role="switch" id="link-{{ forloop.counter0 }}"{% if network.state == 'up' %} checked{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label" for="link-{{ forloop.counter0 }}">{% if network.state == 'up' %}{% trans "Link up" %}{% else %}{% trans "Link down" %}{% endif %}</label>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="col-12 col-md-4 d-flex flex-wrap justify-content-md-end gap-2">
|
||
{# a NIC that is a host device is changed in the XML editor #}
|
||
{% if network.type != "hostdev" %}
|
||
<button class="btn btn-sm btn-secondary" type="button" data-bs-toggle="modal" data-bs-target="#editInstanceNetwork{{ forloop.counter0 }}">{% trans "Edit" %}</button>
|
||
{% endif %}
|
||
<button class="btn btn-sm btn-secondary" type="button" data-bs-toggle="modal" data-bs-target="#AddQos{{ forloop.counter0 }}">{% trans "Add QoS" %}</button>
|
||
{% if network.type != "hostdev" %}
|
||
<form action="{% url 'instances:delete_network' instance.id %}" method="post" aria-label="{% trans "Delete network device form" %}">{% csrf_token %}
|
||
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network"
|
||
data-confirm="{% blocktrans with mac=network.mac %}Remove network card {{ mac }}? A running VM loses its connection through it at once.{% endblocktrans %}">{% trans "Delete" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<details class="small mt-2">
|
||
<summary class="text-body-secondary">{% trans "Device details" %}</summary>
|
||
<dl class="row mb-0 mt-2" style="max-width: 32rem">
|
||
<dt class="col-4 fw-normal text-body-secondary">{% trans "Type" %}</dt><dd class="col-8 mb-1">{{ network.type }}</dd>
|
||
<dt class="col-4 fw-normal text-body-secondary">{% trans "MAC" %}</dt><dd class="col-8 mb-1 text-break">{{ network.mac }}</dd>
|
||
<dt class="col-4 fw-normal text-body-secondary">{% trans "Model" %}</dt><dd class="col-8 mb-1">{{ network.model|default:"—" }}</dd>
|
||
<dt class="col-4 fw-normal text-body-secondary">{% trans "NWFilter" %}</dt><dd class="col-8 mb-1 text-break">{{ network.filterref|default:_("None") }}</dd>
|
||
<dt class="col-4 fw-normal text-body-secondary">{% trans "IPv6" %}</dt><dd class="col-8 mb-1 text-break">{{ network.ipv6|join:", "|default:_("unknown") }}</dd>
|
||
</dl>
|
||
</details>
|
||
<details class="small">
|
||
<summary class="text-body-secondary">{% trans "QoS limits" %} · {% if network.inbound and network.outbound %}{% trans "inbound, outbound" %}{% elif network.inbound %}{% trans "inbound" %}{% elif network.outbound %}{% trans "outbound" %}{% else %}{% trans "none" %}{% endif %}</summary>
|
||
{% if network.inbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="inbound" limit=network.inbound %}{% endif %}
|
||
{% if network.outbound %}{% include 'instances/nic_qos_form.html' with nic=forloop.counter0 direction="outbound" limit=network.outbound %}{% endif %}
|
||
<p class="text-body-secondary mt-2 mb-0">{% trans "Average and peak are in KiB/s, burst in KiB. A running VM gets a change after it is powered off and on." %}</p>
|
||
</details>
|
||
{% if network.type == 'direct' %}
|
||
<p class="small text-body-secondary border-top pt-2 mt-2 mb-0">{% bs_icon 'info-circle' %} {% trans "In most configurations, macvtap does not work for host to guest network communication" %}</p>
|
||
{% endif %}
|
||
{# the dialogs of Edit and Add QoS #}
|
||
{% if network.type != "hostdev" %}
|
||
<form action="{% url 'instances:change_network' instance.id %}" method="post" name="edit_network{{ forloop.counter0 }}">{% csrf_token %}
|
||
<div class="modal fade" id="editInstanceNetwork{{ forloop.counter0 }}" tabindex="-1" aria-labelledby="editInstanceNetworkLabel{{ forloop.counter0 }}" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header align-items-start">
|
||
<div>
|
||
<h5 class="modal-title" id="editInstanceNetworkLabel{{ forloop.counter0 }}">{% blocktrans with nic=forloop.counter0 %}Edit eth{{ nic }}{% endblocktrans %}</h5>
|
||
<p class="small text-body-secondary mb-0 mt-1">{{ network.model|default:"—" }} · {{ network.mac }}</p>
|
||
</div>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
{% if instance.status != 5 %}
|
||
<p class="small text-body-secondary">{% trans "The VM is running: the changes apply at its next start." %}</p>
|
||
{% endif %}
|
||
<input type="hidden" name="net-old-mac-{{ forloop.counter0 }}" value="{{ network.mac }}">
|
||
<div class="row g-3">
|
||
<div class="col-12">
|
||
<label class="form-label" for="net-source-{{ forloop.counter0 }}">{% trans "Source" %}</label>
|
||
<select class="form-select" id="net-source-{{ forloop.counter0 }}" name="net-source-{{ forloop.counter0 }}">
|
||
{# the NIC keeps its source, whatever its kind, unless another is chosen #}
|
||
<option value="" selected>{% blocktrans with nic=network.nic|default:_("none") %}{{ nic }}, as it is{% endblocktrans %}</option>
|
||
<optgroup label="{% trans "Networks" %}">
|
||
{% for c_net in networks_host %}
|
||
<option value="net:{{ c_net }}">{{ c_net }}</option>
|
||
{% endfor %}
|
||
</optgroup>
|
||
<optgroup label="{% trans "Interfaces" %}">
|
||
{% for c_iface in interfaces_host %}
|
||
<option value="iface:{{ c_iface }}">{{ c_iface }}</option>
|
||
{% endfor %}
|
||
</optgroup>
|
||
</select>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<label class="form-label" for="net-model-{{ forloop.counter0 }}">{% trans "Model" %}</label>
|
||
<select class="form-select" id="net-model-{{ forloop.counter0 }}" name="net-model-{{ forloop.counter0 }}">
|
||
{% for model in net_models_host %}
|
||
<option value="{{ model }}"{% if model == network.model %} selected{% endif %}>{{ model }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<label class="form-label" for="net-nwfilter-{{ forloop.counter0 }}">{% trans "NWFilter" %}</label>
|
||
<select class="form-select" id="net-nwfilter-{{ forloop.counter0 }}" name="net-nwfilter-{{ forloop.counter0 }}">
|
||
<option value="">{% trans "None" %}</option>
|
||
{% for c_filters in nwfilters_host %}
|
||
<option value="{{ c_filters }}"{% if c_filters == network.filterref %} selected{% endif %}>{{ c_filters }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label" for="net-mac-{{ forloop.counter0 }}">{% trans "MAC" %}</label>
|
||
<input class="form-control font-monospace" type="text" id="net-mac-{{ forloop.counter0 }}" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||
<button class="btn btn-primary" name="change_network">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
{% endif %}
|
||
<form action="{% url 'instances:set_qos' instance.id %}" method="post" name="add_qos{{ forloop.counter0 }}" aria-label="{% trans "Add network QoS form" %}">{% csrf_token %}
|
||
<input type="hidden" name="net-mac" value="{{ network.mac }}">
|
||
{% include 'add_network_qos.html' with id=forloop.counter0 %}
|
||
</form>
|
||
</article>
|
||
{% empty %}
|
||
<div class="list-group-item small text-body-secondary">{% trans "The VM has no network device." %}</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
{% if show_hostdevs %}
|
||
<div role="tabpanel" class="tab-pane" id="hostdevs">
|
||
<p class="small text-body-secondary">{% trans "Devices of the host given to the VM. The VM starts only on a host that has them; it is not migrated while it has them, and its clones get none." %}</p>
|
||
{% if request.user.is_superuser %}
|
||
{# the add forms come when the tab opens: their choices scan every VM of the host #}
|
||
<div id="host-devices" class="mb-3" data-url="{% url 'instances:host_devices' instance.id %}">
|
||
<span class="small text-body-secondary">{% trans "Reading the host's devices…" %}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if usb_hostdevs %}
|
||
<div class="table-responsive position-relative">
|
||
<table class="table align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">{% trans "USB device" %}</th>
|
||
<th scope="col">{% trans "Chosen by" %}</th>
|
||
<th scope="col">{% trans "Running VM" %}</th>
|
||
<th scope="col">{% trans "Next start" %}</th>
|
||
{% if request.user.is_superuser %}<th scope="col"><span class="visually-hidden">{% trans "Actions" %}</span></th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for device in usb_hostdevs %}
|
||
<tr>
|
||
<td>{{ device.name|default:"–" }} <span class="small text-body-secondary font-monospace">{{ device.vendor|slice:"2:" }}:{{ device.product|slice:"2:" }}</span></td>
|
||
<td>{% if device.address %}{% blocktrans with bus=device.address.0 device=device.address.1 %}Its address {{ bus }}/{{ device }}{% endblocktrans %}{% else %}{% trans "Its model: any device of it" %}{% endif %}</td>
|
||
<td>{% if device.now %}{% blocktrans with bus=device.now.0 device=device.now.1 %}Has {{ bus }}/{{ device }}{% endblocktrans %}{% else %}–{% endif %}</td>
|
||
<td>{% if device.next %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
|
||
{% if request.user.is_superuser %}
|
||
<td class="text-end">
|
||
<form method="post" action="{% url 'instances:delete_usb_hostdev' instance.id %}">{% csrf_token %}
|
||
<input type="hidden" name="vendor" value="{{ device.vendor }}">
|
||
<input type="hidden" name="product" value="{{ device.product }}">
|
||
<input type="hidden" name="address" value="{% if device.address %}{{ device.address.0 }}/{{ device.address.1 }}{% endif %}">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" aria-label="{% trans "Remove the USB device" %}"
|
||
data-confirm="{% trans "Remove this USB device from the VM? A running VM loses it at once." %}">{% bs_icon 'trash' %}</button>
|
||
</form>
|
||
</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
{% if pci_hostdevs %}
|
||
<div class="table-responsive position-relative">
|
||
<table class="table align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">{% trans "PCI device" %}</th>
|
||
<th scope="col">{% trans "Running VM" %}</th>
|
||
<th scope="col">{% trans "Next start" %}</th>
|
||
{% if request.user.is_superuser %}<th scope="col"><span class="visually-hidden">{% trans "Actions" %}</span></th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for device in pci_hostdevs %}
|
||
<tr>
|
||
<td>{{ device.name|default:"–" }} <span class="small text-body-secondary font-monospace">{{ device.address }}</span></td>
|
||
<td>{% if device.now %}{% trans "Has it" %}{% else %}–{% endif %}</td>
|
||
<td>{% if device.next %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
|
||
{% if request.user.is_superuser %}
|
||
<td class="text-end">
|
||
<form method="post" action="{% url 'instances:delete_pci_hostdev' instance.id %}">{% csrf_token %}
|
||
<input type="hidden" name="address" value="{{ device.address }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" aria-label="{% trans "Remove the PCI device" %}"
|
||
{% if instance.status != 1 and instance.status != 5 or instance.has_managed_save_image %}disabled title="{% trans "A paused VM, or one with a saved state, does not change its PCI devices" %}"{% endif %}
|
||
data-confirm="{% trans "Remove this PCI device from the VM? A running VM's guest has to let it go." %}">{% bs_icon 'trash' %}</button>
|
||
</form>
|
||
</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
{% for device in other_hostdevs %}
|
||
<p class="mb-1">
|
||
{% if device.type == "nic" %}{% trans "Network interface" %}{% else %}{{ device.type|upper }}{% endif %} <span class="font-monospace">{{ device.address }}</span>
|
||
<span class="small text-body-secondary">{% if device.now and device.next %}{% trans "running VM and next start" %}{% elif device.now %}{% trans "running VM only" %}{% else %}{% trans "next start only" %}{% endif %}; {% trans "changed in the XML editor" %}</span>
|
||
</p>
|
||
{% endfor %}
|
||
{% if not usb_hostdevs and not pci_hostdevs and not other_hostdevs %}
|
||
<p class="small text-body-secondary mb-0">{% trans "The VM has no host devices." %}</p>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
<div role="tabpanel" class="tab-pane" id="migrate">
|
||
<form action="{% url 'instances:migrate' instance.id %}" method="post" aria-labelledby="migrate-route">{% csrf_token %}{% submission_key %}
|
||
<header class="border-bottom pb-3 mb-4">
|
||
<h2 class="h6" id="migrate-route">{% trans "Migration route" %}</h2>
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col">
|
||
<label class="form-label small" for="migrate-from">{% trans "From" %}</label>
|
||
<input class="form-control" id="migrate-from" value="{{ compute.name }}" readonly>
|
||
</div>
|
||
<div class="col-auto pb-2 text-body-secondary" aria-hidden="true">→</div>
|
||
<div class="col">
|
||
<label class="form-label small" for="migrate_compute">{% trans "To" %}</label>
|
||
<select class="form-select" name="compute_id" id="migrate_compute">
|
||
{% for comp in computes %}
|
||
{% if comp.id != compute.id %}<option value="{{ comp.id }}">{{ comp.name }}</option>{% endif %}
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<p class="small text-body-secondary mt-3 mb-0">
|
||
{% if instance.status == 1 %}{% trans "The VM is running: it migrates live, or paused for the transfer when Live migration is off." %}
|
||
{% elif instance.status == 3 %}{% trans "The VM is paused: it stays paused on the destination." %}
|
||
{% elif instance.status == 5 %}{% trans "The VM is shut off: it moves offline." %}{% endif %}
|
||
{% trans "Both hosts need the same emulator; the VM's definition leaves the source host." %}
|
||
</p>
|
||
</header>
|
||
{# a VM that cannot go anywhere gets the reason, not the disks and settings #}
|
||
{% if not migrate_refusal %}
|
||
<section class="card mb-3" aria-labelledby="migrate-storage">
|
||
<div class="card-body">
|
||
<h2 class="h6 mb-3" id="migrate-storage">{% bs_icon 'device-hdd' %} {% trans "Storage" %}</h2>
|
||
<div id="migrate-plan" data-url="{% url 'instances:migrate_plan' instance.id %}" data-targets-url="{% url 'instances:migrate_targets' instance.id %}" aria-live="polite"></div>
|
||
</div>
|
||
</section>
|
||
<section class="card mb-4" aria-labelledby="migrate-transfer">
|
||
<div class="card-body">
|
||
<h2 class="h6 mb-3" id="migrate-transfer">{% bs_icon 'arrow-left-right' %} {% trans "Transfer settings" %}</h2>
|
||
<div class="row g-3">
|
||
{% if instance.status == 1 or instance.status == 3 %}
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="live_migrate" value="true" id="vm_live_migrate" aria-describedby="live-help" checked>
|
||
<label class="form-check-label fw-semibold" for="vm_live_migrate">{% trans "Live migration" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="live-help">{% trans "The VM keeps running while its memory is copied. Off, it is paused for the transfer." %}</p>
|
||
</div>
|
||
{% endif %}
|
||
{% if instance.status == 1 %}
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="autoconverge" value="true" id="autoconverge" aria-describedby="autoconverge-help" checked>
|
||
<label class="form-check-label fw-semibold" for="autoconverge">{% trans "Auto converge" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="autoconverge-help">{% trans "Slows the guest's CPUs so that a busy VM's live migration can finish. Only with Live migration." %}</p>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="compress" value="true" id="compress" aria-describedby="compress-help">
|
||
<label class="form-check-label fw-semibold" for="compress">{% trans "Compressed" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="compress-help">{% trans "Compresses the memory sent: less network, more CPU on both hosts." %}</p>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="unsafe_migrate" value="true" id="vm_unsafe_migrate" aria-describedby="unsafe-help">
|
||
<label class="form-check-label fw-semibold" for="vm_unsafe_migrate">{% trans "Unsafe migration" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="unsafe-help">{% trans "Skips libvirt's safety checks, such as a disk cache mode that can corrupt data in a migration." %}</p>
|
||
</div>
|
||
{% endif %}
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="delete_source" value="true" id="delete_source" aria-describedby="delete-source-help">
|
||
<label class="form-check-label fw-semibold" for="delete_source">{% trans "Delete the source files of copied disks" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="delete-source-help">{% trans "After a migration that copied disks, their files on the source host go. Disks on shared storage stay." %}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% endif %}
|
||
<footer class="border-top pt-3">
|
||
{% if migrate_refusal %}<p class="small text-body-secondary mb-2">{{ migrate_refusal }}</p>{% endif %}
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||
<p class="small text-body-secondary mb-0">{% trans "Migration runs as a background job." %}</p>
|
||
{% if migrate_refusal %}
|
||
<button type="button" class="btn btn-primary ms-auto" disabled>{% trans "Migrate" %}</button>
|
||
{% else %}
|
||
{# enabled once the disks of the chosen destination are shown #}
|
||
<button type="submit" class="btn btn-primary ms-auto" name="migrate" disabled>{% trans "Migrate" %}</button>
|
||
{% endif %}
|
||
</div>
|
||
</footer>
|
||
</form>
|
||
<div class="clearfix"></div>
|
||
</div>
|
||
<div role="tabpanel" class="tab-pane" id="xmledit">
|
||
{% if instance.status != 5 %}
|
||
<p class="small text-body-secondary" id="xml-next-start">{% trans "This is the definition for the VM's next start; a saved change applies then." %}</p>
|
||
{% endif %}
|
||
<form action="{% url 'instances:change_xml' instance.id %}" method="post" role="form" aria-label="Edit instance XML form" id="xml-form">
|
||
{% csrf_token %}
|
||
<div class="col-sm-12 xmlheight">
|
||
<textarea id="editor">{{ instance.inst_xml }}</textarea>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary float-end" name="change_xml">{% trans "Review changes" %}</button>
|
||
</form>
|
||
<div class="clearfix"></div>
|
||
<div class="modal fade" id="xml-review" tabindex="-1" aria-labelledby="xml-review-title" aria-hidden="true"
|
||
data-failed="{% trans "The request did not go through; your edit is still in the editor. Try again, or copy it and reload the page." %}">
|
||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="xml-review-title">{% trans "Review the XML change" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="alert alert-danger d-none" role="alert" id="xml-review-error"></div>
|
||
<p class="d-none" id="xml-review-same">{% trans "The XML is the same as the VM's definition; there is nothing to save." %}</p>
|
||
<pre class="d-none mb-0" id="xml-review-diff"></pre>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||
<button type="button" class="btn btn-primary" id="xml-review-save">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div role="tabpanel" class="tab-pane" id="users">
|
||
<section aria-labelledby="owners-heading">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||
<h2 class="h6 mb-0" id="owners-heading">{% trans "Instance owners" %}</h2>
|
||
{% include 'add_instance_owner_block.html' %}
|
||
</div>
|
||
<p class="small text-body-secondary">{% trans "Every owner can power the VM and open its console. A new owner has no other right until it is given one." %}</p>
|
||
{% if userinstances %}
|
||
<ul class="list-group">
|
||
{% for userinstance in userinstances %}
|
||
<li class="list-group-item p-3">
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-md">
|
||
<a class="fw-semibold text-break" href="{% url 'accounts:account' userinstance.user.id %}">{{ userinstance.user }}</a>
|
||
<p class="small mb-0 mt-1">
|
||
{% if userinstance.is_change or userinstance.is_delete %}
|
||
{% if userinstance.is_change %}{% trans "Change settings" %}{% endif %}{% if userinstance.is_change and userinstance.is_delete %} · {% endif %}{% if userinstance.is_delete %}{% trans "Delete the VM" %}{% endif %}{% if userinstance.is_change and userinstance.is_vnc %} · {% trans "Change console settings" %}{% endif %}
|
||
{% else %}
|
||
<span class="text-body-secondary">{% trans "Power and console only" %}</span>
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
{% if request.user.is_superuser %}
|
||
<div class="col-12 col-md-auto d-flex flex-wrap gap-2">
|
||
<a class="btn btn-sm btn-secondary" href="{% url 'accounts:user_instance_update' userinstance.id %}?next={% filter urlencode:'' %}{% url 'instances:instance' instance.id %}#users{% endfilter %}">{% trans "Edit rights" %}<span class="visually-hidden"> {{ userinstance.user }}</span></a>
|
||
<a class="btn btn-sm btn-danger" href="{% url 'accounts:user_instance_delete' userinstance.id %}?next={% filter urlencode:'' %}{% url 'instances:instance' instance.id %}#users{% endfilter %}">{% trans "Remove" %}<span class="visually-hidden"> {{ userinstance.user }}</span></a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
{% else %}
|
||
<p class="mb-0">{% trans "The VM has no owner: only administrators can use it." %}</p>
|
||
{% endif %}
|
||
</section>
|
||
</div>
|
||
{% endif %}
|
||
{% if can_manage_console %}
|
||
<div role="tabpanel" class="tab-pane" id="vncsettings">
|
||
<form method="post" action="{% url 'instances:update_console' instance.id %}" aria-label="{% trans "Console settings" %}">{% csrf_token %}
|
||
{% if instance.status != 5 %}
|
||
<p class="small text-body-secondary">{% trans "The running console keeps its current settings; changes here apply after the VM is shut down and started." %}</p>
|
||
{% endif %}
|
||
<section class="mb-4" aria-labelledby="console-heading">
|
||
<h2 class="h6" id="console-heading">{% trans "Console" %}</h2>
|
||
<label class="form-label" for="{{ console_form.console_type.id_for_label }}">{{ console_form.console_type.label }}</label>
|
||
{{ console_form.console_type }}
|
||
{% if console_form.keymap %}
|
||
<p class="small text-body-secondary mt-1 mb-0">{% trans "The settings below apply to a VNC console." %}</p>
|
||
{% else %}
|
||
<p class="small text-body-secondary mt-1 mb-0">{% trans "The VNC settings appear once the console type VNC is saved." %}</p>
|
||
{% endif %}
|
||
</section>
|
||
{% if console_form.keymap %}
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-12 col-lg-6">
|
||
<section class="card h-100" aria-labelledby="console-connection">
|
||
<div class="card-body">
|
||
<h2 class="h6 mb-3" id="console-connection">{% bs_icon 'display' %} {% trans "VNC connection" %}</h2>
|
||
<div class="mb-4">
|
||
<label class="form-label" for="{{ console_form.listen_on.id_for_label }}">{{ console_form.listen_on.label }}</label>
|
||
{{ console_form.listen_on }}
|
||
<p class="small text-body-secondary mt-1 mb-0">
|
||
{% if not request.user.is_superuser %}{% trans "Only administrators can change this." %} {% endif %}{% trans "Beyond localhost the console is reachable from the network." %}
|
||
</p>
|
||
</div>
|
||
<label class="form-label" for="{{ console_form.keymap.id_for_label }}">{{ console_form.keymap.label }}</label>
|
||
{{ console_form.keymap }}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<div class="col-12 col-lg-6">
|
||
<section class="card h-100" aria-labelledby="console-password">
|
||
<div class="card-body">
|
||
<h2 class="h6 mb-3" id="console-password">{% bs_icon 'key' %} {% trans "VNC password" %}</h2>
|
||
<fieldset>
|
||
<legend class="fs-6 mb-1">{{ console_form.password_action.label }}</legend>
|
||
<p class="small text-body-secondary mb-2">{% if instance.config_console_passwd %}{% trans "A password is set." %}{% else %}{% trans "No password is set." %}{% endif %}</p>
|
||
{% for choice in console_form.password_action %}
|
||
<div class="form-check">
|
||
{{ choice.tag }}
|
||
<label class="form-check-label" for="{{ choice.id_for_label }}">{{ choice.choice_label }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</fieldset>
|
||
<label class="form-label mt-3" for="{{ console_form.password.id_for_label }}">{{ console_form.password.label }}</label>
|
||
{{ console_form.password }}
|
||
<p class="small text-body-secondary mt-1 mb-0">{% trans "Up to 8 characters (an accented letter counts as two), used with \"Set a new one\"." %}</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
<div class="d-flex justify-content-end">
|
||
<button type="submit" class="btn btn-sm btn-primary">{% trans "Save console settings" %}</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
{% endif %}
|
||
{% if can_clone %}
|
||
<div role="tabpanel" class="tab-pane" id="clone">
|
||
<form action="{% url 'instances:clone' instance.id %}" method="post" aria-labelledby="clone-heading">{% csrf_token %}{% submission_key %}
|
||
<header class="mb-4">
|
||
<h2 class="h6 mb-1" id="clone-heading">{% trans "Create a clone" %}</h2>
|
||
<p class="small text-body-secondary mb-0">{% blocktrans with name=instance.name %}Source VM: {{ name }}{% endblocktrans %}</p>
|
||
{% if instance.config_tpm == "emulator" %}
|
||
<p class="small text-body-secondary mb-0" id="clone-tpm">{% trans "The clone gets a new, empty TPM: keys in this VM's TPM, such as BitLocker's, do not come along." %}</p>
|
||
{% endif %}
|
||
</header>
|
||
<div class="mb-4">
|
||
{% if request.user.is_superuser %}
|
||
<label class="form-label" for="clone_name">{% trans "Clone name" %}</label>
|
||
<div class="row g-2">
|
||
<div class="col-12 col-sm">
|
||
<input class="form-control" id="clone_name" name="name" type="text" value="{{ instance.name }}-clone" aria-describedby="clone-name-help">
|
||
</div>
|
||
<div class="col-12 col-sm-auto">
|
||
<button class="btn btn-secondary" type="button" onclick="guess_clone_name()">{% trans "Guess" %}</button>
|
||
</div>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="clone-name-help"></p>
|
||
{% elif app_settings.CLONE_INSTANCE_AUTO_NAME == 'True' %}
|
||
<label class="form-label" for="clone_instance_auto_name">{% trans "Clone name" %}</label>
|
||
<input class="form-control" id="clone_instance_auto_name" type="text" value="{% trans "Automatic" %}" disabled>
|
||
{% else %}
|
||
<label class="form-label" for="select_clone_name">{% trans "Clone name" %}</label>
|
||
<select class="form-select" id="select_clone_name" name="name">
|
||
{% for name in clone_free_names %}
|
||
<option value="{{ name }}">{{ name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
{% endif %}
|
||
</div>
|
||
{% if request.user.is_superuser %}
|
||
<div class="row g-3 mb-4">
|
||
{% if instance.config_networks %}
|
||
<div class="col-12 col-lg-6">
|
||
<section class="card h-100" aria-labelledby="clone-network-heading">
|
||
<div class="card-body">
|
||
<h3 class="h6 mb-3" id="clone-network-heading">{% bs_icon 'ethernet' %} {% trans "Network" %}</h3>
|
||
{% for network in instance.config_networks %}
|
||
<div{% if not forloop.first %} class="border-top pt-3 mt-3"{% endif %}>
|
||
<p class="small mb-2"><strong>eth{{ forloop.counter0 }}</strong> <span class="text-body-secondary">· {{ network.nic }}</span></p>
|
||
<label class="form-label small" for="clone-net-mac-{{ forloop.counter0 }}">{% trans "New MAC address" %}</label>
|
||
<input class="form-control" id="clone-net-mac-{{ forloop.counter0 }}" name="clone-net-mac-{{ forloop.counter0 }}" type="text" value="{{ network.mac }}">
|
||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||
<button class="btn btn-sm btn-secondary" type="button" onclick="random_mac('clone-net-mac-{{ forloop.counter0 }}')">{% trans "Random" %}</button>
|
||
{% if forloop.first %}
|
||
{# the DHCP host entry of the clone's name holds one address: the first NIC's #}
|
||
<button class="btn btn-sm btn-secondary" type="button" onclick="guess_mac_address('#clone_name', 0)">{% trans "Guess" %}</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
{% endif %}
|
||
<div class="col-12{% if instance.config_networks %} col-lg-6{% endif %}">
|
||
<section class="card h-100" aria-labelledby="clone-storage-heading">
|
||
<div class="card-body">
|
||
<h3 class="h6 mb-3" id="clone-storage-heading">{% bs_icon 'device-hdd' %} {% trans "Storage" %}</h3>
|
||
{% for disk in instance.config_disks %}
|
||
<div{% if not forloop.first %} class="border-top pt-3 mt-3"{% endif %}>
|
||
<p class="small mb-2"><strong>{{ disk.dev }}</strong> <span class="text-body-secondary">· {% blocktrans with pool=disk.storage %}Pool: {{ pool }}{% endblocktrans %} · {{ disk.format }}</span></p>
|
||
<label class="form-label small" for="disk_name-{{ disk.dev }}">{% trans "New volume name" %}</label>
|
||
<input class="form-control" id="disk_name-{{ disk.dev }}" name="disk-{{ disk.dev }}" type="text" value="{{ disk.image }}">
|
||
{# a running or paused VM's disks are copied by a backup job, which does not preallocate #}
|
||
{% if disk.format == 'qcow2' and instance.status == 5 %}
|
||
<div class="form-check mt-2">
|
||
<input class="form-check-input" id="meta-{{ disk.dev }}" name="meta-{{ disk.dev }}" type="checkbox" value="true">
|
||
<label class="form-check-label small" for="meta-{{ disk.dev }}">{% trans "Preallocate qcow2 metadata" %}</label>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% empty %}
|
||
<p class="small text-body-secondary mb-0">{% trans "The VM has no disk to copy." %}</p>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
<fieldset class="mb-4">
|
||
<legend class="h6">{% trans "Optional details" %}</legend>
|
||
<label class="form-label" for="clone-title">{% trans "Title" %}</label>
|
||
<input class="form-control mb-3" id="clone-title" name="clone-title" type="text">
|
||
<label class="form-label" for="clone-description">{% trans "Description" %}</label>
|
||
<textarea class="form-control" id="clone-description" name="clone-description" rows="3"></textarea>
|
||
</fieldset>
|
||
<footer class="border-top pt-3">
|
||
{% if usb_hostdevs or pci_hostdevs or other_hostdevs %}
|
||
<p class="small text-body-secondary mb-2">{% trans "The clone gets none of the VM's host devices: one VM holds a device of the host." %}</p>
|
||
{% endif %}
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||
<p class="small text-body-secondary mb-0">{% trans "Cloning runs as a background job." %}</p>
|
||
{% if instance.status == 1 or instance.status == 3 or instance.status == 5 %}
|
||
<button type="submit" class="btn btn-primary ms-auto" name="clone">{% trans "Clone" %}</button>
|
||
{% else %}
|
||
<button type="button" class="btn btn-primary ms-auto" disabled name="clone">{% trans "Clone" %}</button>
|
||
{% endif %}
|
||
</div>
|
||
</footer>
|
||
</form>
|
||
<div class="clearfix"></div>
|
||
</div>
|
||
{% endif %}
|
||
{% if can_change %}
|
||
<div role="tabpanel" class="tab-pane" id="options">
|
||
<div class="list-group">
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:change_options' instance.id %}" method="post" aria-labelledby="options-details">{% csrf_token %}
|
||
<div class="row g-3">
|
||
<div class="col-12 col-lg-4">
|
||
<h2 class="h6" id="options-details">{% bs_icon 'pencil-square' %} {% trans "VM details" %}</h2>
|
||
<p class="small text-body-secondary mb-0">{% trans "A title and description to identify the VM." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<label class="form-label" for="options-title">{% trans "Title" %}</label>
|
||
<input class="form-control mb-3" type="text" id="options-title" name="title" value="{{ instance.config_title }}">
|
||
<label class="form-label" for="options-description">{% trans "Description" %}</label>
|
||
<textarea class="form-control mb-3" id="options-description" name="description" rows="2">{{ instance.config_description }}</textarea>
|
||
<div class="text-end">
|
||
<button class="btn btn-primary" type="submit" name="change_options">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_template' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="is_template">{% trans "Template" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="template-help">{% trans "A template is a VM to clone: it is not counted in quotas and cannot be started." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="is_template" value="True" id="is_template" aria-describedby="template-help template-state"
|
||
{% if instance.is_template %}checked{% endif %}
|
||
{% if instance.status != 5 or not request.user.is_superuser and not request.user.is_staff %}disabled{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label" for="is_template">{% trans "Use as a template" %}</label>
|
||
</div>
|
||
<p class="small text-body-secondary mt-1 mb-0" id="template-state">
|
||
{% if not request.user.is_superuser and not request.user.is_staff %}{% trans "Only administrators and staff can change this." %}
|
||
{% elif instance.status != 5 %}{% trans "Shut the VM down to change this." %}{% endif %}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
{% if request.user.is_superuser %}
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_video_model' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="video_model_select">{% trans "Primary video model" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="video-help">{% trans "The graphics device the guest sees." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<div class="row g-2">
|
||
<div class="col-12 col-sm">
|
||
<select id="video_model_select" class="form-select" name="video_model" aria-describedby="video-help">
|
||
{% for vmodel in instance.video_models %}
|
||
<option value="{{ vmodel }}"{% if vmodel == instance.config_video_model %} selected{% endif %}>{{ vmodel }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-sm-auto d-grid">
|
||
<button class="btn btn-secondary" type="submit" name="set_video_model">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_rng' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="rng">{% trans "Random number generator" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="rng-help">{% trans "A virtio device that feeds the guest's entropy from the host: a new VM does not wait for it at boot. From the next start." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="rng" value="True" id="rng" aria-describedby="rng-help rng-state"
|
||
{% if instance.config_rng %}checked{% endif %}{% if instance.config_rng == "custom" %} disabled{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label" for="rng">{% trans "Virtio RNG" %}</label>
|
||
</div>
|
||
{% if instance.config_rng == "custom" %}
|
||
<p class="small text-body-secondary mt-1 mb-0" id="rng-state">{% trans "The VM has its own RNG device: change it in the XML editor." %}</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_tpm' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="tpm">{% trans "TPM" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="tpm-help">{% trans "An emulated TPM 2.0, as Windows 11 needs with UEFI Secure Boot. Its state stays on the host until the VM is destroyed: a TPM added again has it back. From the next start." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" role="switch" name="tpm" value="True" id="tpm" aria-describedby="tpm-help tpm-state"
|
||
{% if instance.config_tpm %}checked{% endif %}{% if instance.config_tpm == "custom" or not instance.config_tpm and not tpm_support %} disabled{% endif %} onchange="this.form.submit()">
|
||
<label class="form-check-label" for="tpm">{% trans "TPM 2.0" %}</label>
|
||
</div>
|
||
{% if instance.config_tpm == "custom" %}
|
||
<p class="small text-body-secondary mt-1 mb-0" id="tpm-state">{% trans "The VM has its own TPM: change it in the XML editor." %}</p>
|
||
{% elif not instance.config_tpm and not tpm_support %}
|
||
<p class="small text-body-secondary mt-1 mb-0" id="tpm-state">{% trans "This host offers no TPM 2.0: install swtpm on it." %}</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_on_crash' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="on_crash">{% trans "When the guest crashes" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="on-crash-help">{% trans "For a crash the guest reports, not a hang. Stop keeps a VM that crashes again and again down for a look. From the next start." %}</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
{% if instance.config_on_crash == "restart" or instance.config_on_crash == "destroy" %}
|
||
<div class="row g-2">
|
||
<div class="col-12 col-sm">
|
||
<select id="on_crash" class="form-select" name="on_crash" aria-describedby="on-crash-help">
|
||
<option value="restart"{% if instance.config_on_crash == "restart" %} selected{% endif %}>{% trans "Restart" %}</option>
|
||
<option value="destroy"{% if instance.config_on_crash == "destroy" %} selected{% endif %}>{% trans "Stop" %}</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-sm-auto d-grid">
|
||
<button class="btn btn-secondary" type="submit" name="set_on_crash">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="small mb-0" id="on_crash">{% blocktrans with action=instance.config_on_crash %}{{ action }}: change it in the XML editor.{% endblocktrans %}</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="list-group-item p-3 p-md-4" action="{% url 'instances:set_guest_agent' instance.id %}" method="post">{% csrf_token %}
|
||
<div class="row g-3 align-items-center">
|
||
<div class="col-12 col-lg-4">
|
||
<label class="form-label fw-semibold mb-0" for="guest_agent">{% trans "QEMU guest agent" %}</label>
|
||
<p class="small text-body-secondary mb-0" id="agent-help">{% trans "The channel to the agent in the guest: a running VM gets it at once." %}</p>
|
||
<p class="small mt-2 mb-0">{% trans "Agent" %}:
|
||
{% if instance.status != 1 and instance.status != 3 %}<span class="badge bg-secondary">{% trans "VM not running" %}</span>
|
||
{% elif not instance.guest_agent %}<span class="badge bg-secondary">{% trans "No channel" %}</span>
|
||
{% elif instance.guest_agent_ready %}<span class="badge bg-success">{% trans "Connected" %}</span>
|
||
{% else %}<span class="badge bg-danger">{% trans "Not connected" %}</span>{% endif %}
|
||
</p>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<div class="row g-2">
|
||
<div class="col-12 col-sm">
|
||
<select id="guest_agent" class="form-select" name="guest_agent" aria-describedby="agent-help">
|
||
<option value="True"{% if instance.config_guest_agent %} selected{% endif %}>{% trans "Enabled" %}</option>
|
||
<option value="False"{% if not instance.config_guest_agent %} selected{% endif %}>{% trans "Disabled" %}</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-sm-auto d-grid">
|
||
<button class="btn btn-secondary" type="submit" name="set_guest_agent">{% trans "Save" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|