mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-10 12:16:49 +00:00
feat(tables): tables sort with tablesort, the grouped VM list within each host
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.
This commit is contained in:
parent
ee72e36ad1
commit
7cf12b997b
20 changed files with 84 additions and 180 deletions
|
|
@ -2,9 +2,6 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Instances" %}{% endblock %}
|
||||
{% block style %}
|
||||
<link rel="stylesheet" href="{% static 'css/sortable-theme-bootstrap.css' %}" />
|
||||
{% endblock %}
|
||||
{% block page_heading %}{% trans "Instances" %}{% endblock page_heading %}
|
||||
|
||||
{% block page_heading_extra %}
|
||||
|
|
@ -47,7 +44,6 @@
|
|||
</div>
|
||||
{% endblock content %}
|
||||
{% block script %}
|
||||
<script src="{% static 'js/sortable.min.js' %}"></script>
|
||||
<script src="{% static 'js/instance_filter.js' %}"></script>
|
||||
{% if request.user.is_superuser %}
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
{% load i18n %}
|
||||
{% load bootstrap_icons %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
|
||||
<table class="table table-hover" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-sortable="false"><a class="link-primary" href="#" id="hide-all-instances" onclick="hide_all_host_instances()">#</a></th>
|
||||
<th scope="col" data-sort-method="none"><a class="link-primary" href="#" id="hide-all-instances" onclick="hide_all_host_instances()">#</a></th>
|
||||
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "User"%}</th>
|
||||
<th scope="col">{% trans "Status" %}</th>
|
||||
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
||||
<th scope="col">{% trans "Role/Disk" %}</th>
|
||||
{% endif %}
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "VCPU" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "Memory" %}</th>
|
||||
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %} & {% trans "Mem Usage" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell" data-sort-method="number">{% trans "VCPU" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell" data-sort-method="number">{% trans "Memory" %}</th>
|
||||
<th scope="col" style="width:200px;" data-sort-method="none">{% trans "Actions" %} & {% trans "Mem Usage" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for compute in computes %}
|
||||
<tbody class="searchable" data-host="{{ compute.name }}">
|
||||
{% if compute.instance_set.count > 0 %}
|
||||
<tr class="table-secondary fw-bold border-bottom border-dark">
|
||||
<tr class="table-secondary fw-bold border-bottom border-dark" data-sort-method="none">
|
||||
<td>
|
||||
<span id="collapse_host_instances_{{ compute.id }}" onclick="hide_host_instances('{{ compute.id }}');" class="bi bi-chevron-up" ></span>
|
||||
</td>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
</tr>
|
||||
{% for instance in compute.instance_set.all %}
|
||||
<tr host="{{ compute.id }}">
|
||||
<td class="text-right">{{ forloop.counter }} </td>
|
||||
<td></td>
|
||||
<td>
|
||||
<a class="link-primary" href="{% url 'instances:instance' instance.id %}">{{ instance.name }}</a>
|
||||
<br>
|
||||
|
|
@ -80,8 +80,8 @@
|
|||
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="badge bg-success-subtle text-success-emphasis">{% else %}<span class="badge bg-danger-subtle text-danger-emphasis">{% endif %}{{ instance.drbd }}</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ instance.info.3 }}</td>
|
||||
<td>{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
|
||||
<td data-sort="{{ instance.info.3 }}">{{ instance.info.3 }}</td>
|
||||
<td data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
|
|
@ -89,8 +89,6 @@
|
|||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
{% empty %}
|
||||
<tbody></tbody>{# sortable.js needs a table body #}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
|
||||
<table class="table table-hover" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
||||
<th scope="col">{% trans 'Role/Disk' %}</th>
|
||||
{% endif %}
|
||||
<th scope="col">{% trans 'VCPU' %}</th>
|
||||
<th scope="col">{% trans 'Memory' %}</th>
|
||||
<th scope="col" data-sortable="false">{% trans 'Actions' %}</th>
|
||||
<th scope="col" data-sort-method="number">{% trans 'VCPU' %}</th>
|
||||
<th scope="col" data-sort-method="number">{% trans 'Memory' %}</th>
|
||||
<th scope="col" data-sort-method="none">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="badge bg-success-subtle text-success-emphasis">{% else %}<span class="badge bg-danger-subtle text-danger-emphasis">{% endif %}{{ instance.drbd }}</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ instance.info.3 }}</td>
|
||||
<td>{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
|
||||
<td data-sort="{{ instance.info.3 }}">{{ instance.info.3 }}</td>
|
||||
<td data-sort="{{ instance.info.2 }}">{% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %}</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap mt-3" data-sortable>
|
||||
<table class="table table-striped mt-3">
|
||||
<tbody class="searchable">
|
||||
{% for userinstance in userinstances %}
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="logs">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap" id="logs_table" data-sortable>
|
||||
<table class="table table-striped" id="logs_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Date" %}</th>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from instances.models import Instance
|
||||
from lxml import html
|
||||
|
||||
|
||||
class InstancesIndexTests(TestCase):
|
||||
|
|
@ -44,10 +42,3 @@ class InstancesIndexTests(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, "test4")
|
||||
self.assertEqual(list(response.context["computes"][0].instance_set.all()), [])
|
||||
|
||||
def test_the_grouped_list_without_computes_has_a_table_body(self):
|
||||
# sortable.js reads the first tbody's rows: without one it throws and leaves the table unsortable
|
||||
AppSettings.objects.filter(key="VIEW_INSTANCES_LIST_STYLE").update(value="grouped")
|
||||
self.client.force_login(get_user_model().objects.create_superuser("grouped-admin", "g@example.com", "pw"))
|
||||
page = html.fromstring(self.client.get(reverse("instances:index")).content)
|
||||
self.assertEqual(len(page.xpath("//table[@data-sortable]/tbody")), 1)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ class StateTests(TestCase):
|
|||
self.assertEqual(options, ["All states", *LABELS.values(), "Unknown"])
|
||||
marks = page.xpath("//tbody[@data-host='st']//tr//@data-state")
|
||||
self.assertEqual(marks, list(LABELS.values()))
|
||||
# the host row stays on top of its VMs; memory and vCPUs sort as numbers
|
||||
rows = page.xpath("//tbody[@data-host='st']/tr")
|
||||
self.assertEqual(rows[0].get("data-sort-method"), "none")
|
||||
self.assertEqual([r.xpath("td[@data-sort]/@data-sort") for r in rows[1:2]], [["2", "2097152"]])
|
||||
|
||||
|
||||
class DetailStateTests(TestCase):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue