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:
