mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The Add disk, Add network device and bandwidth dialogs of the VM page set their labels in a narrow column beside the fields and closed with a grey Close: Add disk split "GB" over two lines and offered an unexplained Metadata box, its existing-volume pool was a dropdown of links that wrote a hidden field; the network list prefixed every choice with "Network" or "Interface"; the bandwidth fields asked for "kilobytes" where libvirt counts KiB/s and KiB. Now their labels sit above the fields, tied to them, in two columns. Add disk is one form whose source, New disk or Existing volume, is a radio card as in the new VM form, the chosen source's fields shown and sent and its action taken; the size has a GiB suffix and "Preallocate metadata (qcow2)", the existing volume's pool is a select that loads its volumes, the bus and the cache are shared. Add network device picks its source from Networks and Interfaces groups and says how its MAC is filled; the bandwidth limit names its units and when a running VM gets it. Move says when the host has no other pool and is disabled then. Every dialog closes with Cancel and its action; the posted fields are unchanged.
474 lines
23 KiB
HTML
474 lines
23 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
{% load vm_states %}
|
||
{% load i18n %}
|
||
{% load operations %}
|
||
{% load bootstrap_icons %}
|
||
|
||
{% block title %}{% trans "Instance" %} - {{ instance.name }}{% endblock %}
|
||
|
||
{% block page_header %}{% endblock page_header %}{# the card below has the heading #}
|
||
|
||
{% block content %}
|
||
{% include 'pleasewaitdialog.html' %}
|
||
{# the VM at a glance in one card: who and where it is, what it has, and its sections as the card's footer #}
|
||
<section class="card mb-3" aria-labelledby="vm-name">
|
||
<div class="card-body p-3">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
|
||
<div>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<h1 class="h4 mb-0" id="vm-name">{{ instance.name }}</h1>
|
||
{% vm_state instance.status as state %}
|
||
<span class="badge text-bg-{{ state.1 }}">{{ state.0 }}</span>
|
||
</div>
|
||
{% if instance.title %}<p class="text-body-secondary mb-1">{{ instance.title }}</p>{% endif %}
|
||
{% include 'instances/host_link.html' %}
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
{% if instance.guest_agent_ready %}
|
||
<button type="button" class="btn btn-sm btn-secondary" onclick="get_osinfo();">{% bs_icon 'info-circle' %} {% trans "OS details" %}</button>
|
||
{% endif %}
|
||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:instance' instance.id %}">{% bs_icon 'arrow-clockwise' %} {% trans "Refresh" %}</a>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex flex-wrap column-gap-3 row-gap-1 small mt-2">
|
||
{% if not instance.guest_agent %}
|
||
<span>{% bs_icon 'dash-circle' %} {% trans "Guest agent off" %}</span>
|
||
{% elif instance.guest_agent_ready %}
|
||
<span><span class="text-success">{% bs_icon 'check-circle' %}</span> {% trans "Guest agent connected" %}</span>
|
||
{% else %}
|
||
<span><span class="text-warning">{% bs_icon 'exclamation-circle' %}</span> {% trans "Guest agent not connected" %}</span>
|
||
{% endif %}
|
||
<span>{% bs_icon 'camera' %} {% blocktrans count counter=instance.snapshots|length %}{{ counter }} snapshot{% plural %}{{ counter }} snapshots{% endblocktrans %}</span>
|
||
</div>
|
||
<dl class="row g-2 small mb-0 mt-1">
|
||
<div class="col-6 col-lg-2">
|
||
<dt class="fw-normal text-body-secondary">{% trans "CPU" %}</dt>
|
||
<dd class="mb-0">{% firstof instance.cur_vcpu instance.vcpu %} {% trans "of" %} {{ instance.vcpu }} {% trans "vCPU" %}</dd>
|
||
</div>
|
||
<div class="col-6 col-lg-2">
|
||
<dt class="fw-normal text-body-secondary">{% trans "Memory" %}</dt>
|
||
<dd class="mb-0">{{ instance.cur_memory }} MiB</dd>
|
||
</div>
|
||
<div class="col-6 col-lg-5">
|
||
<dt class="fw-normal text-body-secondary">{% trans "Disks" %}</dt>
|
||
<dd class="mb-0">{% for disk in instance.disks %}{{ disk.dev }} {{ disk.size|filesizeformat }}{% if not forloop.last %} · {% endif %}{% empty %}{% trans "None" %}{% endfor %}</dd>
|
||
</div>
|
||
<div class="col-6 col-lg-3">
|
||
<dt class="fw-normal text-body-secondary">{% trans "IPv4" %}</dt>
|
||
<dd class="mb-0 font-monospace">{{ ipv4_addresses|join:", "|default:"—" }}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
<div class="card-footer p-2">
|
||
<ul class="nav nav-pills flex-wrap flex-lg-nowrap gap-1 small" id="navbtn" role="tablist" aria-label="{% trans "Instance actions" %}">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap active" id="power-tab" aria-controls="power" data-bs-toggle="pill" data-bs-target="#power" type="button" role="tab" aria-selected="true">{% bs_icon 'power' %} {% trans "Power" %}</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="access-tab" aria-controls="access" data-bs-toggle="pill" data-bs-target="#access" type="button" role="tab" aria-selected="false">{% bs_icon 'lock' %} {% trans "Access" %}</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="resize-tab" aria-controls="resize" data-bs-toggle="pill" data-bs-target="#resize" type="button" role="tab" aria-selected="false">{% bs_icon 'arrows-angle-expand' %} {% trans "Resize" %}</button>
|
||
</li>
|
||
{% if can_snapshot %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="snapshots-tab" aria-controls="snapshots" data-bs-toggle="pill" data-bs-target="#snapshots" type="button" role="tab" aria-selected="false">{% bs_icon 'camera' %} {% trans "Snapshot" %}</button>
|
||
</li>
|
||
{% endif %}
|
||
{% if show_settings %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="settings-tab" aria-controls="settings" data-bs-toggle="pill" data-bs-target="#settings" type="button" role="tab" aria-selected="false">{% bs_icon 'gear-wide-connected' %} {% trans "Settings" %}</button>
|
||
</li>
|
||
{% endif %}
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="graphics-tab" aria-controls="graphics" data-bs-toggle="pill" data-bs-target="#graphics" type="button" role="tab" aria-selected="false">{% bs_icon 'bar-chart-line' %} {% trans "Stats" %}</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link text-nowrap" id="undefine-tab" aria-controls="undefine" data-bs-toggle="pill" data-bs-target="#undefine" type="button" role="tab" aria-selected="false">{% bs_icon 'trash' %} {% trans "Destroy" %}</button>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<div id="job-panel" aria-live="polite"></div>
|
||
<div id="mainTabMenu">
|
||
<!-- Tab panes -->
|
||
<div class="tab-content" id=mainTabMenuContent>
|
||
{% include 'instances/power_tab.html' %}
|
||
{% include 'instances/access_tab.html' %}
|
||
{% include 'instances/resize_tab.html' %}
|
||
{% if can_snapshot %}{% include 'instances/snapshots_tab.html' %}{% endif %}
|
||
{% if show_settings %}{% include 'instances/settings_tab.html' %}{% endif %}
|
||
{% include 'instances/stats_tab.html' %}
|
||
{% include 'instances/destroy_tab.html' %}
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
{% block script %}
|
||
<script src="{% static 'js/ace/ace.js' %}" type="text/javascript" charset="utf-8"></script>
|
||
<script>
|
||
// the volumes of the pool chosen in the Add disk dialog
|
||
function get_volumes(compute_id, pool) {
|
||
$.getJSON("/computes/" + compute_id + "/storage/" + encodeURIComponent(pool) + "/volumes/", function (data) {
|
||
var vols = $("#vols").empty();
|
||
$.each(data['vols'], function (i, item) {
|
||
vols.append($("<option>").val(item).text(item));
|
||
});
|
||
});
|
||
}
|
||
</script>
|
||
<script src="{% static 'js/xml_editor.js' %}"></script>
|
||
<script>
|
||
function random_mac(net) {
|
||
$.getJSON("{% url 'instances:random_mac_address' %}", function (data) {
|
||
$('input[name="' + net + '"]').val(data['mac']);
|
||
});
|
||
}
|
||
</script>
|
||
<script>
|
||
function guess_mac_address(src_elem, net) {
|
||
new_vname = $(src_elem).val();
|
||
guess_mac_address_url = "{% url 'instances:guess_mac_address' 1 %}".replace(1, new_vname);
|
||
$.getJSON(guess_mac_address_url, function(data) {
|
||
$('input[name="clone-net-mac-'+net+'"]').val(data['mac']);
|
||
});
|
||
}
|
||
</script>
|
||
<script>
|
||
function guess_clone_name() {
|
||
$.getJSON("{% url 'instances:guess_clone_name' %}", function(data) {
|
||
// a free name of the DHCP file, if there is one
|
||
if (!data.name) {
|
||
$('#clone-name-help').text("{{ _("The DHCP file has no free name to suggest.")|escapejs }}");
|
||
return;
|
||
}
|
||
$('#clone-name-help').text('');
|
||
guessed_name = data['name'].split(".")[0];
|
||
$('#clone_name').val(guessed_name);
|
||
update_clone_disk_name(guessed_name);
|
||
guess_mac_address('#clone_name', 0);
|
||
});
|
||
}
|
||
</script>
|
||
<script>
|
||
function update_clone_disk_name(new_vname) {
|
||
vname = '{{ instance.name|escapejs }}';
|
||
{% for disk in instance.config_disks %}
|
||
disk_name = '{{ disk.image|escapejs }}';
|
||
disk_dot = disk_name.split('.');
|
||
disk_dot_suffix = disk_dot[disk_dot.length-1];
|
||
if (disk_name.startsWith(vname)) {
|
||
image = disk_name.replace(vname, new_vname);
|
||
} else if (disk_name.lastIndexOf('.') > -1 && disk_dot_suffix.length <= 7) {
|
||
disk_dot.pop();
|
||
disk_name_only = disk_dot.join('-');
|
||
image = new_vname + "-" + disk_name_only + "." + disk_dot_suffix
|
||
} else if (new_vname != disk_name) {
|
||
image = new_vname
|
||
} else {
|
||
image = new_vname + '-clone';
|
||
}
|
||
$('#disk_name-{{ disk.dev|escapejs }}').val(image);
|
||
{% endfor %}
|
||
}
|
||
</script>
|
||
<script>
|
||
$('#clone_name').on('input', function () {
|
||
update_clone_disk_name($(this).val());
|
||
});
|
||
$(document).ready(function () {
|
||
// get video model or fall back to default
|
||
let video_model = "{{ instance.config_video_model }}";
|
||
if (video_model != '') {
|
||
$("#video_model_select option[value='" + video_model + "']").prop('selected', true);
|
||
}
|
||
});
|
||
{% if can_open_console and vdi_reachable %}
|
||
$(document).ready(function () {
|
||
// set vdi url
|
||
$.get("{% url 'vdi_url' compute.id instance.name %}", function(data) {
|
||
$("#vdi_url_input").attr("value", data);
|
||
$("#vdi_url").attr("href", data);
|
||
});
|
||
});
|
||
{% endif %}
|
||
{% if request.user.is_superuser %}
|
||
$(document).ready(function () {
|
||
// every NIC of the clone a new address: one the VM has would clash with it
|
||
$('input[name^="clone-net-mac-"]').each(function () { random_mac(this.name); });
|
||
random_mac('add-net-mac');
|
||
update_clone_disk_name($('#clone_name').val());
|
||
});
|
||
{% endif %}
|
||
</script>
|
||
<script>
|
||
$(document).ready(function(){
|
||
$('[data-bs-toggle="popover"]').popover({
|
||
placement : 'top'
|
||
});
|
||
});
|
||
</script>
|
||
<script>
|
||
// the boot order: the checked devices, top to bottom, as "disk:vda,cdrom:sda"
|
||
function set_orderlist() {
|
||
var order = $('#b_order input:checked').map(function () { return this.value; }).get();
|
||
$('#bootorder').val(order.join(','));
|
||
}
|
||
$(function () {
|
||
set_orderlist();
|
||
$('#b_order').on('change', 'input', set_orderlist);
|
||
|
||
$('#boot_order_up').on('click', function () {
|
||
$('#b_order label input:checked').each(function () {
|
||
var label = $(this).parent();
|
||
var newPos = label.index() - 1;
|
||
if (newPos > -1) {
|
||
$('#b_order label').eq(newPos).before(label);
|
||
}
|
||
});
|
||
set_orderlist();
|
||
});
|
||
|
||
$('#boot_order_down').on('click', function () {
|
||
var countOptions = $('#b_order label').length;
|
||
var countSelected = $('#b_order label input:checked').length;
|
||
$('#b_order label input:checked').each(function () {
|
||
var label = $(this).parent();
|
||
var newPos = label.index() + countSelected;
|
||
if (newPos < countOptions) {
|
||
$('#b_order label').eq(newPos).after(label);
|
||
}
|
||
});
|
||
set_orderlist();
|
||
});
|
||
});
|
||
</script>
|
||
<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 %}
|
||
{% trans "In" as rx_label %}{% trans "Out" as tx_label %}{% trans "Read" as rd_label %}{% trans "Write" as wr_label %}
|
||
var stats_tab = document.querySelector('#graphics-tab');
|
||
var stats_poll = null;
|
||
// the seconds each CPU point averages
|
||
var cpuWindows = [];
|
||
// the usage sample of the previous answer: the next one averages since then
|
||
var statsSample = null;
|
||
// The charts are polled while the Stats tab is shown; they are made once.
|
||
// a reading for the line above a chart: one decimal, a dash where it is missing
|
||
function reading(value) {
|
||
return value == null ? "–" : String(Math.round(value * 10) / 10);
|
||
}
|
||
stats_tab.addEventListener('hidden.bs.tab', function (event) {
|
||
stats_poll.stop();
|
||
});
|
||
stats_tab.addEventListener('shown.bs.tab', function (event) {
|
||
if (stats_poll !== null) {
|
||
stats_poll.start();
|
||
return;
|
||
}
|
||
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"), "MiB", ["Usage (RSS)"]);
|
||
var diskChart = {};
|
||
{% for disk in instance.disks %}
|
||
diskChart['{{ disk.dev|escapejs }}'] = lineChart(document.getElementById("blk{{ disk.dev|escapejs }}Chart"), "MB/s", ["Read", "Write"]);
|
||
{% endfor %}
|
||
// two NICs with one MAC (a misconfiguration) share the first one's chart
|
||
var netChart = {};
|
||
{% for net in instance.networks %}
|
||
netChart['{{ net.mac|escapejs }}'] = netChart['{{ net.mac|escapejs }}'] || lineChart(document.getElementById("net{{ net.mac|escapejs }}Chart"), "Mbit/s", ["Inbound", "Outbound"]);
|
||
{% endfor %}
|
||
|
||
// the first point at once, then every 5 s like the host graph
|
||
stats_poll = pollWhileVisible(5000, function () {
|
||
return $.getJSON("{% url 'instances:stats' instance.id %}", statsSample ? {sample: statsSample} : {})
|
||
.fail(function () {
|
||
statsSample = null;
|
||
});
|
||
}, function graph_usage(data) {
|
||
statsSample = data.sample || null;
|
||
cpuWindows.push(data.window);
|
||
if (cpuWindows.length > CHART_POINTS) {
|
||
cpuWindows.shift();
|
||
}
|
||
addPoint(cpuChart, data.timeline, [data.cpudata]);
|
||
document.getElementById("cpuNow").textContent = reading(data.cpudata) + " %";
|
||
// a VM whose memory could not be read gives total 0: a gap, same axis;
|
||
// the RSS may pass the balloon size: then the axis grows to it
|
||
if (data.memdata.total) {
|
||
var used = Math.round(data.memdata.used / 1024), total = Math.round(data.memdata.total / 1024);
|
||
memChart.options.scales.y.max = Math.max(total, used);
|
||
addPoint(memChart, data.timeline, [used]);
|
||
document.getElementById("memNow").textContent = used + " / " + total + " MiB";
|
||
} else {
|
||
addPoint(memChart, data.timeline, [null]);
|
||
document.getElementById("memNow").textContent = "";
|
||
}
|
||
// a disk or NIC added after the page loaded has no chart until a reload,
|
||
// and one removed keeps its last points
|
||
function push(chart, id, labels, unit, values) {
|
||
if (chart) {
|
||
addPoint(chart, data.timeline, values);
|
||
document.getElementById(id).textContent =
|
||
labels[0] + " " + reading(values[0]) + " · " + labels[1] + " " + reading(values[1]) + " " + unit;
|
||
}
|
||
}
|
||
data.blkdata.forEach(function (disk) {
|
||
push(diskChart[disk.dev], "blk" + disk.dev + "Now", ["{{ rd_label|escapejs }}", "{{ wr_label|escapejs }}"], "MB/s", disk.data);
|
||
});
|
||
data.netdata.forEach(function (nic) {
|
||
push(netChart[nic.mac], "net" + nic.mac + "Now", ["{{ rx_label|escapejs }}", "{{ tx_label|escapejs }}"], "Mbit/s", nic.data);
|
||
});
|
||
}, 0);
|
||
});
|
||
</script>
|
||
<script>
|
||
backgroundJobRunning = false;
|
||
// not "status": that is window.status, which the browser may not keep
|
||
var vm_status = {{ instance.status|lower }};
|
||
var status_poll = pollWhileVisible(5000, function () {
|
||
return $.getJSON("{% url 'instances:status' instance.id %}");
|
||
}, function get_status(data) {
|
||
// an XML edit not saved yet stays: the reload waits until it is saved or undone
|
||
if (data['status'] != vm_status && !backgroundJobRunning && !(window.xmlEditUnsaved && window.xmlEditUnsaved())) {
|
||
window.location.reload()
|
||
}
|
||
});
|
||
|
||
// Stop getting status info before delete instance
|
||
$('#delete_form').submit(function(){
|
||
status_poll.stop();
|
||
return true;
|
||
});
|
||
</script>
|
||
{% job_panel_script job_resource %}
|
||
<script src="{% static 'js/instance_tabs.js' %}"></script>
|
||
<script>
|
||
// Open the tab whose control targets "#<pane id>"
|
||
function show_tab(target) {
|
||
var controls = document.querySelectorAll('[data-bs-target], a[data-bs-toggle][href]');
|
||
for (var i = 0; i < controls.length; i++) {
|
||
var c = controls[i];
|
||
if ((c.getAttribute('data-bs-target') || c.getAttribute('href')) === target) {
|
||
bootstrap.Tab.getOrCreateInstance(c).show();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// after the whole page is parsed: load_osinfo is defined further down
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
restoreInstanceTab(document, location.hash, show_tab, load_osinfo);
|
||
});
|
||
</script>
|
||
<script>
|
||
// the Host devices tab's add forms: loaded once, when the tab first opens
|
||
var hostDevices = document.getElementById("host-devices");
|
||
if (hostDevices) {
|
||
document.querySelector('[data-bs-target="#hostdevs"]').addEventListener("shown.bs.tab", function () {
|
||
if (hostDevices.dataset.loaded) return;
|
||
hostDevices.dataset.loaded = "true";
|
||
fetch(hostDevices.dataset.url).then(function (response) {
|
||
if (!response.ok) throw new Error(response.statusText);
|
||
return response.text();
|
||
}).then(function (html) {
|
||
hostDevices.innerHTML = html;
|
||
}).catch(function () {
|
||
hostDevices.textContent = "{{ _("The host's devices could not be read.")|escapejs }}";
|
||
});
|
||
});
|
||
}
|
||
// the Migrate tab's disks: loaded for the chosen destination when the tab opens or the choice changes
|
||
// the form is not sent until the disks of the chosen destination are shown
|
||
var migratePlan = document.getElementById("migrate-plan");
|
||
var migrateCompute = document.getElementById("migrate_compute");
|
||
var migrateLoading = null;
|
||
function loadMigratePlan() {
|
||
if (!migrateCompute.value) return;
|
||
if (migrateLoading) migrateLoading.abort();
|
||
var loading = migrateLoading = new AbortController();
|
||
var submit = migratePlan.closest("form").querySelector('button[name="migrate"]');
|
||
submit.disabled = true;
|
||
migratePlan.textContent = "{{ _("Checking the disks…")|escapejs }}";
|
||
fetch(migratePlan.dataset.url + "?compute_id=" + encodeURIComponent(migrateCompute.value),
|
||
{signal: loading.signal}).then(function (response) {
|
||
if (!response.ok) throw new Error(response.statusText);
|
||
return response.text();
|
||
}).then(function (html) {
|
||
migratePlan.innerHTML = html;
|
||
// a destination that did not answer, or a disk that cannot go there
|
||
submit.disabled = migratePlan.querySelector("[data-refused]") !== null;
|
||
}).catch(function () {
|
||
if (loading.signal.aborted) return; // another destination was chosen
|
||
migratePlan.textContent = "{{ _("The disks could not be checked.")|escapejs }}";
|
||
submit.disabled = false; // the migration checks them again
|
||
});
|
||
}
|
||
// the destinations that cannot be chosen (unreachable, or this host under
|
||
// another record) are marked first; then the disks of the first one left
|
||
function loadMigrateTargets() {
|
||
var submit = migratePlan.closest("form").querySelector('button[name="migrate"]');
|
||
submit.disabled = true;
|
||
migrateCompute.disabled = true; // no disks are checked for a host before the hosts are
|
||
migratePlan.textContent = "{{ _("Checking the hosts…")|escapejs }}";
|
||
fetch(migratePlan.dataset.targetsUrl).then(function (response) {
|
||
if (!response.ok) throw new Error(response.statusText);
|
||
return response.json();
|
||
}).then(function (refused) {
|
||
migrateCompute.disabled = false;
|
||
Array.prototype.forEach.call(migrateCompute.options, function (option) {
|
||
if (refused[option.value]) {
|
||
option.disabled = true;
|
||
option.textContent = refused[option.value]; // it names the host
|
||
}
|
||
});
|
||
var first = Array.prototype.find.call(migrateCompute.options, function (option) { return !option.disabled; });
|
||
if (!first) {
|
||
migratePlan.textContent = "{{ _("No host can take the VM now.")|escapejs }}";
|
||
return;
|
||
}
|
||
if (migrateCompute.selectedOptions[0].disabled) migrateCompute.value = first.value;
|
||
loadMigratePlan();
|
||
}).catch(function () {
|
||
migratePlan.textContent = "{{ _("The hosts could not be checked.")|escapejs }}";
|
||
});
|
||
}
|
||
if (migratePlan && migrateCompute) {
|
||
document.querySelector('[data-bs-target="#migrate"]').addEventListener("shown.bs.tab", function () {
|
||
if (!migratePlan.dataset.loaded) {
|
||
migratePlan.dataset.loaded = "true";
|
||
loadMigrateTargets();
|
||
}
|
||
});
|
||
migrateCompute.addEventListener("change", loadMigratePlan);
|
||
}
|
||
function get_osinfo() {
|
||
show_tab('#settings');
|
||
show_tab('#osinfo');
|
||
load_osinfo();
|
||
}
|
||
function load_osinfo() {
|
||
$.getJSON("{% url 'instances:osinfo' instance.id %}", function (data) {
|
||
$('#osinfo-error').addClass('d-none');
|
||
$('#oshostname').text(data['host-name'] || '');
|
||
$('#osname').text(data.id || '');
|
||
$('#osprettyname').text(data['pretty-name'] || '');
|
||
$('#oskernelrelease').text(data['kernel-release'] || '');
|
||
$('#oskernelversion').text(data['kernel-version'] || '');
|
||
$('#osversion').text(data.version || '');
|
||
$('#ostimezone').text(data.zone ? data.zone + " / " + data.offset : '');
|
||
}).fail(function (xhr) {
|
||
var error = xhr.responseJSON && xhr.responseJSON.error;
|
||
$('#osinfo-error').text(error || "{% trans 'The guest agent did not answer' %}").removeClass('d-none');
|
||
});
|
||
}
|
||
</script>
|
||
{% endblock %}
|