1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00
webvirtcloud/computes/templates/overview.html
catborise 1b99d2eac5 feat(computes): the host overview shows its VMs, capacity and load, also when it cannot be reached
A host the panel could not reach made its overview the 500 page,
without the host's sections, and the Computes list gave such a host an
Overview button that led nowhere. The page now keeps the host's name,
sections, "Edit connection" and "Remove from panel" (the existing
confirmation), and says "Not connected" with the reason and a "Try
again". Details that cannot be read are said in an alert; the VMs,
capacity and load still show, as they are read on their own.

A reachable host's page leads with what it runs and what it gave:
- its VMs by state (running, shut off, paused, other), linked to
  Instances;
- the vCPUs, memory and storage pools given, as on the Computes list,
  which now shares those parts (computes/capacity_*.html) and their
  preparation (views.shown_capacity) with it;
- CPU and memory in use, side by side, with the current reading in
  words, a note on what the charts hold and when they were last
  updated, or since when not; memory in GB like the rest of the page;
- Hardware and Software and connection: logical CPUs and clock from
  virNodeGetInfo, but physical cores, sockets and NUMA nodes counted
  from the CPUs of the capabilities' NUMA cells, as virNodeGetInfo
  makes a topology up for hosts it cannot describe (asymmetric NUMA);
  the hypervisor's own name and version, libvirt, emulator, the guest
  architectures as text instead of menus of dead links. A host without
  sysinfo shows no processor instead of failing.
Empty notes no longer show "None"; labels stack over their values on a
phone instead of running into them.

Pools that cannot be read no longer drop a host's vCPUs and memory from
its capacity: the storage part says so. get_capacity also counts the
VMs by state.
2026-10-27 14:51:18 +03:00

