Update WIP

This commit is contained in:
SimonFair
2025-11-13 16:29:40 +00:00
parent 5d5282bca7
commit ff6a5adb50
9 changed files with 798 additions and 159 deletions

View File

@@ -55,19 +55,19 @@ function saveVFSettingsConfig(pciId,vd,interactive=1) {
$.post( "/plugins/dynamix/include/update.sriov-cfg.php", { type:"sriovsettings", pciid: pciId, vd:vd, vfio:vfio, mac:mac } )
.done(function(d) {
if (d==1) {
addRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
//addRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
if (interactive == 1)
swal({
title: "VF Settings",
text: "Configuration saved.",
title: _("VF Settings"),
text: _("Configuration saved") + ".",
type: "success",
timer: 3000,
showConfirmButton: false
});
} else {
removeRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
//removeRebootNotice(message);
//document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
}
});
}
@@ -78,19 +78,19 @@ function saveVFsConfig(pciId,vd,interactive=1) {
$.post( "/plugins/dynamix/include/update.sriov-cfg.php", { type:"sriov", pciid: pciId, vd:vd, numvfs:numvfs } )
.done(function(d) {
if (d==1) {
addRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
// addRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
if (interactive == 1)
swal({
title: "VFs",
text: "Configuration saved.",
text: _("Configuration saved") + ".",
type: "success",
timer: 3000,
showConfirmButton: false
});
} else {
removeRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
// removeRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
}
if (interactive == 1) $('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
});
@@ -99,7 +99,7 @@ function saveVFsConfig(pciId,vd,interactive=1) {
function generateMAC(pciId) {
if (!pciId) {
swal("Error", "No PCI ID provided to generateMAC()", "error");
swal("Error", _("No PCI ID provided to generateMAC()"), "error");
return;
}
@@ -107,7 +107,7 @@ function generateMAC(pciId) {
var pciPattern = /^([0-9a-fA-F]{4}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2})\.([0-7])$/;
var match = pciId.match(pciPattern);
if (!match) {
swal("Invalid PCI ID", "Expected format: 0000:03:00.2", "error");
swal(_("Invalid PCI ID"), _("Expected format: 0000:03:00.2"), "error");
return;
}
@@ -314,11 +314,7 @@ function applyVFSettings(pciId, vd, currentvfio, currentmac) {
return;
}
saveVFSettingsConfig(pciId, vd, 0);
doVFSettingApply(pciId, vd, vfio, mac, currentvfio,currentmac,message);
doVFSettingApply(pciId, vd, vfio, mac, currentvfio,currentmac,message);
})
.fail(function (xhr, status, error) {
console.error("Active check failed:", status, error, xhr.responseText);
@@ -370,9 +366,9 @@ function doVFSettingApply(pciId, vd, vfio, mac, currentvfio, currentmac, message
// Handle structured PHP result
if (data.success) {
addRebootNotice(message);
document.getElementById("warning").innerHTML =
"<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
//addRebootNotice(message);
// document.getElementById("warning").innerHTML =
// "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
saveVFSettingsConfig(pciId, vd, 0);
swal({
title: "Update Complete",
@@ -382,8 +378,8 @@ function doVFSettingApply(pciId, vd, vfio, mac, currentvfio, currentmac, message
showConfirmButton: false
});
} else {
removeRebootNotice(message);
const errorMsg = data.error || "No changes detected or operation failed.";
//removeRebootNotice(message);
// const errorMsg = data.error || "No changes detected or operation failed.";
document.getElementById("warning").innerHTML = "<b>" + errorMsg + "</b>";
swal({
@@ -407,6 +403,18 @@ function doVFSettingApply(pciId, vd, vfio, mac, currentvfio, currentmac, message
});
}
// Helper: safely stringify nested structures
function formatDetails(details) {
if (details == null) return "";
if (typeof details === "string") return details;
if (Array.isArray(details))
return details.map(formatDetails).join("\n");
if (typeof details === "object")
return Object.entries(details)
.map(([k, v]) => `${k}: ${formatDetails(v)}`)
.join("\n\n");
return String(details);
}
function doVFApply(pciId, vd, numvfs, message) {
// Show "updating" alert
@@ -426,55 +434,73 @@ function doVFApply(pciId, vd, numvfs, message) {
vd: vd,
numvfs: numvfs
})
.done(function (data) {
// Normalize to JSON object
if (typeof data === "string") {
try { data = JSON.parse(data); }
catch (e) {
data = { success: false, error: "Invalid JSON: " + e.message };
.done(function (data) {
// Parse JSON safely
if (typeof data === "string") {
try { data = JSON.parse(data); }
catch (e) {
data = { success: false, error: "Invalid JSON: " + e.message };
}
}
}
// Extract a clean message
let msg = "Configuration successfully applied.";
// Helper: recursively format details to plain text
function formatDetails(obj, indent = 0) {
const pad = " ".repeat(indent);
if (obj == null) return "";
if (typeof obj === "string") return pad + obj;
if (typeof obj === "number" || typeof obj === "boolean") return pad + obj;
if (Array.isArray(obj)) return obj.map(x => formatDetails(x, indent)).join("\n");
if (typeof obj === "object") {
return Object.entries(obj).map(([key, val]) => {
if (typeof val === "object" && val !== null) {
// Special case for VF result objects
if ("success" in val || "error" in val || "details" in val) {
const symbol = val.success ? "✔" : "✖";
const msg = val.error || val.details || "";
return `${pad}${key}\n${pad} ${symbol} ${msg}`;
}
return `${pad}${key}:\n${formatDetails(val, indent + 1)}`;
}
return `${pad}${key}: ${val}`;
}).join("\n");
}
return pad + String(obj);
}
if (data.error) {
msg = "Error: " + data.error;
} else if (data.details) {
if (Array.isArray(data.details)) {
msg = data.details.join("\n");
} else if (typeof data.details === "object") {
msg = Object.entries(data.details)
.map(([k, v]) => `${k}: ${JSON.stringify(v, null, 2)}`)
.join("\n\n");
} else if (typeof data.details === "string") {
msg = data.details;
}
}
// Build message
let msg = "";
if (data.error) {
msg = "Error: " + data.error;
} else if (data.details) {
msg = formatDetails(data.details);
} else {
msg = "Configuration successfully applied.";
}
// Pick alert type
// Show alert
const ok = data.success === true || data.success === 1;
saveVFsConfig(pciId, vd, 0);
// Show success or failure alert
swal({
title: data.success ? "Update Complete" : "Update Failed",
text: msg,
type: data.success ? "success" : "error",
timer: data.success ? 3000 : null,
showConfirmButton: !data.success
});
// Always reload the table after handling
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
})
.fail(function (xhr, status, error) {
swal({
title: "Network Error",
text: "Failed to communicate with the server.\n" + (error || status),
type: "error",
showConfirmButton: true
});
});
saveVFsConfig(pciId, vd, 0);
swal({
title: ok ? "Update Complete" : "Update Failed",
text: msg,
type: ok ? "success" : "error",
timer: ok ? 3000: null,
showConfirmButton: !ok
});
// Reload table
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
})
.fail(function (xhr, status, error) {
swal({
title: "Network Error",
text: "Failed to communicate with the server.\n" + (error || status),
type: "error",
showConfirmButton: true
});
});
}

View File

@@ -0,0 +1,583 @@
Menu="UNRAID-OS"
Title="System Devices"
Icon="icon-hardware"
Tag="server"
---
<?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.
*/
?>
<script>
$(function(){
$('#t1').load('/webGui/include/SysDevs.php',{table:'t1'});
$('#t2').load('/webGui/include/SysDevs.php',{table:'t2'});
$('#t3').load('/webGui/include/SysDevs.php',{table:'t3'});
$('#t4').load('/webGui/include/SysDevs.php',{table:'t4'});
});
function applyCfg() {
var message = "_(System Devices)_: _(A reboot is required to apply changes)_";
var string = "BIND=";
var elements = document.getElementById("vfiopci").elements;
for (var i = 0, element; element = elements[i++];) {
if (element.type === "checkbox" && element.checked === true && element.className.substring(0, 5) === "iommu")
string = string + element.value + " ";
}
string = string.trim();
if (string === "BIND=") {
string = "";
}
$.post( "/plugins/dynamix/include/update.vfio-pci-cfg.php", { cfg: string } )
.done(function(d) {
if (d==1) {
addRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
} else {
removeRebootNotice(message);
document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
}
$("#applycfg").attr("disabled",true);
});
}
function saveVFSettingsConfig(pciId,vd,interactive=1) {
var message = "_(System Devices)_: _(A reboot is required to apply changes)_";
var mac = document.getElementById("vfmac" + pciId).value;
var vfio = document.getElementById("vfvfio" + pciId).checked;
$.post( "/plugins/dynamix/include/update.sriov-cfg.php", { type:"sriovsettings", pciid: pciId, vd:vd, vfio:vfio, mac:mac } )
.done(function(d) {
if (d==1) {
//addRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
if (interactive == 1)
swal({
title: "VF Settings",
text: "Configuration saved.",
type: "success",
timer: 3000,
showConfirmButton: false
});
} else {
//removeRebootNotice(message);
//document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
}
});
}
function saveVFsConfig(pciId,vd,interactive=1) {
var message = "_(System Devices)_: _(A reboot is required to apply changes)_";
var numvfs = document.getElementById("vf" + pciId).value;
$.post( "/plugins/dynamix/include/update.sriov-cfg.php", { type:"sriov", pciid: pciId, vd:vd, numvfs:numvfs } )
.done(function(d) {
if (d==1) {
// addRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
if (interactive == 1)
swal({
title: "VFs",
text: "Configuration saved.",
type: "success",
timer: 3000,
showConfirmButton: false
});
} else {
// removeRebootNotice(message);
// document.getElementById("warning").innerHTML = "<b>_(No changes)_.</b>";
}
if (interactive == 1) $('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
});
}
function generateMAC(pciId) {
if (!pciId) {
swal("Error", "No PCI ID provided to generateMAC()", "error");
return;
}
// Validate PCI ID format
var pciPattern = /^([0-9a-fA-F]{4}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2})\.([0-7])$/;
var match = pciId.match(pciPattern);
if (!match) {
swal("Invalid PCI ID", "Expected format: 0000:03:00.2", "error");
return;
}
// Ask for host ID using Unraid swal input
swal({
title: "Enter Host ID",
text: "Provide a 2-digit hex host ID (e.g. 01)",
type: "input",
showCancelButton: true,
closeOnConfirm: false,
inputPlaceholder: "00"
}, function(hostId) {
if (hostId === false) return; // cancelled
if (!/^[0-9a-fA-F]{1,2}$/.test(hostId)) {
swal.showInputError("Please enter 12 hex digits (00ff)");
return false;
}
// Extract PCI parts
var domain = match[1];
var bus = match[2];
var dev = match[3];
var func = match[4];
var dom_lo = ("0" + (parseInt(domain, 16) & 0xFF).toString(16)).slice(-2);
// Format: 62:<domain>:<bus>:<device>:<function>:<host>
var mac = [
"62",
dom_lo,
bus.padStart(2, "0"),
dev.padStart(2, "0"),
func.padStart(2, "0"),
hostId.padStart(2, "0")
].join(":").toUpperCase();
// Build target input ID (e.g., #vfmac0000:04:11.1)
var inputId = "vfmac" + pciId;
// Update the field if it exists
var $field = $("#" + CSS.escape(inputId));
if ($field.length) {
$field.val(mac);
}
swal({
title: "MAC",
text: "Generated successfully.",
type: "success",
timer: 3000,
showConfirmButton: false
});
});
}
function applyVFsConfig(pciId, vd, vfs) {
const message = "_(System Devices)_: _(A reboot is required to apply changes)_";
const numvfs = parseInt(document.getElementById("vf" + pciId).value, 10);
vfs = parseInt(vfs, 10);
// --- Step 1: Check for active VMs (always returns JSON) ---
$.post("/plugins/dynamix/include/apply.sriov-cfg.php", {
type: "inuse",
pciid: pciId,
pcitype: "PF"
}, null, "json") // automatically parse JSON
.done(function (data) {
console.log("Active check result:", data);
// If device is in use, show VM list and stop here
if (data.inuse === true) {
let vmListText = "";
let vmCount = Array.isArray(data.vms) ? data.vms.length : 0;
if (vmCount > 0) {
vmListText = data.vms.join("\n");
} else {
vmListText = "No VM names returned.";
}
const plural = vmCount === 1 ? "VM is" : "VMs are";
const messageText =
`The following ${plural} currently using this device:\n\n` +
vmListText +
"\n\nPlease stop " +
(vmCount === 1 ? "it" : "them") +
" before applying changes.";
swal({
title: "Active VMs Detected",
text: messageText,
type: "warning",
confirmButtonText: "OK",
closeOnConfirm: true
});
return;
}
// --- Step 2: Continue only if NOT in use ---
// Case 1: VFs will be removed
if (vfs !== 0 && numvfs === 0) {
swal({
title: "VFs will be removed",
text: "Card will reset.",
type: "warning",
showCancelButton: true,
confirmButtonText: "OK",
cancelButtonText: "Cancel",
closeOnConfirm: true
}, function(isConfirm) {
if (isConfirm) {
setTimeout(function() {
doVFApply(pciId, vd, numvfs, message);
}, 300);
} else {
swal("Cancelled", "No changes were made.", "info");
}
});
return;
}
// Case 2: Number of VFs changed
if (vfs !== numvfs && vfs !== 0) {
swal({
title: "Number of VFs changed",
text: "Will need to remove and re-add VFs.",
type: "warning",
showCancelButton: true,
confirmButtonText: "OK",
cancelButtonText: "Cancel",
closeOnConfirm: true
}, function(isConfirm) {
if (isConfirm) {
setTimeout(function() {
doVFApply(pciId, vd, numvfs, message);
}, 300);
} else {
swal("Cancelled", "No changes were made.", "info");
}
});
return;
}
// Case 3: All VFs removed
if (vfs !== numvfs && vfs === 0) {
doVFApply(pciId, vd, numvfs, message);
}
})
.fail(function (xhr, status, error) {
console.error("Active check failed:", status, error, xhr.responseText);
swal({
title: "Error",
text: "Could not verify active device state.",
type: "error",
showConfirmButton: true
});
});
}
function applyVFSettings(pciId, vd, currentvfio, currentmac) {
const message = "_(System Devices)_: _(A reboot is required to apply changes)_";
const mac = document.getElementById("vfmac" + pciId).value;
const vfio = document.getElementById("vfvfio" + pciId).checked;
//vfs = parseInt(vfs, 10);
// --- Step 1: Check for active VMs (always returns JSON) ---
$.post("/plugins/dynamix/include/apply.sriov-cfg.php", {
type: "inuse",
pciid: pciId,
pcitype: "VF"
}, null, "json") // automatically parse JSON
.done(function (data) {
console.log("Active check result:", data);
// If device is in use, show VM list and stop here
if (data.inuse === true) {
let vmListText = "";
let vmCount = Array.isArray(data.vms) ? data.vms.length : 0;
if (vmCount > 0) {
vmListText = data.vms.join("\n");
} else {
vmListText = "No VM names returned.";
}
const plural = vmCount === 1 ? "VM is" : "VMs are";
const messageText =
`The following ${plural} currently using this device:\n\n` +
vmListText +
"\n\nPlease stop " +
(vmCount === 1 ? "it" : "them") +
" before applying changes.";
swal({
title: "Active VMs Detected",
text: messageText,
type: "warning",
confirmButtonText: "OK",
closeOnConfirm: true
});
return;
}
doVFSettingApply(pciId, vd, vfio, mac, currentvfio,currentmac,message);
})
.fail(function (xhr, status, error) {
console.error("Active check failed:", status, error, xhr.responseText);
swal({
title: "Error",
text: "Could not verify active device state.",
type: "error",
showConfirmButton: true
});
});
}
function doVFSettingApply(pciId, vd, vfio, mac, currentvfio, currentmac, message) {
// Show "updating" alert
swal({
title: "Updating...",
text: "Please wait while configuration is applied.",
type: "info",
showConfirmButton: false,
allowOutsideClick: false,
closeOnConfirm: false
});
// Perform the POST
$.post("/plugins/dynamix/include/apply.sriov-cfg.php", {
type: "sriovsettings",
pciid: pciId,
vd: vd,
vfio: vfio,
mac: mac,
currentvfio: currentvfio,
currentmac: currentmac
})
.done(function (response) {
let data;
// Try to parse JSON safely
try {
data = (typeof response === "string") ? JSON.parse(response) : response;
} catch (e) {
swal({
title: "Error",
text: "Invalid JSON response from server.",
type: "error",
showConfirmButton: true
});
return;
}
// Handle structured PHP result
if (data.success) {
//addRebootNotice(message);
// document.getElementById("warning").innerHTML =
// "<b>_(ALERT)_: _(Changes saved)_. _(Reboot to take effect)_.</b>";
saveVFSettingsConfig(pciId, vd, 0);
swal({
title: "Update Complete",
text: data.details ? data.details.join("\n") : "Configuration successfully applied.",
type: "success",
timer: 3000,
showConfirmButton: false
});
} else {
//removeRebootNotice(message);
// const errorMsg = data.error || "No changes detected or operation failed.";
document.getElementById("warning").innerHTML = "<b>" + errorMsg + "</b>";
swal({
title: "Operation Failed",
text: errorMsg,
type: "error",
showConfirmButton: true
});
}
// Always reload the table after handling
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
})
.fail(function (xhr, status, error) {
swal({
title: "Network Error",
text: "Failed to communicate with the server.\n" + (error || status),
type: "error",
showConfirmButton: true
});
});
}
// Helper: safely stringify nested structures
function formatDetails(details) {
if (details == null) return "";
if (typeof details === "string") return details;
if (Array.isArray(details))
return details.map(formatDetails).join("\n");
if (typeof details === "object")
return Object.entries(details)
.map(([k, v]) => `${k}: ${formatDetails(v)}`)
.join("\n\n");
return String(details);
}
function doVFApply(pciId, vd, numvfs, message) {
// Show "updating" alert
swal({
title: "Updating...",
text: "Please wait while configuration is applied.",
type: "info",
showConfirmButton: false,
allowOutsideClick: false,
closeOnConfirm: false
});
// Perform the POST
$.post("/plugins/dynamix/include/apply.sriov-cfg.php", {
type: "sriov",
pciid: pciId,
vd: vd,
numvfs: numvfs
})
.done(function (data) {
// Normalize to JSON
if (typeof data === "string") {
try { data = JSON.parse(data); }
catch (e) {
data = { success: false, error: "Invalid JSON: " + e.message };
}
}
// Extract a clean message
//let msg = "Configuration successfully applied.";
if (data.error) {
msg = "Error: " + data.error;
} else if (data.details) {
if (Array.isArray(data.details)) {
msg = data.details.join("\n");
} else if (typeof data.details === "object") {
msg = Object.entries(data.details)
.map(([k, v]) => `${k}: ${JSON.stringify(v, null, 2)}`)
.join("\n\n");
} else if (typeof data.details === "string") {
msg = data.details;
}
}
// Build the message
let msg = "";
if (data.error) msg = "Error: " + data.error;
else if (data.details) msg = formatDetails(data.details);
else msg = "Configuration successfully applied.";
// Show alert
const ok = data.success === true || data.success === 1;
saveVFsConfig(pciId, vd, 0);
swal({
title: ok ? "Update Complete" : "Update Failed",
text: msg,
type: ok ? "success" : "error",
timer: ok ? 3000 : null,
showConfirmButton: !ok
});
// Reload table
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
})
.fail(function (xhr, status, error) {
swal({
title: "Network Error",
text: "Failed to communicate with the server.\n" + (error || status),
type: "error",
showConfirmButton: true
});
});
}
function formatFullInput(input) {
return input
.split(';')
.filter(Boolean) // remove empty trailing entry
.map(entry => {
let [pci, status] = entry.split(',');
status = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
return `${pci} _(${status})_`;
})
.join('<br>');
}
function formatVMS(input) {
return input
.split(';')
.filter(Boolean) // remove empty trailing entry
.join('<br>');
}
function ackPCI(pcidevice, action) {
$.post('/webGui/include/PCIUpdate.php', { action: "getvm", pciid: pcidevice }).done(function(vms) {
let swaltext = "";
switch(action) {
case 'removed':
swaltext = "_(Acknowledge removal of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
case 'changed':
swaltext = "_(Acknowledge update of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>"+ formatVMS(vms);
break;
case 'added':
swaltext = "_(Acknowledge addition of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
case 'all':
swaltext = "_(Acknowledge all PCI Address modifications)_: <br>" + formatFullInput(pcidevice) + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
}
swal({
title: "Are you sure?",
text: swaltext,
type: "warning",
html: true,
showCancelButton: true
}, function() {
$.post('/webGui/include/PCIUpdate.php', { action: action, pciid: pcidevice }).done(function(d) {
if (d === "OK") {
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
}
});
});
});
}
</script>
<?exec('ls /sys/kernel/iommu_groups/',$groups); ?>
<?if (count($groups) > 0):?>
**_(PCI Devices and IOMMU Groups)_**
<?else:?>
**_(PCI Devices (No IOMMU Groups Available))_**
<?endif;?>
:sysdevs_iommu_groups_help:
<?if (strpos(file_get_contents('/proc/cmdline'), 'pcie_acs_override=') !== false):?>
<p class="notice" style="line-height:30px;height:auto">_(Warning)_: _(Your system has booted with the PCIe ACS Override setting enabled)_. _(The below list doesn't not reflect the way IOMMU would naturally group devices)_.<br>
<?=my_hyperlink(_("To see natural IOMMU groups for your hardware, go to the [VM Manager] page and set the **PCIe ACS override** setting to **Disabled**"),'/Settings/VMSettings')?>.</p>
<?endif;?>
<pre><form id="vfiopci" class="js-confirm-leave" onsubmit="return false"><table id='t1' class='pre'><tr><td><div class="spinner"></div></td></tr></table></form></pre><br>
**_(CPU Thread Pairings)_**
:sysdevs_thread_pairings_help:
<pre><table id='t2' class='pre'><tr><td><div class="spinner"></div></td></tr></table></pre><br>
**_(USB Devices)_**
:sysdevs_usb_devices_help:
<pre><table id='t3' class='pre'><tr><td><div class="spinner"></div></td></tr></table></pre><br>
**_(SCSI Devices)_**
:sysdevs_scsi_devices_help:
<pre><table id='t4' class='pre'><tr><td><div class="spinner"></div></td></tr></table></pre>
<input type="button" value="_(Done)_" onclick="done()">

View File

@@ -13,16 +13,11 @@
?>
<?
#$allowedPCIClass = ['0x02','0x03'];
$allowedPCIClass = ['0x02'];
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
/**
* Get available kernel modules for this PCI device based on its modalias
*/
@@ -35,7 +30,6 @@ function getModulesFromModalias(string $pci): array {
return $out ? preg_split('/\s+/', $out) : [];
}
/**
* Enumerate SR-IOV capable PCI devices (keyed by PCI address).
*
@@ -150,14 +144,14 @@ function rebindVfDriver($vf, $sriov, $target = 'original')
$res = ['pci'=>$vf,'success'=>false,'error'=>null,'details'=>[]];
$vf_path = "/sys/bus/pci/devices/$vf";
$physfn = "$vf_path/physfn";
if (!is_link($physfn)) return $res + ['error'=>"Missing physfn link"];
if (!is_link($physfn)) return $res + ['error'=>_("Missing physfn link")];
$pf = basename(readlink($physfn));
$vf_info = $sriov[$pf]['vfs'][$vf] ?? null;
if (!$vf_info) return $res + ['error'=>"VF not found in \$sriov for PF $pf"];
if (!$vf_info) return $res + ['error'=>_("VF not found in sriov for PF")." $pf"];
$orig_mod = $vf_info['modules'][0] ?? $sriov[$pf]['module'] ?? null;
$curr_drv = $vf_info['driver'] ?? null;
if (!$orig_mod) return $res + ['error'=>"No module info for $vf"];
if (!$orig_mod) return $res + ['error'=>_("No module info for")." $vf"];
$drv_override = "$vf_path/driver_override";
@@ -187,13 +181,12 @@ function rebindVfDriver($vf, $sriov, $target = 'original')
$bound = basename(readlink($drv_link));
if ($bound === $new_drv) {
$res['success'] = true;
$res['details'][] = "Bound to $new_drv";
$res['details'][] = _("Bound to")." $new_drv";
return $res;
}
return $res + ['error'=>"Bound to $bound instead of $new_drv"];
return $res + ['error'=> sprintf(_("Bound to %s instead of %s"),$bound,$new_drv)];
}
return $res + ['error'=>"No driver link after reprobe"];
return $res + ['error'=>_("No driver link after reprobe")];
}
@@ -268,7 +261,7 @@ function getVfListByIommuGroup(): array {
$iommu_group = "unknown";
}
$groups[] = "IOMMU group " . $iommu_group;
$groups[] = "IOMMU group " . $iommu_group;
$groups[] = $vf_pci;
}
@@ -280,28 +273,26 @@ function getVfListByIommuGroup(): array {
// Parse SR-IOV VF counts
// ----------------------
function parseVFvalues() {
$sriov_devices = [];
$DBDF_SRIOV_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]\|[[:xdigit:]]{4}:[[:xdigit:]]{4}\|[[:digit:]]+$/';
if (is_file("/boot/config/sriov.cfg")) {
$file = trim(file_get_contents("/boot/config/sriov.cfg"));
$file = preg_replace('/^VFS=/', '', $file); // Remove prefix
$entries = preg_split('/\s+/', $file, -1, PREG_SPLIT_NO_EMPTY);
$sriov_devices = [];
$DBDF_SRIOV_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]\|[[:xdigit:]]{4}:[[:xdigit:]]{4}\|[[:digit:]]+$/';
if (is_file("/boot/config/sriov.cfg")) {
$file = trim(file_get_contents("/boot/config/sriov.cfg"));
$file = preg_replace('/^VFS=/', '', $file); // Remove prefix
$entries = preg_split('/\s+/', $file, -1, PREG_SPLIT_NO_EMPTY);
foreach ($entries as $entry) {
if (preg_match($DBDF_SRIOV_REGEX, $entry)) {
// Format: <DBDF>|<Vendor:Device>|<VF_count>
[$dbdf, $ven_dev, $vf_count] = explode('|', $entry);
$sriov_devices[$dbdf] = [
'dbdf' => $dbdf,
'vendor' => $ven_dev,
'vf_count' => (int)$vf_count,
];
}
}
# $sriov_devices = array_values(array_unique($sriov_devices, SORT_REGULAR));
foreach ($entries as $entry) {
if (preg_match($DBDF_SRIOV_REGEX, $entry)) {
// Format: <DBDF>|<Vendor:Device>|<VF_count>
[$dbdf, $ven_dev, $vf_count] = explode('|', $entry);
$sriov_devices[$dbdf] = [
'dbdf' => $dbdf,
'vendor' => $ven_dev,
'vf_count' => (int)$vf_count,
];
}
}
}
return $sriov_devices;
}
}
// ---------------------------------
@@ -311,27 +302,25 @@ function parseVFSettings() {
$sriov_devices_settings = [];
$DBDF_SRIOV_SETTINGS_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]\|[[:xdigit:]]{4}:[[:xdigit:]]{4}\|[01]\|([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}$/';
if (is_file("/boot/config/sriovvfs.cfg")) {
$file = trim(file_get_contents("/boot/config/sriovvfs.cfg"));
$file = preg_replace('/^VFSETTINGS=/', '', $file); // Remove prefix
$entries = preg_split('/\s+/', $file, -1, PREG_SPLIT_NO_EMPTY);
$file = trim(file_get_contents("/boot/config/sriovvfs.cfg"));
$file = preg_replace('/^VFSETTINGS=/', '', $file); // Remove prefix
$entries = preg_split('/\s+/', $file, -1, PREG_SPLIT_NO_EMPTY);
foreach ($entries as $entry) {
if (preg_match($DBDF_SRIOV_SETTINGS_REGEX, $entry)) {
// Format: <DBDF>|<Vendor:Device>|<VFIO_flag>|<MAC>
[$dbdf, $ven_dev, $vfio_flag, $mac] = explode('|', $entry);
if ($mac == "00:00:00:00:00:00") $mac = "";
$sriov_devices_settings[$dbdf] = [
'dbdf' => $dbdf,
'vendor' => $ven_dev,
'vfio' => (int)$vfio_flag,
'mac' => strtoupper($mac),
];
}
}
# $sriov_devices_settings = array_values(array_unique($sriov_devices_settings, SORT_REGULAR));
foreach ($entries as $entry) {
if (preg_match($DBDF_SRIOV_SETTINGS_REGEX, $entry)) {
// Format: <DBDF>|<Vendor:Device>|<VFIO_flag>|<MAC>
[$dbdf, $ven_dev, $vfio_flag, $mac] = explode('|', $entry);
if ($mac == "00:00:00:00:00:00") $mac = "";
$sriov_devices_settings[$dbdf] = [
'dbdf' => $dbdf,
'vendor' => $ven_dev,
'vfio' => (int)$vfio_flag,
'mac' => strtoupper($mac),
];
}
}
}
return $sriov_devices_settings;
}
}
/**
@@ -364,14 +353,14 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
];
if (!is_dir($vf_path)) {
$result['error'] = "VF path not found: $vf_path";
$result['error'] = _("VF path not found").": $vf_path";
return $result;
}
// --- Find parent PF (Physical Function) ---
$pf_link = "$vf_path/physfn";
if (!is_link($pf_link)) {
$result['error'] = "No PF link for $vf_pci (not an SR-IOV VF?)";
$result['error'] = sprintf("No PF link for %s (not an SR-IOV VF?)",$vf_pci);
return $result;
}
$pf_pci = basename(readlink($pf_link));
@@ -381,7 +370,7 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
$pf_net = glob("/sys/bus/pci/devices/{$pf_pci}/net/*");
$pf_iface = ($pf_net && isset($pf_net[0])) ? basename($pf_net[0]) : null;
if (!$pf_iface) {
$result['error'] = "Could not detect PF interface for $pf_pci";
$result['error'] = _("Could not detect PF interface for")." $pf_pci";
return $result;
}
$result['pf_iface'] = $pf_iface;
@@ -389,7 +378,7 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
// --- Detect VF index ---
$vf_index = getVfIndex($pf_pci, $vf_pci);
if ($vf_index === null) {
$result['error'] = "Could not determine VF index for $vf_pci under $pf_pci";
$result['error'] = sprintf(_("Could not determine VF index for %s under %s"),$vf_pci,$pf_pci);
return $result;
}
$result['vf_index'] = $vf_index;
@@ -406,7 +395,7 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
file_put_contents($unbind_path, $vf_pci);
$result['unbind'] = true;
} else {
$result['error'] = "Cannot unbind VF $vf_pci from $vf_driver (permissions)";
$result['error'] = sprintf(_("Cannot unbind VF %s from %s (permissions)"),$vf_pci,$vf_driver);
return $result;
}
}
@@ -424,7 +413,7 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
if ($ret === 0) {
$result['mac_set'] = true;
} else {
$result['error'] = "Failed to set MAC: " . implode("; ", $output);
$result['error'] = _("Failed to set MAC").": " . implode("; ", $output);
}
// --- Rebind logic ---
@@ -437,7 +426,7 @@ function setVfMacAddress(string $vf_pci, string $mac, ?string $rebindDriver = nu
$result['rebind'] = true;
$result['driver_after'] = $target_driver;
} else {
$result['error'] = "Cannot rebind VF $vf_pci to $target_driver (permissions)";
$result['error'] = sprintf(_("Cannot rebind VF %s to %s (permissions)"),$vf_pci,$target_driver);
}
}
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/php
#!/usr/bin/php
<?PHP
function SysDriverslog($m, $type='NOTICE') {
if ($type == 'DEBUG') return;

View File

@@ -20,6 +20,7 @@ require_once "$docroot/webGui/include/Secure.php";
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
require_once "$docroot/webGui/include/SriovHelpers.php";
require_once "$docroot/webGui/include/Translations.php";
function json_response($success, $error = null, $details = [])
{
@@ -41,10 +42,29 @@ function json_response($success, $error = null, $details = [])
'details' => $details
];
echo json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$json = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
file_put_contents("/tmp/vfactionjson",$json);
echo $json;
exit;
}
function safe_file_put_contents($path, $data)
{
// Check that the file or its directory is writable
$dir = is_dir($path) ? $path : dirname($path);
if (!is_writable($dir)) {
throw new RuntimeException("Path not writable: $path");
}
$result = @file_put_contents($path, $data);
if ($result === false) {
$err = error_get_last();
throw new RuntimeException("Failed to write to $path: " . ($err['message'] ?? 'unknown error'));
}
return $result;
}
function action_settings($pciid)
{
@@ -64,6 +84,8 @@ function action_settings($pciid)
// Skip if no action needed
if ($vfio == 0 && $mac == "") continue;
if ($mac == "") $mac="00:00:00:00:00:00";
$cmd = "/usr/local/sbin/sriov-vfsettings.sh " .
escapeshellarg($vfpci) . " " .
escapeshellarg($vf['vd']) . " " .
@@ -77,19 +99,17 @@ function action_settings($pciid)
// Clean output: remove blank lines and trim whitespace
$output = array_filter(array_map('trim', $output));
file_put_contents("/tmp/vfaction", "$ret\n$cmd\n" . json_encode($output) . "\n");
if ($ret !== 0) {
// Only include relevant lines for error reporting
$results[] = [
$results[$vfpci] = [
'success' => false,
'error' => implode("\n", $output) ?: "Unknown error (exit code $ret)"
'error' => implode("\n", $output) ?: sprintf(_("Unknown error (exit code %s)"),$ret)
];
} else {
// Success: include minimal details or last few lines
$results[] = [
$results[$vfpci] = [
'success' => true,
'details' => 'Applied VF settings'
'details' => _('Applied VF settings')
];
}
}
@@ -103,47 +123,63 @@ $pciid = _var($_POST, 'pciid');
$vd = _var($_POST, 'vd');
if (!isset($pciid) || !isset($vd)) {
echo json_response(false, "Missing PCI ID or virtual device");
echo json_response(false, _("Missing PCI ID or virtual device"));
exit;
}
switch ($type) {
// --------------------------------------------------------
// SR-IOV enable/disable & VF count changes
// --------------------------------------------------------
case "sriov":
$numvfs = _var($_POST, 'numvfs');
$numvfs = _var($_POST, 'numvfs');
$currentvfs = _var($_POST, 'currentvfs');
$filepath = "/sys/bus/pci/devices/$pciid/sriov_numvfs";
$filepath = "/sys/bus/pci/devices/$pciid/sriov_numvfs";
if (!is_writable($filepath)) {
echo json_response(false, "Cannot modify $filepath");
break;
json_response(false, "Cannot modify $filepath");
}
try {
// Disable all VFs
if ($numvfs == 0) {
file_put_contents($filepath, 0);
echo json_response(true, null, ["Disabled all VFs"]);
break;
safe_file_put_contents($filepath, 0);
json_response(true, null, [_("Disabled all VFs")]);
}
// Change VF count
if ($numvfs != $currentvfs) {
file_put_contents($filepath, 0);
file_put_contents($filepath, $numvfs);
safe_file_put_contents($filepath, 0);
safe_file_put_contents($filepath, $numvfs);
$results = action_settings($pciid);
echo json_response(true, null, ["Changed VF count to $numvfs", $results]);
break;
$all_success = array_reduce($results, fn($ok, $r) => $ok && ($r['success'] ?? false), true);
safe_file_put_contents(
"/tmp/vfactionres2",
json_encode(['all_success' => $all_success, 'results' => $results], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
json_response(true, null, [sprintf(_("Changed VF count to %d"), $numvfs), $results]);
}
file_put_contents($filepath, $numvfs);
// Reapply VF settings
safe_file_put_contents($filepath, $numvfs);
$results = action_settings($pciid);
echo json_response(true, null, ["Reapplied VF settings", $results]);
json_response(true, null, [_("Reapplied VF settings"), $results]);
} catch (Throwable $e) {
echo json_response(false, "Failed to change VF count: " . $e->getMessage());
// Log internal details for debugging
@file_put_contents("/tmp/vfaction_error.log", date('c') . " " . $e->getMessage() . "\n", FILE_APPEND);
json_response(false, _("Failed to change VF count") . ": " . $e->getMessage());
}
break;
// --------------------------------------------------------
// VF driver binding, MAC changes
// --------------------------------------------------------
@@ -181,9 +217,9 @@ switch ($type) {
}
// Nothing changed
echo json_response(true, null, ["No changes detected"]);
echo json_response(true, null, [_("No changes detected")]);
} catch (Throwable $e) {
echo json_response(false, "Error applying VF settings: " . $e->getMessage());
echo json_response(false, _("Error applying VF settings").": ".$e->getMessage());
}
break;
@@ -197,7 +233,7 @@ switch ($type) {
break;
default:
echo json_response(false, "Unknown request type: $type");
echo json_response(false, _("Unknown request type").": $type");
break;
}

View File

@@ -49,7 +49,7 @@ if (isset($pciid) && isset($vd)) {
}
}
}
if (!$found) $newexplode[] = $newelement_check.$numvfs;
if (!$found && $numvfs != "0") $newexplode[] = $newelement_check.$numvfs;
$new = "VFS=".implode(" ",$newexplode);
$file = $sriov;
break;
@@ -73,10 +73,10 @@ if (isset($pciid) && isset($vd)) {
}
}
}
if (!$found) $newexplode[] = $newelement_check.$vfio."|".$mac;
$new = "VFSETTINGS=".implode(" ",$newexplode);
$file = $sriovvfs;
break;
if (!$found && $vfio != 0) $newexplode[] = $newelement_check.$vfio."|".$mac;
$new = "VFSETTINGS=".implode(" ",$newexplode);
$file = $sriovvfs;
break;
}
}