mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The host pages lose their last inline scripts: the overview's charts (host_graphs.js), the New pool, New network and New interface dialogs (pool_dialog.js, network_dialog.js, interface_dialog.js), a network's and a network filter's XML editors and dialogs (network_page.js, nwfilter_page.js, nwfilters_page.js), the secrets page's labels (secrets_page.js, its texts from the view as usage_texts) and a pool's page with the ISO upload (storage_page.js). Their URLs and messages are data attributes of the elements they belong to; the upload reads its token and address from its form. No template has an inline script or an inline event handler any more.
219 lines
14 KiB
HTML
219 lines
14 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n %}
|
|
{% load operations %}
|
|
{% load static %}
|
|
{% load bootstrap_icons %}
|
|
{% block title %}{{ compute }} - {% trans "Storage" %}: {{ pool }}{% endblock %}
|
|
|
|
{% block page_heading %}{{ compute }} - {% trans "Storage" %}: {{ pool }}{% endblock page_heading %}
|
|
|
|
{% block page_heading_extra %}
|
|
<div class="d-flex flex-wrap gap-2">
|
|
{% include 'create_stg_vol_block.html' %}
|
|
{# the pool's own actions: starting or stopping it, the others in a menu #}
|
|
<div class="btn-group" role="group" aria-label="{% blocktrans %}Actions for pool {{ pool }}{% endblocktrans %}">
|
|
{% if state %}
|
|
<form method="post" class="btn-group">{% csrf_token %}
|
|
<button type="submit" class="btn btn-outline-secondary" name="stop"
|
|
data-confirm="{% blocktrans with name=pool %}Stop pool {{ name }}? Its files stay, but until it is started again its volumes cannot be managed and VMs with disks in it may not start.{% endblocktrans %}">{% bs_icon 'stop-fill' %} {% trans "Stop" %}</button>
|
|
</form>
|
|
{% else %}
|
|
<form method="post" class="btn-group">{% csrf_token %}
|
|
<button type="submit" class="btn btn-outline-secondary" name="start">{% bs_icon 'play-fill' %} {% trans "Start" %}</button>
|
|
</form>
|
|
{% endif %}
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false" aria-label="{% trans "More actions" %}">{% bs_icon 'three-dots' %}</button>
|
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
|
<li><form method="post">{% csrf_token %}
|
|
{% if autostart %}
|
|
<button type="submit" class="dropdown-item" name="unset_autostart"
|
|
data-confirm="{% blocktrans with name=pool %}Disable autostart of pool {{ name }}? After the host restarts it stays stopped.{% endblocktrans %}">{% bs_icon 'toggle-off' %} {% trans "Disable autostart" %}</button>
|
|
{% else %}
|
|
<button type="submit" class="dropdown-item" name="set_autostart">{% bs_icon 'toggle-on' %} {% trans "Enable autostart" %}</button>
|
|
{% endif %}
|
|
</form></li>
|
|
{% if not state %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><form method="post">{% csrf_token %}
|
|
<button type="submit" class="dropdown-item text-danger" name="delete"
|
|
data-confirm="{% blocktrans with name=pool %}Delete pool {{ name }}? Only its definition goes; its volumes and their files stay.{% endblocktrans %}">{% bs_icon 'trash' %} {% trans "Delete pool" %}</button>
|
|
</form></li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock page_heading_extra %}
|
|
|
|
{% block content %}
|
|
<div id="job-panel" aria-live="polite"></div>
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
|
|
{% include "computes/host_nav.html" with section="storages" detail=True %}
|
|
</div>
|
|
</div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header">{% trans "Space" %}</div>
|
|
<div class="card-body">
|
|
{% if not state %}
|
|
<div class="text-body-secondary">{% trans "Unavailable while the pool is stopped." %}</div>
|
|
{% elif size is None %}
|
|
<div class="text-body-secondary">{% trans "The pool's space could not be read." %}</div>
|
|
{% elif not size %}
|
|
<div class="text-body-secondary">{% trans "The pool has no size." %}</div>
|
|
{% else %}
|
|
<div class="fs-4 lh-sm">{{ percent }} %</div>
|
|
<div class="small text-body-secondary">
|
|
{% blocktrans with used=used|filesizeformat size=size|filesizeformat %}{{ used }} used of {{ size }}{% endblocktrans %}
|
|
{# what the space is the space of: other pools may share it #}
|
|
{% if type == "dir" or type == "fs" or type == "netfs" %}· {% trans "the filesystem of its path" %}
|
|
{% elif type == "logical" %}· {% trans "its volume group" %}
|
|
{% elif type == "rbd" %}· {% trans "the Ceph cluster" %}{% endif %}
|
|
</div>
|
|
<div class="progress my-2" role="progressbar" style="--bs-progress-height: 0.3rem;" aria-label="{% trans "Space used" %}"
|
|
aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
|
<div class="progress-bar {% if percent > 90 %}bg-danger{% elif percent > 75 %}bg-warning{% endif %}" style="width: {{ percent }}%"></div>
|
|
</div>
|
|
<div class="small text-body-secondary">{% blocktrans with free=free|filesizeformat %}{{ free }} free{% endblocktrans %}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header">{% trans "Pool" %}</div>
|
|
<ul class="list-group list-group-flush">
|
|
<li class="list-group-item py-1 d-flex justify-content-between gap-3">
|
|
<span class="text-body-secondary">{% trans "State" %}</span>
|
|
<span class="d-flex flex-wrap justify-content-end gap-1">
|
|
{% if state %}<span class="badge text-bg-success">{% trans "Active" %}</span>{% else %}<span class="badge text-bg-secondary">{% trans "Stopped" %}</span>{% endif %}
|
|
{% if status == "Running degraded" %}<span class="badge text-bg-warning">{% trans "Degraded" %}</span>{% endif %}
|
|
{% if autostart %}<span class="badge text-bg-light border">{% trans "Autostart" %}</span>{% endif %}
|
|
</span>
|
|
</li>
|
|
<li class="list-group-item py-1 d-flex justify-content-between gap-3">
|
|
<span class="text-body-secondary">{% trans "Type" %}</span><span>{{ type|default:"—" }}</span>
|
|
</li>
|
|
<li class="list-group-item py-1 d-flex justify-content-between gap-3">
|
|
<span class="text-body-secondary">{% trans "Path" %}</span>
|
|
<span class="font-monospace text-break text-end">{{ path|default:"—" }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if not state %}
|
|
<div class="alert alert-secondary">
|
|
{% bs_icon 'info-circle' %} {% trans "This pool is stopped: its volumes cannot be listed or managed until it is started. Their files stay." %}
|
|
</div>
|
|
{% elif volumes %}
|
|
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
|
|
<h5 class="me-auto mb-0">{% trans "Volumes" %}</h5>
|
|
<input id="filter" class="form-control w-auto" type="search" placeholder="{% trans "Search volumes" %}" aria-label="{% trans "Search volumes" %}">
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle" data-sortable>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">{% trans "Name" %}</th>
|
|
<th scope="col">{% trans "Format" %}</th>
|
|
<th scope="col" class="d-none d-sm-table-cell" data-sort-method="number">{% trans "Capacity" %}</th>
|
|
<th scope="col" data-sort-method="none"><span class="visually-hidden">{% trans "Actions" %}</span></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="searchable">
|
|
{% for volume in volumes %}
|
|
<tr data-search="{{ volume.name }}">
|
|
<td class="text-break">{{ volume.name }}</td>
|
|
<td><span class="badge text-bg-light border">{{ volume.type }}</span></td>
|
|
{# capacity: what the guest sees; allocated: what it takes on the storage now #}
|
|
<td class="d-none d-sm-table-cell" data-sort="{{ volume.size }}">
|
|
<div>{{ volume.size|filesizeformat }}</div>
|
|
<div class="small text-body-secondary">{% blocktrans with allocated=volume.allocation|filesizeformat %}{{ allocated }} allocated{% endblocktrans %}</div>
|
|
</td>
|
|
<td class="text-end text-nowrap">
|
|
<div class="btn-group" role="group" aria-label="{% blocktrans with name=volume.name %}Actions for {{ name }}{% endblocktrans %}">
|
|
{# an ISO image is uploaded, not cloned (storages.jobs.is_iso) #}
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#CloneVolume" data-volume="{{ volume.name }}"
|
|
aria-label="{% trans "Clone" %}"{% if volume.iso %} disabled{% endif %}>{% bs_icon 'files' %} <span class="d-none d-sm-inline">{% trans "Clone" %}</span></button>
|
|
<div class="btn-group">
|
|
{# fixed: the menu would be cut off by the table's scrolling box #}
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false"
|
|
data-bs-popper-config='{"strategy": "fixed"}' aria-label="{% trans "More actions" %}">{% bs_icon 'three-dots' %}</button>
|
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
|
<li><form method="post">{% csrf_token %}
|
|
<input type="hidden" name="volname" value="{{ volume.name }}">
|
|
<button type="submit" class="dropdown-item text-danger" name="del_volume"
|
|
data-confirm="{% blocktrans with name=volume.name %}Delete volume {{ name }}? Its file and data are removed; a volume a VM uses is not deleted.{% endblocktrans %}">{% bs_icon 'trash' %} {% trans "Delete" %}</button>
|
|
</form></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{# a live region from the start: screen readers hear the message when the search empties the table #}
|
|
<div role="status">
|
|
<div id="no-match" class="text-center text-body-secondary py-4" hidden>{% trans "No volume matches the search." %}</div>
|
|
</div>
|
|
|
|
{# one dialog for every volume: the button that opens it names the volume #}
|
|
<div class="modal fade" id="CloneVolume" tabindex="-1" aria-labelledby="CloneVolumeLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="CloneVolumeLabel">{% trans "Clone image" %} <span class="font-monospace text-break" id="clone-image-name"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
|
|
</div>
|
|
<form method="post" aria-labelledby="CloneVolumeLabel">{% csrf_token %}{% submission_key %}
|
|
<div class="modal-body">
|
|
<input type="hidden" name="image" id="clone-image">
|
|
<div class="mb-3">
|
|
<label class="form-label" for="clone-name">{% trans "Name of the copy" %}</label>
|
|
<input type="text" class="form-control" name="name" id="clone-name" required pattern="[a-zA-Z0-9\.\-_]+" aria-describedby="clone-name-help">
|
|
<div class="form-text" id="clone-name-help">{% trans "Letters, digits, dot, dash and underscore." %}</div>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="convert" value="true" id="clone-convert">
|
|
<label class="form-check-label" for="clone-convert">{% trans "Convert it to another format" %}</label>
|
|
</div>
|
|
<div class="mt-3 d-none" id="clone-format-row">
|
|
<label class="form-label" for="clone-format">{% trans "Format" %}</label>
|
|
<select name="format" class="form-select" id="clone-format">
|
|
<option value="raw">{% trans "raw" %}</option>
|
|
<option value="qcow">{% trans "qcow" %}</option>
|
|
<option value="qcow2">{% trans "qcow2" %}</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-check mt-3 d-none" id="clone-prealloc-row">
|
|
<input class="form-check-input" type="checkbox" name="meta_prealloc" value="true" id="clone-prealloc">
|
|
<label class="form-check-label" for="clone-prealloc">{% trans "Preallocate metadata (qcow2)" %}</label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="submit" class="btn btn-primary" name="cln_volume">{% trans "Clone" %}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-body-secondary py-4">{% trans "This pool has no volumes." %}</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|
|
{% block script %}
|
|
<script src="{% static 'js/poll.js' %}"></script>
|
|
{% job_panel_script job_resource %}
|
|
{% if volumes %}<script src="{% static 'js/table_filter.js' %}"></script>{% endif %}
|
|
<script src="{% static 'js/storage_page.js' %}"></script>
|
|
{% endblock %}
|