VM Manager: completely revamp templates, added iso downloader, auto vdisk location

This commit is contained in:
Eric Schultz
2015-11-25 23:14:21 -06:00
parent 351f306baf
commit 72b4c0746c
17 changed files with 2277 additions and 791 deletions

View File

@@ -1,4 +1,5 @@
Title="Add VM"
Title="Add {$_GET['template']} VM"
Png="addvm.png"
Cond="(pgrep('libvirtd')!==false)"
Markdown="false"
---

View File

@@ -1,4 +1,4 @@
Menu="VMs"
Menu="VMs:1"
Title="Virtual Machines"
Cond="(pgrep('libvirtd')!==false)"
---
@@ -157,7 +157,7 @@ if ($action) {
$mem = number_format($mem, 0);
$vcpu = $dom['nrVirtCpu'];
$auto = $is_autostart ? 'checked="checked"':"";
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@name');
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
if (empty($template)) {
$template = 'Custom';
}
@@ -192,7 +192,7 @@ if ($action) {
// fallback icon for users that created VMs before metadata support was added
$vmicon = '/plugins/dynamix.vm.manager/templates/images/' . ($lv->domain_get_clock_offset($res) == 'localtime' ? 'windows.png' : 'linux.png');
$vmtemplateicon = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@icon');
$vmtemplateicon = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@icon');
if (!empty($vmtemplateicon)) {
if (file_exists($vmtemplateicon)) {
$vmicon = $vmtemplateicon;
@@ -202,7 +202,7 @@ if ($action) {
}
//Domain information
echo "<tr style='background-color:".bcolor($i,$theme)."'>
echo "<tr style='background-color:".bcolor($i)."'>
<td style=\"width: 48px; padding: 4px\">
<div id=\"vm-" . htmlspecialchars($uuid) . "\" style=\"display:block; cursor:pointer\">
<div style=\"position: relative; width: 48px; height: 48px; margin: 0px auto;\">
@@ -363,7 +363,7 @@ if ($action) {
$val = "";}
else
$val = $info;
echo "<tr style='background-color:".bcolor($ii,$theme)."'>
echo "<tr style='background-color:".bcolor($ii)."'>
<td>".($ii+1)."</td>
<td>$backup</td>
<td>$date</td>
@@ -427,7 +427,7 @@ if ($action) {
$val = "";}
else
$val = $info;
echo "<tr style='background-color:".bcolor($ii,$theme)."'>
echo "<tr style='background-color:".bcolor($ii)."'>
<td>".($ii+1)."</td>
<td>$snap</td>
<td>$date</td>
@@ -472,7 +472,9 @@ if($msg){
}
?>
<? if ($display['tabs']==0): ?>
<input type="button" id="btnAddVM" value="Add VM"/>
<? endif ?>
<script>
function vncOpen() {
@@ -594,7 +596,7 @@ function addVMContext(name, uuid, template, state, vncurl, log){
if (state == "shutoff") {
opts.push({text: "Edit", icon: "fa-pencil", href: path+'/UpdateVM?uuid='+uuid });
opts.push({text: "Edit XML", icon: "fa-code", href: path+'/UpdateVM?template=XML_Expert&amp;uuid='+uuid });
opts.push({text: "Edit XML", icon: "fa-code", href: path+'/UpdateVM?template=Custom&amp;uuid='+uuid });
opts.push({divider: true});
@@ -612,7 +614,7 @@ function addVMContext(name, uuid, template, state, vncurl, log){
} else {
opts.push({text: "View XML", icon: "fa-code", href: path+'/UpdateVM?template=XML_Expert&amp;uuid='+uuid });
opts.push({text: "View XML", icon: "fa-code", href: path+'/UpdateVM?template=Custom&amp;uuid='+uuid });
}
@@ -624,7 +626,7 @@ $(function() {
$('.text').click(showInput);
$('.input').blur(hideInput);
$('#btnAddVM').click(function AddVMEvent() {
window.location = '/VMs/AddVM';
$('.tab>input#tab2').click();
});
$('.autostart').switchButton({

View File

@@ -71,18 +71,39 @@ if ($boolACSEnabled != $boolACSInSyslinux) {
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
<style>
body { -webkit-overflow-scrolling: touch;}
.fileTree {
width: 305px;
max-height: 150px;
overflow: scroll;
position: absolute;
z-index: 100;
display: none;
}
body { -webkit-overflow-scrolling: touch;}
.fileTree {
width: 305px;
max-height: 150px;
overflow: scroll;
position: absolute;
z-index: 100;
display: none;
}
.basic{display:block;}
.advanced{display:none;white-space:nowrap;}
.switch-button-label.off{color: inherit;}
#winvirtio{display:none;}
#download_status {
margin-left: 5px;
color: #777;
display: none;
}
#download_button {
cursor: pointer;
margin-left: -5px;
color: #08C;
font-size: 1.4em;
display: none;
transform: translate(0px, 4px);
}
#download_button.fa-spin {
cursor: default;
color: #777;
}
</style>
<span class="status" style="margin-top: -44px;"><input type="checkbox" class="advancedview"></span>
<span class="status advanced" style="margin-top: -4px;"><a id="openlog" title="/var/log/libvirt/libvirtd.log" href="#" onclick="openWindow('/webGui/scripts/tail_log&amp;arg1=libvirt/libvirtd.log','Log Information',600,900);">view libvirt log</a></span>
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="<?=$domain_cfgfile;?>" />
<input type="hidden" id="command" name="#command" value="" />
@@ -95,23 +116,61 @@ Enable VMs:
> Stopping the VM Manager will first attempt to shutdown all running VMs. After 40 seconds, any remaining VM instances will be terminated.
<div class="advanced" markdown="1">
<? if ($libvirt_service == 'enable') {
$libvirt_info = libvirt_version('libvirt');
$qemu_info = $lv->get_connect_information(); ?>
Libvirt Version:
Libvirt version:
: <?= $libvirt_info['libvirt.major'] . '.' . $libvirt_info['libvirt.minor'] . '.' . $libvirt_info['libvirt.release']; ?>
QEMU Version:
QEMU version:
: <?= $qemu_info['hypervisor_major'] . '.' . $qemu_info['hypervisor_minor'] . '.' . $qemu_info['hypervisor_release']; ?>
<? } ?>
ISO Library Share <span style="font-weight: normal">(optional)</span>:
: <input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="MEDIADIR" value="<?=$domain_cfg['MEDIADIR']?>" placeholder="Click to Select">
Default VM storage path:
: <input type="text" id="domaindir" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="DOMAINDIR" value="<?=$domain_cfg['DOMAINDIR']?>" placeholder="Click to Select">
> Specify a user share that contains all your VM subdirectories with vdisks
Default ISO storage path:
: <input type="text" id="mediadir" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="MEDIADIR" value="<?=$domain_cfg['MEDIADIR']?>" placeholder="Click to Select">
> Specify a user share that contains all your installation media for operating systems
</div>
VirtIO Windows Drivers ISO <span style="font-weight: normal">(optional)</span>:
: <input type="text" data-pickfilter="iso" data-pickcloseonfile="true" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="VIRTIOISO" value="<?=$domain_cfg['VIRTIOISO']?>" placeholder="Click to Select">
<div markdown="1">
Default Windows VirtIO driver ISO <span style="font-weight: normal">(optional)</span>:
: <select id="winvirtio_select" class="narrow">
<?
$iso_dir = $domain_cfg['MEDIADIR'];
if (empty($iso_dir) || !is_dir($iso_dir)) {
$iso_dir = '/mnt/user/isos/';
} else {
$iso_dir = str_replace('//', '/', $iso_dir.'/');
}
$strManual = '';
if (!empty($domain_cfg['MEDIADIR']) &&
!empty($domain_cfg['VIRTIOISO']) &&
dirname($domain_cfg['VIRTIOISO']) != '.' &&
strpos($iso_dir, dirname($domain_cfg['VIRTIOISO'])) !== 0) {
$strManual = 'manual';
}
$strMatch = $strManual;
foreach ($virtio_isos as $key => $value) {
if (empty($strMatch) &&
(basename($domain_cfg['VIRTIOISO']) == $value['name']) &&
is_file($iso_dir.$value['name'])
) {
$strMatch = $value['name'];
}
echo mk_option($strMatch, $value['name'], $value['name']);
}
echo mk_option($strManual, 'manual', 'Manual');
?>
</select><input type="text" id="winvirtio" name="VIRTIOISO" data-pickfilter="iso" data-pickcloseonfile="true" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" value="<?=$domain_cfg['VIRTIOISO']?>" placeholder="Click to Select"><i class="fa fa-download" id="download_button" title="Download Windows VirtIO driver ISO"></i><span id="download_status"></span>
> Specify the virtual CD-ROM (ISO) that contains the VirtIO Windows drivers as provided by the Fedora Project.
> Download the latest ISO from here: <a href="https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download" target="_blank">https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download</a>
@@ -121,13 +180,10 @@ VirtIO Windows Drivers ISO <span style="font-weight: normal">(optional)</span>:
> Inside there will be various folders for the different versions of Windows. Open the folder for the version of Windows
> you are installing and then select the AMD64 subfolder inside (even if you are on an Intel system, select AMD64).
> Three drivers will be found. Select them all, click next, and the vDisks you have assigned will appear.
</div>
<!--
vDisk Share <span style="font-weight: normal">(optional)</span>:
: <input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="/mnt/" name="DISKDIR" value="<?=$domain_cfg['DISKDIR']?>" placeholder="Click to Select (ie. /mnt/cache/images)" title="Click to Select (ie. /mnt/cache/images)">
-->
Default Network Bridge:
<div class="advanced" markdown="1">
Default network bridge:
: <select id="bridge" name="BRNAME">
<?php
foreach ($arrValidBridges as $strBridge) {
@@ -142,7 +198,7 @@ Default Network Bridge:
>
> NOTE: You can also specify an network bridge on a per-VM basis.
PCIe ACS Override:
Enable PCIe ACS Override:
: <select id="pcie_acs_override" class="narrow">
<?= mk_option(($boolACSInSyslinux ? '1' : '0'), '0', 'No'); ?>
<?= mk_option(($boolACSInSyslinux ? '1' : '0'), '1', 'Yes'); ?>
@@ -151,6 +207,7 @@ PCIe ACS Override:
> Warning: Use of this setting could cause possible data corruption with certain hardware configurations. Please visit the Lime Technology forums for more information.
>
> A reboot will be required for changes to this setting to take affect.
</div>
&nbsp;
: <input type="button" id="applyBtn" value="Apply"/><input type="button" value="Done" onclick="done()">
@@ -179,5 +236,103 @@ $(function(){
});
$("input[data-pickroot]").fileTreeAttach();
$("#mediadir").on("input change", function() {
$("#winvirtio_select").change();
});
var checkDownloadTimer = null;
var checkOrInitDownload = function(checkonly) {
clearTimeout(checkDownloadTimer);
var $button = $("#download_button");
var $form = $button.closest('form');
var postdata = {
action: "virtio-win-iso-download",
download_version: $('#winvirtio_select').val(),
download_path: $('#mediadir').val(),
checkonly: ((typeof checkonly === 'undefined') ? false : !!checkonly) ? 1 : 0
};
$form.find('input,select').prop('disabled', true);
$button.removeClass('fa-download').addClass('fa-circle-o-notch fa-spin');
$.post("/plugins/dynamix.vm.manager/VMajax.php", postdata, function( data ) {
if (data.error) {
$("#download_status").html('<span style="color: red">' + data.error + '</span>');
} else {
$("#download_status").html(data.status);
if (data.pid) {
checkDownloadTimer = setTimeout(checkOrInitDownload, 1000);
return;
}
if (data.status == 'Done') {
$("#winvirtio_select").change();
}
}
$button.removeClass('fa-circle-o-notch fa-spin').addClass('fa-download');
$form.find('input,select').prop('disabled', false);
}, "json");
};
$("#download_button").click(function changeVirtIOVersion() {
if (!$(this).hasClass('fa-spin')) {
checkOrInitDownload(false);
}
});
// Fire events below once upon showing page
$("#winvirtio_select").change(function changeVirtIOVersion() {
clearTimeout(checkDownloadTimer);
if ($(this).val()=='manual') {
$("#download_button,#download_status").hide('fast');
$("#winvirtio").show('fast');
return;
}
$("#winvirtio").hide('fast');
var params = {
action: "virtio-win-iso-info",
path: $("#mediadir").val(),
file: $(this).val()
};
$.get("/plugins/dynamix.vm.manager/VMajax.php", params, function( data ) {
if (!data.exists) {
$("#download_button").show('fast');
$("#download_status").html('').show('fast');
if (data.pid) {
checkOrInitDownload(true);
}
} else {
$("#download_button,#download_status").hide('fast');
$("#winvirtio").val(data.path);
}
}, "json");
}).change(); // Fire now too!
if ($.cookie('vmsettings_view_mode') == 'advanced') {
$('.advanced').show();
$('.basic').hide();
}
$('.advancedview').switchButton({
labels_placement: "left",
on_label: 'Advanced View',
off_label: 'Basic View',
checked: $.cookie('vmsettings_view_mode') == 'advanced'
});
$('.advancedview').change(function () {
$('.advanced').toggle('slow');
$('.basic').toggle('slow');
$.cookie('vmsettings_view_mode', $('.advancedview').is(':checked') ? 'advanced' : 'basic', { expires: 3650 });
});
});
</script>

View File

@@ -0,0 +1,55 @@
Menu="VMs:2"
Title="Templates"
Cond="(pgrep('libvirtd')!==false)"
---
<?PHP
/* Copyright 2015, Lime Technology
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
*
* 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.
*/
?>
<style type="text/css">
.vmheader {
padding: 10px;
font-size: 16px;
text-align: left;
color: #888;
}
.vmtemplate {
display: inline-block;
width: 80px;
height: 90px;
margin-bottom: 15px;
margin-left: 10px;
text-align: center;
vertical-align: top;
}
.vmtemplate img {
width: 48px;
height: 48px;
}
.vmtemplate p {
text-align: center;
line-height: 12px;
}
</style>
<? foreach($arrAllTemplates as $strName => $arrTemplate):
if (empty($arrTemplate)) {
// render header
echo '<div class="vmheader">'.$strName.'</div>';
continue;
}
?>
<div class="vmtemplate">
<a href="/VMs/AddVM?template=<?=htmlentities(urlencode($strName))?>">
<img src="/plugins/dynamix.vm.manager/templates/images/<?=$arrTemplate['icon']?>">
<p><?=$strName?></p>
</a>
</div>
<? endforeach; ?>

View File

@@ -202,10 +202,7 @@ switch ($action) {
mkdir($dir);
// determine the actual disk if user share is being used
if (strpos($dir, '/mnt/user/') === 0) {
$tmp = parse_ini_string(shell_exec("getfattr -n user.LOCATION " . escapeshellarg($dir) . " | grep user.LOCATION"));
$dir = str_replace('/mnt/user', '/mnt/' . $tmp['user.LOCATION'], $dir); // replace 'user' with say 'cache' or 'disk1' etc
}
$dir = transpose_user_path($dir);
@exec("chattr +C -R " . escapeshellarg($dir) . " >/dev/null");
@@ -248,7 +245,7 @@ switch ($action) {
// if file, get size and format info
if (is_file($file)) {
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($file)), true);
$json_info = getDiskImageInfo($file);
if (!empty($json_info)) {
$intDisplaySize = (int)$json_info['virtual-size'];
$intShifts = 0;
@@ -295,6 +292,47 @@ switch ($action) {
];
break;
case 'get-vm-icons':
$arrImages = [];
foreach (glob("/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/*.png") as $png_file) {
$arrImages[] = [
'custom' => false,
'basename' => basename($png_file),
'url' => '/plugins/dynamix.vm.manager/templates/images/' . basename($png_file)
];
}
$arrResponse = $arrImages;
break;
case 'get-usb-devices':
$arrValidUSBDevices = getValidUSBDevices();
$arrResponse = $arrValidUSBDevices;
break;
case 'hot-attach-usb':
//TODO - If usb is a block device, then attach as a <disk type="usb"> otherwise <hostdev type="usb">
/*
<hostdev mode='subsystem' type='usb'>
<source startupPolicy='optional'>
<vendor id='0x1234'/>
<product id='0xbeef'/>
</source>
</hostdev>
<disk type='block' device='disk'>
<driver name='qemu' type='raw'/>
<source dev='/dev/sda'/>
<target dev='hdX' bus='virtio'/>
</disk>
*/
break;
case 'hot-detach-usb':
//TODO
break;
case 'acs-override-enable':
// Check the /boot/syslinux/syslinux.cfg for the existance of pcie_acs_override=downstream, add it in if not found
$arrSyslinuxCfg = file('/boot/syslinux/syslinux.cfg');
@@ -360,6 +398,216 @@ switch ($action) {
$arrResponse = ['success' => true, 'label' => $strCurrentLabel];
break;
case 'virtio-win-iso-info':
$path = $_REQUEST['path'];
$file = $_REQUEST['file'];
if (empty($file)) {
$arrResponse = ['exists' => false];
break;
}
if (is_file($file)) {
$arrResponse = ['exists' => true, 'path' => $file];
break;
}
if (empty($path) || !is_dir($path)) {
$path = '/mnt/user/isos/';
} else {
$path = str_replace('//', '/', $path.'/');
}
$file = $path.$file;
if (is_file($file)) {
$arrResponse = ['exists' => true, 'path' => $file];
break;
}
$arrResponse = ['exists' => false];
break;
case 'virtio-win-iso-download':
$arrDownloadVirtIO = [];
$strKeyName = basename($_POST['download_version'], '.iso');
if (array_key_exists($strKeyName, $virtio_isos)) {
$arrDownloadVirtIO = $virtio_isos[$strKeyName];
}
if (empty($arrDownloadVirtIO)) {
$arrResponse = ['error' => 'Unknown version: ' . $_POST['download_version']];
} else if (empty($_POST['download_path'])) {
$arrResponse = ['error' => 'Specify a ISO storage path first'];
//} else if (!is_dir($_POST['download_path'])) {
// $arrResponse = ['error' => 'ISO storage path doesn\'t exist, please create the user share (or empty folder) first'];
} else {
@mkdir($_POST['download_path'], 0777, true);
$_POST['download_path'] = realpath($_POST['download_path']) . '/';
$boolCheckOnly = !empty($_POST['checkonly']);
$strInstallScript = '/tmp/VirtIOWin_' . $strKeyName . '_install.sh';
$strInstallScriptPgrep = '-f "VirtIOWin_' . $strKeyName . '_install.sh"';
$strTargetFile = $_POST['download_path'] . $arrDownloadVirtIO['name'];
$strLogFile = $strTargetFile . '.log';
$strMD5File = $strTargetFile . '.md5';
$strMD5StatusFile = $strTargetFile . '.md5status';
// Save to /boot/config/domain.conf
$domain_cfg['MEDIADIR'] = $_POST['download_path'];
$domain_cfg['VIRTIOISO'] = $strTargetFile;
$tmp = '';
foreach ($domain_cfg as $key => $value) $tmp .= "$key=\"$value\"\n";
file_put_contents($domain_cfgfile, $tmp);
$strDownloadCmd = 'wget -nv -c -O ' . escapeshellarg($strTargetFile) . ' ' . escapeshellarg($arrDownloadVirtIO['url']);
$strDownloadPgrep = '-f "wget.*' . $strTargetFile . '.*' . $arrDownloadVirtIO['url'] . '"';
$strVerifyCmd = 'md5sum -c ' . escapeshellarg($strMD5File);
$strVerifyPgrep = '-f "md5sum.*' . $strMD5File . '"';
$strCleanCmd = '(chmod 777 ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strTargetFile) . '; chown nobody:users ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strTargetFile) . '; rm ' . escapeshellarg($strMD5File) . ' ' . escapeshellarg($strMD5StatusFile) . ')';
$strCleanPgrep = '-f "chmod.*chown.*rm.*' . $strMD5StatusFile . '"';
$strAllCmd = "#!/bin/bash\n\n";
$strAllCmd .= $strDownloadCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= 'echo "' . $arrDownloadVirtIO['md5'] . ' ' . $strTargetFile . '" > ' . escapeshellarg($strMD5File) . ' && ';
$strAllCmd .= $strVerifyCmd . ' >' . escapeshellarg($strMD5StatusFile) . ' 2>/dev/null && ';
$strAllCmd .= $strCleanCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= 'rm ' . escapeshellarg($strLogFile) . ' && ';
$strAllCmd .= 'rm ' . escapeshellarg($strInstallScript);
$arrResponse = [];
if (file_exists($strTargetFile)) {
if (!file_exists($strLogFile)) {
if (!pgrep($strDownloadPgrep)) {
// Status = done
$arrResponse['status'] = 'Done';
$arrResponse['localpath'] = $strTargetFile;
$arrResponse['localfolder'] = dirname($strTargetFile);
} else {
// Status = cleanup
$arrResponse['status'] = 'Cleanup ... ';
}
} else {
if (pgrep($strDownloadPgrep)) {
// Get Download percent completed
$intSize = filesize($strTargetFile);
$strPercent = 0;
if ($intSize > 0) {
$strPercent = round(($intSize / $arrDownloadVirtIO['size']) * 100);
}
$arrResponse['status'] = 'Downloading ... ' . $strPercent . '%';
} else if (pgrep($strVerifyPgrep)) {
// Status = running md5 check
$arrResponse['status'] = 'Verifying ... ';
} else if (file_exists($strMD5StatusFile)) {
// Status = running extract
$arrResponse['status'] = 'Cleanup ... ';
if (!pgrep($strExtractPgrep)) {
// Examine md5 status
$strMD5StatusContents = file_get_contents($strMD5StatusFile);
if (strpos($strMD5StatusContents, ': FAILED') !== false) {
// ERROR: MD5 check failed
unset($arrResponse['status']);
$arrResponse['error'] = 'MD5 verification failed, your download is incomplete or corrupted.';
}
}
} else if (!file_exists($strMD5File)) {
// Status = running md5 check
$arrResponse['status'] = 'Downloading ... 100%';
if (!pgrep($strInstallScriptPgrep) && !$boolCheckOnly) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
chmod($strInstallScript, 0777);
exec($strInstallScript . ' >/dev/null 2>&1 &');
}
}
}
} else if (!$boolCheckOnly) {
if (!pgrep($strInstallScriptPgrep)) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
chmod($strInstallScript, 0777);
exec($strInstallScript . ' >/dev/null 2>&1 &');
}
$arrResponse['status'] = 'Downloading ... ';
}
$arrResponse['pid'] = pgrep($strInstallScriptPgrep);
}
break;
case 'virtio-win-iso-cancel':
$arrDownloadVirtIO = [];
$strKeyName = basename($_POST['download_version'], '.iso');
if (array_key_exists($strKeyName, $virtio_isos)) {
$arrDownloadVirtIO = $virtio_isos[$strKeyName];
}
if (empty($arrDownloadVirtIO)) {
$arrResponse = ['error' => 'Unknown version: ' . $_POST['download_version']];
} else if (empty($_POST['download_path'])) {
$arrResponse = ['error' => 'ISO storage path was empty'];
} else if (!is_dir($_POST['download_path'])) {
$arrResponse = ['error' => 'ISO storage path doesn\'t exist'];
} else {
$strInstallScriptPgrep = '-f "VirtIOWin_' . $strKeyName . '_install.sh"';
$pid = pgrep($strInstallScriptPgrep);
if (!$pid) {
$arrResponse = ['error' => 'Not running'];
} else {
if (!posix_kill($pid, SIGTERM)) {
$arrResponse = ['error' => 'Wasn\'t able to stop the process'];
} else {
$strTargetFile = $_POST['download_path'] . $arrDownloadVirtIO['name'];
$strLogFile = $strTargetFile . '.log';
$strMD5File = $strTargetFile . '.md5';
$strMD5StatusFile = $strTargetFile . '.md5status';
@unlink($strTargetFile);
@unlink($strMD5File);
@unlink($strMD5StatusFile);
@unlink($strLogFile);
$arrResponse['status'] = 'Done';
}
}
}
break;
default:
$arrResponse = ['error' => 'Unknown action \'' . $action . '\''];

View File

@@ -15,25 +15,34 @@ require_once('/usr/local/emhttp/webGui/include/Helpers.php');
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php');
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php');
$strSelectedTemplate = array_keys($arrAllTemplates)[1];
if (!empty($_GET['template']) && !(empty($arrAllTemplates[$_GET['template']]))) {
$strSelectedTemplate = $_GET['template'];
}
$arrLoad = [
'name' => '',
'icon' => 'default.png',
'desc' => '',
'autostart' => false
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
'autostart' => false,
'form' => $arrAllTemplates[$strSelectedTemplate]['form']
];
$strSelectedTemplate = 'Custom';
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/'.$arrLoad['icon'];
if (!empty($_GET['uuid'])) {
// Edit VM mode
$res = $lv->domain_get_domain_by_uuid($_GET['uuid']);
$strIcon = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@icon');
if ($res === false) {
echo "<p class='notice'>Invalid VM to edit.</p>";
return;
}
$strIcon = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@icon');
if (!empty($strIcon)) {
if (file_exists($strIcon)) {
if (is_file($strIcon)) {
$strIconURL = $strIcon;
} else if (file_exists('/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/' . $strIcon)) {
} else if (is_file('/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/' . $strIcon)) {
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/' . $strIcon;
}
} else {
@@ -44,39 +53,31 @@ if (!empty($_GET['uuid'])) {
$arrLoad = [
'name' => $lv->domain_get_name($res),
'icon' => $strIcon,
'desc' => $lv->domain_get_description($res),
'autostart' => $lv->domain_get_autostart($res)
'autostart' => $lv->domain_get_autostart($res),
'form' => $arrAllTemplates[$strSelectedTemplate]['form']
];
if (!empty($_GET['template'])) {
$strSelectedTemplate = $_GET['template'];
} else {
$strTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@name');
if (!empty($strTemplate)) {
$strSelectedTemplate = $strTemplate;
if (empty($_GET['template'])) {
$strTemplateOS = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@os');
if (empty($strTemplateOS)) {
$strTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
if (!empty($strTemplate)) {
$strSelectedTemplate = $strTemplate;
}
} else {
// Legacy VM support for <6.2 but need it going forward too
foreach ($arrAllTemplates as $strName => $arrTemplate) {
if (!empty($arrTemplate) && !empty($arrTemplate['os']) && $arrTemplate['os'] == $strTemplateOS) {
$strSelectedTemplate = $strName;
break;
}
}
}
if (empty($strSelectedTemplate) || empty($arrAllTemplates[$strSelectedTemplate])) {
$strSelectedTemplate = 'Custom';
}
}
} else {
// New VM mode
$strIcon = 'windows.png';
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/windows.png';
$arrLoad['icon'] = $strIcon;
if (!empty($_GET['template'])) {
$strSelectedTemplate = $_GET['template'];
}
}
$arrTemplates = array();
// Read files from the templates folder
foreach (glob('plugins/dynamix.vm.manager/templates/*.form.php') as $template) {
$arrTemplates[] = basename($template, '.form.php');
}
if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
$strSelectedTemplate = $arrTemplates[0];
$arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form'];
}
?>
@@ -93,31 +94,31 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
z-index: 100;
display: none;
}
#createform table {
#vmform table {
margin-top: 0;
}
#createform div#title + table {
#vmform div#title + table {
margin-top: -21px;
}
#createform table tr {
#vmform table tr {
vertical-align: top;
line-height: 24px;
}
#createform table tr td:nth-child(odd) {
#vmform table tr td:nth-child(odd) {
width: 150px;
text-align: right;
padding-right: 10px;
}
#createform table tr td:nth-child(even) {
#vmform table tr td:nth-child(even) {
width: 80px;
}
#createform table tr td:last-child {
#vmform table tr td:last-child {
width: inherit;
}
#createform .multiple {
#vmform .multiple {
position: relative;
}
#createform .sectionbutton {
#vmform .sectionbutton {
position: absolute;
left: 2px;
cursor: pointer;
@@ -129,10 +130,10 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
transition-duration: 0.1s;
transition-timing-function: linear;
}
#createform .sectionbutton.remove { top: 0; opacity: 0.3; }
#createform .sectionbutton.add { bottom: 0; }
#createform .sectionbutton:hover { opacity: 1.0; }
#createform .sectiontab {
#vmform .sectionbutton.remove { top: 0; opacity: 0.3; }
#vmform .sectionbutton.add { bottom: 0; }
#vmform .sectionbutton:hover { opacity: 1.0; }
#vmform .sectiontab {
position: absolute;
top: 2px;
bottom: 2px;
@@ -144,20 +145,38 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
transition-duration: 0.1s;
transition-timing-function: linear;
}
#createform .multiple:hover .sectionbutton {
#vmform .multiple:hover .sectionbutton {
opacity: 0.7;
left: 4px;
}
#createform .multiple:hover .sectionbutton.remove {
#vmform .multiple:hover .sectionbutton.remove {
opacity: 0.6;
}
#createform .multiple:hover .sectiontab {
#vmform .multiple:hover .sectiontab {
background-color: #CCCCCC;
width: 8px;
}
#form_content {
margin-top: -21px;
#vmform table.multiple {
margin: 10px 0;
<?if ($display['theme'] == 'black'):?>
background:linear-gradient(90deg, #0A0A0A, #000000);
<?else:?>
background:linear-gradient(90deg, #F5F5F5, #FFFFFF);
<?endif;?>
background-size: 600px 100%;
background-position: -600px;
background-repeat: no-repeat;
background-clip: content-box;
transition: background 0.3s linear;
}
#vmform table.multiple:hover {
background-position: 0 0;
}
#vmform table.multiple td {
padding: 5px 0;
}
span.advancedview_panel {
display: none;
line-height: 16px;
@@ -172,63 +191,121 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
.switch-button-label.off {
color: inherit;
}
#template_img {
cursor: pointer;
}
#template_img:hover {
opacity: 0.5;
}
#template_img:hover i {
opacity: 1.0;
}
.template_img_chooser_inner {
display: inline-block;
width: 80px;
margin-bottom: 15px;
margin-right: 10px;
text-align: center;
}
.template_img_chooser_inner img {
width: 48px;
height: 48px;
}
.template_img_chooser_inner p {
text-align: center;
line-height: 8px;
}
#template_img_chooser {
width: 560px;
height: 300px;
overflow: scroll;
position: relative;
}
#template_img_chooser div:hover {
background-color: #eee;
cursor: pointer;
}
#template_img_chooser_outer {
position: absolute;
display: none;
border-radius:5px;
<?if ($display['theme'] == 'black'):?>
border:1px solid #202020;
background:-webkit-radial-gradient(#303030,#101010);
background:linear-gradient(#303030,#101010);
<?else:?>
border:1px solid #D0D0D0;
background:-webkit-radial-gradient(#B0B0B0,#F0F0F0);
background:linear-gradient(#B0B0B0,#F0F0F0);
<?endif;?>
z-index: 10;
}
#form_content {
display: none;
}
#vmform .four label {
float: left;
display: table-cell;
width: 25%;
}
#vmform .four label:nth-child(4n+4) {
float: none;
clear: both;
}
#vmform .mac_generate {
cursor: pointer;
margin-left: -5px;
color: #08C;
font-size: 1.3em;
transform: translate(0px, 2px);
}
#vmform .disk {
display: none;
}
#vmform .disk_preview {
display: inline-block;
color: #BBB;
transform: translate(0px, 1px);
}
<?if ($display['theme'] == 'black'):?>
span#dropbox{border:1px solid #202020;border-radius:5px;background:-webkit-radial-gradient(#303030,#101010);background:linear-gradient(#303030,#101010);padding:28px 12px;line-height:72px;margin-right:16px;}
<?else:?>
span#dropbox{border:1px solid #D0D0D0;border-radius:5px;background:-webkit-radial-gradient(#B0B0B0,#F0F0F0);background:linear-gradient(#B0B0B0,#F0F0F0);padding:28px 12px;line-height:72px;margin-right:16px;}
<?endif;?>
</style>
<span class="status advancedview_panel" style="margin-top:-44px;"><input type="checkbox" class="advancedview"></span>
<div id="content" style="margin-top:-21px;margin-left:0px">
<form id="createform" method="POST">
<form id="vmform" method="POST">
<input type="hidden" name="domain[type]" value="kvm" />
<table>
<tr <? if (!empty($arrLoad['name'])) echo 'style="display: none"'; ?>>
<td>Template:</td>
<td>
<select id="domain_template" name="template[name]" class="narrow" title="Choose a preconfigured template or select Custom to create your own from scratch">
<?php
foreach ($arrTemplates as $strTemplate) {
echo mk_option($strSelectedTemplate, $strTemplate, str_replace('_', ' ', $strTemplate));
}
?>
</select><img src="/webGui/images/spinner.gif" style="display: none">
</td>
</tr>
</table>
<div <? if (!empty($arrLoad['name'])) echo 'style="display: none"'; ?>>
<blockquote class="inline_help">
<p>Choose a preconfigured template or select Custom to create your own from scratch.</p>
</blockquote>
</div>
<input type="hidden" name="template[name]" value="<?=$strSelectedTemplate?>" />
<table>
<tr>
<td>Icon:</td>
<td><input type="hidden" name="template[icon]" id="template_icon" value="<?=$arrLoad['icon']?>" /><img id="template_img" src="<?=htmlentities($strIconURL)?>" width="48" height="48" /></td>
<td>
<input type="hidden" name="template[icon]" id="template_icon" value="<?=$arrLoad['icon']?>" />
<img id="template_img" src="<?=htmlentities($strIconURL)?>" width="48" height="48" title="Change Icon..."/>
<div id="template_img_chooser_outer">
<div id="template_img_chooser">
<?
$arrImagePaths = [
'/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/*.png' => '/plugins/dynamix.vm.manager/templates/images/',
'/usr/local/emhttp/boot/config/plugins/dynamix.vm.manager/templates/images/*.png' => '/boot/config/plugins/dynamix.vm.manager/templates/images/'
];
foreach ($arrImagePaths as $strGlob => $strIconURLBase) {
foreach (glob($strGlob) as $png_file) {
echo '<div class="template_img_chooser_inner"><img src="'.$strIconURLBase.basename($png_file).'" basename="'.basename($png_file).'"><p>'.basename($png_file,'.png').'</p></div>';
}
}
?>
</div>
</div>
</td>
</tr>
</table>
<table>
<tr class="non_expert_xml">
<td>Name:</td>
<td><input type="text" name="domain[name]" class="textTemplate" title="Name of virtual machine" placeholder="e.g. My Workstation" value="<?=htmlentities($arrLoad['name'])?>" /></td>
</tr>
</table>
<div class="non_expert_xml">
<blockquote class="inline_help">
<p>Give the VM a name (e.g. Work, Gaming, Media Player, Firewall, Bitcoin Miner)</p>
</blockquote>
</div>
<table>
<tr class="non_expert_xml">
<td>Description:</td>
<td><input type="text" name="domain[desc]" title="description of virtual machine" placeholder="description of virtual machine (optional)" value="<?=htmlentities($arrLoad['desc'])?>" /></td>
</tr>
</table>
<div class="non_expert_xml">
<blockquote class="inline_help">
<p>Give the VM a brief description (optional field).</p>
</blockquote>
</div>
<table>
<tr style="line-height: 15px; vertical-align: middle;">
<td>Autostart:</td>
@@ -239,16 +316,12 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
<p>If you want this VM to start with the array, set this to yes.</p>
</blockquote>
<div id="title">
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/preferences.png" class="icon">Template Settings</span>
<span class="status advancedview_panel"><input type="checkbox" class="advancedview"></span>
</div>
<div id="form_content"></div>
<div id="form_content"><? include('/usr/local/emhttp/plugins/dynamix.vm.manager/templates/'.$arrLoad['form']); ?></div>
</form>
</div>
<script src="/webGui/javascript/jquery.filedrop.js"></script>
<script src="/webGui/javascript/jquery.filetree.js"></script>
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
<script src="/plugins/dynamix.vm.manager/scripts/dynamix.vm.manager.js"></script>
@@ -256,29 +329,6 @@ if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
function isVMAdvancedMode() {
return ($.cookie('vmmanager_listview_mode') == 'advanced');
}
function checkForAdvancedMode() {
var $el = $('#form_content');
var $advanced = $el.find('.advanced');
var $basic = $el.find('.basic');
if ($advanced.length || $basic.length) {
$('.advancedview_panel').fadeIn('fast');
if (isVMAdvancedMode()) {
$('.basic').hide();
$('.advanced').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
} else {
$('.advanced').hide();
$('.basic').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
}
} else {
$('.advancedview_panel').fadeOut('fast');
}
}
$(function() {
$('.autostart').switchButton({
@@ -301,29 +351,52 @@ $(function() {
$.cookie('vmmanager_listview_mode', $(this).is(':checked') ? 'advanced' : 'basic', { expires: 3650 });
});
$('#domain_template').change(function loadDomainTemplate(){
var $el = $('#form_content');
var templateName = $(this).val();
$('#template_img').click(function (){
var p = $(this).position();
p.left -= 4;
p.top -= 4;
$('#template_img_chooser_outer').css(p);
$('#template_img_chooser_outer').slideDown();
});
$('#template_img_chooser').on('click', 'div', function (){
$('#template_img').attr('src', $(this).find('img').attr('src'));
$('#template_icon').val($(this).find('img').attr('basename'));
$('#template_img_chooser_outer').slideUp();
});
$(document).keyup(function(e) {
if (e.which == 27) $('#template_img_chooser_outer').slideUp();
});
toggleRows('non_expert_xml', (templateName != 'XML_Expert'));
$("#vmform table[data-category]").each(function () {
var category = $(this).data('category');
$el.fadeOut(100, function(){
$el.html('<div style="padding-left: 80px; padding-top: 20px; font-size: 1.3em"><img src="/webGui/images/spinner.gif"> Loading...</div>').fadeIn('fast');
});
updatePrefixLabels(category);
bindSectionEvents(category);
});
$.get('/plugins/dynamix.vm.manager/templates/' + templateName + '.form.php' + location.search, function(data) {
$el.stop(true, false).fadeOut(100, function() {
$el.html(data);
$("#vmform input[data-pickroot]").fileTreeAttach();
checkForAdvancedMode();
var $el = $('#form_content');
var $advanced = $el.find('.advanced');
var $basic = $el.find('.basic');
if ($.cookie('help')=='help') {
$('.inline_help').show();
}
if ($advanced.length || $basic.length) {
$('.advancedview_panel').fadeIn('fast');
if (isVMAdvancedMode()) {
$('.basic').hide();
$('.advanced').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
} else {
$('.advanced').hide();
$('.basic').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
}
} else {
$('.advancedview_panel').fadeOut('fast');
}
$el.fadeIn('fast');
});
});
}).change(); // Fire now too
$('#form_content').fadeIn('fast');
});
</script>

View File

@@ -69,11 +69,7 @@
return false;
}
// determine the actual disk if user share is being used
if (strpos($folder, '/mnt/user/') === 0) {
$tmp = parse_ini_string(shell_exec("getfattr -n user.LOCATION " . escapeshellarg($folder) . " | grep user.LOCATION"));
$folder = str_replace('/mnt/user', '/mnt/' . $tmp['user.LOCATION'], $folder); // replace 'user' with say 'cache' or 'disk1' etc
}
$folder = transpose_user_path($folder);
@shell_exec("chattr +C -R " . escapeshellarg($folder) . " &>/dev/null");
@@ -120,7 +116,7 @@
}
if (is_file($disk['image'])) {
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($disk['image'])), true);
$json_info = getDiskImageInfo($disk['image']);
$disk['driver'] = $json_info['format'];
if (!empty($disk['size'])) {
@@ -199,14 +195,33 @@
if (is_file($strImgPath)) {
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($strImgPath)), true);
$json_info = getDiskImageInfo($strImgPath);
$disk['driver'] = $json_info['format'];
$return_value = 0;
} else {
$strLastLine = exec("qemu-img create -q -f " . escapeshellarg($disk['driver']) . " " . escapeshellarg($strImgPath) . " " . escapeshellarg($disk['size']), $output, $return_value);
chmod($strImgPath, 0777);
chown($strImgPath, 'nobody');
chgrp($strImgPath, 'users');
$strImgRawLocationPath = $strImgPath;
if (!empty($disk['select']) && (!in_array($disk['select'], ['auto', 'manual'])) && (is_dir('/mnt/'.$disk['select']))) {
// Force qemu disk creation to happen directly on either cache/disk1/disk2 ect based on dropdown selection
$strImgRawLocationPath = str_replace('/mnt/user/', '/mnt/'.$disk['select'].'/', $strImgPath);
// create folder if needed
$strImgRawLocationParent = dirname($strImgRawLocationPath);
if (!is_dir($strImgRawLocationParent)) {
mkdir($strImgRawLocationParent, 0777, true);
chown($strImgRawLocationParent, 'nobody');
chgrp($strImgRawLocationParent, 'users');
}
$this->set_folder_nodatacow($strImgRawLocationParent);
}
$strLastLine = exec("qemu-img create -q -f ".escapeshellarg($disk['driver'])." ".escapeshellarg($strImgRawLocationPath)." ".escapeshellarg($disk['size'])." 2>&1", $output, $return_value);
if (is_file($strImgPath)) {
chmod($strImgPath, 0777);
chown($strImgPath, 'nobody');
chgrp($strImgPath, 'users');
}
}
if ($return_value != 0) {
@@ -246,40 +261,44 @@
$usb = $config['usb'];
$shares = $config['shares'];
$gpus = $config['gpu'];
$pcis = $config['pci'];
$audios = $config['audio'];
$template = $config['template'];
$type = $domain['type'];
$name = $domain['name'];
$mem = $domain['mem'];
$maxmem = $mem;
if (!empty($domain['maxmem'])) {
$maxmem = $domain['maxmem'];
}
$maxmem = (!empty($domain['maxmem'])) ? $domain['maxmem'] : $mem;
$uuid = (!empty($domain['uuid']) ? $domain['uuid'] : $this->domain_generate_uuid());
$machine = $domain['machine'];
$machine_type = (stripos($machine, 'q35') !== false ? 'q35' : 'pc');
$os_type = ((empty($template['os']) || stripos($template['os'], 'windows') === false) ? 'other' : 'windows');
$emulator = $this->get_default_emulator();
//$emulator = $this->get_default_emulator();
$emulator = '/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/qemu.sh';
$arch = $domain['arch'];
$pae = '';
if ($arch == 'i686'){
$pae = '<pae/>';
}
$pae = ($arch == 'i686') ? '<pae/>' : '';
$loader = '';
if (!empty($domain['ovmf'])) {
$loader = "<loader type='pflash'>/usr/share/qemu/ovmf-x64/OVMF-pure-efi.fd</loader>";
if (!is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir('/etc/libvirt/qemu/nvram/', 0777, true);
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
}
$loader = "<loader readonly='yes' type='pflash'>/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd</loader>
<nvram>/etc/libvirt/qemu/nvram/".$uuid."_VARS-pure-efi.fd</nvram>";
}
$metadata = '';
if (!empty($template)) {
$metadata .= "<metadata>";
$template_options = '';
foreach ($template as $key => $value) {
$template_options .= $key . "='" . htmlspecialchars($value, ENT_QUOTES | ENT_XML1) . "' ";
}
$metadata = "<metadata><vmtemplate " . $template_options . "/></metadata>";
$metadata .= "<vmtemplate xmlns='unraid' " . $template_options . "/>";
$metadata .= "</metadata>";
}
$vcpus = 1;
@@ -297,29 +316,62 @@
}
}
$intCores = $vcpus;
$intThreads = 1;
$intCPUThreadsPerCore = 1;
$cpumode = '';
if (!empty($domain['cpumode']) && $domain['cpumode'] == 'host-passthrough') {
$cpumode .= "mode='host-passthrough'";
// detect if the processor is hyperthreaded:
$intCPUThreadsPerCore = max(intval(shell_exec('/usr/bin/lscpu | grep \'Thread(s) per core\' | awk \'{print $4}\'')), 1);
// even amount of cores assigned and cpu is hyperthreaded: pass that info along to the cpu section below
if ($intCPUThreadsPerCore > 1 && ($vcpus % $intCPUThreadsPerCore == 0)) {
$intCores = $vcpus / $intCPUThreadsPerCore;
$intThreads = $intCPUThreadsPerCore;
}
}
$cpustr = "<cpu $cpumode>
<topology sockets='1' cores='{$vcpus}' threads='1'/>
<topology sockets='1' cores='{$intCores}' threads='{$intThreads}'/>
</cpu>
<vcpu placement='static'>{$vcpus}</vcpu>
<cputune>
$vcpupinstr
</cputune>";
$bus = "ide";
$usbmode = 'usb3';
if (!empty($domain['usbmode'])) {
$usbmode = $domain['usbmode'];
}
$ctrl = '';
if ($machine_type == 'q35'){
$bus = "sata";
$ctrl = "<controller type='usb' index='0' model='ich9-ehci1'/>
<controller type='usb' index='0' model='ich9-uhci1'/>";
if (!empty($domain['ovmf'])) {
// OVMF + Q35 needs the bus set to usb for cdroms
$bus = "usb";
}
switch ($usbmode) {
case 'usb3':
$ctrl = "<controller type='usb' index='0' model='nec-xhci'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</controller>";
break;
case 'usb2':
$ctrl = "<controller type='usb' index='0' model='ich9-ehci1'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x7'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci1'>
<master startport='0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0' multifunction='on'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci2'>
<master startport='2'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci3'>
<master startport='4'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/>
</controller>";
break;
}
$clock = "<clock offset='" . $domain['clock'] . "'>
@@ -334,6 +386,7 @@
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='8191'/>
<vendor id='none'/>
</hyperv>";
$clock = "<clock offset='" . $domain['clock'] . "'>
@@ -346,7 +399,7 @@
if (!empty($usb)) {
foreach($usb as $i => $v){
$usbx = explode(':', $v);
$usbstr .= "<hostdev mode='subsystem' type='usb' managed='yes'>
$usbstr .= "<hostdev mode='subsystem' type='usb'>
<source>
<vendor id='0x".$usbx[0]."'/>
<product id='0x".$usbx[1]."'/>
@@ -362,6 +415,11 @@
$arrUsedBootOrders = [];
//media settings
$bus = "ide";
if ($machine_type == 'q35'){
$bus = "sata";
}
$mediastr = '';
if (!empty($media['cdrom'])) {
unset($arrAvailableDevs['hda']);
@@ -369,7 +427,7 @@
$mediastr = "<disk type='file' device='cdrom'>
<driver name='qemu'/>
<source file='" . htmlspecialchars($media['cdrom'], ENT_QUOTES | ENT_XML1) . "'/>
<target dev='hda' bus='$bus'/>
<target dev='hda' bus='" . (!empty($media['cdrombus']) ? $media['cdrombus'] : $bus) . "'/>
<readonly/>
<boot order='2'/>
</disk>";
@@ -381,7 +439,7 @@
$driverstr = "<disk type='file' device='cdrom'>
<driver name='qemu'/>
<source file='" . htmlspecialchars($media['drivers'], ENT_QUOTES | ENT_XML1) . "'/>
<target dev='hdb' bus='$bus'/>
<target dev='hdb' bus='" . (!empty($media['driversbus']) ? $media['driversbus'] : $bus) . "'/>
<readonly/>
</disk>";
}
@@ -390,6 +448,11 @@
$diskstr = '';
$diskcount = 0;
if (!empty($disks)) {
// force any hard drives to start with hdc, hdd, hde, etc
unset($arrAvailableDevs['hda']);
unset($arrAvailableDevs['hdb']);
foreach ($disks as $i => $disk) {
if (!empty($disk['image']) | !empty($disk['new']) ) {
//TODO: check if image/new is a block device
@@ -406,7 +469,7 @@
$disk['driver'] = 'raw';
if (is_file($disk['image'])) {
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($disk['image'])), true);
$json_info = getDiskImageInfo($disk['image']);
$disk['driver'] = $json_info['format'];
}
}
@@ -441,7 +504,7 @@
}
if (is_file($strImgPath)) {
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($strImgPath)), true);
$json_info = getDiskImageInfo($strImgPath);
$disk['driver'] = $json_info['format'];
}
@@ -503,10 +566,15 @@
continue;
}
$netmodel = 'virtio';
if (!empty($nic['model'])) {
$netmodel = $nic['model'];
}
$netstr .= "<interface type='bridge'>
<mac address='{$nic['mac']}'/>
<source bridge='" . htmlspecialchars($nic['network'], ENT_QUOTES | ENT_XML1) . "'/>
<model type='virtio'/>
<model type='{$netmodel}'/>
</interface>";
}
}
@@ -525,25 +593,13 @@
}
}
$passwdstr = '';
if (!empty($domain['password'])){
$passwdstr = "passwd='" . htmlspecialchars($domain['password'], ENT_QUOTES | ENT_XML1) . "'";
}
$pcidevs='';
$gpuargs='';
$gpudevs=[];
$gpuargsdevs=[];
$gpuincr=0;
$gpudevs_used=[];
$vnc='';
if (!empty($gpus)) {
foreach ($gpus as $i => $gpu) {
if (empty($gpu['id'])) {
continue;
}
// Skip duplicate video devices
if (in_array($gpu['id'], $gpudevs)) {
if (empty($gpu['id']) || in_array($gpu['id'], $gpudevs_used)) {
continue;
}
@@ -553,143 +609,96 @@
$strKeyMap = "keymap='" . $gpu['keymap'] . "'";
}
$passwdstr = '';
if (!empty($domain['password'])){
$passwdstr = "passwd='" . htmlspecialchars($domain['password'], ENT_QUOTES | ENT_XML1) . "'";
}
$strModelType = 'qxl';
if (!empty($gpu['model'])) {
$strModelType = $gpu['model'];
if (!empty($domain['ovmf']) && $strModelType == 'vmvga') {
// OVMF doesn't work with vmvga
$strModelType = 'qxl';
}
}
$vnc = "<input type='tablet' bus='usb'/>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' websocket='-1' listen='0.0.0.0' $passwdstr $strKeyMap>
<listen type='address' address='0.0.0.0'/>
</graphics>";
</graphics>
<video>
<model type='$strModelType'/>
</video>";
$gpudevs_used[] = $gpu['id'];
if (!empty($domain['ovmf'])) {
// OVMF doesn't work with vmvga
$vnc .= "<video>
<model type='cirrus'/>
</video>";
} else {
// SeaBIOS is cool with vmvga
$vnc .= "<video>
<model type='vmvga'/>
</video>";
}
continue;
}
list($gpu_bus, $gpu_slot, $gpu_function) = explode(":", str_replace('.', ':', $gpu['id']));
if (!empty($domain['ovmf'])) {
$pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'".((empty($gpudevs_used) && empty($domain['ovmf'])) ? " xvga='on'" : "").">
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x".$gpu_bus."' slot='0x".$gpu_slot."' function='0x".$gpu_function."'/>
</source>
</hostdev>";
// OVMF passthrough uses the normal hostdev and libvirt can fully manage the device
$pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x" . $gpu_bus . "' slot='0x" . $gpu_slot . "' function='0x" . $gpu_function . "'/>
</source>
</hostdev>";
} else {
// VGA BIOS passthrough uses qemu args and we have to manage the device (libvirt wont attach/detach the device driver to vfio-pci)
switch ($machine_type) {
case 'q35':
$gpuargs .= "<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host={$gpu_bus}:{$gpu_slot}.{$gpu_function},bus=pcie.0,multifunction=on" . ((empty($gpuargs) && empty($vnc)) ? ',x-vga=on' : '') . "'/>";
$gpuargsdevs[$gpu['id']] = ""; // no address needed
break;
case 'pc':
$gpuargs .= "<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host={$gpu_bus}:{$gpu_slot}.{$gpu_function},bus=root.1,addr=0{$gpuincr}.0,multifunction=on" . ((empty($gpuargs) && empty($vnc)) ? ',x-vga=on' : '') . "'/>";
$gpuargsdevs[$gpu['id']] = "0{$gpuincr}.0";
break;
}
}
$gpudevs[] = $gpu['id'];
$gpuincr++;
$gpudevs_used[] = $gpu['id'];
}
}
$audioargs='';
$audiodevs=[];
$audiodevs_used=[];
if (!empty($audios)) {
foreach ($audios as $i => $audio) {
if (empty($audio['id'])) {
continue;
}
// Skip duplicate audio devices
if (in_array($audio['id'], $audiodevs)) {
if (empty($audio['id']) || in_array($audio['id'], $audiodevs_used)) {
continue;
}
list($audio_bus, $audio_slot, $audio_function) = explode(":", str_replace('.', ':', $audio['id']));
if (!empty($domain['ovmf']) || empty($gpuargsdevs)) {
$pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x".$audio_bus."' slot='0x".$audio_slot."' function='0x".$audio_function."'/>
</source>
</hostdev>";
$pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x" . $audio_bus . "' slot='0x" . $audio_slot . "' function='0x" . $audio_function . "'/>
</source>
</hostdev>";
} else {
// VGA BIOS passthrough uses qemu args and we have to manage the device (libvirt wont attach/detach the device driver to vfio-pci)
switch ($machine_type) {
case 'q35':
$audioargs .= "<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host={$audio_bus}:{$audio_slot}.{$audio_function},bus=pcie.0'/>";
break;
case 'pc':
// Look for video device and see if this sound device is a function of that video card
$addr = '';
if (isset($gpuargsdevs[$audio_bus . ':' . $audio_slot . '.0'])) {
$addr = str_replace('.0', '.' . $audio_function, $gpuargsdevs[$audio_bus . ':' . $audio_slot . '.0']);
} else {
$addr = "0" . $gpuincr++ . ".0";
}
$audioargs .= "<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host={$audio_bus}:{$audio_slot}.{$audio_function},bus=root.1,addr=" . $addr . "'/>";
break;
}
}
$audiodevs[] = $audio['id'];
$audiodevs_used[] = $audio['id'];
}
}
$cmdargs='';
if (!empty($gpuargs) || !empty($audioargs)) {
switch ($machine_type) {
$pcidevs_used=[];
if (!empty($pcis)) {
foreach ($pcis as $i => $pci_id) {
// Skip duplicate other pci devices
if (empty($pci_id) || in_array($pci_id, $pcidevs_used)) {
continue;
}
case 'q35':
$cmdargs .= "<qemu:commandline>
<qemu:arg value='-device'/>
<qemu:arg value='ioh3420,bus=pcie.0,addr=1c.0,multifunction=on,port=2,chassis=1,id=root.1'/>
$gpuargs
$audioargs
</qemu:commandline>";
break;
list($pci_bus, $pci_slot, $pci_function) = explode(":", str_replace('.', ':', $pci_id));
case 'pc':
$cmdargs .= "<qemu:commandline>
<qemu:arg value='-device'/>
<qemu:arg value='ioh3420,bus=pci.0,addr=1c.0,multifunction=on,port=2,chassis=1,id=root.1'/>
$gpuargs
$audioargs
</qemu:commandline>";
break;
$pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x" . $pci_bus . "' slot='0x" . $pci_slot . "' function='0x" . $pci_function . "'/>
</source>
</hostdev>";
$pcidevs_used[] = $pci_id;
}
}
$memorybacking='<nosharepages/>';
if (!empty($pcidevs)) {
$memorybacking .= '<locked/>';
}
return "<domain type='$type' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<uuid>$uuid</uuid>
@@ -699,8 +708,7 @@
<currentMemory>$mem</currentMemory>
<memory>$maxmem</memory>
<memoryBacking>
<nosharepages/>
<locked/>
$memorybacking
</memoryBacking>
$cpustr
<os>
@@ -736,7 +744,6 @@
<alias name='balloon0'/>
</memballoon>
</devices>
$cmdargs
</domain>";
}
@@ -1538,6 +1545,12 @@
if (!$dom)
return false;
$uuid = $this->domain_get_uuid($dom);
// remove OVMF VARS if this domain had them
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
}
$tmp = libvirt_domain_undefine($dom);
return ($tmp) ? $tmp : $this->_set_last_error();
}
@@ -1547,7 +1560,7 @@
if (!$dom)
return false;
$disks = $this->get_disk_stats($dom);
$tmp = libvirt_domain_undefine($dom);
$tmp = $this->domain_undefine($dom);
if (!$tmp)
return $this->_set_last_error();
@@ -1559,24 +1572,39 @@
// remove the vm config
$cfg_vm = $dir.'/'.$domain.'.cfg';
if (file_exists($cfg_vm));
unlink($cfg_vm);
if (is_file($cfg_vm)) unlink($cfg_vm);
$cfg = $dir.'/'.$pathinfo['filename'].'.cfg';
$xml = $dir.'/'.$pathinfo['filename'].'.xml';
if (file_exists($disk));
unlink($disk);
if (file_exists($cfg));
unlink($cfg);
if (file_exists($xml));
unlink($xml);
if (is_dir($dir) && $this->is_dir_empty($dir))
rmdir($dir);
if (is_file($disk)) unlink($disk);
if (is_file($cfg)) unlink($cfg);
if (is_file($xml)) unlink($xml);
if (is_dir($dir) && $this->is_dir_empty($dir)) rmdir($dir);
}
return true;
}
function nvram_backup($uuid) {
// move OVMF VARS to a backup file if this domain has them
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup');
return true;
}
return false;
}
function nvram_restore($uuid) {
// restore backup OVMF VARS if this domain had them
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup')) {
rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
return true;
}
return false;
}
function is_dir_empty($dir) {
if (!is_readable($dir)) return NULL;
$handle = opendir($dir);
@@ -1637,6 +1665,17 @@
return $var;
}
function domain_get_vnc_model($domain) {
$tmp = $this->get_xpath($domain, '//domain/devices/video/model/@type', false);
if (!$tmp)
return 'qxl';
$var = $tmp[0];
unset($tmp);
return $var;
}
function domain_get_vnc_keymap($domain) {
$tmp = $this->get_xpath($domain, '//domain/devices/graphics/@keymap', false);
if (!$tmp)

View File

@@ -16,35 +16,381 @@
if (! isset($var)){
if (! is_file("/usr/local/emhttp/state/var.ini")) shell_exec("wget -qO /dev/null localhost:$(lsof -nPc emhttp | grep -Po 'TCP[^\d]*\K\d+')");
$var = @parse_ini_file("/usr/local/emhttp/state/var.ini");
$disks = @parse_ini_file("/usr/local/emhttp/state/disks.ini", true);
extract(parse_plugin_cfg("dynamix",true));
}
// Check if program is running and
$libvirt_running = trim(shell_exec( "[ -f /proc/`cat /var/run/libvirt/libvirtd.pid 2> /dev/null`/exe ] && echo 'yes' || echo 'no' 2> /dev/null" ));
$arrAllTemplates = [
' Windows ' => '', /* Windows Header */
'Windows 10' => [
'form' => 'Custom.form.php',
'icon' => 'windows.png',
'os' => 'windows10',
'overrides' => [
'domain' => [
'mem' => 2048 * 1024,
'maxmem' => 2048 * 1024
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows 8.x' => [
'form' => 'Custom.form.php',
'icon' => 'windows.png',
'os' => 'windows',
'overrides' => [
'domain' => [
'name' => 'Windows 8.1',
'mem' => 2048 * 1024,
'maxmem' => 2048 * 1024
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows 7' => [
'form' => 'Custom.form.php',
'icon' => 'windows7.png',
'os' => 'windows7',
'overrides' => [
'domain' => [
'mem' => 2048 * 1024,
'maxmem' => 2048 * 1024,
'ovmf' => 0,
'usbmode' => 'usb2'
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows XP' => [
'form' => 'Custom.form.php',
'icon' => 'windowsxp.png',
'os' => 'windowsxp',
'overrides' => [
'domain' => [
'ovmf' => 0,
'usbmode' => 'usb2'
],
'disk' => [
[
'size' => '15G'
]
]
]
],
'Windows Server 2016' => [
'form' => 'Custom.form.php',
'icon' => 'windows.png',
'os' => 'windows2016',
'overrides' => [
'domain' => [
'mem' => 2048 * 1024,
'maxmem' => 2048 * 1024
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows Server 2012' => [
'form' => 'Custom.form.php',
'icon' => 'windows.png',
'os' => 'windows2012',
'overrides' => [
'domain' => [
'mem' => 2048 * 1024,
'maxmem' => 2048 * 1024
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows Server 2008' => [
'form' => 'Custom.form.php',
'icon' => 'windowsxp.png',
'os' => 'windows2008',
'overrides' => [
'domain' => [
'usbmode' => 'usb2'
],
'disk' => [
[
'size' => '30G'
]
]
]
],
'Windows Server 2003' => [
'form' => 'Custom.form.php',
'icon' => 'windowsxp.png',
'os' => 'windows2003',
'overrides' => [
'domain' => [
'usbmode' => 'usb2'
],
'disk' => [
[
'size' => '15G'
]
]
]
],
' Pre-packaged ' => '', /* Pre-built Header */
'OpenELEC' => [
'form' => 'OpenELEC.form.php',
'icon' => 'openelec.png'
],
' Linux ' => '', /* Linux Header */
'Linux' => [
'form' => 'Custom.form.php',
'icon' => 'linux.png',
'os' => 'linux'
],
'Arch' => [
'form' => 'Custom.form.php',
'icon' => 'arch.png',
'os' => 'arch'],
'CentOS' => [
'form' => 'Custom.form.php',
'icon' => 'centos.png',
'os' => 'centos'
],
'ChromeOS' => [
'form' => 'Custom.form.php',
'icon' => 'chromeos.png',
'os' => 'chromeos'
],
'CoreOS' => [
'form' => 'Custom.form.php',
'icon' => 'coreos.png',
'os' => 'coreos'
],
'Debian' => [
'form' => 'Custom.form.php',
'icon' => 'debian.png',
'os' => 'debian'
],
'Fedora' => [
'form' => 'Custom.form.php',
'icon' => 'fedora.png',
'os' => 'fedora'
],
'FreeBSD' => [
'form' => 'Custom.form.php',
'icon' => 'freebsd.png',
'os' => 'freebsd'
],
'OpenSUSE' => [
'form' => 'Custom.form.php',
'icon' => 'opensuse.png',
'os' => 'opensuse'
],
'RedHat' => [
'form' => 'Custom.form.php',
'icon' => 'redhat.png',
'os' => 'redhat'
],
'Scientific' => [
'form' => 'Custom.form.php',
'icon' => 'scientific.png',
'os' => 'scientific'
],
'Slackware' => [
'form' => 'Custom.form.php',
'icon' => 'slackware.png',
'os' => 'slackware'
],
'SteamOS' => [
'form' => 'Custom.form.php',
'icon' => 'steamos.png',
'os' => 'steamos'
],
'Ubuntu' => [
'form' => 'Custom.form.php',
'icon' => 'ubuntu.png',
'os' => 'ubuntu'
],
'VyOS' => [
'form' => 'Custom.form.php',
'icon' => 'vyos.png',
'os' => 'vyos'
],
' ' => '', /* Custom / XML Expert Header */
'Custom' => [
'form' => 'XML_Expert.form.php',
'icon' => 'default.png'
]
];
$virtio_isos = [
'virtio-win-0.1.110-2' => [
'name' => 'virtio-win-0.1.110-2.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.110-2/virtio-win-0.1.110.iso',
'size' => 56586240,
'md5' => '93357a5105f1255591f1c389748288a9'
],
'virtio-win-0.1.110-1' => [
'name' => 'virtio-win-0.1.110-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.110-1/virtio-win-0.1.110.iso',
'size' => 56586240,
'md5' => '239e0eb442bb63c177deb4af39397731'
],
'virtio-win-0.1.109-2' => [
'name' => 'virtio-win-0.1.109-2.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.109-2/virtio-win-0.1.109.iso',
'size' => 56606720,
'md5' => '2a9f78f648f03fe72decdadb38837db3'
],
'virtio-win-0.1.109-1' => [
'name' => 'virtio-win-0.1.109-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.109-1/virtio-win-0.1.109.iso',
'size' => 56606720,
'md5' => '1b0da008d0ec79a6223d21be2fcce2ee'
],
'virtio-win-0.1.108-1' => [
'name' => 'virtio-win-0.1.108-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.108-1/virtio-win-0.1.108.iso',
'size' => 56598528,
'md5' => '46deb991f8c382f2d9af0fb786792990'
],
'virtio-win-0.1.106-1' => [
'name' => 'virtio-win-0.1.106-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.106-1/virtio-win-0.1.106.iso',
'size' => 56586240,
'md5' => '66228ea20fae1a28d7a1583b9a5a1b8b'
],
'virtio-win-0.1.105-1' => [
'name' => 'virtio-win-0.1.105-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.105-1/virtio-win-0.1.105.iso',
'size' => 56584192,
'md5' => 'c3194fa62a4a1ccbecfe784a52feda66'
],
'virtio-win-0.1.104-1' => [
'name' => 'virtio-win-0.1.104-1.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.104-1/virtio-win-0.1.104.iso',
'size' => 56584192,
'md5' => '9aa28b6f5b18770d796194aaaeeea31a'
],
'virtio-win-0.1.103-2' => [
'name' => 'virtio-win-0.1.103-2.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.103-2/virtio-win-0.1.103.iso',
'size' => 56340480,
'md5' => '07c4356880f0b385d6908392e48d6e75'
],
'virtio-win-0.1.103' => [
'name' => 'virtio-win-0.1.103.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.103/virtio-win-0.1.103.iso',
'size' => 49903616,
'md5' => 'd31069b620820b75730d2def7690c271'
],
'virtio-win-0.1.102' => [
'name' => 'virtio-win-0.1.102.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.102/virtio-win-0.1.102.iso',
'size' => 160755712,
'md5' => '712561dd78ef532c54f8fee927c1ce2e'
],
'virtio-win-0.1.101' => [
'name' => 'virtio-win-0.1.101.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.101/virtio-win-0.1.101.iso',
'size' => 160755712,
'md5' => 'cf73576efc03685907c1fa49180ea388'
],
'virtio-win-0.1.100' => [
'name' => 'virtio-win-0.1.100.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.100/virtio-win-0.1.100.iso',
'size' => 160704512,
'md5' => '8b21136f988bef7981ee580e9101b6b4'
],
'virtio-win-0.1.96' => [
'name' => 'virtio-win-0.1.96.iso',
'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.96/virtio-win-0.1.96.iso',
'size' => 160659456,
'md5' => 'd406bf6748b9ba4c872c5b5301ba7272'
]
];
$domain_cfg_defaults = [
"SERVICE" => "disable",
"DEBUG" => "no",
"DOMAINDIR" => "/mnt/user/domains/",
"MEDIADIR" => "/mnt/user/isos/",
"VIRTIOISO" => "",
"BRNAME" => "",
"VMSTORAGEMODE" => "auto"
];
$domain_cfg = $domain_cfg_defaults;
// Create domain config if needed
$domain_cfgfile = "/boot/config/domain.cfg";
if (!file_exists($domain_cfgfile)) {
file_put_contents($domain_cfgfile, 'SERVICE="disable"'."\n".'DEBUG="no"'."\n".'MEDIADIR="/mnt/"'."\n".'VIRTIOISO=""'."\n".'DISKDIR="/mnt/"'."\n".'BRNAME=""'."\n");
$tmp = '';
foreach ($domain_cfg_defaults as $key => $value) $tmp .= "$key=\"$value\"\n";
file_put_contents($domain_cfgfile, $tmp);
} else {
// This will clean any ^M characters (\r) caused by windows from the config file
shell_exec("sed -i 's!\r!!g' '$domain_cfgfile'");
$domain_cfg_existing = parse_ini_file($domain_cfgfile);
if (!empty($domain_cfg_existing)) {
$domain_cfg = array_merge($domain_cfg_defaults, $domain_cfg_existing);
}
}
$domain_cfg = parse_ini_file($domain_cfgfile);
if (!isset($domain_cfg['VIRTIOISO'])) {
$domain_cfg['VIRTIOISO'] = "";
}
$domain_debug = isset($domain_cfg['DEBUG']) ? $domain_cfg['DEBUG'] : "no";
if ($domain_debug != "yes") {
if ($domain_cfg['DEBUG'] != "yes") {
error_reporting(0);
}
if (empty($domain_cfg['VMSTORAGEMODE'])) {
$domain_cfg['VMSTORAGEMODE'] = "auto";
}
if (!empty($domain_cfg['DOMAINDIR'])) {
$domain_cfg['DOMAINDIR'] = rtrim($domain_cfg['DOMAINDIR'], '/') . '/';
}
if (!empty($domain_cfg['MEDIADIR'])) {
$domain_cfg['MEDIADIR'] = rtrim($domain_cfg['MEDIADIR'], '/') . '/';
}
$domain_bridge = (!($domain_cfg['BRNAME'])) ? 'virbr0' : $domain_cfg['BRNAME'];
$msg = (empty($domain_bridge)) ? "Error: Setup Bridge in Settings/Network Settings" : false;
$libvirt_service = isset($domain_cfg['SERVICE']) ? $domain_cfg['SERVICE'] : "disable";
$libvirt_service = isset($domain_cfg['SERVICE']) ? $domain_cfg['SERVICE'] : "disable";
if ($libvirt_running == "yes"){
$lv = new Libvirt('qemu:///system', null, null, false);
@@ -53,55 +399,16 @@
$maxmem = number_format(($arrHostInfo['memory'] / 1048576), 1, '.', ' ');
}
$theme = $display['theme'];
//set color on even rows for white or black theme
function bcolor($row, $color) {
if ($color == "white")
$color = ($row % 2 == 0) ? "transparent" : "#F8F8F8";
else
$color = ($row % 2 == 0) ? "transparent" : "#0C0C0C";
return $color;
}
function bcolor($row) {
global $display;
//create checkboxes for usb devices
function usb_checkbox($usb, $key) {
$deviceid = substr(strstr($usb, 'ID'),3,9);
echo '<input class="checkbox" type="checkbox" value="'.$deviceid.'" name="usb['.$key.']" />';
echo "<label>$usb</label><br />";
}
if (empty($display) || $display['theme'] == "white")
return ($row % 2 == 0) ? "transparent" : "#F8F8F8";
//create memory drop down option based on max memory
function memOption($maxmem) {
for ($i = 1; $i <= ($maxmem*2); $i++) {
$mem = ($i*512);
echo "<option value='$mem'>$mem</option>";
}
return ($row % 2 == 0) ? "transparent" : "#0C0C0C";
}
//create drop down options from arrays
function arrayOptions($ValueArray, $DisplayArray, $value) {
for ($i = 0; $i < sizeof($ValueArray); $i++) {
echo "<option value='$ValueArray[$i]'";
if ($ValueArray[$i] == $value)
echo " selected='selected'>$DisplayArray[$i] *</option>";
else
echo ">$DisplayArray[$i]</option>";
}
}
//create memory drop down options
function memOptions($maxmem, $mem) {
for ($i = 1; $i <= ($maxmem*2); $i++) {
$mem2 = ($i*512);
echo "<option value=".$mem2*1024;
if ((int)$mem == $mem2*1024)
echo " selected='selected'>$mem2 *</option>";
else
echo ">$mem2</option>";
}
}
function mk_dropdown_options($arrOptions, $strSelected) {
foreach ($arrOptions as $key => $label) {
echo mk_option($strSelected, $key, $label);
@@ -120,6 +427,11 @@
return $abbreviation;
}
function getDiskImageInfo($strImgPath) {
$arrJSON = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($strImgPath) . " 2>/dev/null"), true);
return $arrJSON;
}
$cacheValidPCIDevices = null;
function getValidPCIDevices() {
@@ -177,6 +489,15 @@
continue;
}
// Attempt to get the current kernel-bound driver for this pci device
$strDriver = '';
if (is_link('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/driver')) {
$strLink = @readlink('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/driver');
if (!empty($strLink)) {
$strDriver = basename($strLink);
}
}
// Specialized vendor name cleanup
// e.g.: Advanced Micro Devices, Inc. [AMD/ATI] --> Advanced Micro Devices, Inc.
if (preg_match('/(?P<gpuvendor>.+) \[.+\]/', $arrMatch['vendorname'], $arrGPUMatch)) {
@@ -197,6 +518,7 @@
'productid' => $arrMatch['productid'],
'productname' => $arrMatch['productname'],
'class' => $strClass,
'driver' => $strDriver,
'name' => $arrMatch['vendorname'] . ' ' . $arrMatch['productname']
);
}
@@ -241,6 +563,17 @@
}
function getValidOtherStubbedDevices() {
$arrValidPCIDevices = getValidPCIDevices();
$arrValidOtherStubbedDevices = array_filter($arrValidPCIDevices, function($arrDev) {
return ($arrDev['class'] == 'other' && in_array($arrDev['driver'], ['pci-stub', 'vfio-pci']));
});
return $arrValidOtherStubbedDevices;
}
$cacheValidUSBDevices = null;
function getValidUSBDevices() {
global $cacheValidUSBDevices;
@@ -346,6 +679,29 @@
}
function getValidBusTypes() {
$arrValidBusTypes = [
'virtio' => 'VirtIO',
'sata' => 'SATA',
'ide' => 'IDE',
'usb' => 'USB'
];
return $arrValidBusTypes;
}
function getValidVNCModels() {
$arrValidVNCModels = [
'cirrus' => 'Cirrus',
'qxl' => 'QXL (best)',
'vmvga' => 'vmvga'
];
return $arrValidVNCModels;
}
function getValidKeyMaps() {
$arrValidKeyMaps = [
'ar' => 'Arabic (ar)',
@@ -414,6 +770,7 @@
function domain_to_config($uuid) {
global $lv;
global $domain_cfg;
$arrValidGPUDevices = getValidGPUDevices();
$arrValidAudioDevices = getValidAudioDevices();
@@ -436,7 +793,7 @@
$xmldoc = new DOMDocument();
$xmldoc->loadXML($strDOMXML);
$xpath = new DOMXPath($xmldoc);
$objNodes = $xpath->query('//domain/metadata/vmtemplate/@*');
$objNodes = $xpath->query('//domain/metadata/*[local-name()=\'vmtemplate\']/@*');
$arrTemplateValues = [];
if ($objNodes->length > 0) {
@@ -459,6 +816,7 @@
if (!empty($intVNCPort)) {
$arrGPUDevices[] = [
'id' => 'vnc',
'model' => $lv->domain_get_vnc_model($res),
'keymap' => $lv->domain_get_vnc_keymap($res)
];
}
@@ -500,10 +858,17 @@
'size' => '',
'driver' => 'raw',
'dev' => $disk['device'],
'bus' => $disk['bus']
'bus' => $disk['bus'],
'select' => $domain_cfg['VMSTORAGEMODE']
];
}
// HACK: If there's only 1 cdrom and the dev=hdb then it's most likely a VirtIO Driver ISO instead of the OS Install ISO
if (!empty($medias) && count($medias) == 1 && array_key_exists('device', $medias[0]) && $medias[0]['device'] == 'hdb') {
$medias[] = null;
$medias = array_reverse($medias);
}
return [
'template' => $arrTemplateValues,
'domain' => [
@@ -523,11 +888,14 @@
'hyperv' => ($lv->domain_get_feature($res, 'hyperv') ? 1 : 0),
'autostart' => ($lv->domain_get_autostart($res) ? 1 : 0),
'state' => $lv->domain_state_translate($dom['state']),
'ovmf' => ($lv->domain_get_ovmf($res) ? 1 : 0)
'ovmf' => ($lv->domain_get_ovmf($res) ? 1 : 0),
'usbmode' => ($lv->_get_single_xpath_result($res, '//domain/devices/controller[@model=\'nec-xhci\']') ? 'usb3' : 'usb2')
],
'media' => [
'cdrom' => (!empty($medias) && !empty($medias[0]) && array_key_exists('file', $medias[0])) ? $medias[0]['file'] : '',
'drivers' => (!empty($medias) && !empty($medias[1]) && array_key_exists('file', $medias[1])) ? $medias[1]['file'] : ''
'cdrombus' => (!empty($medias) && !empty($medias[0]) && array_key_exists('bus', $medias[0])) ? $medias[0]['bus'] : 'ide',
'drivers' => (!empty($medias) && !empty($medias[1]) && array_key_exists('file', $medias[1])) ? $medias[1]['file'] : '',
'driversbus' => (!empty($medias) && !empty($medias[1]) && array_key_exists('bus', $medias[1])) ? $medias[1]['bus'] : 'ide'
],
'disk' => $arrDisks,
'gpu' => $arrGPUDevices,

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -17,6 +17,14 @@ function getLibvirtSchema() {
"!value": ""
};
root.domain.maxMemory = {
"!attrs": {
slots: null,
unit: ["MiB", "KiB", "GiB"]
},
"!value": 512
};
root.domain.memory = {
"!attrs": {
unit: ["MiB", "KiB", "GiB"]
@@ -32,6 +40,15 @@ function getLibvirtSchema() {
};
root.domain.memoryBacking = {};
root.domain.memoryBacking.hugepages = {};
root.domain.memoryBacking.hugepages.page = {
"!attrs": {
size: [2, 1],
unit: ["M", "G", "K"],
nodeset: null
},
"!novalue": 1
};
root.domain.memoryBacking.nosharepages = {
"!novalue": 1
};
@@ -39,6 +56,24 @@ function getLibvirtSchema() {
"!novalue": 1
};
root.domain.numatune = {};
root.domain.numatune.memory = {
"!attrs": {
mode: ["strict", "preferred", "interleave"],
nodeset: null,
placement: ["static", "auto"]
},
"!novalue": 1
};
root.domain.numatune.memnode = {
"!attrs": {
cellid: null,
mode: ["strict", "preferred", "interleave"],
nodeset: null
},
"!novalue": 1
};
root.domain.vcpu = {
"!attrs": {
placement: ["static"]
@@ -51,14 +86,62 @@ function getLibvirtSchema() {
"!attrs": {
vcpu: null,
cpuset: null
}
},
"!novalue": 1
};
root.domain.cputune.emulatorpin = {
"!attrs": {
cpuset: null
},
"!novalue": 1
};
root.domain.cputune.iothreadpin = {
"!attrs": {
iothread: null,
cpuset: null
},
"!novalue": 1
};
root.domain.cputune.shares = {
"!value": null
};
root.domain.cputune.period = {
"!value": null
};
root.domain.cputune.quota = {
"!value": null
};
root.domain.cputune.emulator_period = {
"!value": null
};
root.domain.cputune.emulator_quota = {
"!value": null
};
root.domain.cputune.vcpusched = {
"!attrs": {
vcpus: null,
scheduler: ["batch", "idle", "fifo", "rr"],
priority: null
},
"!novalue": 1
};
root.domain.cputune.iothreadsched = {
"!attrs": {
iothreads: null,
scheduler: ["batch", "idle", "fifo", "rr"]
},
"!novalue": 1
};
root.domain.cpu = {
"!attrs": {
mode: ["host-passthrough"]
match: ["exact", "minimum", "strict"],
mode: ["host-passthrough", "host-model", "custom"]
}
};
root.domain.cpu.model = {
"!value": ""
};
root.domain.cpu.topology = {
"!attrs": {
sockets: null,
@@ -77,9 +160,13 @@ function getLibvirtSchema() {
};
root.domain.os.loader = {
"!attrs": {
readonly: ["yes"],
type: ["pflash"]
},
"!value": "/usr/share/qemu/ovmf-x64/OVMF-pure-efi.fd"
"!value": "/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd"
};
root.domain.os.nvram = {
"!value": "/etc/libvirt/qemu/nvram/{{UUID}}_VARS-pure-efi.fd"
};
root.domain.features = {};
@@ -150,9 +237,16 @@ function getLibvirtSchema() {
root.domain.devices.disk.driver = {
"!attrs": {
name: ["qemu"],
type: ["raw", "qcow2"],
cache: ["none"],
io: ["native"]
type: ["raw", "bochs", "qcow2", "qed"],
error_policy: ["report", "stop", "ignore", "enospace"],
rerror_policy: ["report", "stop", "ignore"],
cache: ["writeback", "default", "directsync", "none", "writethrough", "unsafe"],
io: ["native", "threads"],
ioeventfd: ["on", "off"],
event_idx: ["on", "off"],
copy_on_read: ["off", "on"],
discard: ["ignore", "unmap"],
iothread: null
}
};
root.domain.devices.disk.source = {
@@ -166,7 +260,7 @@ function getLibvirtSchema() {
root.domain.devices.disk.target = {
"!attrs": {
dev: null,
bus: ["ide", "sata", "virtio"]
bus: ["ide", "sata", "scsi", "virtio", "usb"]
}
};
root.domain.devices.disk.readonly = {

View File

@@ -98,7 +98,7 @@ function toggleRows(what, val, what2, onComplete) {
}
function updatePrefixLabels(category) {
$("#form_content table:data(category)").filter(function() {
$("#vmform table:data(category)").filter(function() {
return $(this).data('category') == category;
}).each(function (index) {
var oldprefix = $(this).data('prefix');
@@ -124,7 +124,7 @@ function updatePrefixLabels(category) {
}
function bindSectionEvents(category) {
var $Filtered = $("#form_content table:data(category)").filter(function(index) {
var $Filtered = $("#vmform table:data(category)").filter(function(index) {
return $(this).data('category') == category;
});
@@ -209,15 +209,21 @@ function clickAddSection() {
$el_showable = $template.find('tr').not("." + (isVMAdvancedMode() ? 'basic' : 'advanced'));
slideDownRows($el_showable);
$table.parent().trigger('spawn_section', [ $template, $template.data() ]);
}
function clickRemoveSection() {
var $table = $(this).closest('table');
var category = $table.data('category');
var $parent = $table.parent();
var tabledata = $table.data();
slideUpRows($table.find('tr'), function() {
slideUpRows($table, function() {
$table.detach();
$parent.trigger('destroy_section', [ $table, tabledata ]);
$table.remove();
updatePrefixLabels(category);
bindSectionEvents(category);
updatePrefixLabels(tabledata.category);
bindSectionEvents(tabledata.category);
});
}

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env php
<?php
if (!isset($argv[1])) {
exit(0);
}
# We simply use this script to replace any arguments containing a user share path (e.g. /mnt/user/domains/) with the real backing disk (e.g. /mnt/disk1/domains/).
# arguments will look something like this:
# -name Windows Server 2012 R2 -S -machine pc-i440fx-2.3,accel=kvm,usb=off,mem-merge=off -cpu host,hv_time,hv_relaxed,hv_vapic,hv_spinlocks=0x1fff -m 4096 -realtime mlock=on -smp 2,sockets=1,cores=2,threads=1 -uuid 5c0a8b66-a09b-63f1-7835-7f538acee978 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/Windows Server 2012 R2.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=localtime -no-hpet -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x4 -drive file=/mnt/cache/domains/kvm/win2012r2/win2012r2.qcow2,if=none,id=drive-virtio-disk2,format=qcow2,cache=writeback -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x5,drive=drive-virtio-disk2,id=virtio-disk2,bootindex=1 -drive file=/mnt/user/isos/virtio-win-0.1.109-1.iso,if=none,id=drive-ide0-0-1,readonly=on,format=raw -device ide-cd,bus=ide.0,unit=1,drive=drive-ide0-0-1,id=ide0-0-1 -netdev tap,fd=22,id=hostnet0,vhost=on,vhostfd=23 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:de:f0:95,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/channel/target/Windows Server 2012 R2.org.qemu.guest_agent.0,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 -device usb-tablet,id=input0 -vnc 0.0.0.0:0,websocket=5700 -k en-us -device vmware-svga,id=video0,vgamem_mb=16,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x6 -msg timestamp=on
function detect_user_share(&$arg) {
$arg = preg_replace_callback('|(/mnt/user/[^, ]+)|', function($match) {
if (is_file($match[0])) {
// resolve the actual disk or cache backing device for this user share path
$realdisk = trim(shell_exec("getfattr --absolute-names -n user.LOCATIONS ".escapeshellarg($match[0])." 2>/dev/null|grep -Po '^user.LOCATIONS=\"\K[^\\\"]+'"));
// replace 'user' with say 'cache' or 'disk1' etc
$replacement = str_replace('/mnt/user/', '/mnt/'.$realdisk.'/', $match[0]);
if (!empty($realdisk) && is_file($replacement)) {
// the replacement path (e.g. /mnt/disk1/domains/vmname/vdisk1.img) checks out so use it
return $replacement;
}
}
return $match[0];
}, $arg);
};
array_shift($argv);
array_walk($argv, 'detect_user_share');
$whole_cmd = '';
foreach ($argv as $arg) {
$whole_cmd .= escapeshellarg($arg).' ';
}
echo trim($whole_cmd);

View File

@@ -0,0 +1,3 @@
#!/bin/bash
eval exec /usr/bin/qemu-system-x86_64 $(/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/qemu.php "$@")

View File

@@ -21,66 +21,55 @@
$arrValidOtherDevices = getValidOtherDevices();
$arrValidUSBDevices = getValidUSBDevices();
$arrValidDiskDrivers = getValidDiskDrivers();
$arrValidBusTypes = getValidBusTypes();
$arrValidVNCModels = getValidVNCModels();
$arrValidKeyMaps = getValidKeyMaps();
$arrValidBridges = getNetworkBridges();
$strCPUModel = getHostCPUModel();
$arrOperatingSystems = [
'windows' => 'Windows 8.1 / 2012',
'windows7' => 'Windows 7 / 2008',
'windowsxp' => 'Windows XP / 2003',
'linux' => 'Linux',
'arch' => 'Arch',
'centos' => 'CentOS',
'chromeos' => 'ChromeOS',
'coreos' => 'CoreOS',
'debian' => 'Debian',
'fedora' => 'Fedora',
'freebsd' => 'FreeBSD',
'openelec' => 'OpenELEC',
'opensuse' => 'OpenSUSE',
'redhat' => 'RedHat',
'scientific' => 'Scientific',
'slackware' => 'Slackware',
'steamos' => 'SteamOS',
'ubuntu' => 'Ubuntu'
];
$arrConfigDefaults = [
'template' => [
'name' => 'Custom',
'icon' => 'windows.png'
'name' => $strSelectedTemplate,
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
'os' => $arrAllTemplates[$strSelectedTemplate]['os']
],
'domain' => [
'name' => $strSelectedTemplate,
'persistent' => 1,
'uuid' => $lv->domain_generate_uuid(),
'clock' => 'localtime',
'arch' => 'x86_64',
'machine' => 'pc',
'mem' => 512 * 1024,
'maxmem' => 512 * 1024,
'mem' => 1024 * 1024,
'maxmem' => 1024 * 1024,
'password' => '',
'cpumode' => 'host-passthrough',
'vcpus' => 1,
'vcpu' => [0],
'hyperv' => 1,
'ovmf' => 0
'ovmf' => 1,
'usbmode' => 'usb3'
],
'media' => [
'cdrom' => '',
'drivers' => $domain_cfg['VIRTIOISO']
'cdrombus' => 'ide',
'drivers' => is_file($domain_cfg['VIRTIOISO']) ? $domain_cfg['VIRTIOISO'] : '',
'driversbus' => 'ide'
],
'disk' => [
[
'new' => '',
'size' => '',
'driver' => 'raw',
'dev' => 'hda'
'dev' => 'hda',
'select' => $domain_cfg['VMSTORAGEMODE'],
'bus' => 'sata'
]
],
'gpu' => [
[
'id' => 'vnc',
'model' => 'qxl',
'keymap' => 'en-us'
]
],
@@ -105,6 +94,11 @@
]
];
// Merge in any default values from the VM template
if (!empty($arrAllTemplates[$strSelectedTemplate]) && !empty($arrAllTemplates[$strSelectedTemplate]['overrides'])) {
$arrConfigDefaults = array_replace_recursive($arrConfigDefaults, $arrAllTemplates[$strSelectedTemplate]['overrides']);
}
// If we are editing a existing VM load it's existing configuration details
$arrExistingConfig = (!empty($_GET['uuid']) ? domain_to_config($_GET['uuid']) : []);
@@ -116,6 +110,7 @@
$arrConfig['template']['os'] = ($arrConfig['domain']['clock'] == 'localtime' ? 'windows' : 'linux');
}
$boolNew = empty($arrExistingConfig);
$boolRunning = (!empty($arrConfig['domain']['state']) && $arrConfig['domain']['state'] == 'running');
@@ -131,9 +126,9 @@
$arrResponse = ['success' => true];
// Fire off the vnc popup if available
$res = $lv->get_domain_by_name($_POST['domain']['name']);
$vncport = $lv->domain_get_vnc_port($res);
$wsport = $lv->domain_get_ws_port($res);
$dom = $lv->get_domain_by_name($_POST['domain']['name']);
$vncport = $lv->domain_get_vnc_port($dom);
$wsport = $lv->domain_get_ws_port($dom);
if ($vncport > 0) {
$vnc = '/plugins/dynamix.vm.manager/vnc.html?autoconnect=true&host='.$var['IPADDR'].'&port='.$wsport;
@@ -153,17 +148,40 @@
// Backup xml for existing domain in ram
$strOldXML = '';
$boolOldAutoStart = false;
$res = $lv->domain_get_name_by_uuid($_POST['domain']['uuid']);
if ($res) {
$strOldXML = $lv->domain_get_xml($res);
$boolOldAutoStart = $lv->domain_get_autostart($res);
$dom = $lv->domain_get_domain_by_uuid($_POST['domain']['uuid']);
if ($dom) {
$strOldXML = $lv->domain_get_xml($dom);
$boolOldAutoStart = $lv->domain_get_autostart($dom);
$strOldName = $lv->domain_get_name($dom);
$strNewName = $_POST['domain']['name'];
if (!empty($strOldName) &&
!empty($strNewName) &&
is_dir($domain_cfg['DOMAINDIR'].$strOldName.'/') &&
!is_dir($domain_cfg['DOMAINDIR'].$strNewName.'/')) {
// mv domain/vmname folder
if (rename($domain_cfg['DOMAINDIR'].$strOldName, $domain_cfg['DOMAINDIR'].$strNewName)) {
// replace all disk paths in xml
foreach ($_POST['disk'] as &$arrDisk) {
if (!empty($arrDisk['new'])) {
$arrDisk['new'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['new']);
}
if (!empty($arrDisk['image'])) {
$arrDisk['image'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['image']);
}
}
}
}
//DEBUG
file_put_contents('/tmp/debug_libvirt_oldxml.xml', $strOldXML);
}
// Remove existing domain
$lv->domain_undefine($res);
$lv->nvram_backup($_POST['domain']['uuid']);
$lv->domain_undefine($dom);
$lv->nvram_restore($_POST['domain']['uuid']);
// Save new domain
$tmp = $lv->domain_new($_POST);
@@ -186,43 +204,35 @@
}
?>
<style type="text/css">
.four label {
float: left;
display: table-cell;
width: 25%;
}
.four label:nth-child(4n+4) {
float: none;
clear: both;
}
.mac_generate {
cursor: pointer;
margin-left: -8px;
color: #08C;
font-size: 1.1em;
}
</style>
<input type="hidden" name="template[os]" id="template_os" value="<?=$arrConfig['template']['os']?>">
<input type="hidden" name="domain[persistent]" value="<?=$arrConfig['domain']['persistent']?>">
<input type="hidden" name="domain[uuid]" value="<?=$arrConfig['domain']['uuid']?>">
<input type="hidden" name="domain[clock]" id="domain_clock" value="<?=$arrConfig['domain']['clock']?>">
<input type="hidden" name="domain[arch]" value="<?=$arrConfig['domain']['arch']?>">
<input type="hidden" name="domain[oldname]" id="domain_oldname" value="<?=htmlentities($arrConfig['domain']['name'])?>">
<table>
<tr>
<td>Operating System:</td>
<td>
<select name="template[os]" id="domain_os" class="narrow" title="define the base OS">
<?php mk_dropdown_options($arrOperatingSystems, $arrConfig['template']['os']); ?>
</select>
</td>
<td>Name:</td>
<td><input type="text" name="domain[name]" id="domain_name" class="textTemplate" title="Name of virtual machine" placeholder="e.g. My Workstation" value="<?=htmlentities($arrConfig['domain']['name'])?>" required /></td>
</tr>
</table>
<blockquote class="inline_help">
<p>Select Windows for any Microsoft operating systems</p>
<p>Give the VM a name (e.g. Work, Gaming, Media Player, Firewall, Bitcoin Miner)</p>
</blockquote>
<table>
<tr class="advanced">
<td>Description:</td>
<td><input type="text" name="domain[desc]" title="description of virtual machine" placeholder="description of virtual machine (optional)" value="<?=htmlentities($arrConfig['domain']['desc'])?>" /></td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>Give the VM a brief description (optional field).</p>
</blockquote>
</div>
<table>
<tr class="advanced">
<td>CPU Mode:</td>
@@ -268,15 +278,13 @@
</td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>By default, VMs created will be pinned to physical CPU cores to improve performance. From this view, you can adjust which actual CPU cores a VM will be pinned (minimum 1).</p>
</blockquote>
</div>
<blockquote class="inline_help">
<p>By default, VMs created will be pinned to physical CPU cores to improve performance. From this view, you can adjust which actual CPU cores a VM will be pinned (minimum 1).</p>
</blockquote>
<table>
<tr>
<td>Initial Memory:</td>
<td><span class="advanced">Initial </span>Memory:</td>
<td>
<select name="domain[mem]" id="domain_mem" class="narrow" title="define the amount memory">
<?php
@@ -346,7 +354,7 @@
<?php
echo mk_option($arrConfig['domain']['ovmf'], '0', 'SeaBIOS');
if (file_exists('/usr/share/qemu/ovmf-x64/OVMF-pure-efi.fd')) {
if (file_exists('/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd')) {
echo mk_option($arrConfig['domain']['ovmf'], '1', 'OVMF');
} else {
echo mk_option('', '0', 'OVMF (Not Available)', 'disabled="disabled"');
@@ -388,7 +396,7 @@
<div class="domain_os windows">
<div class="advanced">
<blockquote class="inline_help">
<p>Exposes the guest to hyper-v extensions for Microsoft operating systems. Set to "Yes" by default, but set to "No" automatically if an NVIDIA-based GPU is assigned to the guest (but can be user-toggled back to "Yes").</p>
<p>Exposes the guest to hyper-v extensions for Microsoft operating systems.</p>
</blockquote>
</div>
</div>
@@ -400,29 +408,52 @@
<input type="text" data-pickcloseonfile="true" data-pickfilter="iso" data-pickroot="<?=$domain_cfg['MEDIADIR']?>" name="media[cdrom]" value="<?=$arrConfig['media']['cdrom']?>" placeholder="Click and Select cdrom image to install operating system">
</td>
</tr>
<tr class="advanced">
<td>OS Install CDRom Bus:</td>
<td>
<select name="media[cdrombus]" class="cdrom_bus narrow">
<?php mk_dropdown_options($arrValidBusTypes, $arrConfig['media']['cdrombus']); ?>
</select>
</td>
</tr>
</table>
<blockquote class="inline_help">
<p>Select the virtual CD-ROM (ISO) that contains the installation media for your operating system. Clicking this field displays a list of ISOs found in the directory specified on the Settings page.</p>
<p class="advanced">
<b>CDRom Bus</b><br>
Specify what interface this virtual cdrom uses to connect inside the VM.
</p>
</blockquote>
<table class="domain_os windows">
<tr class="advanced">
<td><a href="https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download" target="_blank">VirtIO Drivers ISO:</a></td>
<td>VirtIO Drivers ISO:</td>
<td>
<input type="text" data-pickcloseonfile="true" data-pickfilter="iso" data-pickroot="<?=$domain_cfg['MEDIADIR']?>" name="media[drivers]" value="<?=$arrConfig['media']['drivers']?>" placeholder="Download, Click and Select virtio drivers image">
</td>
</tr>
<tr class="advanced">
<td>VirtIO Drivers CDRom Bus:</td>
<td>
<select name="media[driversbus]" class="cdrom_bus narrow">
<?php mk_dropdown_options($arrValidBusTypes, $arrConfig['media']['driversbus']); ?>
</select>
</td>
</tr>
</table>
<div class="domain_os windows">
<div class="advanced">
<blockquote class="inline_help">
<p>Specify the virtual CD-ROM (ISO) that contains the VirtIO Windows drivers as provided by the Fedora Project. Download the latest ISO from here: <a href="https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download" target="_blank">https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download</a></p>
<p>When installing Windows, you will reach a step where no disk devices will be found. There is an option to browse for drivers on that screen. Click browse and locate the additional CD-ROM in the menu. Inside there will be various folders for the different versions of Windows. Open the folder for the version of Windows you are installing and then select the AMD64 subfolder inside (even if you are on an Intel system, select AMD64). Three drivers will be found. Select them all, click next, and the vDisks you have assigned will appear.</p>
<p>
<b>CDRom Bus</b><br>
Specify what interface this virtual cdrom uses to connect inside the VM.
</p>
</blockquote>
</div>
</div>
<? foreach ($arrConfig['disk'] as $i => $arrDisk) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : 'Primary';
@@ -431,14 +462,87 @@
<tr>
<td>vDisk Location:</td>
<td>
<input type="text" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickroot="/mnt/" name="disk[<?=$i?>][new]" class="disk" id="disk_<?=$i?>" value="<?=$arrDisk['new']?>" placeholder="Separate sub-folder and image will be created based on Name">
<select name="disk[<?=$i?>][select]" class="disk_select narrow">
<?
if ($i == 0) {
echo '<option value="">None</option>';
}
$default_option = $arrDisk['select'];
if (!empty($domain_cfg['DOMAINDIR']) && file_exists($domain_cfg['DOMAINDIR'])) {
$boolShowAllDisks = (strpos($domain_cfg['DOMAINDIR'], '/mnt/user/') === 0);
if (!empty($arrDisk['new'])) {
if (strpos($domain_cfg['DOMAINDIR'], dirname(dirname($arrDisk['new']))) === false || basename($arrDisk['new']) != 'vdisk'.($i+1).'.img') {
$default_option = 'manual';
}
if (file_exists(dirname(dirname($arrDisk['new'])).'/'.$arrConfig['domain']['name'].'/vdisk'.($i+1).'.img')) {
// hide all the disks because the auto disk already has been created
$boolShowAllDisks = false;
}
}
echo mk_option($default_option, 'auto', 'Auto');
if ($boolShowAllDisks) {
$strShareUserLocalInclude = '';
$strShareUserLocalExclude = '';
$strShareUserLocalUseCache = 'no';
// Get the share name and its configuration
$arrDomainDirParts = explode('/', $domain_cfg['DOMAINDIR']);
$strShareName = $arrDomainDirParts[3];
if (!empty($strShareName) && is_file('/boot/config/shares/'.$strShareName.'.cfg')) {
$arrShareCfg = parse_ini_file('/boot/config/shares/'.$strShareName.'.cfg');
if (!empty($arrShareCfg['shareInclude'])) {
$strShareUserLocalInclude = $arrShareCfg['shareInclude'];
}
if (!empty($arrShareCfg['shareExclude'])) {
$strShareUserLocalExclude = $arrShareCfg['shareExclude'];
}
if (!empty($arrShareCfg['shareUseCache'])) {
$strShareUserLocalUseCache = $arrShareCfg['shareUseCache'];
}
}
// Determine if cache drive is available:
if (!empty($disks['cache']) && (!empty($disks['cache']['device']))) {
if ($strShareUserLocalUseCache != 'no' && $var['shareCacheEnabled'] == 'yes') {
$strLabel = my_disk('cache').' - '.my_scale($disks['cache']['fsFree']*1024, $strUnit).' '.$strUnit.' free';
echo mk_option($default_option, 'cache', $strLabel);
}
}
// Determine which disks from the array are available for this share:
foreach ($disks as $name => $disk) {
if ((strpos($name, 'disk') === 0) && (!empty($disk['device']))) {
if ((!empty($strShareUserLocalInclude) && (strpos($strShareUserLocalInclude.',', $name.',') === false)) ||
(!empty($strShareUserLocalExclude) && (strpos($strShareUserLocalExclude.',', $name.',') !== false)) ||
(!empty($var['shareUserInclude']) && (strpos($var['shareUserInclude'].',', $name.',') === false)) ||
(!empty($var['shareUserExclude']) && (strpos($var['shareUserExclude'].',', $name.',') !== false))) {
// skip this disk based on local and global share settings
continue;
}
$strLabel = my_disk($name).' - '.my_scale($disk['fsFree']*1024, $strUnit).' '.$strUnit.' free';
echo mk_option($default_option, $name, $strLabel);
}
}
}
}
echo mk_option($default_option, 'manual', 'Manual');
?>
</select><input type="text" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickroot="/mnt/" name="disk[<?=$i?>][new]" class="disk" id="disk_<?=$i?>" value="<?=$arrDisk['new']?>" placeholder="Separate sub-folder and image will be created based on Name"><div class="disk_preview"></div>
</td>
</tr>
<tr class="disk_file_options">
<td>vDisk Size:</td>
<td>
<input type="text" name="disk[<?=$i?>][size]" value="<?=$arrDisk['size']?>" placeholder="e.g. 10M, 1G, 10G...">
<input type="text" name="disk[<?=$i?>][size]" value="<?=$arrDisk['size']?>" class="narrow" placeholder="e.g. 10M, 1G, 10G...">
</td>
</tr>
@@ -451,7 +555,14 @@
</td>
</tr>
<input type="hidden" name="disk[<?=$i?>][dev]" value="<?=$arrDisk['dev']?>">
<tr class="advanced disk_bus_options">
<td>vDisk Bus:</td>
<td>
<select name="disk[<?=$i?>][bus]" class="disk_bus narrow">
<?php mk_dropdown_options($arrValidBusTypes, $arrDisk['bus']); ?>
</select>
</td>
</tr>
</table>
<?php if ($i == 0) { ?>
<blockquote class="inline_help">
@@ -470,6 +581,11 @@
Select RAW for best performance. QCOW2 implementation is still in development.
</p>
<p class="advanced">
<b>vDisk Bus</b><br>
Select virtio for best performance.
</p>
<p>Additional devices can be added/removed by clicking the symbols to the left.</p>
</blockquote>
<? } ?>
@@ -479,14 +595,71 @@
<tr>
<td>vDisk Location:</td>
<td>
<input type="text" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickroot="/mnt/" name="disk[{{INDEX}}][new]" class="disk" id="disk_{{INDEX}}" value="" placeholder="Separate sub-folder and image will be created based on Name">
<select name="disk[{{INDEX}}][select]" class="disk_select narrow">
<?
if (!empty($domain_cfg['DOMAINDIR']) && file_exists($domain_cfg['DOMAINDIR'])) {
$default_option = $domain_cfg['VMSTORAGEMODE'];
echo mk_option($default_option, 'auto', 'Auto');
if (strpos($domain_cfg['DOMAINDIR'], '/mnt/user/') === 0) {
$strShareUserLocalInclude = '';
$strShareUserLocalExclude = '';
$strShareUserLocalUseCache = 'no';
// Get the share name and its configuration
$arrDomainDirParts = explode('/', $domain_cfg['DOMAINDIR']);
$strShareName = $arrDomainDirParts[3];
if (!empty($strShareName) && is_file('/boot/config/shares/'.$strShareName.'.cfg')) {
$arrShareCfg = parse_ini_file('/boot/config/shares/'.$strShareName.'.cfg');
if (!empty($arrShareCfg['shareInclude'])) {
$strShareUserLocalInclude = $arrShareCfg['shareInclude'];
}
if (!empty($arrShareCfg['shareExclude'])) {
$strShareUserLocalExclude = $arrShareCfg['shareExclude'];
}
if (!empty($arrShareCfg['shareUseCache'])) {
$strShareUserLocalUseCache = $arrShareCfg['shareUseCache'];
}
}
// Determine if cache drive is available:
if (!empty($disks['cache']) && (!empty($disks['cache']['device']))) {
if ($strShareUserLocalUseCache != 'no' && $var['shareCacheEnabled'] == 'yes') {
$strLabel = my_disk('cache').' - '.my_scale($disks['cache']['fsFree']*1024, $strUnit).' '.$strUnit.' free';
echo mk_option($default_option, 'cache', $strLabel);
}
}
// Determine which disks from the array are available for this share:
foreach ($disks as $name => $disk) {
if ((strpos($name, 'disk') === 0) && (!empty($disk['device']))) {
if ((!empty($strShareUserLocalInclude) && (strpos($strShareUserLocalInclude.',', $name.',') === false)) ||
(!empty($strShareUserLocalExclude) && (strpos($strShareUserLocalExclude.',', $name.',') !== false)) ||
(!empty($var['shareUserInclude']) && (strpos($var['shareUserInclude'].',', $name.',') === false)) ||
(!empty($var['shareUserExclude']) && (strpos($var['shareUserExclude'].',', $name.',') !== false))) {
// skip this disk based on local and global share settings
continue;
}
$strLabel = my_disk($name).' - '.my_scale($disk['fsFree']*1024, $strUnit).' '.$strUnit.' free';
echo mk_option($default_option, $name, $strLabel);
}
}
}
}
echo mk_option('', 'manual', 'Manual');
?>
</select><input type="text" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickroot="/mnt/" name="disk[{{INDEX}}][new]" class="disk" id="disk_{{INDEX}}" value="" placeholder="Separate sub-folder and image will be created based on Name"><div class="disk_preview"></div>
</td>
</tr>
<tr class="disk_file_options">
<td>vDisk Size:</td>
<td>
<input type="text" name="disk[{{INDEX}}][size]" value="" placeholder="e.g. 10M, 1G, 10G...">
<input type="text" name="disk[{{INDEX}}][size]" value="" class="narrow" placeholder="e.g. 10M, 1G, 10G...">
</td>
</tr>
@@ -499,11 +672,17 @@
</td>
</tr>
<input type="hidden" name="disk[{{INDEX}}][dev]" value="">
<tr class="advanced disk_bus_options">
<td>vDisk Bus:</td>
<td>
<select name="disk[{{INDEX}}][bus]" class="disk_bus narrow">
<?php mk_dropdown_options($arrValidBusTypes, ''); ?>
</select>
</td>
</tr>
</table>
</script>
<? foreach ($arrConfig['shares'] as $i => $arrShare) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -561,7 +740,6 @@
</table>
</script>
<? foreach ($arrConfig['gpu'] as $i => $arrGPU) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -588,9 +766,17 @@
</tr>
<? if ($i == 0) { ?>
<tr class="advanced vncmodel">
<td>VNC Video Driver:</td>
<td>
<select id="vncmodel" name="gpu[<?=$i?>][model]" class="narrow" title="video for VNC">
<?php mk_dropdown_options($arrValidVNCModels, $arrGPU['model']); ?>
</select>
</td>
</tr>
<tr class="vncpassword">
<td>VNC Password:</td>
<td><input type="password" name="domain[password]" title="password for VNC" placeholder="Password for VNC (optional)" /></td>
<td><input type="password" name="domain[password]" title="password for VNC" class="narrow" placeholder="Password for VNC (optional)" /></td>
</tr>
<tr class="advanced vnckeymap">
<td>VNC Keyboard:</td>
@@ -609,6 +795,11 @@
If you wish to assign a graphics card to the VM, select it from this list, otherwise leave it set to VNC.
</p>
<p class="advanced vncmodel">
<b>VNC Video Driver</b><br>
If you wish to assign a different video driver to use for a VNC connection, specify one here.
</p>
<p class="vncpassword">
<b>VNC Password</b><br>
If you wish to require a password to connect to the VM over a VNC connection, specify one here.
@@ -642,7 +833,6 @@
</table>
</script>
<? foreach ($arrConfig['audio'] as $i => $arrAudio) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -687,7 +877,6 @@
</table>
</script>
<? foreach ($arrConfig['nic'] as $i => $arrNic) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -755,7 +944,6 @@
</table>
</script>
<table>
<tr>
<td>USB Devices:</td>
@@ -781,12 +969,70 @@
NOTE: USB hotplug support is not yet implemented, so devices must be attached before the VM is started to use them.</p>
</blockquote>
<table>
<tr class="advanced">
<td>USB Mode:</td>
<td>
<select name="domain[usbmode]" id="usbmode" class="narrow" title="Select the USB Mode to emulate. Some OSes won't support USB3 (e.g. Windows 7/XP)">
<?php
echo mk_option($arrConfig['domain']['usbmode'], 'usb2', '2.0 (EHCI)');
echo mk_option($arrConfig['domain']['usbmode'], 'usb3', '3.0 (XHCI)');
?>
</select>
</td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>
<b>USB Mode</b><br>
Select the USB Mode to emulate. Some OSes won't support USB3 (e.g. Windows 7).
</p>
</blockquote>
</div>
<table>
<tr>
<td>Other PCI Devices:</td>
<td>
<div class="textarea" style="width: 540px">
<?
$intAvailableOtherPCIDevices = 0;
if (!empty($arrValidOtherDevices)) {
foreach($arrValidOtherDevices as $i => $arrDev) {
$extra = '';
if (count(array_filter($arrConfig['pci'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) {
$extra .= ' checked="checked"';
} else if (!in_array($arrDev['driver'], ['pci-stub', 'vfio-pci'])) {
//$extra .= ' disabled="disabled"';
continue;
}
$intAvailableOtherPCIDevices++;
?>
<label for="pci<?=$i?>"><input type="checkbox" name="pci[]" id="pci<?=$i?>" value="<?=$arrDev['id']?>" <?=$extra?>/> <?=$arrDev['name']?> | <?=$arrDev['type']?><span class="advanced"> | <?=$arrDev['id']?></span></label><br/>
<?
}
}
if (empty($intAvailableOtherPCIDevices)) {
echo "<i>None available</i>";
}
?>
</div>
</td>
</tr>
</table>
<blockquote class="inline_help">
<p>If you wish to assign any other PCI devices to your guest, you can select them from this list.</p>
</blockquote>
<table>
<tr>
<td></td>
<td>
<? if (!$boolRunning) { ?>
<? if (!empty($arrConfig['domain']['name'])) { ?>
<? if (!$boolNew) { ?>
<input type="hidden" name="updatevm" value="1" />
<input type="button" value="Update" busyvalue="Updating..." readyvalue="Update" id="btnSubmit" />
<? } else { ?>
@@ -806,70 +1052,162 @@
<p>Click Create to generate the vDisks and return to the Virtual Machines page where your new VM will be created.</p>
</blockquote>
<script type="text/javascript">
var OS2ImageMap = {<?php
$arrItems = array();
foreach ($arrOperatingSystems as $key => $value) {
$arrItems[] = "'$key':'{$key}.png'";
}
echo implode(',', $arrItems);
?>};
$(function() {
var initComplete = false;
var regenerateDiskPreview = function (disk_index) {
var domaindir = '<?=$domain_cfg['DOMAINDIR']?>' + $('#domain_oldname').val();
var tl_args = arguments.length;
$("#form_content #domain_mem").change(function changeMemEvent() {
$("#domain_maxmem").val($(this).val());
$("#vmform .disk").closest('table').each(function (index) {
var $table = $(this);
if (tl_args && disk_index != $table.data('index')) {
return;
}
var disk_select = $table.find(".disk_select option:selected").val();
var $disk_file_sections = $table.find('.disk_file_options');
var $disk_bus_sections = $table.find('.disk_bus_options');
var $disk_input = $table.find('.disk');
var $disk_preview = $table.find('.disk_preview');
if (disk_select == 'manual') {
// Manual disk
$disk_preview.fadeOut('fast', function() {
$disk_input.fadeIn('fast');
});
$disk_bus_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($disk_bus_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$.getJSON("/plugins/dynamix.vm.manager/VMajax.php?action=file-info&file=" + encodeURIComponent($disk_input.val()), function( info ) {
if (info.isfile || info.isblock) {
slideUpRows($disk_file_sections);
$disk_file_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
$disk_input.attr('name', $disk_input.attr('name').replace('new', 'image'));
} else {
$disk_file_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($disk_file_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$disk_input.attr('name', $disk_input.attr('name').replace('image', 'new'));
}
});
} else if (disk_select !== '') {
// Auto disk
var auto_disk_path = domaindir + '/vdisk' + (index+1) + '.img';
$disk_preview.html(auto_disk_path);
$disk_input.fadeOut('fast', function() {
$disk_preview.fadeIn('fast');
});
$disk_bus_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($disk_bus_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$.getJSON("/plugins/dynamix.vm.manager/VMajax.php?action=file-info&file=" + encodeURIComponent(auto_disk_path), function( info ) {
if (info.isfile || info.isblock) {
slideUpRows($disk_file_sections);
$disk_file_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
$disk_input.attr('name', $disk_input.attr('name').replace('new', 'image'));
} else {
$disk_file_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($disk_file_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$disk_input.attr('name', $disk_input.attr('name').replace('image', 'new'));
}
});
} else {
// No disk
var $hide_el = $table.find('.disk_bus_options,.disk_file_options,.disk_preview,.disk');
$disk_preview.html('');
slideUpRows($hide_el);
$hide_el.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
}
});
};
<?if ($boolNew):?>
$("#vmform #domain_name").on("input change", function changeNameEvent() {
$('#vmform #domain_oldname').val($(this).val());
regenerateDiskPreview();
});
<?endif?>
$("#form_content .domain_vcpu").change(function changeVCPUEvent() {
var $cores = $(".domain_vcpu:checked");
$("#vmform .domain_vcpu").change(function changeVCPUEvent() {
var $cores = $("#vmform .domain_vcpu:checked");
if ($cores.length == 1) {
$cores.prop("disabled", true);
} else {
$(".domain_vcpu").prop("disabled", false);
$("#vmform .domain_vcpu").prop("disabled", false);
}
});
$("#form_content #domain_maxmem").change(function changeMaxMemEvent() {
if (parseFloat($(this).val()) < parseFloat($("#domain_mem").val())) {
$("#domain_mem").val($(this).val());
$("#vmform #domain_mem").change(function changeMemEvent() {
$("#vmform #domain_maxmem").val($(this).val());
});
$("#vmform #domain_maxmem").change(function changeMaxMemEvent() {
if (parseFloat($(this).val()) < parseFloat($("#vmform #domain_mem").val())) {
$("#vmform #domain_mem").val($(this).val());
}
});
$("#form_content").on("change keyup", ".disk", function changeDiskEvent() {
$("#vmform #domain_machine").change(function changeMachineEvent() {
// Cdrom Bus: select IDE for i440 and SATA for q35
if ($(this).val().indexOf('i440fx') != -1) {
$('#vmform .cdrom_bus').val('ide');
} else {
$('#vmform .cdrom_bus').val('sata');
}
});
$("#vmform #domain_ovmf").change(function changeBIOSEvent() {
// using OVMF - disable vmvga vnc option
if ($(this).val() == '1' && $("#vmform #vncmodel").val() == 'vmvga') {
$("#vmform #vncmodel").val('qxl');
}
$("#vmform #vncmodel option[value='vmvga']").prop('disabled', ($(this).val() == '1'));
}).change(); // fire event now
$("#vmform").on("spawn_section", function spawnSectionEvent(evt, section, sectiondata) {
if (sectiondata.category == 'vDisk') {
regenerateDiskPreview(sectiondata.index);
}
});
$("#vmform").on("destroy_section", function destroySectionEvent(evt, section, sectiondata) {
if (sectiondata.category == 'vDisk') {
regenerateDiskPreview();
}
});
$("#vmform").on("change", ".disk_select", function changeDiskSelectEvent() {
regenerateDiskPreview($(this).closest('table').data('index'));
});
$("#vmform").on("input change", ".disk", function changeDiskEvent() {
var $input = $(this);
var config = $input.data();
if (config.hasOwnProperty('pickfilter')) {
var $other_sections = $input.closest('table').find('.disk_file_options');
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=file-info&file=" + encodeURIComponent($input.val()), function( info ) {
if (info.isfile || info.isblock) {
slideUpRows($other_sections);
$other_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
$input.attr('name', $input.attr('name').replace('new', 'image'));
} else {
$other_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($other_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$input.attr('name', $input.attr('name').replace('image', 'new'));
}
}, "json");
regenerateDiskPreview($input.closest('table').data('index'));
}
});
$("#form_content").on("change", ".gpu", function changeGPUEvent() {
$("#vmform").on("change", ".gpu", function changeGPUEvent() {
var myvalue = $(this).val();
var mylabel = $(this).children('option:selected').text();
$vnc_sections = $('.vncpassword,.vnckeymap');
if ($(".gpu option[value='vnc']:selected").length) {
$vnc_sections = $('.vncmodel,.vncpassword,.vnckeymap');
if ($("#vmform .gpu option[value='vnc']:selected").length) {
$vnc_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($vnc_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
} else {
@@ -877,35 +1215,34 @@ $(function() {
$vnc_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
}
if (mylabel.indexOf('NVIDIA ') > -1) {
$("#hyperv").val(0).change();
}
$(".gpu").not(this).each(function () {
$("#vmform .gpu").not(this).each(function () {
if (myvalue == $(this).val()) {
$(this).prop("selectedIndex", 0).change();
}
});
});
$("#form_content input[data-pickroot]").fileTreeAttach();
$("#form_content").on("click", ".mac_generate", function generateMac() {
$("#vmform").on("click", ".mac_generate", function generateMac() {
var $input = $(this).prev('input');
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=generate-mac", function( data ) {
$.getJSON("/plugins/dynamix.vm.manager/VMajax.php?action=generate-mac", function( data ) {
if (data.mac) {
$input.val(data.mac);
}
}, "json");
});
});
$("#form_content #btnSubmit").click(function frmSubmit() {
$("#vmform #btnSubmit").click(function frmSubmit() {
var $button = $(this);
var $form = $('#domain_template').closest('form');
var $form = $button.closest('form');
//TODO: form validation
$("#vmform .disk_select option:selected").not("[value='manual']").closest('table').each(function () {
var v = $(this).find('.disk_preview').html();
$(this).find('.disk').val(v);
});
$form.find('input').prop('disabled', false); // enable all inputs otherwise they wont post
var postdata = $form.serialize().replace(/'/g,"%27");
@@ -921,70 +1258,58 @@ $(function() {
done();
}
if (data.error) {
swal({title:"VM creation error",text:data.error,type:"error"});
swal({title:"VM creation error",text:data.error,type:"error"});
$form.find('input').prop('disabled', false);
$("#form_content .domain_vcpu").change(); // restore the cpu checkbox disabled states
<? if (!empty($arrConfig['domain']['state'])) echo '$(\'#domain_ovmf\').prop(\'disabled\', true); // restore bios disabled state' . "\n"; ?>
$("#vmform .domain_vcpu").change(); // restore the cpu checkbox disabled states
<? if (!empty($arrConfig['domain']['state'])) echo '$(\'#vmform #domain_ovmf\').prop(\'disabled\', true); // restore bios disabled state' . "\n"; ?>
$button.val($button.attr('readyvalue'));
}
}, "json");
});
$("#form_content #btnCancel").click(done);
$("#vmform #btnCancel").click(done);
// Fire events below once upon showing page
$("#form_content table[data-category]").each(function () {
var category = $(this).data('category');
var os = $("#vmform #template_os").val() || 'linux';
var os_casted = (os.indexOf('windows') == -1 ? 'other' : 'windows');
updatePrefixLabels(category);
bindSectionEvents(category);
});
$('#vmform .domain_os').not($('.' + os_casted)).hide();
$('#vmform .domain_os.' + os_casted).not(isVMAdvancedMode() ? '.basic' : '.advanced').show();
$("#form_content #domain_os").change(function changeOSEvent() {
var os_casted = ($(this).val().indexOf('windows') == -1 ? 'other' : 'windows');
if (initComplete && !$('#template_img').attr('touched')) {
var vmicon = OS2ImageMap[$(this).val()] || OS2ImageMap[os_casted];
$('#template_icon').val(vmicon);
$('#template_img').prop('src', '/plugins/dynamix.vm.manager/templates/images/' + vmicon);
}
slideUpRows($('.domain_os').not($('.' + os_casted)));
slideDownRows($('.domain_os.' + os_casted).not(isVMAdvancedMode() ? '.basic' : '.advanced'));
if (initComplete) {
if (os_casted == 'windows') {
$('#domain_clock').val('localtime');
$("#domain_machine option").each(function(){
if ($(this).val().indexOf('i440fx') != -1) {
$('#domain_machine').val($(this).val());
return false;
}
});
} else {
$('#domain_clock').val('utc');
$("#domain_machine option").each(function(){
if ($(this).val().indexOf('q35') != -1) {
$('#domain_machine').val($(this).val());
return false;
}
});
<?if ($boolNew):?>
if (os_casted == 'windows') {
$('#vmform #domain_clock').val('localtime');
$("#vmform #domain_machine option").each(function(){
if ($(this).val().indexOf('i440fx') != -1) {
$('#vmform #domain_machine').val($(this).val());
return false;
}
}
}).change(); // Fire now too!
if ($(".gpu option[value='vnc']:selected").length) {
$('.vncpassword,.vnckeymap').not(isVMAdvancedMode() ? '.basic' : '.advanced').show();
});
} else {
$('.vncpassword,.vnckeymap').hide();
$('#vmform #domain_clock').val('utc');
$("#vmform #domain_machine option").each(function(){
if ($(this).val().indexOf('q35') != -1) {
$('#vmform #domain_machine').val($(this).val());
return false;
}
});
}
<?endif?>
// disable usb3 option for windows7 / xp / server 2003 / server 2008
var noUSB3 = (os == 'windows7' || os == 'windows2008' || os == 'windowsxp' || os == 'windows2003');
if (noUSB3 && $("#vmform #usbmode").val() == 'usb3') {
$("#vmform #usbmode").val('usb2');
}
$("#vmform #usbmode option[value='usb3']").prop('disabled', noUSB3);
if ($("#vmform .gpu option[value='vnc']:selected").length) {
$('.vncmodel,.vncpassword,.vnckeymap').not(isVMAdvancedMode() ? '.basic' : '.advanced').show();
} else {
$('.vncmodel,.vncpassword,.vnckeymap').hide();
}
$("#form_content .disk").not("[value='']")
.attr('name', function(){ return $(this).attr('name').replace('new', 'image'); })
.closest('table').find('.disk_file_options').hide()
.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
initComplete = true;
regenerateDiskPreview();
});
</script>

View File

@@ -25,8 +25,16 @@
$strCPUModel = getHostCPUModel();
$arrOpenELECVersions = [
'6.0.0_1' => [
'name' => '6.0.0',
'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-6.0.0_1.tar.xz',
'size' => 165658636,
'md5' => '66fb6c3f1b6db49c291753fb3ec7c15c',
'localpath' => '',
'valid' => '0'
],
'5.95.3_1' => [
'name' => '6.0.0 Beta3',
'name' => '5.95.3 (6.0.0 Beta3)',
'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-5.95.3_1.tar.xz',
'size' => 153990180,
'md5' => '8936cda74c28ddcaa165cc49ff2a477a',
@@ -34,7 +42,7 @@
'valid' => '0'
],
'5.95.2_1' => [
'name' => '6.0.0 Beta2',
'name' => '5.95.2 (6.0.0 Beta2)',
'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-5.95.2_1.tar.xz',
'size' => 156250392,
'md5' => 'ac70048eecbda4772e386c6f271cb5e9',
@@ -66,9 +74,6 @@
if (array_key_exists('delete_version', $_POST)) {
//DEBUG
file_put_contents('/tmp/debug_libvirt_postparams.txt', print_r($_POST, true));
$arrDeleteOpenELEC = [];
if (array_key_exists($_POST['delete_version'], $arrOpenELECVersions)) {
$arrDeleteOpenELEC = $arrOpenELECVersions[$_POST['delete_version']];
@@ -97,9 +102,6 @@
if (array_key_exists('download_path', $_POST)) {
//DEBUG
file_put_contents('/tmp/debug_libvirt_postparams.txt', print_r($_POST, true));
$arrDownloadOpenELEC = [];
if (array_key_exists($_POST['download_version'], $arrOpenELECVersions)) {
$arrDownloadOpenELEC = $arrOpenELECVersions[$_POST['download_version']];
@@ -111,10 +113,12 @@
@mkdir($_POST['download_path'], 0777, true);
$_POST['download_path'] = realpath($_POST['download_path']) . '/';
$boolCheckOnly = !empty($_POST['checkonly']);
$strInstallScript = '/tmp/OpenELEC_' . $_POST['download_version'] . '_install.sh';
$strLogFile = $_POST['download_path'] . basename($arrDownloadOpenELEC['url']) . '.log';
$strInstallScriptPgrep = '-f "OpenELEC_' . $_POST['download_version'] . '_install.sh"';
$strTempFile = $_POST['download_path'] . basename($arrDownloadOpenELEC['url']);
$strLogFile = $strTempFile . '.log';
$strMD5File = $strTempFile . '.md5';
$strMD5StatusFile = $strTempFile . '.md5status';
$strExtractedFile = $_POST['download_path'] . basename($arrDownloadOpenELEC['url'], 'tar.xz') . 'img';
@@ -127,7 +131,7 @@
file_put_contents($strOpenELECConfig, $text);
$strDownloadCmd = 'wget -nv -O ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($arrDownloadOpenELEC['url']);
$strDownloadCmd = 'wget -nv -c -O ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($arrDownloadOpenELEC['url']);
$strDownloadPgrep = '-f "wget.*' . $strTempFile . '.*' . $arrDownloadOpenELEC['url'] . '"';
$strVerifyCmd = 'md5sum -c ' . escapeshellarg($strMD5File);
@@ -136,8 +140,8 @@
$strExtractCmd = 'tar Jxf ' . escapeshellarg($strTempFile) . ' -C ' . escapeshellarg(dirname($strTempFile));
$strExtractPgrep = '-f "tar.*' . $strTempFile . '.*' . dirname($strTempFile) . '"';
$strCleanCmd = '(chmod 777 ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; chown nobody:users ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; rm ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($strTempFile.'.md5') . ' ' . escapeshellarg($strTempFile.'.md5status') . ' ' . escapeshellarg($strTempFile.'.log') . ')';
$strCleanPgrep = '-f "chmod.*chown.*rm.*' . $strTempFile . '"';
$strCleanCmd = '(chmod 777 ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; chown nobody:users ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; rm ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($strMD5File) . ' ' . escapeshellarg($strMD5StatusFile) . ')';
$strCleanPgrep = '-f "chmod.*chown.*rm.*' . $strMD5StatusFile . '"';
$strAllCmd = "#!/bin/bash\n\n";
$strAllCmd .= $strDownloadCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
@@ -145,6 +149,7 @@
$strAllCmd .= $strVerifyCmd . ' >' . escapeshellarg($strMD5StatusFile) . ' 2>/dev/null && ';
$strAllCmd .= $strExtractCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= $strCleanCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= 'rm ' . escapeshellarg($strLogFile) . ' && ';
$strAllCmd .= 'rm ' . escapeshellarg($strInstallScript);
$arrResponse = [];
@@ -213,11 +218,20 @@
// Status = running md5 check
$arrResponse['status'] = 'Downloading ... 100%';
if (!pgrep($strInstallScriptPgrep) && !$boolCheckOnly) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
chmod($strInstallScript, 0777);
exec($strInstallScript . ' >/dev/null 2>&1 &');
}
}
} else {
} else if (!$boolCheckOnly) {
if (!pgrep($strDownloadPgrep)) {
if (!pgrep($strInstallScriptPgrep)) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
@@ -230,6 +244,8 @@
}
$arrResponse['pid'] = pgrep($strInstallScriptPgrep);
}
echo json_encode($arrResponse);
@@ -241,12 +257,12 @@
$arrConfigDefaults = [
'template' => [
'os' => 'openelec',
'name' => 'OpenELEC',
'icon' => 'openelec.png',
'name' => $strSelectedTemplate,
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
'openelec' => $strOpenELECVersionID
],
'domain' => [
'name' => $strSelectedTemplate,
'persistent' => 1,
'uuid' => $lv->domain_generate_uuid(),
'clock' => 'utc',
@@ -259,11 +275,14 @@
'vcpus' => 1,
'vcpu' => [0],
'hyperv' => 0,
'ovmf' => 0
'ovmf' => 1,
'usbmode' => 'usb3'
],
'media' => [
'cdrom' => '',
'drivers' => ''
'cdrombus' => '',
'drivers' => '',
'driversbus' => ''
],
'disk' => [
[
@@ -277,6 +296,7 @@
'gpu' => [
[
'id' => '',
'mode' => 'qxl',
'keymap' => 'en-us'
]
],
@@ -301,6 +321,11 @@
]
];
// Merge in any default values from the VM template
if (!empty($arrAllTemplates[$strSelectedTemplate]) && !empty($arrAllTemplates[$strSelectedTemplate]['overrides'])) {
$arrConfigDefaults = array_replace_recursive($arrConfigDefaults, $arrAllTemplates[$strSelectedTemplate]['overrides']);
}
// If we are editing a existing VM load it's existing configuration details
$arrExistingConfig = (!empty($_GET['uuid']) ? domain_to_config($_GET['uuid']) : []);
@@ -311,6 +336,7 @@
$arrConfigDefaults['disk'][0]['image'] = $arrOpenELECVersions[$arrConfig['template']['openelec']]['localpath'];
}
$boolNew = empty($arrExistingConfig);
$boolRunning = (!empty($arrConfig['domain']['state']) && $arrConfig['domain']['state'] == 'running');
@@ -346,17 +372,40 @@
// Backup xml for existing domain in ram
$strOldXML = '';
$boolOldAutoStart = false;
$res = $lv->domain_get_name_by_uuid($_POST['domain']['uuid']);
if ($res) {
$strOldXML = $lv->domain_get_xml($res);
$boolOldAutoStart = $lv->domain_get_autostart($res);
$dom = $lv->domain_get_domain_by_uuid($_POST['domain']['uuid']);
if ($dom) {
$strOldXML = $lv->domain_get_xml($dom);
$boolOldAutoStart = $lv->domain_get_autostart($dom);
$strOldName = $lv->domain_get_name($dom);
$strNewName = $_POST['domain']['name'];
if (!empty($strOldName) &&
!empty($strNewName) &&
is_dir($domain_cfg['DOMAINDIR'].$strOldName.'/') &&
!is_dir($domain_cfg['DOMAINDIR'].$strNewName.'/')) {
// mv domain/vmname folder
if (rename($domain_cfg['DOMAINDIR'].$strOldName, $domain_cfg['DOMAINDIR'].$strNewName)) {
// replace all disk paths in xml
foreach ($_POST['disk'] as &$arrDisk) {
if (!empty($arrDisk['new'])) {
$arrDisk['new'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['new']);
}
if (!empty($arrDisk['image'])) {
$arrDisk['image'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['image']);
}
}
}
}
//DEBUG
file_put_contents('/tmp/debug_libvirt_oldxml.xml', $strOldXML);
}
// Remove existing domain
$lv->domain_undefine($res);
$lv->nvram_backup($_POST['domain']['uuid']);
$lv->domain_undefine($dom);
$lv->nvram_restore($_POST['domain']['uuid']);
// Save new domain
$tmp = $lv->domain_new($_POST);
@@ -380,31 +429,18 @@
?>
<style type="text/css">
.four label {
float: left;
display: table-cell;
width: 25%;
}
.four label:nth-child(4n+4) {
float: none;
clear: both;
}
.mac_generate {
cursor: pointer;
margin-left: -8px;
color: #08C;
font-size: 1.1em;
}
#openelec_image {
color: #BBB;
display: none;
transform: translate(0px, 3px);
}
.delete_openelec_image {
cursor: pointer;
margin-left: 4px;
margin-right: 4px;
margin-left: -5px;
margin-right: 5px;
color: #CC0011;
font-size: 1.1em;
font-size: 1.3em;
transform: translate(0px, 3px);
}
</style>
@@ -412,12 +448,35 @@
<input type="hidden" name="domain[uuid]" value="<?=$arrConfig['domain']['uuid']?>">
<input type="hidden" name="domain[clock]" id="domain_clock" value="<?=$arrConfig['domain']['clock']?>">
<input type="hidden" name="domain[arch]" value="<?=$arrConfig['domain']['arch']?>">
<input type="hidden" name="template[os]" value="<?=$arrConfig['template']['os']?>">
<input type="hidden" name="domain[oldname]" value="<?=htmlentities($arrConfig['domain']['name'])?>">
<input type="hidden" name="disk[0][image]" id="disk_0" value="<?=$arrConfig['disk'][0]['image']?>">
<input type="hidden" name="disk[0][dev]" value="<?=$arrConfig['disk'][0]['dev']?>">
<input type="hidden" name="disk[0][readonly]" value="1">
<div class="installed">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="domain[name]" id="domain_name" class="textTemplate" title="Name of virtual machine" placeholder="e.g. OpenELEC" value="<?=htmlentities($arrConfig['domain']['name'])?>" required /></td>
</tr>
</table>
<blockquote class="inline_help">
<p>Give the VM a name (e.g. OpenELEC Family Room, OpenELEC Theatre, OpenELEC)</p>
</blockquote>
<table>
<tr class="advanced">
<td>Description:</td>
<td><input type="text" name="domain[desc]" title="description of virtual machine" placeholder="description of virtual machine (optional)" value="<?=htmlentities($arrConfig['domain']['desc'])?>" /></td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>Give the VM a brief description (optional field).</p>
</blockquote>
</div>
</div>
<table>
<tr>
@@ -438,7 +497,6 @@
<p>Select which OpenELEC version to download or use for this VM</p>
</blockquote>
<div class="available">
<table>
<tr>
@@ -464,13 +522,12 @@
</table>
</div>
<div class="installed">
<table>
<tr>
<td>Config Folder:</td>
<td>
<input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="/mnt/" value="<?=$arrConfig['shares'][0]['source']?>" name="shares[0][source]" placeholder="e.g. /mnt/user/appdata/openelec" title="path on unRAID share to save OpenELEC settings" />
<input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="/mnt/" value="<?=$arrConfig['shares'][0]['source']?>" name="shares[0][source]" placeholder="e.g. /mnt/user/appdata/openelec" title="path on unRAID share to save OpenELEC settings" required/>
<input type="hidden" value="<?=$arrConfig['shares'][0]['target']?>" name="shares[0][target]" />
</td>
</tr>
@@ -524,11 +581,9 @@
</td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>By default, VMs created will be pinned to physical CPU cores to improve performance. From this view, you can adjust which actual CPU cores a VM will be pinned (minimum 1).</p>
</blockquote>
</div>
<blockquote class="inline_help">
<p>By default, VMs created will be pinned to physical CPU cores to improve performance. From this view, you can adjust which actual CPU cores a VM will be pinned (minimum 1).</p>
</blockquote>
<table>
<tr>
@@ -598,7 +653,7 @@
<?php
echo mk_option($arrConfig['domain']['ovmf'], '0', 'SeaBIOS');
if (file_exists('/usr/share/qemu/ovmf-x64/OVMF-pure-efi.fd')) {
if (file_exists('/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd')) {
echo mk_option($arrConfig['domain']['ovmf'], '1', 'OVMF');
} else {
echo mk_option('', '0', 'OVMF (Not Available)', 'disabled="disabled"');
@@ -624,7 +679,6 @@
</blockquote>
</div>
<? foreach ($arrConfig['gpu'] as $i => $arrGPU) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -635,8 +689,10 @@
<td>
<select name="gpu[<?=$i?>][id]" class="gpu narrow">
<?
if ($i > 0) {
// Only additional video card can be none
if ($i == 0) {
// Only the first video card can be VNC
echo mk_option($arrGPU['id'], 'vnc', 'VNC');
} else {
echo mk_option($arrGPU['id'], '', 'None');
}
@@ -679,7 +735,6 @@
</table>
</script>
<? foreach ($arrConfig['audio'] as $i => $arrAudio) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -726,7 +781,6 @@
</table>
</script>
<? foreach ($arrConfig['nic'] as $i => $arrNic) {
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
@@ -794,7 +848,6 @@
</table>
</script>
<table>
<tr>
<td>USB Devices:</td>
@@ -820,12 +873,70 @@
NOTE: USB hotplug support is not yet implemented, so devices must be attached before the VM is started to use them.</p>
</blockquote>
<table>
<tr class="advanced">
<td>USB Mode:</td>
<td>
<select name="domain[usbmode]" id="usbmode" class="narrow" title="Select the USB Mode to emulate.">
<?php
echo mk_option($arrConfig['domain']['usbmode'], 'usb2', '2.0 (EHCI)');
echo mk_option($arrConfig['domain']['usbmode'], 'usb3', '3.0 (XHCI)');
?>
</select>
</td>
</tr>
</table>
<div class="advanced">
<blockquote class="inline_help">
<p>
<b>USB Mode</b><br>
Select the USB Mode to emulate.
</p>
</blockquote>
</div>
<table>
<tr>
<td>Other PCI Devices:</td>
<td>
<div class="textarea" style="width: 540px">
<?
$intAvailableOtherPCIDevices = 0;
if (!empty($arrValidOtherDevices)) {
foreach($arrValidOtherDevices as $i => $arrDev) {
$extra = '';
if (count(array_filter($arrConfig['pci'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) {
$extra .= ' checked="checked"';
} else if (!in_array($arrDev['driver'], ['pci-stub', 'vfio-pci'])) {
//$extra .= ' disabled="disabled"';
continue;
}
$intAvailableOtherPCIDevices++;
?>
<label for="pci<?=$i?>"><input type="checkbox" name="pci[]" id="pci<?=$i?>" value="<?=$arrDev['id']?>" <?=$extra?>/> <?=$arrDev['name']?> | <?=$arrDev['type']?><span class="advanced"> | <?=$arrDev['id']?></span></label><br/>
<?
}
}
if (empty($intAvailableOtherPCIDevices)) {
echo "<i>None available</i>";
}
?>
</div>
</td>
</tr>
</table>
<blockquote class="inline_help">
<p>If you wish to assign any other PCI devices to your guest, you can select them from this list.</p>
</blockquote>
<table>
<tr>
<td></td>
<td>
<? if (!$boolRunning) { ?>
<? if (!empty($arrConfig['domain']['name'])) { ?>
<? if (!$boolNew) { ?>
<input type="hidden" name="updatevm" value="1" />
<input type="button" value="Update" busyvalue="Updating..." readyvalue="Update" id="btnSubmit" />
<? } else { ?>
@@ -848,79 +959,49 @@
<script type="text/javascript">
$(function() {
var initComplete = false;
var vmicon = 'openelec.png';
$("#form_content #domain_mem").change(function changeMemEvent() {
$("#domain_maxmem").val($(this).val());
});
$("#form_content .domain_vcpu").change(function changeVCPUEvent() {
var $cores = $(".domain_vcpu:checked");
$("#vmform .domain_vcpu").change(function changeVCPUEvent() {
var $cores = $("#vmform .domain_vcpu:checked");
if ($cores.length == 1) {
$cores.prop("disabled", true);
} else {
$(".domain_vcpu").prop("disabled", false);
$("#vmform .domain_vcpu").prop("disabled", false);
}
});
$("#form_content #domain_maxmem").change(function changeMaxMemEvent() {
if (parseFloat($(this).val()) < parseFloat($("#domain_mem").val())) {
$("#domain_mem").val($(this).val());
$("#vmform #domain_mem").change(function changeMemEvent() {
$("#vmform #domain_maxmem").val($(this).val());
});
$("#vmform #domain_maxmem").change(function changeMaxMemEvent() {
if (parseFloat($(this).val()) < parseFloat($("#vmform #domain_mem").val())) {
$("#vmform #domain_mem").val($(this).val());
}
});
$("#form_content").on("change keyup", ".disk", function changeDiskEvent() {
var $input = $(this);
var config = $input.data();
if (config.hasOwnProperty('pickfilter')) {
var $other_sections = $input.closest('table').find('.disk_file_options');
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=file-info&file=" + encodeURIComponent($input.val()), function( info ) {
if (info.isfile || info.isblock) {
slideUpRows($other_sections);
$other_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
$input.attr('name', $input.attr('name').replace('new', 'image'));
} else {
$other_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($other_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
$input.attr('name', $input.attr('name').replace('image', 'new'));
}
}, "json");
}
});
$("#form_content").on("change", ".gpu", function changeGPUEvent() {
$("#vmform").on("change", ".gpu", function changeGPUEvent() {
var myvalue = $(this).val();
$(".gpu").not(this).each(function () {
$("#vmform .gpu").not(this).each(function () {
if (myvalue == $(this).val()) {
$(this).prop("selectedIndex", 0).change();
}
});
});
$("#form_content input[data-pickroot]").fileTreeAttach();
$("#form_content").on("click", ".mac_generate", function generateMac() {
$("#vmform").on("click", ".mac_generate", function generateMac() {
var $input = $(this).prev('input');
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=generate-mac", function( data ) {
$.getJSON("/plugins/dynamix.vm.manager/VMajax.php?action=generate-mac", function (data) {
if (data.mac) {
$input.val(data.mac);
}
}, "json");
});
});
$("#form_content #btnSubmit").click(function frmSubmit() {
$("#vmform #btnSubmit").click(function frmSubmit() {
var $button = $(this);
var $form = $('#domain_template').closest('form');
var $form = $button.closest('form');
//TODO: form validation
@@ -936,24 +1017,27 @@ $(function() {
done();
}
if (data.error) {
swal({title:"VM creation error",text:data.error,type:"error"});
swal({title:"VM creation error",text:data.error,type:"error"});
$form.find('input').prop('disabled', false);
$("#form_content .domain_vcpu").change(); // restore the cpu checkbox disabled states
$("#vmform .domain_vcpu").change(); // restore the cpu checkbox disabled states
$button.val($button.attr('readyvalue'));
}
}, "json");
});
$("#form_content #btnCancel").click(done);
$("#vmform #btnCancel").click(done);
var checkDownloadTimer = null;
var checkOrInitDownload = function(checkonly) {
clearTimeout(checkDownloadTimer);
$("#form_content #btnDownload").click(function frmDownload() {
var $button = $("#form_content #btnDownload");
var $form = $('#domain_template').closest('form');
var $button = $("#vmform #btnDownload");
var $form = $button.closest('form');
var postdata = {
download_version: $('#template_openelec').val(),
download_path: $('#download_path').val()
download_version: $('#vmform #template_openelec').val(),
download_path: $('#vmform #download_path').val(),
checkonly: ((typeof checkonly === 'undefined') ? false : !!checkonly) ? 1 : 0
};
$form.find('input').prop('disabled', true);
@@ -961,95 +1045,85 @@ $(function() {
$.post("/plugins/dynamix.vm.manager/templates/<?=basename(__FILE__)?>", postdata, function( data ) {
if (data.error) {
$("#download_status").html($("#download_status").html() + '<br><span style="color: red">' + data.error + '</span>');
} else {
var old_list = $("#download_status").html().split('<br>');
$("#vmform #download_status").html($("#vmform #download_status").html() + '<br><span style="color: red">' + data.error + '</span>');
} else if (data.status) {
var old_list = $("#vmform #download_status").html().split('<br>');
if (old_list.pop().split(' ... ').shift() == data.status.split(' ... ').shift()) {
old_list.push(data.status);
$("#download_status").html(old_list.join('<br>'));
$("#vmform #download_status").html(old_list.join('<br>'));
} else {
$("#download_status").html($("#download_status").html() + '<br>' + data.status);
$("#vmform #download_status").html($("#vmform #download_status").html() + '<br>' + data.status);
}
if (data.status != 'Done') {
setTimeout(frmDownload, 1000);
if (data.pid) {
checkDownloadTimer = setTimeout(checkOrInitDownload, 1000);
return;
}
$("#template_openelec").find('option:selected').attr({
localpath: data.localpath,
localfolder: data.localfolder,
valid: '1'
});
$("#template_openelec").change();
if (data.status == 'Done') {
$("#vmform #template_openelec").find('option:selected').attr({
localpath: data.localpath,
localfolder: data.localfolder,
valid: '1'
});
$("#vmform #template_openelec").change();
}
}
$button.val($button.attr('readyvalue'));
$form.find('input').prop('disabled', false);
}, "json");
};
$("#vmform #btnDownload").click(function changeVirtIOVersion() {
checkOrInitDownload(false);
});
// Fire events below once upon showing page
$("#template_openelec").change(function changeOpenELECVersion() {
$("#vmform #template_openelec").change(function changeOpenELECVersion() {
clearTimeout(checkDownloadTimer);
$selected = $(this).find('option:selected');
if ($selected.attr('valid') === '0') {
$(".available").slideDown('fast');
$(".installed").slideUp('fast');
$("#download_status").html('');
$("#download_path").val($selected.attr('localfolder'));
$("#vmform .available").slideDown('fast');
$("#vmform .installed").slideUp('fast');
$("#vmform #download_status").html('');
$("#vmform #download_path").val($selected.attr('localfolder'));
if ($selected.attr('localpath') !== '') {
// auto click download button to see status of current running job
$("#btnDownload").click();
// Check status of current running job (but dont initiate a new download)
checkOrInitDownload(true);
}
} else {
$(".available").slideUp('fast');
$(".installed").slideDown('fast', function () {
$("#form_content .domain_vcpu").change(); // restore the cpu checkbox disabled states
$("#vmform .available").slideUp('fast');
$("#vmform .installed").slideDown('fast', function () {
$("#vmform .domain_vcpu").change(); // restore the cpu checkbox disabled states
// attach delete openelec image onclick event
$("#form_content .delete_openelec_image").off().click(function deleteOEVersion() {
swal({title:"Are you sure?",text:"Remove this OpenELEC file: ",type:"warning",showCancelButton:true},function() {
$("#vmform .delete_openelec_image").off().click(function deleteOEVersion() {
swal({title:"Are you sure?",text:"Remove this OpenELEC file:\n"+$selected.attr('localpath'),type:"warning",showCancelButton:true},function() {
$.post("/plugins/dynamix.vm.manager/templates/<?=basename(__FILE__)?>", {delete_version: $selected.val()}, function(data) {
if (data.error) {
swal({title:"VM image deletion error",text:data.error,type:"error"});
swal({title:"VM image deletion error",text:data.error,type:"error"});
} else if (data.status == 'ok') {
$selected.attr({
localpath: '',
valid: '0'
});
}
$("#template_openelec").change();
$("#vmform #template_openelec").change();
}, "json");
});
}).hover(function () {
$("#openelec_image").css('color', '#666');
$("#vmform #openelec_image").css('color', '#666');
}, function () {
$("#openelec_image").css('color', '#BBB');
$("#vmform #openelec_image").css('color', '#BBB');
});
});
$("#form_content #disk_0").val($selected.attr('localpath'));
$("#form_content #openelec_image").html($selected.attr('localpath'));
$("#vmform #disk_0").val($selected.attr('localpath'));
$("#vmform #openelec_image").html($selected.attr('localpath'));
}
}).change(); // Fire now too!
$("#form_content table[data-category]").each(function () {
var category = $(this).data('category');
updatePrefixLabels(category);
bindSectionEvents(category);
});
$("#form_content .disk").not("[value='']")
.attr('name', function(){ return $(this).attr('name').replace('new', 'image'); })
.closest('table').find('.disk_file_options').hide()
.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
$('#template_icon').val(vmicon);
$('#template_img').prop('src', '/plugins/dynamix.vm.manager/templates/images/' + vmicon);
initComplete = true;
});
</script>

View File

@@ -56,17 +56,19 @@
// Backup xml for existing domain in ram
$strOldXML = '';
$boolOldAutoStart = false;
$res = $lv->domain_get_name_by_uuid($_POST['domain']['uuid']);
if ($res) {
$strOldXML = $lv->domain_get_xml($res);
$boolOldAutoStart = $lv->domain_get_autostart($res);
$dom = $lv->domain_get_domain_by_uuid($_POST['domain']['uuid']);
if ($dom) {
$strOldXML = $lv->domain_get_xml($dom);
$boolOldAutoStart = $lv->domain_get_autostart($dom);
//DEBUG
file_put_contents('/tmp/debug_libvirt_oldxml.xml', $strOldXML);
}
// Remove existing domain
$lv->domain_undefine($res);
$lv->nvram_backup($_POST['domain']['uuid']);
$lv->domain_undefine($dom);
$lv->nvram_restore($_POST['domain']['uuid']);
// Save new domain
$tmp = $lv->domain_define($_POST['xmldesc']);
@@ -92,7 +94,7 @@
<link rel="stylesheet" href="/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/show-hint.css">
<style type="text/css">
.CodeMirror { border: 1px solid #eee; cursor: text; }
.CodeMirror { border: 1px solid #eee; cursor: text; margin-top: 15px; margin-bottom: 10px; }
.CodeMirror pre.CodeMirror-placeholder { color: #999; }
</style>
@@ -100,7 +102,6 @@
<textarea id="addcode" name="xmldesc" placeholder="Copy &amp; Paste Domain XML Configuration Here." autofocus><?= htmlspecialchars($strXML); ?></textarea>
<? if (!$boolRunning) { ?>
<? if (!empty($strXML)) { ?>
<input type="hidden" name="updatevm" value="1" />
@@ -111,12 +112,12 @@
<input type="hidden" name="createvm" value="1" />
<input type="button" value="Create" busyvalue="Creating..." readyvalue="Create" id="btnSubmit" />
<? } ?>
<input type="button" value="Cancel" id="btnCancel" />
<input type="button" value="Cancel" id="btnCancel" />
<span><i class="fa fa-warning icon warning"></i> Manual XML edits may be lost if you later edit with the GUI editor.</span>
<? } else { ?>
<input type="button" value="Done" id="btnCancel" />
<? } ?>
<script src="/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.js"></script>
<script src="/plugins/dynamix.vm.manager/scripts/codemirror/addon/display/placeholder.js"></script>
<script src="/plugins/dynamix.vm.manager/scripts/codemirror/addon/fold/foldcode.js"></script>
@@ -170,12 +171,14 @@ $(function() {
editor.refresh();
}, 1);
$("#form_content #btnSubmit").click(function frmSubmit() {
$("#vmform #btnSubmit").click(function frmSubmit() {
var $button = $(this);
var $form = $button.closest('form');
editor.save();
var $form = $('#domain_template').closest('form');
$form.find('input').prop('disabled', false); // enable all inputs otherwise they wont post
var postdata = $form.serialize().replace(/'/g,"%27");
$form.find('input').prop('disabled', true);
@@ -186,13 +189,13 @@ $(function() {
done();
}
if (data.error) {
swal({title:"VM creation error",text:data.error,type:"error"});
swal({title:"VM creation error",text:data.error,type:"error"});
$form.find('input').prop('disabled', false);
$button.val($button.attr('readyvalue'));
}
}, "json");
});
$("#form_content #btnCancel").click(done);
$("#vmform #btnCancel").click(done);
});
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB