diff --git a/storages/api/viewsets.py b/storages/api/viewsets.py index a691402b..7f5d3dbd 100644 --- a/storages/api/viewsets.py +++ b/storages/api/viewsets.py @@ -49,15 +49,20 @@ class StorageViewSet(viewsets.ViewSet): compute.hostname, compute.login, compute.password, compute.type, pk ) + state = conn.is_active() + # first: it refreshes the pool, and libvirt measures a pool's space only then; + # a stopped pool lists none + volumes = conn.update_volumes() if state else None + size, free = conn.get_size() infoset = { - "state": conn.is_active(), - "size": conn.get_size()[0], - "free": conn.get_size()[1], + "state": state, + "size": size, + "free": free, "status": conn.get_status(), "path": conn.get_target_path(), "type": conn.get_type(), "autostart": conn.get_autostart(), - "volumes": conn.update_volumes(), + "volumes": volumes, } serializer = StorageSerializer( diff --git a/storages/jobs.py b/storages/jobs.py index 9f693e4f..166dd921 100644 --- a/storages/jobs.py +++ b/storages/jobs.py @@ -26,6 +26,11 @@ def _log(op, state, action): return compute, "", "", message if state == Operation.SUCCEEDED else f"{message} ({state})" +def is_iso(name): + """An ISO image, by its name: libvirt calls its format raw. It is uploaded, not cloned.""" + return name.lower().endswith(".iso") + + def _storage(params): c = Compute.objects.get(pk=params["compute"]) return c, wvmStorage(c.hostname, c.login, c.password, c.type, params["pool"]) @@ -51,6 +56,8 @@ class CloneVolume(Kind): volumes = stg.get_volumes() if params["image"] not in volumes: raise util.OperationError(f"The pool has no volume named {params['image']}") + if is_iso(params["image"]): + raise util.OperationError("An ISO image is not cloned") target = stg.clone_name(params["name"], params["format"] or stg.get_volume_format_type(params["image"])) if target in volumes: raise util.OperationError("Name of volume already in use") @@ -102,7 +109,7 @@ class UploadVolume(Kind): name = params["name"] if not name or name in (".", "..") or "\x00" in name: raise ValueError("Invalid file name") - if len(name) <= 4 or not name.lower().endswith(".iso"): + if len(name) <= 4 or not is_iso(name): raise ValueError("Only .iso files can be uploaded") if params["size"] <= 0: raise ValueError("Invalid file size") diff --git a/storages/templates/storage.html b/storages/templates/storage.html index 52d574a0..ddaa2447 100644 --- a/storages/templates/storage.html +++ b/storages/templates/storage.html @@ -28,10 +28,18 @@
{% if not path %}{% trans "None" %}{% else %}{{ path }}{% endif %}
{% trans "Pool status" %}
{% if not status %}{% trans "None" %}{% else %}{{ status }}{% endif %}
+ {% if not state %} +
{% trans "Size" %}
+
{% trans "Unavailable while the pool is stopped." %}
+ {% elif size is None %} +
{% trans "Size" %}
+
{% trans "The pool's space could not be read." %}
+ {% else %}
{% trans "Size" %}
{{size|filesizeformat }}
{% trans "Usage" %} ({{ percent }}%)
{{ used|filesizeformat }}
+ {% endif %}
{% trans "State" %}
{% csrf_token %} @@ -63,129 +71,137 @@ - {% if state %} + {% if not state %} +
+ {% bs_icon 'info-circle' %} {% trans "This pool is stopped: its volumes cannot be listed or managed until it is started. Their files stay." %} +
+ {% elif volumes %}
{% include 'search_block.html' %}
{% trans "Volumes" %}
- {% if volumes %} -
- - - - - - - - - - - - - {% for volume in volumes %} - - - - - - - - - - {% endfor %} - -
#{% trans "Name" %}{% trans "Allocated" %}{% trans "Size" %}{% trans "Format" %}{% trans "Action" %}
{{ forloop.counter }}{{ volume.name|truncatechars:55 }}{{ volume.allocation|filesizeformat }}{{ volume.size|filesizeformat }}{{ volume.type }} - - - {% if volume.type != "iso" %} - - {% else %} - - {% endif %} - -
{% csrf_token %} - - -
-
-
- {% else %} -
-
- {% bs_icon 'exclamation-triangle' %} {% trans "Warning" %}: {% trans "Hypervisor doesn't have any Volumes" %} +
+ + + + + + + + + + + + {% for volume in volumes %} + + + + + + + + + {% endfor %} + +
{% trans "Name" %}{% trans "Allocated" %}{% trans "Size" %}{% trans "Format" %}{% trans "Action" %}
{{ volume.name }}{{ volume.allocation|filesizeformat }}{{ volume.size|filesizeformat }}{{ volume.type }} + {# an ISO image is uploaded, not cloned (storages.jobs.is_iso) #} + + +
{% csrf_token %} + + +
+
+
+ {# a live region from the start: screen readers hear the message when the search empties the table #} +
+ +
+ + {# one dialog for every volume: the button that opens it names the volume #} + + {% else %} +
+ {% bs_icon 'exclamation-triangle' %} {% trans "This pool has no volumes." %} +
{% endif %} {% endblock %} {% block script %} {% job_panel_script job_resource %} - + {% if volumes %}{% endif %}