mirror of
https://github.com/unraid/webgui.git
synced 2026-05-07 21:01:19 -05:00
Merge pull request #1433 from SimonFair/VM-Clone-and-Snapshot-merge
Vm clone and snapshot merge
This commit is contained in:
@@ -18,6 +18,7 @@ Markdown="false"
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$cpus = cpu_list();
|
||||
@@ -121,6 +122,7 @@ div.four label:nth-child(4n+4){float:none;clear:both}
|
||||
div.four label.cpu1{width:32%}
|
||||
div.four label.cpu2{width:26%}
|
||||
div.template,div#dialogWindow,input#upload{display:none}
|
||||
div.template,div#dialogWindow2,input#upload{display:none}
|
||||
table.domdisk thead tr th:nth-child(1){width:56%!important}
|
||||
table.domdisk thead tr th:nth-child(n+2){width:8%!important}
|
||||
table.domdisk thead tr th:nth-child(1){padding-left:72px}
|
||||
@@ -134,7 +136,7 @@ i.mover{margin-right:8px;display:none}
|
||||
.dropdown-menu{z-index:10001}
|
||||
</style>
|
||||
<table id="kvm_table" class="tablesorter four shift">
|
||||
<thead><tr><th class="th1"><a id="resetsort" class="nohand" onclick="resetSorting()" title="Reset sorting"><i class="fa fa-th-list"></i></a>_(Name)_</th><th class="th2">_(Description)_</th><th>_(CPUs)_</th><th>_(Memory)_</th><th>_(vDisks)_</th><th>_(Graphics)_</th><th class="th3">_(Autostart)_</th></tr></thead>
|
||||
<thead><tr><th class="th1"><a id="resetsort" class="nohand" onclick="resetSorting()" title="Reset sorting"><i class="fa fa-th-list"></i></a>_(Name)_</th><th class="th2">_(Description)_</th><th>_(CPUs)_</th><th>_(Memory)_</th><th>_(vDisks / vCDs)_</th><th>_(Graphics)_</th><th class="th3">_(Autostart)_</th></tr></thead>
|
||||
<tbody id="kvm_list"><tr><td colspan='8'></td></tr></tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addVM()" id="btnAddVM" value="_(Add VM)_" style="display:none">
|
||||
@@ -163,7 +165,54 @@ function resetSorting() {
|
||||
function changemedia(uuid,dev,bus,file) {
|
||||
if (file === "--select") getisoimage(uuid,dev,bus,file);
|
||||
if (file === "--eject") ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:file}, "loadlist");
|
||||
}
|
||||
function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow");
|
||||
box.html($("#templateISOboth").html());
|
||||
|
||||
box.find('#target').attr('data-pickroot',root).attr('data-picktop',root).attr('data-pickmatch',match).attr('value', file).fileTreeAttach(null,null,function(path){
|
||||
var bits = path.substr(1).split('/');
|
||||
var auto = bits.length>3 ? '' : share;
|
||||
box.find('#target').val(path+auto).change();
|
||||
});
|
||||
box.find('#target2').attr('data-pickroot',root).attr('data-picktop',root).attr('data-pickmatch',match).attr('value', file2).fileTreeAttach(null,null,function(path){
|
||||
var bits = path.substr(1).split('/');
|
||||
var auto = bits.length>3 ? '' : share;
|
||||
box.find('#target2').val(path+auto).change();
|
||||
});
|
||||
var height = 100;
|
||||
|
||||
box.dialog({
|
||||
title: "Select ISOs for CDROMs",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 300,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
buttons: {
|
||||
"_(Update)_": function(){
|
||||
var target = box.find('#target');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
} else target = '';
|
||||
var target2 = box.find('#target2');
|
||||
if (target2.length) {
|
||||
target2 = target2.val();
|
||||
} else target2 = '';
|
||||
box.find('#target').prop('disabled',true);
|
||||
box.find('#target2').prop('disabled',true);
|
||||
ajaxVMDispatch({action:"change-media-both", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:target, dev2:dev2 , bus2:bus2 , file2:target2}, "loadlist");
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
function getisoimage(uuid,dev,bus,file){
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
@@ -203,8 +252,220 @@ function getisoimage(uuid,dev,bus,file){
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
function VMClone(uuid, name){
|
||||
|
||||
//var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow");
|
||||
var height = 200;
|
||||
box.html($("#templateClone").html());
|
||||
box.find('#VMBeingCloned').html(name).change() ;
|
||||
box.find('#target').val(name + "_clone") ;
|
||||
document.getElementById("Free").checked = true ;
|
||||
document.getElementById("Overwrite").checked = true ;
|
||||
var Cancel = _("Cancel") ;
|
||||
box.dialog({
|
||||
title: "VM Clone",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 500,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
buttons: {
|
||||
"_(Clone)_" : function(){
|
||||
var target = box.find('#target');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
//if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
|
||||
var clone = box.find("#target").prop('value') ;
|
||||
x = box.find('#Start').prop('checked') ;
|
||||
if (x) start = 'yes' ; else start = 'no' ;
|
||||
x = box.find('#Edit').prop('checked') ;
|
||||
if (x) edit = 'yes' ; else edit = 'no' ;
|
||||
x = box.find('#Overwrite').prop('checked') ;
|
||||
if (x) overwrite = 'yes' ; else overwrite = 'no' ;
|
||||
x = box.find('#Free').prop('checked') ;
|
||||
if (x) free = 'yes' ; else free = 'no' ;
|
||||
scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone" , name:name ,clone:clone, overwrite:overwrite , edit:edit, start,start, free:free})) ;
|
||||
openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist") ;
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
|
||||
|
||||
function selectsnapshot(uuid, name ,snaps, opt, getlist,state){
|
||||
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow2");
|
||||
box.html($("#templatesnapshot"+opt).html());
|
||||
var height = 200;
|
||||
const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ;
|
||||
var optiontext = Capopt + " Snapshot" ;
|
||||
box.find('#VMName').html(name) ;
|
||||
box.find('#targetsnap').val(snaps) ;
|
||||
box.find('#targetsnapl').html(snaps) ;
|
||||
if (getlist) {
|
||||
var only = 1 ;
|
||||
if (opt == "remove") only = 0;
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid , snapshotname:snaps, only:only}, function(data) {
|
||||
if (data.html) {
|
||||
box.find('#targetsnapimages').html(data.html) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
document.getElementById("targetsnaprmv").checked = true ;
|
||||
document.getElementById("targetsnaprmvmeta").checked = true ;
|
||||
document.getElementById("targetsnapkeep").checked = true ;
|
||||
document.getElementById("targetsnapfspc").checked = true ;
|
||||
|
||||
box.dialog({
|
||||
title: "_("+optiontext+ ")_",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 500,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
|
||||
buttons: {
|
||||
"_(Proceed)_": function(){
|
||||
var target = box.find('#targetsnap');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
var remove = 'yes'
|
||||
var keep = 'yes'
|
||||
var removemeta = 'yes'
|
||||
var free = 'yes'
|
||||
var desc = ''
|
||||
box.find('#targetsnap').prop('disabled',true);
|
||||
if (opt == "revert") {
|
||||
var x = box.find('#targetsnaprmv').prop('checked') ;
|
||||
if (x) remove = 'yes' ; else remove = 'no' ;
|
||||
x = box.find('#targetsnaprmvmeta').prop('checked') ;
|
||||
if (x) removemeta = 'yes' ; else removemeta = 'no' ;
|
||||
x = box.find('#targetsnapkeep').prop('checked') ;
|
||||
if (x) keep = 'yes' ; else keep = 'no' ;
|
||||
}
|
||||
if (opt == "create") {
|
||||
var x = box.find('#targetsnapfspc').prop('checked') ;
|
||||
if (x) free = 'yes' ; else free = 'no' ;
|
||||
var desc = box.find("#targetsnapdesc").prop('value') ;
|
||||
}
|
||||
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist");
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
|
||||
function selectblock(uuid, name ,snaps, opt, getlist,state){
|
||||
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow2");
|
||||
box.html($("#templateblock").html());
|
||||
var height = 200;
|
||||
const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ;
|
||||
var optiontext = Capopt + " Block Devices" ;
|
||||
box.find('#VMName').html(name) ;
|
||||
box.find('#targetsnap').val(snaps) ;
|
||||
box.find('#targetsnapl').html(snaps) ;
|
||||
|
||||
getlist = true ;
|
||||
if (getlist) {
|
||||
var only = 1 ;
|
||||
if (opt == "remove") only = 0;
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-list", uuid:uuid }, function(data) {
|
||||
if (data.html) {
|
||||
var targetbase = document.getElementById("targetblockbase") ;
|
||||
htmlstrbase = "<select class='targetblockbase' name='targetblockbase' id='targetblockbase'><option value='--base'>--base</option>" + data.html + "</select>"
|
||||
htmlstrtop = "<select class='targetblocktop' name='targetblocktop' id='targetblocktop'><option value='--top'>--top</option>" + data.html + "</select>"
|
||||
$("select.targetblockbase").replaceWith(htmlstrbase) ;
|
||||
$("select.targetblocktop").replaceWith(htmlstrtop) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
document.getElementById("targetsnaprmv").checked = true ;
|
||||
document.getElementById("targetsnaprmvmeta").checked = true ;
|
||||
document.getElementById("targetsnapkeep").checked = true ;
|
||||
document.getElementById("targetsnapfspc").checked = true ;
|
||||
if (opt== "pull") {
|
||||
$('.toprow').hide();
|
||||
$('.targetpivotrow').hide();
|
||||
$('.targetdeleterow').hide();
|
||||
} else {
|
||||
$('.toprow').show();
|
||||
$('.targetpivotrow').show();
|
||||
$('.targetdeleterow').show();
|
||||
}
|
||||
|
||||
box.dialog({
|
||||
title: "_("+optiontext+ ")_",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 500,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
buttons: {
|
||||
_("Action")_: function(){
|
||||
var target = box.find('#targetsnap');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
var remove = 'yes'
|
||||
var keep = 'yes'
|
||||
var removemeta = 'yes'
|
||||
var free = 'yes'
|
||||
var delete_file = 'yes'
|
||||
var pivot = 'yes'
|
||||
var desc = ''
|
||||
box.find('#targetsnap').prop('disabled',true);
|
||||
if (opt == "create") {
|
||||
var x = box.find('#targetsnapfspc').prop('checked') ;
|
||||
if (x) free = 'yes' ; else free = 'no' ;
|
||||
var desc = box.find("#targetsnapdesc").prop('value') ;
|
||||
}
|
||||
var targetbase = box.find("#targetblockbase").prop('value') ;
|
||||
var targettop = box.find("#targetblocktop").prop('value') ;
|
||||
|
||||
x = box.find('#targetpivot').prop('checked') ;
|
||||
if (x) pivot = 'yes' ; else pivot = 'no' ;
|
||||
x = box.find('#targetdelete').prop('checked') ;
|
||||
if (x) delete_file = 'yes' ; else delete_file = 'no' ;
|
||||
Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:opt , name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, targetpivot:pivot ,removemeta:removemeta ,targetdelete:delete_file})) ;
|
||||
openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ;
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
|
||||
function dialogStyle() {
|
||||
$('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('<i class="fa fa-times"></i>').prop('title',"_(Close)_").prop('onclick',null).off('click').click(function(){box.dialog('close');});
|
||||
$('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('<i class="fa fa-times"></i>').prop('title');
|
||||
$('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'});
|
||||
$('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'});
|
||||
$('.ui-button-text').css({'padding':'0px 5px'});
|
||||
@@ -304,5 +565,119 @@ $(function() {
|
||||
<div markdown="1" id="templateISO" class="template">
|
||||
_(ISO Image)_:
|
||||
: <input type="text" id="target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="templateISOboth" class="template">
|
||||
_(CD1 ISO Image)_:
|
||||
: <input type="text" id="target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop=""><br>
|
||||
_(CD2 ISO Image)_:
|
||||
: <input type="text" id="target2" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div id="dialogWindow2"></div>
|
||||
<div markdown="1" id="templatesnapshotcreate" class="template">
|
||||
<table id='snapshot'>
|
||||
<br><br>
|
||||
<tr><td> _(VM Name)_:</td><td>
|
||||
<label id="VMName"></label></td></tr>
|
||||
<tr><td>_(Snapshot Name)_:</td><td>
|
||||
<input type="text" id="targetsnap" autocomplete="off" spellcheck="false" value="--generate" onclick="this.select()">
|
||||
_(Check free space)_:
|
||||
<input type="checkbox" id="targetsnapfspc" checked></td></tr>
|
||||
<tr><td>_(Description )_:</td><td>
|
||||
<input type="text" id="targetsnapdesc" autocomplete="off" spellcheck="false" value="" onclick="this.select()"></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="templatesnapshotrevert" class="template">
|
||||
_(VM Name)_:
|
||||
<label id="VMName"></label>
|
||||
<br>
|
||||
_(Snapshot Name)_:
|
||||
<input type="text" id="targetsnap" hidden>
|
||||
<label id="targetsnapl"></label><br>
|
||||
_(Remove Images)_:
|
||||
<input type="checkbox" id="targetsnaprmv" checked >
|
||||
_(Remove Meta)_:
|
||||
<input type="checkbox" id="targetsnaprmvmeta" checked>
|
||||
<!--_(Keep snapshot)_:-->
|
||||
<input type="checkbox" id="targetsnapkeep" hidden><br>
|
||||
|
||||
<label id="targetsnapimages"></label><br>
|
||||
|
||||
<div markdown="1" id="templatesnapshotremove" class="template" post>
|
||||
_(!! Warning removing Snapshots can break the chain !!)_<br><br>
|
||||
|
||||
_(VM Name)_:
|
||||
<label id="VMName"></label>
|
||||
<br>
|
||||
_(Snapshot Name)_:
|
||||
<input type="text" id="targetsnap" hidden>
|
||||
<label id="targetsnapl"></label><br>
|
||||
|
||||
<label id="targetsnapimages"></label><br>
|
||||
|
||||
<div markdown="1" id="templateblock" class="template">
|
||||
_(VM Name)_:
|
||||
<label id="VMName"></label>
|
||||
<br>
|
||||
_(Snapshot Name)_:
|
||||
<input type="text" id="targetsnap" hidden>
|
||||
<label id="targetsnapl"></label><br>
|
||||
<br><br><br><br>
|
||||
<table id='block'>
|
||||
<tr><td>_(Base Image)_:</td><td>
|
||||
<select class="targetblockbase" s>
|
||||
</select></td></tr>
|
||||
<tr name="toprow" class="toprow" ><td>_(Top Image )_:</td><td>
|
||||
<select class="targetblocktop" name="targetblocktop" id="targetblocktop">
|
||||
</select></td><td>
|
||||
<tr name="targetpivotrow" class="targetpivotrow" ><td>_(Pivot)_:</td><td>
|
||||
<input type="checkbox" id="targetpivot" checked></td></tr>
|
||||
<tr name="targetdeleterow" class="targetdeleterow" ><td>_(Delete)_:</td><td>
|
||||
<input type="checkbox" id="targetdelete" checked></td></tr>
|
||||
</table>
|
||||
<input type="checkbox" id="targetsnapkeep" hidden><br>
|
||||
<label id="targetsnapimages"></label><br>
|
||||
</div>
|
||||
|
||||
<div id="dialogWindow"></div>
|
||||
<div markdown="1" id="templateClone" class="template">
|
||||
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=1600">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="same-origin">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<table>
|
||||
<tr><td>_(VM Being Cloned)_:</td>
|
||||
<td><span id="VMBeingCloned"></span></td></tr>
|
||||
|
||||
<tr><td>_(New VM)_:</td>
|
||||
<td><input type="text" id="target" autocomplete="off" spellcheck="false" value="" onclick="this.select()" ></td></tr>
|
||||
|
||||
<tr><td>_(Overwrite)_:</td>
|
||||
<td><input type="checkbox" id="Overwrite" value="" ></td></tr>
|
||||
|
||||
<tr hidden><td>_(Start Cloned VM)_:</td>
|
||||
<td><input type="checkbox" id="Start" value="" ></td></tr>
|
||||
|
||||
<tr hidden><td>_(Edit VM after clone)_:</td>
|
||||
<td><input type="checkbox" id="Edit" value="" ></td></tr>
|
||||
|
||||
<tr><td>_(Check Free Space)_:</td>
|
||||
<td><input type="checkbox" id="Free" value="" ></td></tr>
|
||||
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -21,6 +21,7 @@ require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg';
|
||||
if (file_exists('/boot/config/plugins/dynamix.vm.manager/vmpreview')) $vmpreview = true ; else $vmpreview = false ;
|
||||
$vms = $lv->get_domains();
|
||||
if (empty($vms)) {
|
||||
echo '<tr><td colspan="8" style="text-align:center;padding-top:12px">'._('No Virtual Machines installed').'</td></tr>';
|
||||
@@ -51,6 +52,8 @@ foreach ($vms as $vm) {
|
||||
$icon = $lv->domain_get_icon_url($res);
|
||||
$image = substr($icon,-4)=='.png' ? "<img src='$icon' class='img'>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
|
||||
$arrConfig = domain_to_config($uuid);
|
||||
$snapshots = getvmsnapshots($vm) ;
|
||||
$cdroms = $lv->get_cdrom_stats($res) ;
|
||||
if ($state == 'running') {
|
||||
$mem = $dom['memory']/1024;
|
||||
} else {
|
||||
@@ -101,7 +104,7 @@ foreach ($vms as $vm) {
|
||||
}
|
||||
unset($dom);
|
||||
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole);
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole,$vmpreview);
|
||||
$kvm[] = "kvm.push({id:'$uuid',state:'$state'});";
|
||||
switch ($state) {
|
||||
case 'running':
|
||||
@@ -123,12 +126,33 @@ foreach ($vms as $vm) {
|
||||
}
|
||||
|
||||
/* VM information */
|
||||
|
||||
if ($snapshots != null) $snapshotstr = _("(Snapshots :").count($snapshots).')' ; else $snapshotstr = _("(Snapshots :None)") ;
|
||||
$cdbus = $cdbus2 = $cdfile = $cdfile2 = "" ;
|
||||
$cdromcount = 0 ;
|
||||
foreach ($cdroms as $arrCD) {
|
||||
$disk = $arrCD['file'] ?? $arrCD['partition'];
|
||||
$dev = $arrCD['device'];
|
||||
$bus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
|
||||
if ($dev == "hda") {
|
||||
$cdbus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
|
||||
$cdfile = $arrCD['file'] ?? $arrCD['partition'];
|
||||
if ($cdfile != "") $cdromcount++ ;
|
||||
}
|
||||
if ($dev == "hdb") {
|
||||
$cdbus2 = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
|
||||
$cdfile2 = $arrCD['file'] ?? $arrCD['partition'];
|
||||
if ($cdfile2 != "") $cdromcount++ ;
|
||||
}
|
||||
}
|
||||
$changemedia = "getisoimageboth(\"{$uuid}\",\"hda\",\"{$cdbus}\",\"{$cdfile}\",\"hdb\",\"{$cdbus2}\",\"{$cdfile2}\")";
|
||||
$cdstr = $cdromcount." / 2<a class='hand' title='$title' href='#' onclick='$changemedia'> <i class='fa fa-bullseye' ></i></a>";
|
||||
echo "<tr parent-id='$i' class='sortable'><td class='vm-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
|
||||
echo "<span class='outer'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'><a href='#' onclick='return toggle_id(\"name-$i\")' title='click for more VM info'>$vm</a><br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span></td>";
|
||||
echo "<span class='outer'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'><a href='#' onclick='return toggle_id(\"name-$i\")' title='click for more VM info'>$vm</a><br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)." $snapshotstcount</span></span></span></td>";
|
||||
echo "<td>$desc</td>";
|
||||
echo "<td><a class='vcpu-$uuid' style='cursor:pointer'>$vcpu</a></td>";
|
||||
echo "<td>$mem</td>";
|
||||
echo "<td title='$diskdesc'>$disks</td>";
|
||||
echo "<td title='$diskdesc'><span class='state' >$disks $cdstr<br>$snapshotstr</span></td>";
|
||||
echo "<td>$graphics</td>";
|
||||
echo "<td><input class='autostart' type='checkbox' name='auto_{$vm}' title=\""._('Toggle VM autostart')."\" uuid='$uuid' $autostart></td></tr>";
|
||||
|
||||
@@ -168,13 +192,13 @@ foreach ($vms as $vm) {
|
||||
}
|
||||
|
||||
/* Display VM cdroms */
|
||||
foreach ($lv->get_cdrom_stats($res) as $arrCD) {
|
||||
foreach ($cdroms as $arrCD) {
|
||||
$capacity = $lv->format_size($arrCD['capacity'], 0);
|
||||
$allocation = $lv->format_size($arrCD['allocation'], 0);
|
||||
$disk = $arrCD['file'] ?? $arrCD['partition'];
|
||||
$disk = $arrCD['file'] ?? $arrCD['partition'] ?? "" ;
|
||||
$dev = $arrCD['device'];
|
||||
$bus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
|
||||
$boot= $arrCD["boot order"];
|
||||
$boot= $arrCD["boot order"] ?? "" ;
|
||||
if ($boot < 1) $boot="Not set";
|
||||
if ($disk != "" ) {
|
||||
$title = _("Eject CD Drive").".";
|
||||
@@ -214,12 +238,43 @@ foreach ($vms as $vm) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if ($gastate == "disconnected") echo "<tr><td>"._('Guest agent not installed')."</td><td></td><td></td><td></td></tr>";
|
||||
else echo "<tr><td>"._('Guest not running')."</td><td></td><td></td><td></td><td></td></tr>";
|
||||
}
|
||||
echo "</tbody></table>";
|
||||
echo "</td></tr>";
|
||||
echo "</tbody>";
|
||||
/* Display VM Snapshots */
|
||||
if ($snapshots != null) {
|
||||
$j=0 ;
|
||||
$steps = array() ;
|
||||
foreach($snapshots as $snap) {
|
||||
if ($snap['parent'] == "" || $snap['parent'] == "Base") $j++;
|
||||
$steps[$j] .= $snap['name'].';' ;
|
||||
}
|
||||
echo "<thead class='child' child-id='$i'><tr><th><i class='fa fa-clone'></i> <b>"._('Snapshots')."</b></th><th></th><th>"._('Date/Time')."</th><th>"._('Type')."</th><th>"._('Parent')."</th><th>"._('Memory')."</th></tr></thead>";
|
||||
echo "<tbody class='child'child-id='$i'>";
|
||||
foreach($steps as $stepsline)
|
||||
{
|
||||
$snapshotlist = explode(";",$stepsline) ;
|
||||
$tab = " " ;
|
||||
foreach($snapshotlist as $snapshotitem) {
|
||||
if ($snapshotitem == "") continue ;
|
||||
$snapshot = $snapshots[$snapshotitem] ;
|
||||
$snapshotstate = _(ucfirst($snapshot["state"])) ;
|
||||
$snapshotdesc = $snapshot["desc"] ;
|
||||
$snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ;
|
||||
$snapshotparent = $snapshot["parent"] ? $snapshot["parent"] : "None";
|
||||
$snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."<br>".my_time($snapshot["creationtime"],"H:i:s") ;
|
||||
$snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$vmpreview);
|
||||
echo "<tr><td><span id='vmsnap-$uuid' $snapmenu class='hand'>$tab|__ <i class='fa fa-clone'></i></span> ".$snapshot["name"]."</td><td>$snapshotdesc</td><td><span class='inner' style='font-size:1.1rem;'>$snapshotdatetime</span></td><td>$snapshotstate</td><td>$snapshotparent</td><td>$snapshotmemory</td></tr>";
|
||||
$tab .=" " ;
|
||||
}
|
||||
echo "</tbody>";
|
||||
}
|
||||
}
|
||||
echo "</table>";
|
||||
}
|
||||
|
||||
echo "\0".implode($kvm);
|
||||
?>
|
||||
|
||||
@@ -265,6 +265,40 @@ case 'change-media':
|
||||
: ['error' => "Change Media Failed"];
|
||||
break;
|
||||
|
||||
case 'change-media-both':
|
||||
requireLibvirt();
|
||||
$res = $lv->get_domain_by_name($domName);
|
||||
$cdroms = $lv->get_cdrom_stats($res) ;
|
||||
$hda = $hdb = false ;
|
||||
foreach ($lv->get_cdrom_stats($res) as $cd){
|
||||
if ($cd['device'] == 'hda') $hda = true ;
|
||||
if ($cd['device'] == 'hdb') $hdb = true ;
|
||||
}
|
||||
$file= $_REQUEST['file'];
|
||||
if ($file != "" && $hda == false) {
|
||||
$cmdstr = "virsh attach-disk '$domName' '$file' hda --type cdrom --targetbus sata --config" ;
|
||||
} else {
|
||||
if ($file == "") $cmdstr = "virsh change-media '$domName' hda --eject --current";
|
||||
else $cmdstr = "virsh change-media '$domName' hda '$file'";
|
||||
}
|
||||
$rtn=shell_exec($cmdstr)
|
||||
? ['success' => true]
|
||||
: ['error' => "Change Media Failed"];
|
||||
|
||||
if (isset($rtn['error'])) return ;
|
||||
|
||||
$file2 = $_REQUEST['file2'];
|
||||
if ($file2 != "" && $hdb == false) {
|
||||
$cmdstr = "virsh attach-disk '$domName' '$file2' hdb --type cdrom --targetbus sata --config" ;
|
||||
} else {
|
||||
if ($file2 == "") $cmdstr = "virsh change-media '$domName' hdb --eject --current";
|
||||
else $cmdstr = "virsh change-media '$domName' hdb '$file2' ";
|
||||
}
|
||||
$rtn=shell_exec($cmdstr)
|
||||
? ['success' => true]
|
||||
: ['error' => "Change Media Failed"];
|
||||
break;
|
||||
|
||||
case 'memory-change':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_set_memory($domName, $_REQUEST['memory']*1024)
|
||||
@@ -302,6 +336,40 @@ case 'snap-create':
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-create-external':
|
||||
requireLibvirt();
|
||||
$arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ;
|
||||
break;
|
||||
|
||||
case 'snap-images':
|
||||
requireLibvirt();
|
||||
$html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ;
|
||||
$arrResponse = ['html' => $html , 'success' => true] ;
|
||||
break;
|
||||
|
||||
case 'snap-list':
|
||||
requireLibvirt();
|
||||
$arrResponse = ($data = getvmsnapshots($domName))
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$datartn = "";
|
||||
foreach($data as $snap=>$snapdetail) {
|
||||
$snapshotdatetime = date("Y-m-d H:i:s",$snapdetail["creationtime"]) ;
|
||||
$datartn .= "<option value='$snap'>$snap $snapshotdatetime</option>" ;
|
||||
}
|
||||
$arrResponse['html'] = $datartn ;
|
||||
break;
|
||||
|
||||
case 'snap-revert-external':
|
||||
requireLibvirt();
|
||||
$arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove'], $_REQUEST['removemeta']) ;
|
||||
break;
|
||||
|
||||
case 'snap-remove-external':
|
||||
requireLibvirt();
|
||||
$arrResponse = vm_snapremove($domName,$_REQUEST['snapshotname']) ;
|
||||
break;
|
||||
|
||||
case 'snap-delete':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap'])
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
}
|
||||
|
||||
|
||||
function config_to_xml($config) {
|
||||
function config_to_xml($config,$vmclone = false) {
|
||||
$domain = $config['domain'];
|
||||
$media = $config['media'];
|
||||
$nics = $config['nic'];
|
||||
@@ -463,19 +463,26 @@
|
||||
$usbstr = '';
|
||||
if (!empty($usb)) {
|
||||
foreach($usb as $i => $v){
|
||||
$usbx = explode(':', $v);
|
||||
if ($vmclone) $usbx = explode(':', $v['id']); else $usbx = explode(':', $v);
|
||||
$startupPolicy = '' ;
|
||||
if (isset($usbopt[$v])) {
|
||||
if (isset($usbopt[$v]) && !$vmclone ) {
|
||||
if (strpos($usbopt[$v], "#remove") == false) $startupPolicy = 'startupPolicy="optional"' ; else $startupPolicy = '' ;
|
||||
}
|
||||
}
|
||||
if (isset($v["startupPolicy"]) && $vmclone ) {
|
||||
if ($v["startupPolicy"] == "optional" ) $startupPolicy = 'startupPolicy="optional"' ; else $startupPolicy = '' ;
|
||||
}
|
||||
|
||||
$usbstr .= "<hostdev mode='subsystem' type='usb'>
|
||||
<source $startupPolicy>
|
||||
<vendor id='0x".$usbx[0]."'/>
|
||||
<product id='0x".$usbx[1]."'/>
|
||||
</source>" ;
|
||||
if (!empty($usbboot[$v])) {
|
||||
if (!empty($usbboot[$v]) && !$vmclone ) {
|
||||
$usbstr .= "<boot order='".$usbboot[$v]."'/>" ;
|
||||
}
|
||||
}
|
||||
if (isset($v["usbboot"]) && $vmclone ) {
|
||||
if ($v["usbboot"] != NULL) $usbstr .= "<boot order='".$v["usbboot"]."'/>" ;
|
||||
}
|
||||
$usbstr .= "</hostdev>";
|
||||
}
|
||||
}
|
||||
@@ -774,17 +781,20 @@
|
||||
|
||||
if ($strAutoport == "yes") $strPort = $strWSport = "-1" ;
|
||||
if (($gpu['copypaste'] == "yes") && ($strProtocol == "spice")) $vmrcmousemode = "<mouse mode='server'/>" ; else $vmrcmousemode = "" ;
|
||||
if ($strProtocol == "spice") $virtualaudio = "spice" ; else $virtualaudio = "none" ;
|
||||
|
||||
$vmrc = "<input type='tablet' bus='usb'/>
|
||||
<input type='mouse' bus='ps2'/>
|
||||
<input type='keyboard' bus='ps2'/>
|
||||
<graphics type='$strProtocol' port='$strPort' autoport='$strAutoport' websocket='$strWSport' listen='0.0.0.0' $passwdstr $strKeyMap>
|
||||
<graphics type='$strProtocol' sharePolicy='ignore' port='$strPort' autoport='$strAutoport' websocket='$strWSport' listen='0.0.0.0' $passwdstr $strKeyMap>
|
||||
<listen type='address' address='0.0.0.0'/>
|
||||
$vmrcmousemode
|
||||
</graphics>
|
||||
<video>
|
||||
<model type='$strModelType'/>
|
||||
</video>";
|
||||
</video>
|
||||
<audio id='1' type='$virtualaudio'/>";
|
||||
|
||||
|
||||
if ($gpu['copypaste'] == "yes") {
|
||||
if ($strProtocol == "spice") {
|
||||
@@ -865,20 +875,23 @@
|
||||
if (empty($pci_id) || in_array($pci_id, $pcidevs_used)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$pci_bus, $pci_slot, $pci_function] = my_explode(":", str_replace('.', ':', $pci_id), 3);
|
||||
if ($vmclone) [$pci_bus, $pci_slot, $pci_function] = my_explode(":", str_replace('.', ':', $pci_id['id']), 3);
|
||||
else [$pci_bus, $pci_slot, $pci_function] = my_explode(":", str_replace('.', ':', $pci_id), 3);
|
||||
|
||||
$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>" ;
|
||||
if (!empty($pciboot[$pci_id])) {
|
||||
if (!empty($pciboot[$pci_id]) && !$vmclone) {
|
||||
$pcidevs .= "<boot order='".$pciboot[$pci_id]."'/>" ;
|
||||
}
|
||||
if (!empty($pci_id["boot"]) && $vmclone) {
|
||||
$pcidevs .= "<boot order='".$pci_id["boot"]."'/>" ;
|
||||
}
|
||||
$pcidevs .= "</hostdev>";
|
||||
|
||||
$pcidevs_used[] = $pci_id;
|
||||
if ($vmclone) $pcidevs_used[] = $pci_id['d']; else $pcidevs_used[] = $pci_id ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,7 +1157,7 @@
|
||||
$tmp = libvirt_domain_get_block_info($dom, $disks[$i]);
|
||||
if ($tmp) {
|
||||
$tmp['bus'] = $buses[$i];
|
||||
$tmp["boot order"] = $boot[$i] ;
|
||||
$tmp["boot order"] = $boot[$i] ?? "";
|
||||
$ret[] = $tmp;
|
||||
}
|
||||
else {
|
||||
@@ -1195,7 +1208,7 @@
|
||||
|
||||
if ($tmp) {
|
||||
$tmp['bus'] = $disk->target->attributes()->bus->__toString();
|
||||
$tmp["boot order"] = $disk->boot->attributes()->order ;
|
||||
$tmp["boot order"] = $disk->boot->attributes()->order ?? "";
|
||||
$tmp['serial'] = $disk->serial ;
|
||||
|
||||
// Libvirt reports 0 bytes for raw disk images that haven't been
|
||||
@@ -1454,7 +1467,7 @@
|
||||
function domain_change_xml($domain, $xml) {
|
||||
$dom = $this->get_domain_object($domain);
|
||||
|
||||
if (!($old_xml = domain_get_xml($dom)))
|
||||
if (!($old_xml = $this->domain_get_xml($dom)))
|
||||
return $this->_set_last_error();
|
||||
if (!libvirt_domain_undefine($dom))
|
||||
return $this->_set_last_error();
|
||||
@@ -1487,6 +1500,11 @@
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
function get_interface_addresses($domain,$flag) {
|
||||
$tmp = libvirt_domain_interface_addresses($domain,$flag);
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
function get_node_device_res($res) {
|
||||
if ($res == false)
|
||||
return false;
|
||||
@@ -1590,7 +1608,7 @@
|
||||
if (!$dom)
|
||||
return false;
|
||||
|
||||
$tmp = libvirt_domain_get_xml_desc($dom, $xpath);
|
||||
$tmp = libvirt_domain_get_xml_desc($dom, 0);
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
@@ -1854,6 +1872,48 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function nvram_create_snapshot($uuid,$snapshotname) {
|
||||
// snapshot backup OVMF VARS if this domain had them
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
|
||||
copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
|
||||
return true;
|
||||
}
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
|
||||
copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function nvram_revert_snapshot($uuid,$snapshotname) {
|
||||
// snapshot backup OVMF VARS if this domain had them
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
|
||||
copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
|
||||
unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd') ;
|
||||
return true;
|
||||
}
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
|
||||
copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
|
||||
unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd') ;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function nvram_delete_snapshot($uuid,$snapshotname) {
|
||||
// snapshot backup OVMF VARS if this domain had them
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
|
||||
unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd') ;
|
||||
return true;
|
||||
}
|
||||
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
|
||||
unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd') ;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function is_dir_empty($dir) {
|
||||
if (!is_readable($dir)) return NULL;
|
||||
$handle = opendir($dir);
|
||||
@@ -1992,7 +2052,7 @@
|
||||
|
||||
function domain_get_description($domain) {
|
||||
$tmp = $this->get_xpath($domain, '//domain/description', false);
|
||||
$var = $tmp[0];
|
||||
$var = $tmp[0] ?? "";
|
||||
unset($tmp);
|
||||
|
||||
return $var;
|
||||
@@ -2194,33 +2254,34 @@
|
||||
|
||||
// Get any pci devices contained in the qemu args
|
||||
$args = $this->get_xpath($domain, '//domain/*[name()=\'qemu:commandline\']/*[name()=\'qemu:arg\']/@value', false);
|
||||
if (isset($args['num'])) {
|
||||
for ($i = 0; $i < $args['num']; $i++) {
|
||||
if (strpos($args[$i], 'vfio-pci') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $args['num']; $i++) {
|
||||
if (strpos($args[$i], 'vfio-pci') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$arg_list = explode(',', $args[$i]);
|
||||
|
||||
$arg_list = explode(',', $args[$i]);
|
||||
foreach ($arg_list as $arg) {
|
||||
$keypair = explode('=', $arg);
|
||||
|
||||
foreach ($arg_list as $arg) {
|
||||
$keypair = explode('=', $arg);
|
||||
|
||||
if ($keypair[0] == 'host' && !empty($keypair[1])) {
|
||||
$devid = 'pci_0000_' . str_replace([':', '.'], '_', $keypair[1]);
|
||||
$tmp2 = $this->get_node_device_information($devid);
|
||||
[$bus, $slot, $func] = my_explode(":", str_replace('.', ':', $keypair[1]), 3);
|
||||
$devs[] = [
|
||||
'domain' => '0x0000',
|
||||
'bus' => '0x' . $bus,
|
||||
'slot' => '0x' . $slot,
|
||||
'func' => '0x' . $func,
|
||||
'id' => $keypair[1],
|
||||
'vendor' => $tmp2['vendor_name'],
|
||||
'vendor_id' => $tmp2['vendor_id'],
|
||||
'product' => $tmp2['product_name'],
|
||||
'product_id' => $tmp2['product_id']
|
||||
];
|
||||
break;
|
||||
if ($keypair[0] == 'host' && !empty($keypair[1])) {
|
||||
$devid = 'pci_0000_' . str_replace([':', '.'], '_', $keypair[1]);
|
||||
$tmp2 = $this->get_node_device_information($devid);
|
||||
[$bus, $slot, $func] = my_explode(":", str_replace('.', ':', $keypair[1]), 3);
|
||||
$devs[] = [
|
||||
'domain' => '0x0000',
|
||||
'bus' => '0x' . $bus,
|
||||
'slot' => '0x' . $slot,
|
||||
'func' => '0x' . $func,
|
||||
'id' => $keypair[1],
|
||||
'vendor' => $tmp2['vendor_name'],
|
||||
'vendor_id' => $tmp2['vendor_id'],
|
||||
'product' => $tmp2['product_name'],
|
||||
'product_id' => $tmp2['product_id']
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2249,15 +2310,17 @@
|
||||
$pid = $this->get_xpath($domain, $xpath.'product/@id', false);
|
||||
|
||||
$devs = [];
|
||||
for ($i = 0; $i < $vid['num']; $i++) {
|
||||
$dev = $this->_lookup_device_usb($vid[$i], $pid[$i]);
|
||||
$devs[] = [
|
||||
'id' => str_replace('0x', '', $vid[$i] . ':' . $pid[$i]),
|
||||
'vendor_id' => $vid[$i],
|
||||
'product_id' => $pid[$i],
|
||||
'product' => $dev['product_name'],
|
||||
'vendor' => $dev['vendor_name']
|
||||
];
|
||||
if (isset($vid['num'])) {
|
||||
for ($i = 0; $i < $vid['num']; $i++) {
|
||||
$dev = $this->_lookup_device_usb($vid[$i], $pid[$i]);
|
||||
$devs[] = [
|
||||
'id' => str_replace('0x', '', $vid[$i] . ':' . $pid[$i]),
|
||||
'vendor_id' => $vid[$i],
|
||||
'product_id' => $pid[$i],
|
||||
'product' => $dev['product_name'],
|
||||
'vendor' => $dev['vendor_name']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $devs;
|
||||
@@ -2291,7 +2354,7 @@
|
||||
'mac' => $macs[$i],
|
||||
'network' => $net[0],
|
||||
'model' => $model[0],
|
||||
'boot' => $boot[0]
|
||||
'boot' => $boot[0] ?? ""
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2469,6 +2532,12 @@
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
//list all snapshots for domain
|
||||
function domain_snapshot_get_xml($domain) {
|
||||
$tmp = libvirt_domain_snapshot_get_xml($domain);
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
// create a snapshot and metadata node for description
|
||||
function domain_snapshot_create($domain) {
|
||||
$this->domain_set_metadata($domain);
|
||||
@@ -2478,10 +2547,9 @@
|
||||
}
|
||||
|
||||
//delete snapshot and metadata
|
||||
function domain_snapshot_delete($domain, $name) {
|
||||
$this->snapshot_remove_metadata($domain, $name);
|
||||
function domain_snapshot_delete($domain, $name, $flags=0) {
|
||||
$name = $this->domain_snapshot_lookup_by_name($domain, $name);
|
||||
$tmp = libvirt_domain_snapshot_delete($name);
|
||||
$tmp = libvirt_domain_snapshot_delete($name,$flags);
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt.php";
|
||||
require_once "$docroot/webGui/include/Custom.php";
|
||||
|
||||
// Load emhttp variables if needed.
|
||||
if (!isset($var)){
|
||||
@@ -1203,6 +1204,7 @@ private static $encoding = 'UTF-8';
|
||||
$arrDisks[] = [
|
||||
'new' => $strPath,
|
||||
'size' => '',
|
||||
'driver' => $disk['type'],
|
||||
'driver' => 'raw',
|
||||
'dev' => $disk['device'],
|
||||
'bus' => $disk['bus'],
|
||||
@@ -1370,7 +1372,7 @@ private static $encoding = 'UTF-8';
|
||||
$vendor=$USB->source->vendor->attributes()->id ;
|
||||
$product=$USB->source->product->attributes()->id ;
|
||||
$startupPolicy=$USB->source->attributes()->startupPolicy ;
|
||||
$usbboot= $USB->boot->attributes()->order ;
|
||||
$usbboot= $USB->boot->attributes()->order ?? "" ;
|
||||
$id = str_replace('0x', '', $vendor . ':' . $product) ;
|
||||
$found = false ;
|
||||
foreach($arrValidUSBDevices as $key => $data) {
|
||||
@@ -1420,7 +1422,7 @@ private static $encoding = 'UTF-8';
|
||||
global $lv ;
|
||||
$xml = new SimpleXMLElement($lv->domain_get_xml($res)) ;
|
||||
$data = $xml->xpath('//channel/target[@name="org.qemu.guest_agent.0"]/@state') ;
|
||||
$data = $data[0]->state ;
|
||||
$data = $data[0]->state ?? null ;
|
||||
return $data ;
|
||||
}
|
||||
|
||||
@@ -1454,4 +1456,779 @@ private static $encoding = 'UTF-8';
|
||||
if ($spicevmc || $qemuvdaagent) $copypaste = true ; else $copypaste = false ;
|
||||
return $copypaste ;
|
||||
}
|
||||
|
||||
function vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID) {
|
||||
global $lv,$domain_cfg ;
|
||||
/*
|
||||
Clone.
|
||||
|
||||
Stopped only.
|
||||
|
||||
Get new VM Name
|
||||
Extract XML for VM to be cloned.
|
||||
Check if directory exists.
|
||||
Check for disk space
|
||||
|
||||
Stop VM Starting until clone is finished or fails.
|
||||
|
||||
Create new directory for Clone.
|
||||
Update paths with new directory
|
||||
|
||||
Create new UUID
|
||||
Create new MAC Address for NICs
|
||||
|
||||
Create VM Disks from source. Options full or Sparce. Method of copy?
|
||||
|
||||
release orginal VM to start.
|
||||
|
||||
If option to edit, show VMUpdate
|
||||
*/
|
||||
$uuid = $lv->domain_get_uuid($clone) ;
|
||||
write("addLog\0".htmlspecialchars(_("Checking if clone exists")));
|
||||
if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return false ;}
|
||||
#VM must be shutdown.
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
$vmxml = $lv->domain_get_xml($res) ;
|
||||
file_put_contents("/tmp/cloningxml" ,$vmxml) ;
|
||||
# if VM running shutdown. Record was running.
|
||||
if ($state != 'shutoff') {write("addLog\0".htmlspecialchars(_("Shuting down $vm current $state"))); $arrResponse = $lv->domain_destroy($vm) ; }
|
||||
# Wait for shutdown?
|
||||
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
|
||||
$capacity = 0 ;
|
||||
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
$filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
|
||||
$capacity = $capacity + $disk["capacity"] ;
|
||||
}
|
||||
$dirpath = $pathinfo["dirname"] ;
|
||||
|
||||
#Check free space.
|
||||
write("addLog\0".htmlspecialchars("Checking for free space"));
|
||||
$dirfree = disk_free_space($pathinfo["dirname"]) ;
|
||||
$sourcedir = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($pathinfo["dirname"])." 2>/dev/null"));
|
||||
$repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]);
|
||||
$repdirfree = disk_free_space($repdir) ;
|
||||
$reflink = true ;
|
||||
$capacity *= 1 ;
|
||||
|
||||
if ($free == "yes" && $repdirfree < $capacity) { $reflink = false ;}
|
||||
if ($free == "yes" && $dirfree < $capacity) { write("addLog\0".htmlspecialchars(_("Insufficent storage for clone"))); return false ;}
|
||||
|
||||
#Clone XML
|
||||
$uuid = $lv->domain_get_uuid($vm) ;
|
||||
$config=domain_to_config($uuid) ;
|
||||
|
||||
$config["domain"]["name"] = $clone ;
|
||||
$config["domain"]["uuid"] = $lv->domain_generate_uuid() ;
|
||||
foreach($config["nic"] as $index => $detail) {
|
||||
$config["nic"][$index]["mac"] = $lv->generate_random_mac_addr() ;
|
||||
}
|
||||
$config["domain"]["type"] = "kvm";
|
||||
|
||||
$usbs = getVMUSBs($vmxml) ;
|
||||
foreach($usbs as $i => $usb) {
|
||||
if ($usb["checked"] == "checked") continue ;
|
||||
unset($usbs[$i]) ;
|
||||
}
|
||||
$config["usb"] = $usbs ;
|
||||
|
||||
$files_exist = false ;
|
||||
$files_clone = array() ;
|
||||
foreach ($config["disk"] as $diskid => $disk) {
|
||||
$file_clone[$diskid]["source"] = $config["disk"][$diskid]["new"] ;
|
||||
$config["disk"][$diskid]["new"] = str_replace($vm,$clone,$config["disk"][$diskid]["new"]) ;
|
||||
$pi = pathinfo($config["disk"][$diskid]["new"]) ;
|
||||
$isdir = is_dir($pi['dirname']) ;
|
||||
if (is_file($config["disk"][$diskid]["new"])) $file_exists = true ;
|
||||
$file_clone[$diskid]["target"] = $config["disk"][$diskid]["new"] ;
|
||||
}
|
||||
|
||||
$clonedir = $domain_cfg['DOMAINDIR'].$clone ;
|
||||
if (!is_dir($clonedir)) {
|
||||
mkdir($clonedir,0777,true) ;
|
||||
chown($clonedir, 'nobody');
|
||||
chgrp($clonedir, 'users');
|
||||
}
|
||||
write("addLog\0".htmlspecialchars("Checking for image files"));
|
||||
if ($file_exists && $overwrite != "yes") { write("addLog\0".htmlspecialchars(_("New image file names exist and Overwrite is not allowed"))); return( false) ; }
|
||||
|
||||
#Create duplicate files.
|
||||
foreach($file_clone as $diskid => $disk) {
|
||||
$target = $disk['target'] ;
|
||||
$source = $disk['source'] ;
|
||||
if ($target == $source) { write("addLog\0".htmlspecialchars(_("New image file is same as old"))); return( false) ; }
|
||||
$sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null"));
|
||||
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
|
||||
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
|
||||
#var_dump($repsrc,$reptgt) ;
|
||||
|
||||
$cmdstr = "cp --reflink=always '$repsrc' '$reptgt'" ;
|
||||
if ($reflink == true) { $refcmd = $cmdstr ; } else {$refcmd = false; }
|
||||
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ;
|
||||
$error = execCommand_nchan($cmdstr,$path,$refcmd) ;
|
||||
if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; }
|
||||
}
|
||||
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Completing Clone").": </legend><p class='logLine'></p><span id='wait-$waitID'></span></fieldset>");
|
||||
write("addLog\0".htmlspecialchars("Creating new XML $clone"));
|
||||
|
||||
$xml = $lv->config_to_xml($config, true) ;
|
||||
file_put_contents("/tmp/clonexml" ,$xml) ;
|
||||
$rtn = $lv->domain_define($xml) ;
|
||||
return($rtn) ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function compare_creationtime($a, $b) {
|
||||
return strnatcmp($a['creationtime'], $b['creationtime']);
|
||||
}
|
||||
|
||||
function compare_creationtimelt($a, $b) {
|
||||
return $a['creationtime'] < $b['creationtime'];
|
||||
}
|
||||
|
||||
function getvmsnapshots($vm) {
|
||||
$snaps=array() ;
|
||||
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
|
||||
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
|
||||
$snaps = json_decode($snaps_json,true) ;
|
||||
if (is_array($snaps)) uasort($snaps,'compare_creationtime') ;
|
||||
return $snaps ;
|
||||
}
|
||||
|
||||
function write_snapshots_database($vm,$name) {
|
||||
global $lv ;
|
||||
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
|
||||
if (!is_dir($dbpath)) mkdir($dbpath) ;
|
||||
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
|
||||
$snaps = json_decode($snaps_json,true) ;
|
||||
$snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ;
|
||||
$snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ;
|
||||
$a = simplexml_load_string($snapshot_xml) ;
|
||||
$a = json_encode($a) ;
|
||||
$b= json_decode($a, TRUE);
|
||||
$vmsnap = $b["name"] ;
|
||||
$snaps[$vmsnap]["name"]= $b["name"];
|
||||
$snaps[$vmsnap]["parent"]= $b["parent"] ;
|
||||
$snaps[$vmsnap]["state"]= $b["state"];
|
||||
$snaps[$vmsnap]["desc"]= $b["description"];
|
||||
$snaps[$vmsnap]["memory"]= $b["memory"];
|
||||
$snaps[$vmsnap]["creationtime"]= $b["creationTime"];
|
||||
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = "" ;
|
||||
exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
}
|
||||
|
||||
$snaps[$vmsnap]['backing'][$disk["device"]] = $output ;
|
||||
$rev = "r".$disk["device"] ;
|
||||
$reversed = array_reverse($output) ;
|
||||
$snaps[$vmsnap]['backing'][$rev] = $reversed ;
|
||||
}
|
||||
$parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ;
|
||||
$parendfileinfo = pathinfo($parentfind[1]) ;
|
||||
$snaps[$vmsnap]["parent"]= $parendfileinfo["extension"];
|
||||
$snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ;
|
||||
if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ;
|
||||
|
||||
if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
|
||||
|
||||
|
||||
$value = json_encode($snaps,JSON_PRETTY_PRINT) ;
|
||||
file_put_contents($dbpath."/snapshots.db",$value) ;
|
||||
}
|
||||
|
||||
function refresh_snapshots_database($vm) {
|
||||
global $lv ;
|
||||
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
|
||||
if (!is_dir($dbpath)) mkdir($dbpath) ;
|
||||
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
|
||||
$snaps = json_decode($snaps_json,true) ;
|
||||
foreach($snaps as $vmsnap=>$snap)
|
||||
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = "" ;
|
||||
exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
}
|
||||
|
||||
$snaps[$vmsnap]['backing'][$disk["device"]] = $output ;
|
||||
$rev = "r".$disk["device"] ;
|
||||
$reversed = array_reverse($output) ;
|
||||
$snaps[$vmsnap]['backing'][$rev] = $reversed ;
|
||||
}
|
||||
$parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ;
|
||||
$parendfileinfo = pathinfo($parentfind[1]) ;
|
||||
$snaps[$vmsnap]["parent"]= $parendfileinfo["extension"];
|
||||
$snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ;
|
||||
if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ;
|
||||
|
||||
$value = json_encode($snaps,JSON_PRETTY_PRINT) ;
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ;
|
||||
|
||||
|
||||
#Remove any NVRAMs that are no longer valid.
|
||||
# Get uuid
|
||||
$vmuuid = $lv->domain_get_uuid($vm) ;
|
||||
#Get list of files
|
||||
#$filepath = "/etc/libvirt/qemu/nvram/'.$uuid*" ; #$snapshotname"
|
||||
$filepath = "/etc/libvirt/qemu/nvram/$vmuuid*" ; #$snapshotname"
|
||||
$nvram_files=glob($filepath) ;
|
||||
foreach($nvram_files as $key => $nvram_file) {
|
||||
if ($nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi.fd" || $nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi-tpm.fd" ) unset($nvram_files[$key]) ;
|
||||
foreach ($snaps as $snapshotname => $snap) {
|
||||
$tpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi-tpm.fd" ;
|
||||
$nontpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi.fd" ;
|
||||
if ($nvram_file == $tpmfilename || $nvram_file == $nontpmfilename ) {
|
||||
unset($nvram_files[$key]) ;}
|
||||
}
|
||||
}
|
||||
foreach ($nvram_files as $nvram_file) unlink($nvram_file) ;
|
||||
|
||||
|
||||
file_put_contents($dbpath."/snapshots.db",$value) ;
|
||||
}
|
||||
|
||||
function delete_snapshots_database($vm,$name) {
|
||||
global $lv ;
|
||||
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
|
||||
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
|
||||
$snaps = json_decode($snaps_json,true) ;
|
||||
unset($snaps[$name]) ;
|
||||
$value = json_encode($snaps,JSON_PRETTY_PRINT) ;
|
||||
file_put_contents($dbpath."/snapshots.db",$value) ;
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
||||
function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") {
|
||||
global $lv ;
|
||||
|
||||
#Get State
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
|
||||
#Get disks for --diskspec
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
$diskspec = "" ;
|
||||
$capacity = 0 ;
|
||||
if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ;
|
||||
if ($snapshotdesc != "") $snapshotdesc = " --description '$snapshotdesc'" ;
|
||||
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
$filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
|
||||
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ;
|
||||
$capacity = $capacity + $disk["capacity"] ;
|
||||
}
|
||||
$dirpath = $pathinfo["dirname"] ;
|
||||
#get memory
|
||||
$mem = $lv->domain_get_memory_stats($vm) ;
|
||||
$memory = $mem[6] ;
|
||||
|
||||
if ($memorysnap = "yes") $memspec = ' --memspec "'.$pathinfo["dirname"].'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ;
|
||||
$cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ;
|
||||
|
||||
|
||||
if ($state == "running") {
|
||||
$cmdstr .= " --live ".$memspec.$diskspec ;
|
||||
$capacity = $capacity + $memory ;
|
||||
|
||||
} else {
|
||||
$cmdstr .= " --disk-only ".$diskspec ;
|
||||
}
|
||||
|
||||
#Check free space.
|
||||
$dirfree = disk_free_space($pathinfo["dirname"]) ;
|
||||
|
||||
$capacity *= 1 ;
|
||||
|
||||
if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;}
|
||||
|
||||
#Copy nvram
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ;
|
||||
|
||||
$xmlfile = $pathinfo["dirname"]."/".$name.".running" ;
|
||||
file_put_contents("/tmp/xmltst", "$xmlfile" ) ;
|
||||
if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ;
|
||||
|
||||
$output= [] ;
|
||||
$test = false ;
|
||||
if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ;
|
||||
|
||||
if (strpos(" ".$output[0],"error") ) {
|
||||
$arrResponse = ['error' => substr($output[0],6) ] ;
|
||||
} else {
|
||||
$arrResponse = ['success' => true] ;
|
||||
write_snapshots_database("$vm","$name") ;
|
||||
#remove meta data
|
||||
$ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
|
||||
}
|
||||
return $arrResponse ;
|
||||
|
||||
}
|
||||
|
||||
function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') {
|
||||
global $lv ;
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
|
||||
switch ($snapslist[$snap]['state']) {
|
||||
case "shutoff":
|
||||
case "running":
|
||||
#VM must be shutdown.
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
# if VM running shutdown. Record was running.
|
||||
if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ;
|
||||
# Wait for shutdown?
|
||||
# GetXML
|
||||
$strXML= $lv->domain_get_xml($res) ;
|
||||
$xmlobj = custom::createArray('domain',$strXML) ;
|
||||
|
||||
# Process disks and update path.
|
||||
$disks=($snapslist[$snap]['disks']) ;
|
||||
foreach ($disks as $disk) {
|
||||
$diskname = $disk["@attributes"]["name"] ;
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
if ($diskname == "hdc") {
|
||||
$primarypathinfo = pathinfo($path) ;
|
||||
$primarypath = $primarypathinfo['dirname'] ;
|
||||
}
|
||||
$item = array_search($path,$snapslist[$snap]['backing'][$diskname]) ;
|
||||
$newpath = $snapslist[$snap]['backing'][$diskname][$item + 1];
|
||||
$json_info = getDiskImageInfo($newpath) ;
|
||||
foreach($xmlobj['devices']['disk'] as $ddk => $dd){
|
||||
if ($dd['target']["@attributes"]['dev'] == $diskname) {
|
||||
$xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = "$newpath" ;
|
||||
$xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ;
|
||||
}
|
||||
}
|
||||
}
|
||||
$xml = custom::createXML('domain',$xmlobj)->saveXML();
|
||||
$new = $lv->domain_define($xml);
|
||||
if ($new)
|
||||
$arrResponse = ['success' => true] ; else
|
||||
$arrResponse = ['error' => $lv->get_last_error()] ;
|
||||
|
||||
# remove snapshot meta data and images for all snpahots.
|
||||
|
||||
foreach ($disks as $disk) {
|
||||
$diskname = $disk["@attributes"]["name"] ;
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($path) && $action == "yes") unlink("$path") ;
|
||||
$item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ;
|
||||
$item++ ;
|
||||
while($item > 0)
|
||||
{
|
||||
if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ;
|
||||
$newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ;
|
||||
if (is_file($newpath) && $action == "yes") unlink("$newpath") ;
|
||||
$item++ ;
|
||||
}
|
||||
}
|
||||
|
||||
uasort($snapslist,'compare_creationtimelt') ;
|
||||
foreach($snapslist as $s) {
|
||||
$name = $s['name'] ;
|
||||
|
||||
$xmlfile = $primarypath."/$name.running" ;
|
||||
$memoryfile = $primarypath."/memory$name.mem" ;
|
||||
|
||||
if ($snapslist[$snap]['state'] == "running") {
|
||||
# Set XML to saved XML
|
||||
$xml = file_get_contents($xmlfile) ;
|
||||
$xmlobj = custom::createArray('domain',$xml) ;
|
||||
$xml = custom::createXML('domain',$xmlobj)->saveXML();
|
||||
$rtn = $lv->domain_define($xml) ;
|
||||
|
||||
# Restore Memory.
|
||||
|
||||
$makerun = true ;
|
||||
if ($makerun == true) exec("virsh restore ".escapeshellarg($memoryfile)) ;
|
||||
#exec("virsh restore $memoryfile") ;
|
||||
}
|
||||
#Delete Metadata only.
|
||||
if ($actionmeta == "yes") {
|
||||
$ret = delete_snapshots_database("$vm","$name") ;
|
||||
}
|
||||
if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ;
|
||||
if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ;
|
||||
if ($s['name'] == $snap) break ;
|
||||
}
|
||||
#if VM was started restart.
|
||||
if ($state == 'running' && $snapslist[$snap]['state'] != "running") {
|
||||
$arrResponse = $lv->domain_start($vm) ;
|
||||
}
|
||||
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ;
|
||||
break ;
|
||||
|
||||
}
|
||||
$arrResponse = ['success' => true] ;
|
||||
return($arrResponse) ;
|
||||
}
|
||||
|
||||
function vm_snapimages($vm, $snap, $only) {
|
||||
global $lv ;
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$data = "<br><br>Images and metadata to remove if tickbox checked.<br>" ;
|
||||
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = "" ;
|
||||
exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
}
|
||||
$snaps[$vm][$disk["device"]] = $output ;
|
||||
$rev = "r".$disk["device"] ;
|
||||
$reversed = array_reverse($output) ;
|
||||
$snaps[$vm][$rev] = $reversed ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
$filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
|
||||
$diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ;
|
||||
$capacity = $capacity + $disk["capacity"] ;
|
||||
}
|
||||
|
||||
$snapdisks= $snapslist[$snap]['disks'] ;
|
||||
|
||||
foreach ($snapdisks as $diskkey => $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($path)) $data .= "$path<br>" ;
|
||||
$item = array_search($path,$snaps[$vm]["r".$diskname]) ;
|
||||
$item++ ;
|
||||
if ($only == 0) $item = 0 ;
|
||||
while($item > 0)
|
||||
{
|
||||
if (!isset($snaps[$vm]["r".$diskname][$item])) break ;
|
||||
$newpath = $snaps[$vm]["r".$diskname][$item] ;
|
||||
if (is_file($path)) $data .= "$newpath<br>" ;
|
||||
$item++ ;
|
||||
|
||||
}
|
||||
}
|
||||
$data .= "<br>Snapshots metadata to remove." ;
|
||||
if ($only == 0) {
|
||||
$data .= "<br>$snap";
|
||||
} else {
|
||||
uasort($snapslist,'compare_creationtimelt') ;
|
||||
foreach($snapslist as $s) {
|
||||
$name = $s['name'] ;
|
||||
$data .= "<br>$name";
|
||||
if ($s['name'] == $snap) break ;
|
||||
}
|
||||
}
|
||||
return($data) ;
|
||||
}
|
||||
|
||||
|
||||
function vm_snapremove($vm, $snap) {
|
||||
global $lv ;
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = "" ;
|
||||
exec("qemu-img info --backing-chain -U $file | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
}
|
||||
|
||||
$snaps[$vm][$disk["device"]] = $output ;
|
||||
$rev = "r".$disk["device"] ;
|
||||
$reversed = array_reverse($output) ;
|
||||
$snaps[$vm][$rev] = $reversed ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
}
|
||||
|
||||
# GetXML
|
||||
$strXML= $lv->domain_get_xml($res) ;
|
||||
$xmlobj = custom::createArray('domain',$strXML) ;
|
||||
|
||||
# Process disks.
|
||||
$disks=($snapslist[$snap]['disks']) ;
|
||||
foreach ($disks as $disk) {
|
||||
$diskname = $disk["@attributes"]["name"] ;
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
$item = array_search($path,$snaps[$vm][$diskname]) ;
|
||||
if ($item!==false) {
|
||||
$data = ["error" => "Image currently active for this domain."] ;
|
||||
return ($data) ;
|
||||
}
|
||||
}
|
||||
|
||||
$disks=($snapslist[$snap]['disks']) ;
|
||||
foreach ($disks as $disk) {
|
||||
$diskname = $disk["@attributes"]["name"] ;
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($path)) {
|
||||
if(!unlink("$path")) {
|
||||
$data = ["error" => "Unable to remove image file $path"] ;
|
||||
return ($data) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Delete NVRAM
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ;
|
||||
|
||||
$ret = delete_snapshots_database("$vm","$snap") ;
|
||||
|
||||
if(!$ret)
|
||||
$data = ["error" => "Unable to remove snap metadata $snap"] ;
|
||||
else
|
||||
$data = ["success => 'true"] ;
|
||||
|
||||
return($data) ;
|
||||
}
|
||||
|
||||
function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) {
|
||||
global $lv ;
|
||||
/*
|
||||
NAME
|
||||
blockcommit - Start a block commit operation.
|
||||
|
||||
SYNOPSIS
|
||||
blockcommit <domain> <path> [--bandwidth <number>] [--base <string>] [--shallow] [--top <string>] [--active] [--delete] [--wait] [--verbose] [--timeout <number>] [--pivot] [--keep-overlay] [--async] [--keep-relative] [--bytes]
|
||||
|
||||
DESCRIPTION
|
||||
Commit changes from a snapshot down to its backing image.
|
||||
|
||||
OPTIONS
|
||||
[--domain] <string> domain name, id or uuid
|
||||
[--path] <string> fully-qualified path of disk
|
||||
--bandwidth <number> bandwidth limit in MiB/s
|
||||
--base <string> path of base file to commit into (default bottom of chain)
|
||||
--shallow use backing file of top as base
|
||||
--top <string> path of top file to commit from (default top of chain)
|
||||
--active trigger two-stage active commit of top file
|
||||
--delete delete files that were successfully committed
|
||||
--wait wait for job to complete (with --active, wait for job to sync)
|
||||
--verbose with --wait, display the progress
|
||||
--timeout <number> implies --wait, abort if copy exceeds timeout (in seconds)
|
||||
--pivot implies --active --wait, pivot when commit is synced
|
||||
--keep-overlay implies --active --wait, quit when commit is synced
|
||||
--async with --wait, don't wait for cancel to finish
|
||||
--keep-relative keep the backing chain relatively referenced
|
||||
--bytes the bandwidth limit is in bytes/s rather than MiB/s
|
||||
|
||||
blockcommit Debian --path /mnt/user/domains/Debian/vdisk1.S20230513120410qcow2 --verbose --pivot --delete
|
||||
*/
|
||||
# Error if VM Not running.
|
||||
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
|
||||
foreach($disks as $disk) {
|
||||
$path = $disk['file'] ;
|
||||
$cmdstr = "virsh blockcommit '$vm' --path '$path' --verbose " ;
|
||||
if ($pivot == "yes") $cmdstr .= " --pivot " ;
|
||||
if ($action == "yes") $cmdstr .= " --delete " ;
|
||||
# Process disks and update path.
|
||||
$snapdisks=($snapslist[$snap]['disks']) ;
|
||||
if ($base != "--base" && $base != "") {
|
||||
#get file name from snapshot.
|
||||
$snapdisks=($snapslist[$base]['disks']) ;
|
||||
$basepath = "" ;
|
||||
foreach ($snapdisks as $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname != $disk['device']) continue ;
|
||||
$basepath = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
}
|
||||
if ($basepath != "") $cmdstr .= " --base '$basepath' ";
|
||||
}
|
||||
if ($top != "--top" && $top !="") {
|
||||
#get file name from snapshot.
|
||||
$snapdisks=($snapslist[$top]['disks']) ;
|
||||
$toppath = "" ;
|
||||
foreach ($snapdisks as $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname != $disk['device']) continue ;
|
||||
$toppath = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
}
|
||||
if ($toppath != "") $cmdstr .= " --top '$toppath' ";
|
||||
}
|
||||
|
||||
$error = execCommand_nchan($cmdstr,$path) ;
|
||||
if (!$error) {
|
||||
$arrResponse = ['error' => substr($output[0],6) ] ;
|
||||
return($arrResponse) ;
|
||||
} else {
|
||||
$arrResponse = ['success' => true] ;
|
||||
}
|
||||
|
||||
}
|
||||
# Delete NVRAM
|
||||
#if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ;
|
||||
|
||||
refresh_snapshots_database($vm) ;
|
||||
$ret = $ret = delete_snapshots_database("$vm","$snap") ; ;
|
||||
if($ret)
|
||||
$data = ["error" => "Unable to remove snap metadata $snap"] ;
|
||||
else
|
||||
$data = ["success => 'true"] ;
|
||||
return $data ;
|
||||
|
||||
}
|
||||
|
||||
function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) {
|
||||
global $lv ;
|
||||
/*
|
||||
NAME
|
||||
blockpull - Populate a disk from its backing image.
|
||||
|
||||
SYNOPSIS
|
||||
blockpull <domain> <path> [--bandwidth <number>] [--base <string>] [--wait] [--verbose] [--timeout <number>] [--async] [--keep-relative] [--bytes]
|
||||
|
||||
DESCRIPTION
|
||||
Populate a disk from its backing image.
|
||||
|
||||
OPTIONS
|
||||
[--domain] <string> domain name, id or uuid
|
||||
[--path] <string> fully-qualified path of disk
|
||||
--bandwidth <number> bandwidth limit in MiB/s
|
||||
--base <string> path of backing file in chain for a partial pull
|
||||
--wait wait for job to finish
|
||||
--verbose with --wait, display the progress
|
||||
--timeout <number> with --wait, abort if pull exceeds timeout (in seconds)
|
||||
--async with --wait, don't wait for cancel to finish
|
||||
--keep-relative keep the backing chain relatively referenced
|
||||
--bytes the bandwidth limit is in bytes/s rather than MiB/s
|
||||
|
||||
|
||||
*/
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = "" ;
|
||||
exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
}
|
||||
$snaps[$vm][$disk["device"]] = $output ;
|
||||
$rev = "r".$disk["device"] ;
|
||||
$reversed = array_reverse($output) ;
|
||||
$snaps[$vm][$rev] = $reversed ;
|
||||
}
|
||||
$snaps_json=json_encode($snaps,JSON_PRETTY_PRINT) ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
$dirpath = $pathinfo["dirname"] ;
|
||||
file_put_contents("$dirpath/image.tracker",$snaps_json) ;
|
||||
|
||||
foreach($disks as $disk) {
|
||||
$path = $disk['file'] ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait " ;
|
||||
# Process disks and update path.
|
||||
$snapdisks=($snapslist[$snap]['disks']) ;
|
||||
if ($base != "--base" && $base != "") {
|
||||
#get file name from snapshot.
|
||||
$snapdisks=($snapslist[$base]['disks']) ;
|
||||
$basepath = "" ;
|
||||
foreach ($snapdisks as $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname != $disk['device']) continue ;
|
||||
$basepath = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
}
|
||||
if ($basepath != "") $cmdstr .= " --base '$basepath' ";
|
||||
}
|
||||
|
||||
if ($action) $cmdstr .= " $action ";
|
||||
|
||||
|
||||
$error = execCommand_nchan($cmdstr,$path) ;
|
||||
|
||||
if (!$error) {
|
||||
$arrResponse = ['error' => substr($output[0],6) ] ;
|
||||
return($arrResponse) ;
|
||||
} else {
|
||||
# Remove nvram snapshot
|
||||
$arrResponse = ['success' => true] ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
refresh_snapshots_database($vm) ;
|
||||
if($ret)
|
||||
$data = ["error" => "Unable to remove snap metadata $snap"] ;
|
||||
else
|
||||
$data = ["success => 'true"] ;
|
||||
|
||||
return $data ;
|
||||
|
||||
}
|
||||
|
||||
function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) {
|
||||
/*
|
||||
NAME
|
||||
blockcopy - Start a block copy operation.
|
||||
|
||||
SYNOPSIS
|
||||
blockcopy <domain> <path> [--dest <string>] [--bandwidth <number>] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout <number>] [--pivot] [--finish] [--async] [--xml <string>] [--format <string>] [--granularity <number>] [--buf-size <number>] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml]
|
||||
|
||||
DESCRIPTION
|
||||
Copy a disk backing image chain to dest.
|
||||
|
||||
OPTIONS
|
||||
[--domain] <string> domain name, id or uuid
|
||||
[--path] <string> fully-qualified path of source disk
|
||||
--dest <string> path of the copy to create
|
||||
--bandwidth <number> bandwidth limit in MiB/s
|
||||
--shallow make the copy share a backing chain
|
||||
--reuse-external reuse existing destination
|
||||
--blockdev copy destination is block device instead of regular file
|
||||
--wait wait for job to reach mirroring phase
|
||||
--verbose with --wait, display the progress
|
||||
--timeout <number> implies --wait, abort if copy exceeds timeout (in seconds)
|
||||
--pivot implies --wait, pivot when mirroring starts
|
||||
--finish implies --wait, quit when mirroring starts
|
||||
--async with --wait, don't wait for cancel to finish
|
||||
--xml <string> filename containing XML description of the copy destination
|
||||
--format <string> format of the destination file
|
||||
--granularity <number> power-of-two granularity to use during the copy
|
||||
--buf-size <number> maximum amount of in-flight data during the copy
|
||||
--bytes the bandwidth limit is in bytes/s rather than MiB/s
|
||||
--transient-job the copy job is not persisted if VM is turned off
|
||||
--synchronous-writes the copy job forces guest writes to be synchronously written to the destination
|
||||
--print-xml print the XML used to start the copy job instead of starting the job
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
|
||||
@@ -62,7 +62,7 @@ function ajaxVMDispatchconsoleRV(params, spin){
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web"){
|
||||
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web", preview=false){
|
||||
var opts = [];
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
@@ -106,6 +106,12 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({divider:true});
|
||||
|
||||
opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) {
|
||||
e.preventDefault();
|
||||
selectsnapshot(uuid , name, "--generate" , "create",false,state) ;
|
||||
}});
|
||||
} else if (state == "pmsuspended") {
|
||||
opts.push({text:_("Resume"), icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
@@ -141,14 +147,26 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
|
||||
ajaxVMDispatchconsoleRV({action:"domain-start-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
opts.push({divider:true});
|
||||
if (log !== "") {
|
||||
opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}});
|
||||
}
|
||||
opts.push({text:_("Edit"), icon:"fa-pencil", href:path+'/UpdateVM?uuid='+uuid});
|
||||
|
||||
if (state == "shutoff") {
|
||||
|
||||
opts.push({text:_("Clone"), icon:"fa-clone", action:function(e) {
|
||||
e.preventDefault();
|
||||
var clonename = VMClone(uuid,name) ;
|
||||
}});
|
||||
|
||||
opts.push({divider:true});
|
||||
opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) {
|
||||
e.preventDefault();
|
||||
selectsnapshot(uuid , name, "--generate" , "create",false,state) ;
|
||||
}});
|
||||
opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) {
|
||||
e.preventDefault();
|
||||
swal({
|
||||
@@ -182,6 +200,50 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
|
||||
}
|
||||
context.attach('#vm-'+uuid, opts);
|
||||
}
|
||||
function addVMSnapContext(name, uuid, template, state, snapshotname, preview=false){
|
||||
var opts = [];
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
|
||||
context.settings({right:false,above:false});
|
||||
if (state == "running") {
|
||||
|
||||
opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) {
|
||||
e.preventDefault();
|
||||
selectsnapshot(uuid, name, snapshotname, "revert",true) ;
|
||||
}});
|
||||
|
||||
opts.push({text:_("Block Commit"), icon:"fa-hdd-o", action:function(e) {
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
e.preventDefault();
|
||||
selectblock(uuid, name, snapshotname, "commit",true) ;
|
||||
}});
|
||||
|
||||
opts.push({text:_("Block Pull"), icon:"fa-hdd-o", action:function(e) {
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
e.preventDefault();
|
||||
selectblock(uuid, name, snapshotname, "pull",true) ;
|
||||
}});
|
||||
if (preview) {
|
||||
opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist");
|
||||
}}); }
|
||||
} else {
|
||||
opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) {
|
||||
e.preventDefault();
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
selectsnapshot(uuid, name, snapshotname, "revert",true) ;
|
||||
}});
|
||||
}
|
||||
opts.push({text:_("Remove snapshot"), icon:"fa-trash", action:function(e) {
|
||||
e.preventDefault();
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
selectsnapshot(uuid, name, snapshotname, "remove",true) ;
|
||||
}});
|
||||
context.attach('#vmsnap-'+uuid, opts);
|
||||
}
|
||||
function startAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,vm; vm=kvm[i]; i++) if (vm.state!='running') $('#vm-'+vm.id).parent().find('i').removeClass('fa-square').addClass('fa-refresh fa-spin');
|
||||
@@ -225,3 +287,5 @@ function addVM() {
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
location = path+"/VMTemplates";
|
||||
}
|
||||
|
||||
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = '';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
function write(...$messages){
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/vmaction?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
}
|
||||
function execCommand_nchan($command,$idx) {
|
||||
$waitID = mt_rand();
|
||||
[$cmd,$args] = explode(' ',$command,2);
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br> -",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
|
||||
write("addLog\0<br>") ;
|
||||
write("addToID\0$idx\0 $action") ;
|
||||
$proc = popen("$command 2>&1",'r');
|
||||
while ($out = fgets($proc)) {
|
||||
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
|
||||
if (substr($out,0,1) == "B") { ;
|
||||
write("progress\0$idx\0".htmlspecialchars(substr($out,strrpos($out,"Block Pull")))) ;
|
||||
} else echo write("addToID\0$idx\0 ".htmlspecialchars($out));
|
||||
}
|
||||
$retval = pclose($proc);
|
||||
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
|
||||
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
|
||||
return $retval===0;
|
||||
}
|
||||
|
||||
#{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc}
|
||||
#VM ID [ 99]: pull. .Block Pull: [ 0 %]Block Pull: [100 %].Pull complete.
|
||||
$url = rawurldecode($argv[1]??'');
|
||||
$waitID = mt_rand();
|
||||
$style = ["<style>"];
|
||||
$style[] = ".logLine{font-family:bitstream!important;font-size:1.2rem!important;margin:0;padding:0}";
|
||||
$style[] = "fieldset.docker{border:solid thin;margin-top:8px}";
|
||||
$style[] = "legend{font-size:1.1rem!important;font-weight:bold}";
|
||||
$style[] = "</style>";
|
||||
|
||||
foreach (explode('&', $url) as $chunk) {
|
||||
$param = explode("=", $chunk);
|
||||
if ($param) {
|
||||
${urldecode($param[0])} = urldecode($param[1]) ;
|
||||
}
|
||||
}
|
||||
$id = 1 ;
|
||||
write(implode($style)."<p class='logLine'></p>");
|
||||
$process = " " ;
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Options for Block $action").": </legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>");
|
||||
write("addLog\0".htmlspecialchars("VMName $name "));
|
||||
write("addLog\0".htmlspecialchars("SNAP $snapshotname "));
|
||||
write("addLog\0".htmlspecialchars("Base $targetbase "));
|
||||
if ($action == "commit") {
|
||||
write("addLog\0".htmlspecialchars("Top $targettop "));
|
||||
write("addLog\0".htmlspecialchars("Pivot $targetpivot "));
|
||||
write("addLog\0".htmlspecialchars("Delete $targetdelete "));
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case "commit":
|
||||
vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$targetpivot,$targetdelete) ;
|
||||
break ;
|
||||
case "copy":
|
||||
vm_blockcopy($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ;
|
||||
break;
|
||||
case "pull":
|
||||
vm_blockpull($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ;
|
||||
break ;
|
||||
|
||||
}
|
||||
#execCommand_nchan("ls /") ;
|
||||
write("stop_Wait\0$waitID") ;
|
||||
write('_DONE_','');
|
||||
|
||||
?>
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = '';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
function write(...$messages){
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/vmaction?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
}
|
||||
function execCommand_nchan($command,$idx,$refcmd=false) {
|
||||
$waitID = mt_rand();
|
||||
if ($refcmd) {
|
||||
[$cmd,$args] = explode(' ',$refcmd,2);
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br> -",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
|
||||
$rtn = exec("$refcmd 2>&1", $output,$return) ;
|
||||
if ($return == 0) $reflinkok = true ; else {
|
||||
$reflinkok = false ;
|
||||
write("addLog\0<br><b>{$output[0]}</b>");
|
||||
}
|
||||
$out = $return ? _('The command failed revert to rsync')."." : _('The command finished successfully').'!';
|
||||
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
|
||||
}
|
||||
|
||||
if ($reflinkok) {
|
||||
return true ;
|
||||
} else {
|
||||
$waitID = mt_rand();
|
||||
[$cmd,$args] = explode(' ',$command,2);
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br> -",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
|
||||
|
||||
write("addToID\0$idx\0Cloning VM: ") ;
|
||||
$proc = popen("$command 2>&1 &",'r');
|
||||
while ($out = fread($proc,100)) {
|
||||
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
|
||||
$out = trim($out) ;
|
||||
$values = explode(' ',$out) ;
|
||||
$string = _("Data copied: ").$values[0].' '._(" Percentage: ").$values[1].' '._(" Transfer Rate: ").$values[2].' '._(" Time remaining: ").$values[4].$values[5] ;
|
||||
write("progress\0$idx\0".htmlspecialchars($string)) ;
|
||||
if ($out) $stringsave=$string ;
|
||||
}
|
||||
$retval = pclose($proc);
|
||||
write("progress\0$idx\0".htmlspecialchars($stringsave)) ;
|
||||
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
|
||||
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
|
||||
return $retval===0;
|
||||
}
|
||||
}
|
||||
|
||||
#{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc}
|
||||
#VM ID [ 99]: pull. .Block Pull: [ 0 %]Block Pull: [100 %].Pull complete.
|
||||
$url = rawurldecode($argv[1]??'');
|
||||
$waitID = mt_rand();
|
||||
$style = ["<style>"];
|
||||
$style[] = ".logLine{font-family:bitstream!important;font-size:1.2rem!important;margin:0;padding:0}";
|
||||
$style[] = "fieldset.docker{border:solid thin;margin-top:8px}";
|
||||
$style[] = "legend{font-size:1.1rem!important;font-weight:bold}";
|
||||
$style[] = "</style>";
|
||||
|
||||
foreach (explode('&', $url) as $chunk) {
|
||||
$param = explode("=", $chunk);
|
||||
if ($param) {
|
||||
${urldecode($param[0])} = urldecode($param[1]) ;
|
||||
}
|
||||
}
|
||||
$id = 1 ;
|
||||
write(implode($style)."<p class='logLine'></p>");
|
||||
$process = " " ;
|
||||
$actiontxt = ucfirst($action) ;
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Options for $actiontxt").": </legend><p class='logLine'></p></fieldset>");
|
||||
write("addLog\0".htmlspecialchars("Cloning $name to $clone"));
|
||||
|
||||
switch ($action) {
|
||||
case "clone":
|
||||
$rtn = vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID) ;
|
||||
break ;
|
||||
}
|
||||
write("stop_Wait\0$waitID") ;
|
||||
if ($rtn) write('_DONE_',''); else write('_ERROR_','');
|
||||
?>
|
||||
@@ -181,6 +181,7 @@ div.last{padding-bottom:12px}
|
||||
div.leftside{float:left;width:66%}
|
||||
div.rightside{float:right;margin:0;text-align:center}
|
||||
div[id$=chart]{margin:-12px 8px -24px -18px}
|
||||
div.template,div#dialogWindow,input#upload{display:none}
|
||||
span.green,span.red,span.orange{padding-left:0}
|
||||
span.ctrl{float:right;margin-top:0;margin-right:10px}
|
||||
span.ctrl span{font-size:2rem!important}
|
||||
@@ -814,6 +815,77 @@ var netchart = new ApexCharts(document.querySelector('#netchart'), options_net);
|
||||
if ($.cookie('port_select')!=null && !ports.includes($.cookie('port_select'))) $.removeCookie('port_select');
|
||||
var port_select = $.cookie('port_select')||ports[0];
|
||||
|
||||
function selectsnapshot(uuid, name ,snaps, opt, getlist){
|
||||
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow2");
|
||||
box.html($("#templatesnapshot"+opt).html());
|
||||
var height = 200;
|
||||
const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ;
|
||||
var optiontext = Capopt + " Snapshot" ;
|
||||
box.find('#VMName').html(name) ;
|
||||
box.find('#targetsnap').val(snaps) ;
|
||||
box.find('#targetsnapl').html(snaps) ;
|
||||
if (getlist) {
|
||||
var only = 1 ;
|
||||
if (opt == "remove") only = 0;
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid , snapshotname:snaps, only:only}, function(data) {
|
||||
if (data.html) {
|
||||
box.find('#targetsnapimages').html(data.html) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("targetsnapfspc").checked = true ;
|
||||
|
||||
box.dialog({
|
||||
title: "_("+optiontext+ ")_",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 500,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
|
||||
buttons: {
|
||||
"_(Proceed)_": function(){
|
||||
var target = box.find('#targetsnap');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
var remove = 'yes'
|
||||
var keep = 'yes'
|
||||
var removemeta = 'yes'
|
||||
var free = 'yes'
|
||||
var desc = ''
|
||||
box.find('#targetsnap').prop('disabled',true);
|
||||
if (opt == "revert") {
|
||||
const x = box.find('#targetsnaprmv').prop('checked') ;
|
||||
if (x) remove = 'yes' ; else remove = 'no' ;
|
||||
x = box.find('#targetsnaprmvmeta').prop('checked') ;
|
||||
if (x) removemeta = 'yes' ; else removemeta = 'no' ;
|
||||
x = box.find('#targetsnapkeep').prop('checked') ;
|
||||
if (x) keep = 'yes' ; else keep = 'no' ;
|
||||
}
|
||||
if (opt == "create") {
|
||||
const x = box.find('#targetsnapfspc').prop('checked') ;
|
||||
if (x) free = 'yes' ; else free = 'no' ;
|
||||
var desc = box.find("#targetsnapdesc").prop('value') ;
|
||||
}
|
||||
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free, desc:desc } , "loadlist");
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
|
||||
function initCharts(clear) {
|
||||
$.post('/webGui/include/InitCharts.php',{cmd:'get'},function(data) {
|
||||
data = JSON.parse(data);
|
||||
@@ -1504,4 +1576,110 @@ $(function() {
|
||||
$.post('/webGui/include/InitCharts.php',{cmd:'set',data:JSON.stringify(data)});
|
||||
});
|
||||
});
|
||||
function dialogStyle() {
|
||||
$('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('<i class="fa fa-times"></i>').prop('title');
|
||||
$('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'});
|
||||
$('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'});
|
||||
$('.ui-button-text').css({'padding':'0px 5px'});
|
||||
}
|
||||
function VMClone(uuid, name){
|
||||
|
||||
//var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow");
|
||||
var height = 200;
|
||||
box.html($("#templateClone").html());
|
||||
box.find('#VMBeingCloned').html(name).change() ;
|
||||
box.find('#target').val(name + "_clone") ;
|
||||
document.getElementById("Free").checked = true ;
|
||||
document.getElementById("Overwrite").checked = true ;
|
||||
var Cancel = _("Cancel") ;
|
||||
box.dialog({
|
||||
title: "VM Clone",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 500,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
buttons: {
|
||||
"_(Clone)_" : function(){
|
||||
var target = box.find('#target');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
//if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
|
||||
var clone = box.find("#target").prop('value') ;
|
||||
x = box.find('#Start').prop('checked') ;
|
||||
if (x) start = 'yes' ; else start = 'no' ;
|
||||
x = box.find('#Edit').prop('checked') ;
|
||||
if (x) edit = 'yes' ; else edit = 'no' ;
|
||||
x = box.find('#Overwrite').prop('checked') ;
|
||||
if (x) overwrite = 'yes' ; else overwrite = 'no' ;
|
||||
x = box.find('#Free').prop('checked') ;
|
||||
if (x) free = 'yes' ; else free = 'no' ;
|
||||
scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone" , name:name ,clone:clone, overwrite:overwrite , edit:edit, start,start, free:free})) ;
|
||||
openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist") ;
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="dialogWindow"></div>
|
||||
<div markdown="1" id="templateClone" class="template">
|
||||
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=1600">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="same-origin">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<table>
|
||||
<tr><td>_(VM Being Cloned)_:</td>
|
||||
<td><span id="VMBeingCloned"></span></td></tr>
|
||||
|
||||
<tr><td>_(New VM)_:</td>
|
||||
<td><input type="text" id="target" autocomplete="off" spellcheck="false" value="" onclick="this.select()"></td></tr>
|
||||
|
||||
<tr><td>_(Overwrite)_:</td>
|
||||
<td><input type="checkbox" id="Overwrite" value="" ></td></tr>
|
||||
|
||||
<tr hidden><td>_(Start Cloned VM)_:</td>
|
||||
<td><input type="checkbox" id="Start" value="" ></td></tr>
|
||||
|
||||
<tr hidden><td>_(Edit VM after clone)_:</td>
|
||||
<td><input type="checkbox" id="Edit" value="" ></td></tr>
|
||||
|
||||
<tr><td>_(Check Free Space)_:</td>
|
||||
<td><input type="checkbox" id="Free" value="" ></td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
||||
<div id="dialogWindow2"></div>
|
||||
<div markdown="1" id="templatesnapshotcreate" class="template">
|
||||
<table id='snapshot'>
|
||||
<br><br>
|
||||
<tr><td> _(VM Name)_:</td><td>
|
||||
<label id="VMName"></label></td></tr>
|
||||
<tr><td>_(Snapshot Name)_:</td><td>
|
||||
<input type="text" id="targetsnap" autocomplete="off" spellcheck="false" value="--generate" onclick="this.select()">
|
||||
_(Check free space)_:
|
||||
<input type="checkbox" id="targetsnapfspc" checked></td></tr>
|
||||
<tr><td>_(Description )_:</td><td>
|
||||
<input type="text" id="targetsnapdesc" autocomplete="off" spellcheck="false" value="" onclick="this.select()"></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -342,6 +342,29 @@ function openDocker(cmd,title,plg,func,start=0,button=0) {
|
||||
$('button.confirm').prop('disabled',button==0);
|
||||
});
|
||||
}
|
||||
function openVMAction(cmd,title,plg,func,start=0,button=0) {
|
||||
// start = 0 : run command only when not already running (default)
|
||||
// start = 1 : run command unconditionally
|
||||
// button = 0 : hide CLOSE button (default)
|
||||
// button = 1 : show CLOSE button
|
||||
nchan_vmaction.start();
|
||||
$.post('/webGui/include/StartCommand.php',{cmd:cmd,start:start},function(pid) {
|
||||
if (pid==0) {
|
||||
nchan_vmaction.stop();
|
||||
$('div.spinner.fixed').hide();
|
||||
$(".upgrade_notice").addClass('alert');
|
||||
return;
|
||||
}
|
||||
swal({title:title,text:"<pre id='swaltext'></pre><hr>",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:"<?=_('Close')?>"},function(close){
|
||||
nchan_vmaction.stop();
|
||||
$('div.spinner.fixed').hide();
|
||||
$('.sweet-alert').hide('fast').removeClass('nchan');
|
||||
setTimeout(function(){bannerAlert("<?=_('Attention - operation continues in background')?> ["+pid.toString().padStart(8,'0')+"]<i class='fa fa-bomb fa-fw abortOps' title=\"<?=_('Abort background process')?>\" onclick='abortOperation("+pid+")'></i>",cmd,plg,func,start);});
|
||||
});
|
||||
$('.sweet-alert').addClass('nchan');
|
||||
$('button.confirm').prop('disabled',button==0);
|
||||
});
|
||||
}
|
||||
function abortOperation(pid) {
|
||||
swal({title:"<?=_('Abort background operation')?>",text:"<?=_('This may leave an unknown state')?>",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"<?=_('Proceed')?>",cancelButtonText:"<?=_('Cancel')?>"},function(){
|
||||
$.post('/webGui/include/StartCommand.php',{kill:pid},function() {
|
||||
@@ -387,6 +410,14 @@ function openDone(data) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function openError(data) {
|
||||
if (data == '_ERROR_') {
|
||||
$('div.spinner.fixed').hide();
|
||||
$('button.confirm').text("<?=_('Error')?>").prop('disabled',false).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function showStatus(name,plugin,job) {
|
||||
$.post('/webGui/include/ProcessStatus.php',{name:name,plugin:plugin,job:job},function(status){$(".tabs").append(status);});
|
||||
}
|
||||
@@ -919,7 +950,54 @@ nchan_docker.on('message', function(data) {
|
||||
}
|
||||
box.scrollTop(box[0].scrollHeight);
|
||||
});
|
||||
|
||||
var nchan_vmaction = new NchanSubscriber('/sub/vmaction',{subscriber:'websocket'});
|
||||
nchan_vmaction.on('message', function(data) {
|
||||
if (!data || openDone(data) || openError(data)) return;
|
||||
var box = $('pre#swaltext');
|
||||
data = data.split('\0');
|
||||
switch (data[0]) {
|
||||
case 'addLog':
|
||||
var rows = document.getElementsByClassName('logLine');
|
||||
if (rows.length) {
|
||||
var row = rows[rows.length-1];
|
||||
row.innerHTML += data[1]+'<br>';
|
||||
}
|
||||
break;
|
||||
case 'progress':
|
||||
var rows = document.getElementsByClassName('progress-'+data[1]);
|
||||
if (rows.length) {
|
||||
rows[rows.length-1].textContent = data[2];
|
||||
}
|
||||
break;
|
||||
case 'addToID':
|
||||
var rows = document.getElementById(data[1]);
|
||||
if (rows === null) {
|
||||
rows = document.getElementsByClassName('logLine');
|
||||
if (rows.length) {
|
||||
var row = rows[rows.length-1];
|
||||
row.innerHTML += '<span id="'+data[1]+'">'+data[1]+': <span class="content">'+data[2]+'</span><span class="progress-'+data[1]+'"></span>.</span><br>';
|
||||
}
|
||||
} else {
|
||||
var rows_content = rows.getElementsByClassName('content');
|
||||
if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) {
|
||||
rows.innerHTML += '<span class="content">'+data[2]+'</span><span class="progress-'+data[1]+'"></span>.';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'show_Wait':
|
||||
progress_span[data[1]] = document.getElementById('wait-'+data[1]);
|
||||
progress_dots[data[1]] = setInterval(function(){if (((progress_span[data[1]].innerHTML += '.').match(/\./g)||[]).length > 9) progress_span[data[1]].innerHTML = progress_span[data[1]].innerHTML.replace(/\.+$/,'');},500);
|
||||
break;
|
||||
case 'stop_Wait':
|
||||
clearInterval(progress_dots[data[1]]);
|
||||
progress_span[data[1]].innerHTML = '';
|
||||
break;
|
||||
default:
|
||||
box.html(box.html()+data[0]);
|
||||
break;
|
||||
}
|
||||
box.scrollTop(box[0].scrollHeight);
|
||||
});
|
||||
var backtotopoffset = 250;
|
||||
var backtotopduration = 500;
|
||||
$(window).scroll(function() {
|
||||
|
||||
Reference in New Issue
Block a user