Files
webgui/emhttp/plugins/dynamix/CPUvms.page
Zack Spear 10bf8de500 fix: enhance layout consistency in CPU pages and default styles
- Wrapped tables in CPUisol.page, CPUpin.page, and CPUvms.page with a new div class for improved layout structure.
- Added a new CSS class .TableContainer--no-min-width to handle overflow and ensure consistent table presentation.
- This change continues the effort to enhance visual consistency across the plugin.
2025-05-22 13:04:58 -07:00

341 lines
11 KiB
Plaintext

Menu="CPUset:1"
Title="CPU Pinning VM"
Tag="icon-cpu"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$libvirtd = pgrep('libvirtd')!==false;
$dockerd = pgrep('dockerd')!==false;
$cpus = cpu_list();
$total = count($cpus);
$spinner = "<tr><td colspan='".($total+2)."'><div class='spinner'></div></td></tr>";
$cpuset = implode(';',$cpus);
$is_intel_cpu = is_intel_cpu();
$core_types = $is_intel_cpu ? get_intel_core_types() : [];
function create($type = "") {
// create the table header. Make multiple rows when CPU cores are many ;)
global $total, $cpus, $is_intel_cpu, $core_types;
$loop = floor(($total-1)/32) + 1;
$text = [];
for ($c = 0; $c < $loop; $c++) {
$max = ($c == $loop-1 ? ($total % 32 ?: 32) : 32);
for ($n = 0; $n < $max; $n++) {
[$cpu1, $cpu2] = my_preg_split('/[,-]/', $cpus[$c * 32 + $n]);
if (empty($text[$n])) $text[$n] = '';
$text[$n] .= "$cpu1<br>";
if ($cpu2) $text[$n] .= "$cpu2<br>";
}
}
$label = implode('<br>', array_fill(0, $loop, 'CORES:' . ($cpu2 ? '<br>CORES:' : '')));
if ($type == "vm") echo "<th>VCPUS</th>";
echo "<th>$label</th>" . implode(array_map(function($t) {
global $is_intel_cpu, $core_types;
[$cpu1, $cpu2] = my_preg_split('/[,<br>]/',$t);
if ($is_intel_cpu && count($core_types) > 0) $core_type = "$core_types[$cpu1]"; else $core_type = "";
return "<th title='$core_type'>$t</th>";
}, $text));
}
?>
<script>
String.prototype.strip = function(){return this.replace(/ |\(|\)|\[|\]/g,'');}
String.prototype.encode = function(){return this.replace(/\./g,'%2e');}
function apply(form) {
/* disable buttons */
$(form).find('input[value="_(Apply)_"]').prop('disabled', true);
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick', null).off('click').click(function() {
done();
});
$('input[value="_(Done)_"]').prop('disabled', true);
var id = $(form).prop('name');
var args = {
'id': id,
'names': form.names.value.encode(),
'cpus': {},
'cores': {}
};
/* get the 'checked' cpus */
$(form).find('input[type=checkbox]').each(function() {
if ($(this).prop('checked')) {
args['cpus'][$(this).prop('name').encode()] = 'on';
}
});
/* get the vcpus */
$(form).find('select').each(function() {
if ($(this).prop('id')) {
args['cores'][$(this).prop('name').encode()] = $(this).prop('value');
}
});
/* show the instant wait message */
$('#wait-' + id).show();
/* step 1: prepare the update and report back the changes */
$.post('/webGui/include/UpdateOne.php', {
data: JSON.stringify(args)
}, function(reply) {
if (reply.error) {
swal({
type: 'error',
title: "_(Assignment error)_",
text: reply.error,
html: true,
confirmButtonText: "_(Ok)_"
}, function() {
$('#wait-' + id).hide();
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('disabled', false).prop('onclick', null).off('click').click(function() {
reset($('form[name="' + id + '"]'));
});
});
} else if (reply.success) {
var data = reply.success.split(';');
wait = data.length;
for (var i = 0; i < data.length; i++) {
var name = data[i];
//$('#' + id + '-' + name.strip()).show('slow');
$('#' + CSS.escape(id + '-' + name.strip().replace(/!/g, '%21'))).show('slow');
/* step 2: apply the changes by updating the vm or container */
$.post('/webGui/include/UpdateTwo.php', {
id: id,
name: encodeURIComponent(name)
}, function(reply) {
if (reply.error) {
/* report error and reload table */
swal({
type: 'error',
title: "_(Execution error)_",
text: reply.error,
html: true,
confirmButtonText: "_(Ok)_"
}, function() {
$('#wait-' + id).hide();
$('input[value="_(Done)_"]').prop('disabled', false);
reset($('form[name="' + id + '"]'));
});
} else {
$('#' + id + '-' + reply.success.strip()).hide('slow');
/* cleanup when all is done */
if (!--wait) {
setTimeout(function() {
$('#wait-' + id).hide();
}, 500);
$('input[value="_(Done)_"]').prop('disabled', false);
/* isolated cpus, need reboot notice? */
if (id == 'is') notice();
}
}
});
}
} else {
$('#wait-' + id).hide();
$('input[value="_(Done)_"]').prop('disabled', false);
if (id == 'is') notice();
}
});
}
/* Function to fetch current VM assignments and update the table */
function vm() {
$.post('/webGui/include/CPUset.php', { id: 'vm', cpus: '<?=$cpuset?>' }, function(d) {
var data = d.split('\0');
$('#table-vm').html(data[0]);
$('#names-vm').val(data[1]);
buttons(document.vm);
});
}
/* Function to toggle thread assignment for containers */
function thread2containers(n) {
const selector = $('form[name=ct]').find(`[name$=":${n}"]`);
const checkboxes = selector.length;
const checked = selector.filter(':checked').length;
selector.prop('checked', (checkboxes - checked > checked ? true : false)).change();
}
/* Function to fetch current container assignments and update the table */
function ct() {
$.post('/webGui/include/CPUset.php', { id: 'ct', cpus: '<?=$cpuset?>' }, function(d) {
var data = d.split('\0');
$('#table-ct').html(data[0]);
$('#names-ct').val(data[1]);
buttons(document.ct);
/* Inject thread to containers toggles */
if ($('a[onclick^="thread2containers"]').length === 0) {
$('form[name=ct]').find('thead tr th:gt(1)').each((i, elem) => {
// Preserve the existing title if available
let existingTitle = elem.title || "";
let newTitle = existingTitle ? existingTitle + "&#10;_(Toggle thread to containers)_" : "_(Toggle thread to containers)_";
elem.innerHTML = elem.innerHTML.replace(/(\d+)/g, '<a href="#" onclick="thread2containers(this.innerText);return false;" title="' + newTitle + '">$1</a>');
});
}
});
}
/* Function to fetch current isolated CPU assignments and update the table */
function is() {
$.post('/webGui/include/CPUset.php', { id: 'is', cpus: '<?=$cpuset?>' }, function(d) {
$('#table-is').html(d);
buttons(document.is);
<?if ($safemode):?>
$('#table-is').find('input[type=checkbox]').prop('disabled', true);
<?endif;?>
});
}
/* Function to display a notice to reboot the system after changes */
function notice() {
var message = "_(CPU Isolation: A reboot is required to apply changes)_";
$.post('/webGui/include/CPUset.php', { id: 'cmd' }, function(d) {
if (d == 1) addRebootNotice(message); else removeRebootNotice(message);
});
}
/* Function to reset form changes without a complete page refresh */
function reset(form) {
$(form).find('input[value="_(Apply)_"]').prop('disabled', true);
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick', null).off('click').click(function() { done(); });
switch ($(form).prop('name')) {
case 'vm':
$('#table-vm').html("<?=$spinner?>");
$('div.spinner').html(unraid_logo);
vm();
break;
case 'ct':
$('#table-ct').html("<?=$spinner?>");
$('div.spinner').html(unraid_logo);
ct();
break;
case 'is':
$('#table-is').html("<?=$spinner?>");
$('div.spinner').html(unraid_logo);
is();
break;
}
}
function vcpupins($button) {
var $name = $button.id.split(";")[1];
var $value = $button.value;
var $form = $('form[name="vm"]');
if ($value == "_(Select All)_") {
$("[class='vcpu-" + $name + "']").prop('checked', true);
var $cores = $("[class='vcpu-" + $name + "']:checked");
$("[class~='vcpus-" + $name + "']").prop("disabled", true).prop("value", $cores.length);
$button.value = "_(Deselect All)_";
$($form).find('input[value="_(Apply)_"]').prop('disabled', false);
} else {
$("[class='vcpu-" + $name + "']").prop('checked', false);
$("[class~='vcpus-" + $name + "']").prop("disabled", false).prop("value", 1);
$button.value = "_(Select All)_";
$($form).find('input[value="_(Apply)_"]').prop('disabled', false);
}
}
/* Function to handle checkbox interactions and enable/disable form buttons */
function buttons(form) {
$(form).find('input[type=checkbox]').each(function() {
$(this).on('change', function() {
var total = $(form).find('input[type=checkbox]').length;
var checked = 'input[name^="' + $(this).prop('name').split(':')[0] + ':' + '"]:checked';
var cores = $(form).find(checked).length;
/* Ensure VMs have at least one core selected */
if ($(form).prop('name') == 'vm' ) {
value = $(this).prop('name').split(':')[0];
var testname = "vmbtnvCPUSelect" + value ;
if (cores < 1)
{
$(form).find('select[name="' + $(this).prop('name').split(':')[0] + '"]').prop('disabled', false).prop("value",1);
$(form).find('input[name="vmbtnvCPUSelect' + $(this).prop('name').split(':')[0] + '"]').prop("value","_(Select All)_");
} else {
$(form).find('select[name="' + $(this).prop('name').split(':')[0] + '"]').prop('disabled', true).prop("value",cores);
$(form).find('input[name="vmbtnvCPUSelect' + $(this).prop('name').split(':')[0] + '"]').prop("value","_(Deselect All)_");
}
}
/* Ensure isolation does not have all cores selected */
if ($(form).prop('name') == 'is' && $(this).prop('checked')) $(this).prop('checked', cores < total);
/* Enable Apply and Done buttons when changes are made */
$(form).find('input[value="_(Apply)_"]').prop('disabled', false);
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('onclick', null).off('click').click(function() { reset(form); });
});
});
$(form).find('select').each(function() {
$(this).on('change', function() {
var total = $(form).find('input[type=checkbox]').length;
var checked = 'input[name^="' + $(this).prop('name').split(':')[0] + ':' + '"]:checked';
var cores = $(form).find(checked).length;
/* Enable Apply and Done buttons when changes are made */
$(form).find('input[value="_(Apply)_"]').prop('disabled', false);
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('onclick', null).off('click').click(function() { reset(form); });
});
});
}
/* Initialize the functions on document ready */
$(function() {
<?if ($libvirtd):?>
vm();
<?endif;?>
<?if ($dockerd):?>
ct();
<?endif;?>
is();
notice();
});
</script>
<?if ($libvirtd):?>
<form name="vm">
<input type="hidden" name="names" id="names-vm" value="">
<div class="TableContainer--no-min-width">
<table class="tablesorter shift" style="width: auto">
<thead>
<tr>
<th><i class="fa fa-list"></i> _(VM)_</th>
<?create("vm")?>
</tr>
</thead>
<tbody id="table-vm"><?=$spinner?></tbody>
</table>
</div>
<div class="flex flex-row items-center gap-2">
<input type="button" value="_(Apply)_" onclick="apply(this.form)" disabled>
<input type="button" value="_(Done)_" onclick="done()">
<span id="wait-vm" class="red-text" style="display:none">
<i class="fa fa-spinner fa-spin"></i>
<span>_(Please wait)_...</span>
</span>
</div>
</form>
<?else:?>
<div class="notice">_(No CPU pinning available. VM service must be started)_</div>
<?endif;?>
:cpu_vms_help: