mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 11:46:49 +00:00
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.
212 lines
9.9 KiB
HTML
212 lines
9.9 KiB
HTML
{% 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" %} →</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" %} →</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">–</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">–</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 %}
|