212 lines
9.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% load bootstrap_icons %}
{% block title %}{% trans "Overview" %} - {{ compute.name }}{% endblock %}
{% block page_heading_extra %}
<a class="btn btn-sm btn-outline-primary" href="{% url 'compute_update' compute.id %}">{% bs_icon 'pencil' %} {% trans "Edit connection" %}</a>
<a class="btn btn-sm btn-outline-danger" href="{% url 'compute_delete' compute.id %}">{% bs_icon 'x-circle' %} {% trans "Remove from panel" %}</a>
{% endblock page_heading_extra %}
{% block page_heading %}{{ compute.name }}{% endblock page_heading %}
{% block content %}
<p class="d-flex flex-wrap align-items-baseline column-gap-3 text-body-secondary">
<span>
{% if connected %}
<span class="compute-dot bg-success" aria-hidden="true"></span> <span class="text-body">{% trans "Connected" %}</span>
{% else %}
<span class="compute-dot bg-danger" aria-hidden="true"></span> <span class="text-body">{% trans "Not connected" %}</span>
{% endif %}
</span>
<span class="text-break">{{ compute.hostname }}</span>
{% if compute.details %}<span class="text-break">{{ compute.details }}</span>{% endif %}
</p>
{% include "computes/host_nav.html" with section="overview" %}
{% if not connected %}
<div class="alert alert-warning" role="alert">
<p>{% trans "The panel cannot reach this host, so its VMs, capacity and load are not shown:" %} {{ compute.connection_error }}</p>
<a class="btn btn-sm btn-warning" href="{% url 'overview' compute.id %}">{% bs_icon 'arrow-clockwise' %} {% trans "Try again" %}</a>
<a class="btn btn-sm btn-outline-secondary" href="{% url 'compute_update' compute.id %}">{% trans "Edit connection" %}</a>
</div>
{% else %}
{% if read_error %}
<div class="alert alert-warning" role="alert">{% trans "The host's details could not be read:" %} {{ read_error }}</div>
{% endif %}
<h4 class="page-header">{% trans "VMs and what they were given" %}</h4>
{% if given %}
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-3 mb-4">
<div class="col">
<div class="card h-100"><div class="card-body">
<h5 class="card-title">{% trans "VMs" %}</h5>
<div class="compute-ratio">{{ given.vm_count }}</div>
<div class="small text-body-secondary">
{% blocktrans with running=given.vms.running shutoff=given.vms.shutoff %}{{ running }} running, {{ shutoff }} shut off{% endblocktrans %}{% if given.vms.paused %}, {% blocktrans with paused=given.vms.paused %}{{ paused }} paused{% endblocktrans %}{% endif %}{% if given.vms.other %}, {% blocktrans with other=given.vms.other %}{{ other }} other{% endblocktrans %}{% endif %}
</div>
<a class="small" href="{% url 'instances' compute.id %}">{% trans "Instances" %} &rarr;</a>
</div></div>
</div>
<div class="col">
<div class="card h-100"><div class="card-body">
<h5 class="card-title">{% trans "vCPUs" %}</h5>
{% include "computes/capacity_vcpus.html" %}
</div></div>
</div>
<div class="col">
<div class="card h-100"><div class="card-body compute-memory">
<h5 class="card-title">{% trans "Memory" %}</h5>
{% include "computes/capacity_memory.html" %}
</div></div>
</div>
<div class="col">
<div class="card h-100"><div class="card-body compute-storage">
<h5 class="card-title">{% trans "Storage" %}</h5>
{% include "computes/capacity_storage.html" %}
<a class="small" href="{% url 'storages' compute.id %}">{% trans "Storages" %} &rarr;</a>
</div></div>
</div>
</div>
{% else %}
<p class="text-body-secondary">{% trans "The capacity could not be read." %}</p>
{% endif %}
{# the load is read on its own: shown also when the details could not be #}
<h4 class="page-header">{% trans "Load" %}</h4>
<div class="row g-3">
<div class="col-lg-6">
<div class="card h-100"><div class="card-body">
<h5 class="card-title d-flex justify-content-between">{% trans "CPU in use" %} <span id="cpu_now" class="text-body-secondary">&ndash;</span></h5>
<div class="chart"><canvas id="cpuChart" role="img" aria-label="{% trans "CPU in use, recent readings" %}"></canvas></div>
</div></div>
</div>
<div class="col-lg-6">
<div class="card h-100"><div class="card-body">
<h5 class="card-title d-flex justify-content-between">{% trans "Memory in use" %} <span id="mem_now" class="text-body-secondary">&ndash;</span></h5>
<div class="chart"><canvas id="memChart" role="img" aria-label="{% trans "Memory in use, recent readings" %}"></canvas></div>
</div></div>
</div>
</div>
<p class="small text-body-secondary mt-2 mb-4">
{% trans "The last 10 readings, one every 5 seconds. Memory in use leaves out the host's buffers and cache." %}
<span id="graph_state" data-updated="{% trans "Updated at %(time)s." %}"
data-stale="{% trans "Not updated since %(time)s: the host did not answer." %}">{% trans "Waiting for the first reading." %}</span>
</p>
{% if host %}
<div class="row g-4">
<div class="col-lg-6">
<h4 class="page-header">{% trans "Hardware" %}</h4>
<dl class="row">
<dt class="col-sm-5">{% trans "Hostname" %}</dt>
<dd class="col-sm-7 text-break">{{ host.hostname }}</dd>
<dt class="col-sm-5">{% trans "Architecture" %}</dt>
<dd class="col-sm-7">{{ host.arch }}</dd>
<dt class="col-sm-5">{% trans "Processor" %}</dt>
<dd class="col-sm-7">{{ host.processor|default:"–" }}</dd>
<dt class="col-sm-5">{% trans "Logical CPUs" %}</dt>
<dd class="col-sm-7">{{ host.topology.cpus }}</dd>
<dt class="col-sm-5">{% trans "Physical cores" %}</dt>
<dd class="col-sm-7">{{ host.topology.cores|default:"–" }}</dd>
<dt class="col-sm-5">{% trans "Sockets" %}</dt>
<dd class="col-sm-7">{{ host.topology.sockets|default:"–" }}</dd>
<dt class="col-sm-5">{% trans "NUMA nodes" %}</dt>
<dd class="col-sm-7">{{ host.topology.nodes|default:"–" }}</dd>
<dt class="col-sm-5">{% trans "Clock" %}</dt>
<dd class="col-sm-7">{{ host.topology.mhz }} MHz</dd>
<dt class="col-sm-5">{% trans "Memory" %}</dt>
<dd class="col-sm-7">{{ host.memory|filesizeformat }}</dd>
</dl>
</div>
<div class="col-lg-6">
<h4 class="page-header">{% trans "Software and connection" %}</h4>
<dl class="row">
<dt class="col-sm-5">{% trans "Hypervisor" %}</dt>
<dd class="col-sm-7">{{ host.hypervisor }} {{ host.hypervisor_version }}</dd>
<dt class="col-sm-5">libvirt</dt>
<dd class="col-sm-7">{{ host.libvirt_version }}</dd>
<dt class="col-sm-5">{% trans "Emulator" %}</dt>
<dd class="col-sm-7 text-break">{{ host.emulator|default:"–" }}</dd>
<dt class="col-sm-5">{% trans "Guest architectures" %}</dt>
<dd class="col-sm-7">
{% for arch, types in host.guest_archs.items %}
<div>{{ arch }} <span class="text-body-secondary">({{ types|join:", " }})</span></div>
{% endfor %}
</dd>
<dt class="col-sm-5">{% trans "Connection" %}</dt>
<dd class="col-sm-7 text-break">{{ host.uri }}</dd>
</dl>
</div>
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block script %}
{% if connected %}
<script src="{% static "js/chart.umd.min.js" %}"></script>
<script src="{% static "js/charts.js" %}"></script>
<script src="{% static "js/poll.js" %}"></script>
<script>
{% trans "average over" as average_over %}
// the seconds each CPU point averages
var cpuWindows = [];
var cpuChart = lineChart(document.getElementById("cpuChart"), "%", ["Usage"], {
max: 100,
tooltip: function (context, text) {
var span = cpuWindows[context.dataIndex];
return span ? text + ' ({{ average_over|escapejs }} ' + span + ' s)' : text;
},
});
var memChart = lineChart(document.getElementById("memChart"), "GB", ["Usage"]);
var cpuNow = document.getElementById("cpu_now");
var memNow = document.getElementById("mem_now");
var state = document.getElementById("graph_state");
var lastUpdate = "–";
function gb(bytes) {
return Math.round(bytes / 107374182.4) / 10;
}
// a failed reading leaves the charts as they were: say since when
function stale() {
state.textContent = state.dataset.stale.replace("%(time)s", lastUpdate);
state.classList.add("text-danger");
}
// the CPU sample of the previous answer: usage is taken since then
var cpuSample = null;
pollWhileVisible(5000, function () {
return $.getJSON('{% url 'compute_graph' compute.id %}', cpuSample ? {cpu_sample: cpuSample} : {}).fail(stale);
}, function (data) {
cpuSample = data.cpu_sample || null;
cpuWindows.push(data.window);
if (cpuWindows.length > CHART_POINTS) {
cpuWindows.shift();
}
addPoint(cpuChart, data.timeline, [data.cpudata]);
// a host that could not be read gives no values: a gap, same axis
if (data.memdata.total) {
memChart.options.scales.y.max = gb(data.memdata.total);
addPoint(memChart, data.timeline, [gb(data.memdata.usage)]);
memNow.textContent = gb(data.memdata.usage) + " / " + gb(data.memdata.total) + " GB";
} else {
addPoint(memChart, data.timeline, [null]);
}
if (data.cpudata == null) {
stale();
return;
}
cpuNow.textContent = data.cpudata + " %";
lastUpdate = data.timeline;
state.textContent = state.dataset.updated.replace("%(time)s", data.timeline);
state.classList.remove("text-danger");
}, 0);
</script>
{% endif %}
{% endblock %}