From 3d3544e5c68a6332c07357cca6e8a427d65afe9e Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 26 Feb 2023 14:14:03 +0000 Subject: [PATCH 001/212] Initial Commit. --- plugins/dynamix.vm.manager/CloneVM.page | 111 +++++++ .../dynamix.vm.manager/include/VMMachines.php | 24 +- .../dynamix.vm.manager/include/VMclone.php | 272 ++++++++++++++++++ .../dynamix.vm.manager/include/libvirt.php | 5 + .../javascript/vmmanager.js | 56 ++++ 5 files changed, 457 insertions(+), 11 deletions(-) create mode 100644 plugins/dynamix.vm.manager/CloneVM.page create mode 100644 plugins/dynamix.vm.manager/include/VMclone.php diff --git a/plugins/dynamix.vm.manager/CloneVM.page b/plugins/dynamix.vm.manager/CloneVM.page new file mode 100644 index 000000000..b74cbccbc --- /dev/null +++ b/plugins/dynamix.vm.manager/CloneVM.page @@ -0,0 +1,111 @@ +Title="Clone VM" +Tag="clipboard" +Cond="(pgrep('libvirtd')!==false)" +Markdown="false" +--- + + + + + + +
+
+lang=""> + + + + + + + +"> + + +_(VM Being Cloned)_: +
+ +_(New VM)_: +
+ +_(Overwrite)_: +
+ +_(Start Cloned VM)_: +
+ +_(Edit VM after clone)_: +
+ + + +
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index f69f39cbf..27b11961b 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -191,21 +191,22 @@ foreach ($vms as $vm) { /* Display VM IP Addresses "execute":"guest-network-get-interfaces" --pretty */ echo " "._('Interfaces').""._('Type').""._('IP Address').""._('Prefix').""; $gastate = getgastate($res) ; - if ($gastate == "connected") { - $ip = $lv->domain_qemu_agent_command($res, '{"execute":"guest-network-get-interfaces"}', 10, 0) ; + if ($gastate == "connected") { + #$ip = $lv->domain_qemu_agent_command($res, '{"execute":"guest-network-get-interfaces"}', 10, 0) ; + $ip = $lv->get_interface_addresses($res, 1) ; if ($ip != false) { - $ip = json_decode($ip,true) ; - $ip = $ip["return"] ; + #$ip = json_decode($ip,true) ; + #$ip = $ip["return"] ; $duplicates = []; // hide duplicate interface names foreach ($ip as $arrIP) { $ipname = $arrIP["name"] ; if (preg_match('/^(lo|Loopback)/',$ipname)) continue; // omit loopback interface - $iphdwadr = $arrIP["hardware-address"] == "" ? _("N/A") : $arrIP["hardware-address"] ; - $iplist = $arrIP["ip-addresses"] ; + $iphdwadr = $arrIP["hwaddr"] == "" ? _("N/A") : $arrIP["hwaddr"] ; + $iplist = $arrIP["addrs"] ; foreach ($iplist as $arraddr) { - $ipaddrval = $arraddr["ip-address"] ; + $ipaddrval = $arraddr["addr"] ; if (preg_match('/^f[c-f]/',$ipaddrval)) continue; // omit ipv6 private addresses - $iptype = $arraddr["ip-address-type"] ; + $iptype = $arraddr["type"] ? "ipv6" : "ipv4" ; $ipprefix = $arraddr["prefix"] ; $ipnamemac = "$ipname ($iphdwadr)"; if (!in_array($ipnamemac,$duplicates)) $duplicates[] = $ipnamemac; else $ipnamemac = ""; @@ -213,10 +214,11 @@ foreach ($vms as $vm) { } } } - } else { + } + else { if ($gastate == "disconnected") echo ""._('Guest agent not installed').""; - else echo ""._('Guest not running')."" ; - } + else echo ""._('Guest not running')."" ; + } echo ""; echo ""; } diff --git a/plugins/dynamix.vm.manager/include/VMclone.php b/plugins/dynamix.vm.manager/include/VMclone.php new file mode 100644 index 000000000..233b74256 --- /dev/null +++ b/plugins/dynamix.vm.manager/include/VMclone.php @@ -0,0 +1,272 @@ + + '', + 'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'], + 'autostart' => false, + 'form' => $arrAllTemplates[$strSelectedTemplate]['form'], + 'state' => 'shutoff' +]; +$strIconURL = '/plugins/dynamix.vm.manager/templates/images/'.$arrLoad['icon']; + +if (isset($_GET['uuid'])) { + // Edit VM mode + $res = $lv->domain_get_domain_by_uuid(unscript($_GET['uuid'])); + + if ($res === false) { + echo "

"._('Invalid VM to edit').".

"; + return; + } + + $strIconURL = $lv->domain_get_icon_url($res); + $arrLoad = [ + 'name' => $lv->domain_get_name($res), + 'icon' => basename($strIconURL), + 'autostart' => $lv->domain_get_autostart($res), + 'form' => $arrAllTemplates[$strSelectedTemplate]['form'], + 'state' => $lv->domain_get_state($res) + ]; + + if (empty($_GET['template'])) { + // read vm-template attribute + $strTemplateOS = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@os'); + $strLibreELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@libreelec'); + $strOpenELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@openelec'); + if ($strLibreELEC) $strSelectedTemplate = 'LibreELEC'; + elseif ($strOpenELEC) $strSelectedTemplate = 'OpenELEC'; + elseif ($strTemplateOS) { + $strSelectedTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name'); + } else { + // legacy VM support for <6.2 but need it going forward too + foreach ($arrAllTemplates as $strName => $arrTemplate) { + if (!empty($arrTemplate) && !empty($arrTemplate['os']) && $arrTemplate['os'] == $strTemplateOS) { + $strSelectedTemplate = $strName; + break; + } + } + } + if (empty($strSelectedTemplate) || empty($arrAllTemplates[$strSelectedTemplate])) { + $strSelectedTemplate = 'Windows 10'; //default to Windows 10 + } + } + $arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form']; +} +?> + + + + + + + +
+
+ + + + + + + + +
_(Icon)_: + + +
+
+ '/plugins/dynamix.vm.manager/templates/images/', + "$docroot/boot/config/plugins/dynamix.vm.manager/templates/images/*.png" => '/boot/config/plugins/dynamix.vm.manager/templates/images/' + ]; + foreach ($arrImagePaths as $strGlob => $strIconURLBase) { + foreach (glob($strGlob) as $png_file) { + echo '

'.basename($png_file,'.png').'

'; + } + } + ?> +
+
+
+ + + + + + +
_(Autostart)_:
>
+
+

If you want this VM to start with the array, set this to yes.

+
+ +
'.parse_file("$docroot/plugins/dynamix.vm.manager/templates/{$arrLoad['form']}",false))?>
+ +
+
+ + + + + + diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index 7c1674043..6fbbb297d 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -1441,6 +1441,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; diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index a2cd3f2d9..a85f1dbbb 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -2,6 +2,42 @@ function displayconsole(url) { window.open(url, '_blank', 'scrollbars=yes,resizable=yes'); } +function getCloneName(name){ + var root = "" ; + var match= ".iso" ; + var box = $("#dialogWindow"); + box.html($("#templateClone").html()); + + box.find('#VMBeingCloned').html(name).change() ; + + var height = 100; + box.dialog({ + title: "Enter Clone Name", + resizable: false, + width: 600, + height: 300, + modal: true, + show: {effect:'fade', duration:250}, + hide: {effect:'fade', duration:250}, + buttons: { + "_(Insert)_": function(){ + var target = box.find('#target'); + if (target.length) { + target = target.val(); + if (!target ) {errorTarget(); return;} + } else target = ''; + box.find('#target').prop('disabled',true); + ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:target}, "loadlist"); ; + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + function ajaxVMDispatch(params, spin){ if (spin) $('#vm-'+params['uuid']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); $.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data) { @@ -103,7 +139,27 @@ function addVMContext(name, uuid, template, state, vmrcurl,vmrcprotocol , 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}); + opts.push({text:_("Clone2"), icon:"fa-clone", href:path+'/CloneVM?uuid='+uuid}); + if (state == "shutoff") { + + opts.push({text:_("Clone"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + var clonename = getCloneName(name) ; + /*swal({ + title:_("Enter"), + text:_("From VM:")+name, + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); + ajaxVMDispatch({action:"domain-clone", uuid:uuid }, "loadlist"); + }) ;*/ + + }}); + opts.push({divider:true}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); From 8020bd3518614f1bd5f3d1a3aad5958fd90e37f6 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 6 May 2023 00:00:36 +0100 Subject: [PATCH 002/212] Add snapshots to view --- .../dynamix.vm.manager/include/VMMachines.php | 23 ++++++++++++++++--- plugins/dynamix.vm.manager/include/VMajax.php | 5 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index 7e103c663..c36e56026 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -51,6 +51,7 @@ foreach ($vms as $vm) { $icon = $lv->domain_get_icon_url($res); $image = substr($icon,-4)=='.png' ? "" : (substr($icon,0,5)=='icon-' ? "" : ""); $arrConfig = domain_to_config($uuid); + $snapshots = getvmsnapshots($vm) ; if ($state == 'running') { $mem = $dom['memory']/1024; } else { @@ -123,12 +124,13 @@ foreach ($vms as $vm) { } /* VM information */ + if ($snapshots != null) $snapshotstr = _("(Snapshots :").count($snapshots).')' ; else $snapshotstr = _("(Snapshots :None)") ; echo ""; - echo "$image$vm
"._($status)."
"; + echo "$image$vm
"._($status)." $snapshotstcount
"; echo "$desc"; echo "$vcpu"; echo "$mem"; - echo "$disks"; + echo "$disks
$snapshotstr
"; echo "$graphics"; echo ""; @@ -218,7 +220,22 @@ foreach ($vms as $vm) { if ($gastate == "disconnected") echo ""._('Guest agent not installed').""; else echo ""._('Guest not running').""; } - echo ""; + echo ""; + /* Display VM Snapshots */ + if ($snapshots != null) { + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole); + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; + echo ""; + foreach($snapshots as $snapshotname => $snapshot) { + $snapshotstate = $snapshot["state"] ; + $snapshotmemory = $snapshot["memory"]["@attributes"]["snapshot"] ; + $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; + $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; + echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + } + echo ""; +} + echo ""; echo ""; } echo "\0".implode($kvm); diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 4ee16c5b9..dd816b899 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -300,6 +300,11 @@ case 'snap-create': : ['error' => $lv->get_last_error()]; break; +case 'snap-create-external': + requireLibvirt(); + $arrResponse = vm_snapshot($domName) ; + break; + case 'snap-delete': requireLibvirt(); $arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap']) From f104dfcf2ba3ac60529c7d010b0a244eab7cda0f Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 7 May 2023 19:39:34 +0100 Subject: [PATCH 003/212] Initial Snapshot and revert function --- .../dynamix.vm.manager/include/VMMachines.php | 7 +- plugins/dynamix.vm.manager/include/VMajax.php | 5 + .../dynamix.vm.manager/include/libvirt.php | 24 +- .../include/libvirt_helpers.php | 238 ++++++++++++++++++ .../javascript/vmmanager.js | 80 ++++++ 5 files changed, 348 insertions(+), 6 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index c36e56026..a1df722ee 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -223,14 +223,15 @@ foreach ($vms as $vm) { echo ""; /* Display VM Snapshots */ if ($snapshots != null) { - $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole); + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; echo ""; foreach($snapshots as $snapshotname => $snapshot) { - $snapshotstate = $snapshot["state"] ; - $snapshotmemory = $snapshot["memory"]["@attributes"]["snapshot"] ; + $snapshotstate = _(ucfirst($snapshot["state"])) ; + $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; } echo ""; diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index dd816b899..1ebb5b4f6 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -305,6 +305,11 @@ case 'snap-create-external': $arrResponse = vm_snapshot($domName) ; break; +case 'snap-revert-external': + requireLibvirt(); + $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; + break; + case 'snap-delete': requireLibvirt(); $arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap']) diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index eaa144ef7..66a9c8e7f 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -1846,6 +1846,19 @@ return false; } + function nvram_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 is_dir_empty($dir) { if (!is_readable($dir)) return NULL; $handle = opendir($dir); @@ -2447,6 +2460,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); @@ -2456,10 +2475,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(); } diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 7bf82d5cd..9bbb72582 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -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'], @@ -1454,4 +1456,240 @@ private static $encoding = 'UTF-8'; if ($spicevmc || $qemuvdaagent) $copypaste = true ; else $copypaste = false ; return $copypaste ; } + + function compare_creationtime($a, $b) { + return strnatcmp($a['creationtime'], $b['creationtime']); + } + + function compare_creationtimelt($a, $b) { + return $a['creationtime'] < $b['creationtime']; + } + + function getvmsnapshots($vm) { + global $lv ; + $vmsnaps = $lv->domain_snapshots_list($lv->get_domain_object($vm)) ; + $snaps=array() ; + foreach($vmsnaps as $vmsnap) { + $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$vmsnap) ; + $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; + $a = simplexml_load_string($snapshot_xml) ; + if($a == false) continue ; + $a = json_encode($a) ; + $b= json_decode($a, TRUE); + $vmsnap = $b["name"] ; + $snaps[$vmsnap]["name"]= $b["name"]; + if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + $snaps[$vmsnap]["state"]= $b["state"]; + $snaps[$vmsnap]["memory"]= $b["memory"]; + $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; + $snaps[$vmsnap]["disks"]= $b["disks"]; + } + + if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; + return $snaps ; + } + + function vm_snapshot($vm,$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 ; + $name= "S" . date("YmdHis") ; + $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; + + 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"] ; + } + + #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 --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 ($dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} + + #Copy nvram + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_snapshot($lv->domain_get_uuid($vm),$name) ; + + $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 { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + + return $arrResponse ; + + } + + function vm_revert($vm, $snap="--current",$action="no") { + global $lv ; + $snapslist= getvmsnapshots($vm) ; + + #echo "Revert Vm: $vm snap: $snap\n" ; + + $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"] ; + } + + 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']["disk"]) ; + 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]) ; + $newpath = $snaps[$vm][$diskname][$item + 1]; + $json_info = getDiskImageInfo($newpath) ; + #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + 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()] ; + + #echo "update xml \n" ; + + + # 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"] ; + #echo "rm $path\n" ; + if (is_file($path) && $action == "yes") unlink($path) ; + $item = array_search($path,$snaps[$vm]["r".$diskname]) ; + $item++ ; + while($item > 0) + { + if (!isset($snaps[$vm]["r".$diskname][$item])) break ; + $newpath = $snaps[$vm]["r".$diskname][$item] ; + # rm $newpath + if (is_file($path) && $action == "yes") unlink($newpath) ; + #echo "rm $newpath\n" ; + + $item++ ; + + } + } + + uasort($snapslist,'compare_creationtimelt') ; + foreach($snapslist as $s) { + #var_dump($s['name']) ; + $name = $s['name'] ; + #echo "delete snapshot --metadata ".$s["name"]."\n" ; + #Delete Metadata only. + if ($action == "yes") { + $ret = $lv->domain_snapshot_delete($vm, $name ,2) ; + #echo "Error: $ret\n" ; + } #else echo "Run domain_snapshot_delete($vm, $name ,2\n" ; + if ($s['name'] == $snap) break ; + } + #if VM was started restart. + if ($state == 'running') { + #echo "Restart VM\n" ; + $arrResponse = $lv->domain_start($vm) ; + } #else echo "VM Shutdown\n" ; + + + break ; + + + case "other": + break ; + #VM must be shutdown. + # if VM running shutdown. Record was running. + # Replace disk paths + # remove snapshot meta data and images for all snpahots. + # if VM was started restart. + + + #type running + #Non Live restores + #VM must be shutdown. + # if VM running shutdown. Record was running. + # Replace disk paths + # remove snapshot meta data and images for all snpahots. + # if VM was started restart. + + #Live restore(currently not supported.) + # Freeze VM + # Replace disk paths + # Replace Mem + # remove snapshot meta data and images for all snpahots. + # Unfreeze VM + } + $arrResponse = ['success' => true] ; + return($arrResponse) ; + } + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 5cea015df..dbd941570 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -143,6 +143,21 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } }} opts.push({divider:true}); + opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Create snapshot for VM: ")+name, + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); + ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + }); + }}); + opts.push({divider:true}); if (log !== "") { opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}}); } @@ -182,6 +197,71 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } context.attach('#vm-'+uuid, opts); } +function addVMSnapContext(name, uuid, template, state, snapshotname){ + 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-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); +// }}); +// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +// 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-stop", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Revert Snapshot ") + snapshotname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be removed.", + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); + ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); + }); + }}); +// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +} + + if (state == "shutoff") { + opts.push({divider:true}); +// opts.push({text:_("Remove Snapshot"), icon:"fa-minus", action:function(e) { +// e.preventDefault(); +// snapname = "test" ; +// swal({ +// title:_("Are you sure?"), +// text:_("Remove Snapshot:") + snapname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be become invalid.", +// type:"warning", +// showCancelButton:true, +// confirmButtonText:_('Proceed'), +// cancelButtonText:_('Cancel') +// },function(){ +// $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); +// ajaxVMDispatch({action:"snap-remove-external", uuid:uuid,snapshotname:snapshotname}, "loadlist"); +// }); +// }}); + } + 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'); From 7af43228c85f2a04ae200bc7b0d2166b2c1ac99d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 7 May 2023 20:04:38 +0100 Subject: [PATCH 004/212] Change Snapshot option to show only for shutdown VMs. --- .../javascript/vmmanager.js | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index dbd941570..cb7d25560 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -143,27 +143,26 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } }} opts.push({divider:true}); - opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { - e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Create snapshot for VM: ")+name, - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); - ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "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({divider:true}); + opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Create snapshot for VM: ")+name, + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); + ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + }); + }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); swal({ From a953d903f4bff4ced22190e6b87182630943c7e7 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Mon, 8 May 2023 17:05:01 +0100 Subject: [PATCH 005/212] Add popups and delete function. Error with checkbox on revert page and real file delete is disabled --- plugins/dynamix.vm.manager/VMMachines.page | 100 +++++++++++++- plugins/dynamix.vm.manager/include/VMajax.php | 13 +- .../include/libvirt_helpers.php | 128 +++++++++++++++++- .../javascript/vmmanager.js | 34 ++--- 4 files changed, 246 insertions(+), 29 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 1981a9b8d..3460d1416 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -203,8 +203,68 @@ function getisoimage(uuid,dev,bus,file){ }); dialogStyle(); } + +function selectsnapshot(uuid, name ,snaps, opt, getlist){ + + var root = ; + 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 ; + + 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 = ''; + box.find('#targetsnap').prop('disabled',true); + if (opt == "revert") { + const x = document.querySelector('#targetsnaprmv') ; + if (x.checked) remove = 'yes' ; else remove = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:"yes" } , "loadlist"); + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + +// ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); + function dialogStyle() { - $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').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('').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 +364,43 @@ $(function() {
_(ISO Image)_: : +
+ +
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+ +
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+_(Remove Images)_: +
+ +
+ + +
+ +
+_(!! Warning removing Snapshots can break the chain !!)_

+ +_(VM Name)_: + +
+_(Snapshot Name)_: + +
+ +
+
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 1ebb5b4f6..6c7a05d90 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -302,7 +302,13 @@ case 'snap-create': case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname']) ; + break; + +case 'snap-images': + requireLibvirt(); + $html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ; + $arrResponse = ['html' => $html , 'success' => true] ; break; case 'snap-revert-external': @@ -310,6 +316,11 @@ case 'snap-revert-external': $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; 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']) diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 9bbb72582..3a8c24a68 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1489,7 +1489,7 @@ private static $encoding = 'UTF-8'; return $snaps ; } - function vm_snapshot($vm,$memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { global $lv ; #Get State @@ -1501,7 +1501,7 @@ private static $encoding = 'UTF-8'; $disks =$lv->get_disk_stats($vm) ; $diskspec = "" ; $capacity = 0 ; - $name= "S" . date("YmdHis") ; + if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; foreach($disks as $disk) { @@ -1691,5 +1691,127 @@ private static $encoding = 'UTF-8'; $arrResponse = ['success' => true] ; return($arrResponse) ; } - + + function vm_snapimages($vm, $snap, $only) { + global $lv ; + $snapslist= getvmsnapshots($vm) ; + $data = "

Images and metadata to remove if tickbox checked.
" ; + + #echo "Revert Vm: $vm snap: $snap\n" ; + + $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"] ; + } + #var_dump($snaps) ; + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + #echo "rm $path\n" ; + if (is_file($path)) $data .= "$path
" ; + $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] ; + # rm $newpath + if (is_file($path)) $data .= "$path
" ; + #echo "rm $newpath\n" ; + + $item++ ; + + } + } + $data .= "
Snapshots metadata to remove." ; + if ($only == 0) { + $data .= "
$snap"; + } else { + uasort($snapslist,'compare_creationtimelt') ; + foreach($snapslist as $s) { + $name = $s['name'] ; + $data .= "
$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) ; + $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; + $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $capacity = $capacity + $disk["capacity"] ; + } + + # GetXML + $strXML= $lv->domain_get_xml($res) ; + $xmlobj = custom::createArray('domain',$strXML) ; + + # Process disks and update path. + $disks=($snapslist[$snap]['disks']["disk"]) ; + 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) { + #if currently attached to VM error. + $data = ["error" => "Image currently active for this domain."] ; + return ($data) ; + } + #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + } + + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + #if (is_file($path)) unlink($path) ; + } + + #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + $data = ["success => 'true"] ; + return($data) ; + } + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index cb7d25560..d7ef4d57f 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -149,19 +149,9 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c opts.push({text:_("Edit"), icon:"fa-pencil", href:path+'/UpdateVM?uuid='+uuid}); if (state == "shutoff") { opts.push({divider:true}); - opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Create snapshot for VM: ")+name, - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); - ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); - }); + selectsnapshot(uuid , name, "--generate" , "create",false) ; }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); @@ -217,19 +207,15 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); // }}); } else { - opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Revert Snapshot ") + snapshotname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be removed.", - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); - ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); - }); + $('#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(Delete Disabled)"), 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) ; }}); // opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { // e.preventDefault(); From 5e4bd81b4bcdba7850543c6cec2ef0f9fd8a166a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 17:25:17 +0100 Subject: [PATCH 006/212] Updates to snapshots. --- plugins/dynamix.vm.manager/VMMachines.page | 74 ++++++++++++++++-- plugins/dynamix.vm.manager/include/VMajax.php | 36 ++++++++- .../dynamix.vm.manager/include/libvirt.php | 17 ++++- .../include/libvirt_helpers.php | 76 +++++++------------ 4 files changed, 149 insertions(+), 54 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 3460d1416..b90156384 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -134,7 +134,7 @@ i.mover{margin-right:8px;display:none} .dropdown-menu{z-index:10001} - +
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks)__(Graphics)__(Autostart)_
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks/vCDs)__(Graphics)__(Autostart)_
@@ -163,7 +163,56 @@ 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 = ; + 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: { + "_(Insert)_": 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 = ; @@ -227,6 +276,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ } document.getElementById("targetsnaprmv").checked = true ; + document.getElementById("targetsnapfspc").checked = true ; box.dialog({ title: "_("+optiontext+ ")_", @@ -244,12 +294,18 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ target = target.val(); if (!target ) {errorTarget(); return;} } else target = ''; + var remove = 'yes' + var free = 'yes' box.find('#targetsnap').prop('disabled',true); if (opt == "revert") { - const x = document.querySelector('#targetsnaprmv') ; - if (x.checked) remove = 'yes' ; else remove = 'no' ; + const x = box.find('#targetsnaprmv').prop('checked') ; + if (x) remove = 'yes' ; else remove = 'no' ; } - ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:"yes" } , "loadlist"); + if (opt == "create") { + const x = box.find('#targetsnapfspc').prop('checked') ; + if (x) free = 'yes' ; else free = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -365,6 +421,12 @@ $(function() { _(ISO Image)_: : +
+_(CD1 ISO Image)_: +:
+_(CD2 ISO Image)_: +: +
@@ -373,6 +435,8 @@ _(VM Name)_:
_(Snapshot Name)_: + _(Check free space)_: +
@@ -383,7 +447,7 @@ _(Snapshot Name)_:
_(Remove Images)_: -
+
diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 6c7a05d90..fe1b839bf 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -263,6 +263,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,7 +336,7 @@ case 'snap-create': case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname']) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['free']) ; break; case 'snap-images': diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index 66a9c8e7f..bbf5bbac4 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -1846,7 +1846,7 @@ return false; } - function nvram_snapshot($uuid,$snapshotname) { + 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'); @@ -1859,6 +1859,21 @@ 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 is_dir_empty($dir) { if (!is_readable($dir)) return NULL; $handle = opendir($dir); diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 3a8c24a68..d7a14e3fe 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1482,14 +1482,14 @@ private static $encoding = 'UTF-8'; $snaps[$vmsnap]["state"]= $b["state"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; - $snaps[$vmsnap]["disks"]= $b["disks"]; + if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; } if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; return $snaps ; } - function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { global $lv ; #Get State @@ -1502,13 +1502,13 @@ private static $encoding = 'UTF-8'; $diskspec = "" ; $capacity = 0 ; if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; - $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic " ; 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 ; + $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; $capacity = $capacity + $disk["capacity"] ; } @@ -1516,8 +1516,8 @@ private static $encoding = 'UTF-8'; $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 --atomic" ; + if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory"'.$name.'".mem,snapshot=external' ; else $memspec = "" ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic" ; if ($state == "running") { @@ -1533,10 +1533,10 @@ private static $encoding = 'UTF-8'; $capacity *= 1 ; - #if ($dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} + 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_snapshot($lv->domain_get_uuid($vm),$name) ; + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; $test = false ; if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; @@ -1563,7 +1563,7 @@ private static $encoding = 'UTF-8'; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; - exec("qemu-img info --backing-chain -U $file | grep image:",$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 ; @@ -1581,7 +1581,6 @@ private static $encoding = 'UTF-8'; 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); @@ -1594,7 +1593,7 @@ private static $encoding = 'UTF-8'; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks and update path. - $disks=($snapslist[$snap]['disks']["disk"]) ; + $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1602,10 +1601,9 @@ private static $encoding = 'UTF-8'; $item = array_search($path,$snaps[$vm][$diskname]) ; $newpath = $snaps[$vm][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; - #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; 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]['source']["@attributes"]['file'] = "$newpath" ; $xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ; } } @@ -1615,27 +1613,21 @@ private static $encoding = 'UTF-8'; if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ; - - #echo "update xml \n" ; - - + # 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"] ; - #echo "rm $path\n" ; - if (is_file($path) && $action == "yes") unlink($path) ; + if (is_file($path) && $action == "yes") unlink("$path") ; $item = array_search($path,$snaps[$vm]["r".$diskname]) ; $item++ ; while($item > 0) { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - # rm $newpath - if (is_file($path) && $action == "yes") unlink($newpath) ; - #echo "rm $newpath\n" ; + if (is_file($path) && $action == "yes") unlink("$newpath") ; $item++ ; @@ -1644,23 +1636,20 @@ private static $encoding = 'UTF-8'; uasort($snapslist,'compare_creationtimelt') ; foreach($snapslist as $s) { - #var_dump($s['name']) ; $name = $s['name'] ; - #echo "delete snapshot --metadata ".$s["name"]."\n" ; #Delete Metadata only. if ($action == "yes") { - $ret = $lv->domain_snapshot_delete($vm, $name ,2) ; - #echo "Error: $ret\n" ; - } #else echo "Run domain_snapshot_delete($vm, $name ,2\n" ; + $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; + } if ($s['name'] == $snap) break ; } #if VM was started restart. if ($state == 'running') { #echo "Restart VM\n" ; $arrResponse = $lv->domain_start($vm) ; - } #else echo "VM Shutdown\n" ; - + } + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; break ; @@ -1708,8 +1697,7 @@ private static $encoding = 'UTF-8'; $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } - - $snaps[$vm][$disk["device"]] = $output ; + $snaps[$vm][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; @@ -1718,13 +1706,12 @@ private static $encoding = 'UTF-8'; $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; $capacity = $capacity + $disk["capacity"] ; } - #var_dump($snaps) ; - $disks=($snapslist[$snap]['disks']["disk"]) ; - foreach ($disks as $disk) { - $diskname = $disk["@attributes"]["name"] ; + + $snapdisks= $snapslist[$snap]['disks'] ; + foreach ($snapdisks as $diskkey => $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; - $path = $disk["source"]["@attributes"]["file"] ; - #echo "rm $path\n" ; + $path = $snapdisk["source"]["@attributes"]["file"] ; if (is_file($path)) $data .= "$path
" ; $item = array_search($path,$snaps[$vm]["r".$diskname]) ; $item++ ; @@ -1733,10 +1720,7 @@ private static $encoding = 'UTF-8'; { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - # rm $newpath - if (is_file($path)) $data .= "$path
" ; - #echo "rm $newpath\n" ; - + if (is_file($path)) $data .= "$newpath
" ; $item++ ; } @@ -1787,26 +1771,24 @@ private static $encoding = 'UTF-8'; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks and update path. - $disks=($snapslist[$snap]['disks']["disk"]) ; + $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) { - #if currently attached to VM error. $data = ["error" => "Image currently active for this domain."] ; return ($data) ; - } - #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + } } - $disks=($snapslist[$snap]['disks']["disk"]) ; + $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)) unlink($path) ; + #if (is_file($path)) unlink("$path") ; } #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; From 3907be975848077f722cc136db94dddcd2eb47b3 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 17:25:53 +0100 Subject: [PATCH 007/212] Add CD view to main VM detail --- .../dynamix.vm.manager/include/VMMachines.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index a1df722ee..a19303379 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -52,6 +52,7 @@ foreach ($vms as $vm) { $image = substr($icon,-4)=='.png' ? "" : (substr($icon,0,5)=='icon-' ? "" : ""); $arrConfig = domain_to_config($uuid); $snapshots = getvmsnapshots($vm) ; + $cdroms = $lv->get_cdrom_stats($res) ; if ($state == 'running') { $mem = $dom['memory']/1024; } else { @@ -124,13 +125,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 "; echo ""; echo "$image$vm
"._($status)." $snapshotstcount
"; echo "$desc"; echo "$vcpu"; echo "$mem"; - echo "$disks
$snapshotstr
"; + echo "$disks    $cdstr
$snapshotstr
"; echo "$graphics"; echo ""; @@ -170,7 +191,7 @@ 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']; @@ -226,13 +247,15 @@ foreach ($vms as $vm) { echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; echo ""; + $tab = "    " ; foreach($snapshots as $snapshotname => $snapshot) { $snapshotstate = _(ucfirst($snapshot["state"])) ; $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); - echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + echo "$tab|__   ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + $tab .="    " ; } echo ""; } From 8011ce4533d1e0e4842f5268295a6c076411cc07 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 18:57:43 +0100 Subject: [PATCH 008/212] Add snpshot option to Dashboard --- plugins/dynamix.vm.manager/VMMachines.page | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index b90156384..fa0de4111 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -121,6 +121,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} @@ -200,7 +201,6 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ 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); @@ -301,7 +301,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ const x = box.find('#targetsnaprmv').prop('checked') ; if (x) remove = 'yes' ; else remove = 'no' ; } - if (opt == "create") { + if (opt == "create") { const x = box.find('#targetsnapfspc').prop('checked') ; if (x) free = 'yes' ; else free = 'no' ; } From 4c835ba2faf9c431f81bf991d9b96b05a64713a9 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 18:58:04 +0100 Subject: [PATCH 009/212] Add snapshot option to Dashboard --- plugins/dynamix/DashStats.page | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index 05274201c..85d5167e3 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -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.ctrl{float:right;margin-top:0;margin-right:10px} span.ctrl span{font-size:2rem!important} span.outer{float:left} @@ -723,6 +724,69 @@ 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 = ; + 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 free = 'yes' + box.find('#targetsnap').prop('disabled',true); + if (opt == "revert") { + const x = box.find('#targetsnaprmv').prop('checked') ; + if (x) remove = 'yes' ; else remove = 'no' ; + } + if (opt == "create") { + const x = box.find('#targetsnapfspc').prop('checked') ; + if (x) free = 'yes' ; else free = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "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); @@ -1341,4 +1405,22 @@ $(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('').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'}); +} + + +
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + + _(Check free space)_: +
+
From 0ad1be3c23996abaf510b9e9a72c5296a827a63d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 13 May 2023 12:46:35 +0100 Subject: [PATCH 010/212] Updates to snapshots --- plugins/dynamix.vm.manager/VMMachines.page | 45 ++++-- .../dynamix.vm.manager/include/VMMachines.php | 3 +- plugins/dynamix.vm.manager/include/VMajax.php | 4 +- .../include/libvirt_helpers.php | 135 +++++++++++++++--- .../javascript/vmmanager.js | 2 +- plugins/dynamix/DashStats.page | 16 ++- 6 files changed, 167 insertions(+), 38 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index fa0de4111..5438d131a 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -135,7 +135,7 @@ i.mover{margin-right:8px;display:none} .dropdown-menu{z-index:10001} - +
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks/vCDs)__(Graphics)__(Autostart)_
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks / vCDs)__(Graphics)__(Autostart)_
@@ -192,7 +192,7 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ show: {effect:'fade', duration:250}, hide: {effect:'fade', duration:250}, buttons: { - "_(Insert)_": function(){ + "_(Update)_": function(){ var target = box.find('#target'); if (target.length) { target = target.val(); @@ -253,7 +253,7 @@ function getisoimage(uuid,dev,bus,file){ dialogStyle(); } -function selectsnapshot(uuid, name ,snaps, opt, getlist){ +function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ var root = ; var match= ".iso"; @@ -276,6 +276,8 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ } document.getElementById("targetsnaprmv").checked = true ; + document.getElementById("targetsnaprmvmeta").checked = true ; + document.getElementById("targetsnapkeep").checked = true ; document.getElementById("targetsnapfspc").checked = true ; box.dialog({ @@ -295,17 +297,25 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ 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') ; + 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") { - const x = box.find('#targetsnapfspc').prop('checked') ; + 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 } , "loadlist"); + 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(){ @@ -430,16 +440,21 @@ _(CD2 ISO Image)_:
-_(VM Name)_: - -
-_(Snapshot Name)_: + +

+ + + + +
_(VM Name)_: +
_(Snapshot Name)_: _(Check free space)_: -
+
_(Description )_: +
-
+
_(VM Name)_:
@@ -447,7 +462,11 @@ _(Snapshot Name)_:
_(Remove Images)_: - + +_(Remove Meta)_: + + +

diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index a19303379..d269f27a8 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -250,11 +250,12 @@ foreach ($vms as $vm) { $tab = "    " ; foreach($snapshots as $snapshotname => $snapshot) { $snapshotstate = _(ucfirst($snapshot["state"])) ; + $snapshotdesc = $snapshot["desc"] ; $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); - echo "$tab|__   ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + echo "$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; $tab .="    " ; } echo ""; diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index fe1b839bf..7f3906913 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -336,7 +336,7 @@ case 'snap-create': case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['free']) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; case 'snap-images': @@ -347,7 +347,7 @@ case 'snap-images': case 'snap-revert-external': requireLibvirt(); - $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; + $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove'], $_REQUEST['removemeta']) ; break; case 'snap-remove-external': diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index d7a14e3fe..302b66e53 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1480,6 +1480,7 @@ private static $encoding = 'UTF-8'; $snaps[$vmsnap]["name"]= $b["name"]; if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; $snaps[$vmsnap]["state"]= $b["state"]; + $snaps[$vmsnap]["desc"]= $b["description"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; @@ -1489,7 +1490,7 @@ private static $encoding = 'UTF-8'; return $snaps ; } - function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") { global $lv ; #Get State @@ -1502,8 +1503,8 @@ private static $encoding = 'UTF-8'; $diskspec = "" ; $capacity = 0 ; if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; - $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic " ; - + if ($snapshotdesc != "") $snapshotdesc = " --description '$snapshotdesc'" ; + foreach($disks as $disk) { $file = $disk["file"] ; $pathinfo = pathinfo($file) ; @@ -1517,7 +1518,7 @@ private static $encoding = 'UTF-8'; $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' --atomic" ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ; if ($state == "running") { @@ -1552,12 +1553,9 @@ private static $encoding = 'UTF-8'; } - function vm_revert($vm, $snap="--current",$action="no") { + function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { global $lv ; $snapslist= getvmsnapshots($vm) ; - - #echo "Revert Vm: $vm snap: $snap\n" ; - $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { @@ -1627,7 +1625,7 @@ private static $encoding = 'UTF-8'; { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - if (is_file($path) && $action == "yes") unlink("$newpath") ; + if (is_file($newpath) && $action == "yes") unlink("$newpath") ; $item++ ; @@ -1638,14 +1636,13 @@ private static $encoding = 'UTF-8'; foreach($snapslist as $s) { $name = $s['name'] ; #Delete Metadata only. - if ($action == "yes") { + if ($actionmeta == "yes") { $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; } if ($s['name'] == $snap) break ; } #if VM was started restart. if ($state == 'running') { - #echo "Restart VM\n" ; $arrResponse = $lv->domain_start($vm) ; } @@ -1686,8 +1683,6 @@ private static $encoding = 'UTF-8'; $snapslist= getvmsnapshots($vm) ; $data = "

Images and metadata to remove if tickbox checked.
" ; - #echo "Revert Vm: $vm snap: $snap\n" ; - $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; @@ -1761,16 +1756,13 @@ private static $encoding = 'UTF-8'; $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"] ; } # GetXML $strXML= $lv->domain_get_xml($res) ; $xmlobj = custom::createArray('domain',$strXML) ; - # Process disks and update path. + # Process disks. $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; @@ -1788,12 +1780,115 @@ private static $encoding = 'UTF-8'; $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; - #if (is_file($path)) unlink("$path") ; + if (is_file($path)) { + if(!unlink("$path")) { + $data = ["error" => "Unable to remove image file $path"] ; + return ($data) ; + } + } } - #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; - $data = ["success => 'true"] ; + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + if($ret) + $data = ["error" => "Unable to remove snap metadata $snap"] ; + else + $data = ["success => 'true"] ; + return($data) ; } + +function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockcommit - Start a block commit operation. + + SYNOPSIS + blockcommit [--bandwidth ] [--base ] [--shallow] [--top ] [--active] [--delete] [--wait] [--verbose] [--timeout ] [--pivot] [--keep-overlay] [--async] [--keep-relative] [--bytes] + + DESCRIPTION + Commit changes from a snapshot down to its backing image. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of disk + --bandwidth bandwidth limit in MiB/s + --base path of base file to commit into (default bottom of chain) + --shallow use backing file of top as base + --top 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 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 + */ +} + +function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockcopy - Start a block copy operation. + + SYNOPSIS + blockcopy [--dest ] [--bandwidth ] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout ] [--pivot] [--finish] [--async] [--xml ] [--format ] [--granularity ] [--buf-size ] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml] + + DESCRIPTION + Copy a disk backing image chain to dest. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of source disk + --dest path of the copy to create + --bandwidth 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 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 filename containing XML description of the copy destination + --format format of the destination file + --granularity power-of-two granularity to use during the copy + --buf-size 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 + */ +} + +function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockpull - Populate a disk from its backing image. + + SYNOPSIS + blockpull [--bandwidth ] [--base ] [--wait] [--verbose] [--timeout ] [--async] [--keep-relative] [--bytes] + + DESCRIPTION + Populate a disk from its backing image. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of disk + --bandwidth bandwidth limit in MiB/s + --base path of backing file in chain for a partial pull + --wait wait for job to finish + --verbose with --wait, display the progress + --timeout 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 + + + */ +} ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index d7ef4d57f..0654c87ac 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -151,7 +151,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c opts.push({divider:true}); opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); - selectsnapshot(uuid , name, "--generate" , "create",false) ; + selectsnapshot(uuid , name, "--generate" , "create",false,state) ; }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index 85d5167e3..f556fe7a3 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -766,17 +766,25 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ 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 } , "loadlist"); + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free, desc:desc } , "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -1416,6 +1424,7 @@ $(function() {
+ _(VM Name)_:
@@ -1423,4 +1432,9 @@ _(Snapshot Name)_: _(Check free space)_:
+_(Description )_: + +
+ + From 1911da7fc38e28c8310d273f33e1e48364b9fcee Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 21 May 2023 19:04:35 +0100 Subject: [PATCH 011/212] Add VMAjaxCall for Block commands --- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 plugins/dynamix.vm.manager/scripts/VMAjaxCall.php diff --git a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php new file mode 100644 index 000000000..9e40bf79e --- /dev/null +++ b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php @@ -0,0 +1,76 @@ +#!/usr/bin/php -q + + 'http://localhost/pub/plugins?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) { + $waitID = mt_rand(); + [$cmd,$args] = explode(' ',$command,2); + write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + $proc = popen("$command 2>&1",'r'); + while ($out = fgets($proc)) { + $out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out); + write("addLog\0".htmlspecialchars($out)); + } + $retval = pclose($proc); + $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; + write("stop_Wait\0$waitID","addLog\0
$out"); + return $retval===0; + } + +#{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} +$url = rawurldecode($argv[1]??''); +$waitID = "bob" ; +$style = [""; +$path = "--current" ; $pivot = "yes" ; $action = " " ; +write(implode($style)."

"); +write("

","addLog\0
"._('Block Commit').": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); + +foreach (explode('&', $url) as $chunk) { + $param = explode("=", $chunk); + if ($param) { +# write("addLog\0Parm" . sprintf("Value for parameter \"%s\" is \"%s\"
\n", urldecode($param[0]), urldecode($param[1]))); + ${urldecode($param[0])} = urldecode($param[1]) ; + } +} + +write("VMName $name "); +write("SNAP $snapshotname "); +write("Base $targetbase "); +write("Top $targettop "); +$path = "--current" ; $pivot = "yes" ; $action = " " ; +vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,$action) ; +#execCommand_nchan("ls /root") ; +write('_DONE_',''); + +?> \ No newline at end of file From 34c3de339e40af42d91715feedac2d83dd1caa2a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 21 May 2023 19:24:13 +0100 Subject: [PATCH 012/212] Updates for Blockcommit --- plugins/dynamix.vm.manager/VMMachines.page | 108 ++++++++++++++++-- plugins/dynamix.vm.manager/include/VMajax.php | 19 +++ .../include/libvirt_helpers.php | 97 ++++++++++++++-- .../javascript/vmmanager.js | 19 ++- plugins/dynamix/DashStats.page | 23 ++-- 5 files changed, 231 insertions(+), 35 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 5438d131a..0290b97fc 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -18,6 +18,7 @@ Markdown="false" */ ?> ; + 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 = "" + htmlstrtop = "" + $("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 ; + + 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 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 = $('#targetblockbase').val() + var targettop = $('#targetblocktop').val() + //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); + Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:"snap-commit", name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; + openPlugin((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + // ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); @@ -442,13 +517,12 @@ _(CD2 ISO Image)_:


- - + + +_(Check free space)_: +
_(VM Name)_: -
_(VM Name)_: +
_(Snapshot Name)_: - - _(Check free space)_: -
_(Description )_:
@@ -485,5 +559,25 @@ _(Snapshot Name)_:
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+



+ + +
_(Base Image)_: +
_(Top Image )_: + +
+ +
+
+
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 7f3906913..5c1fa042a 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -339,12 +339,31 @@ case 'snap-create-external': $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; +case 'snap-blockcommit': + requireLibvirt(); + echo "Hello" ; + #$arrResponse = vm_snapshot($domName,'',$_REQUEST['base'],$_REQUEST['top'],"","") ; + 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 .= "" ; + } + $arrResponse['html'] = $datartn ; + break; + case 'snap-revert-external': requireLibvirt(); $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove'], $_REQUEST['removemeta']) ; diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 302b66e53..b19b97fe3 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1538,6 +1538,9 @@ private static $encoding = 'UTF-8'; #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' ; + if ($state == "running") exec("virsh dumpxml '$vm' > $xmlfile",$outxml,$rtnxml) ; $test = false ; if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; @@ -1577,8 +1580,9 @@ private static $encoding = 'UTF-8'; $capacity = $capacity + $disk["capacity"] ; } - switch ($snapslist[$snap]['state']) { - case "shutoff": + 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); @@ -1596,6 +1600,10 @@ private static $encoding = 'UTF-8'; $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,$snaps[$vm][$diskname]) ; $newpath = $snaps[$vm][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; @@ -1638,7 +1646,13 @@ private static $encoding = 'UTF-8'; #Delete Metadata only. if ($actionmeta == "yes") { $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; - } + } + # Remove running XML and memory. + $xmlfile = $primarypath."/$name.running" ; + $memoryfile = $primarypath."/memory$name.mem" ; + #var_dump(is_file($xmlfile), is_file($memoryfile)) ; + 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. @@ -1687,7 +1701,7 @@ private static $encoding = 'UTF-8'; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; - exec("qemu-img info --backing-chain -U $file | grep image:",$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 ; @@ -1703,6 +1717,7 @@ private static $encoding = 'UTF-8'; } $snapdisks= $snapslist[$snap]['disks'] ; + #var_dump($snaps) ; foreach ($snapdisks as $diskkey => $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1763,7 +1778,8 @@ private static $encoding = 'UTF-8'; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks. - $disks=($snapslist[$snap]['disks']) ; + $disks=($snapslist[$snap]['disks']) ; + var_dump($disks,$snaps) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1781,14 +1797,14 @@ private static $encoding = 'UTF-8'; 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) ; - } + #if(!unlink("$path")) { + # $data = ["error" => "Unable to remove image file $path"] ; + # return ($data) ; + #} } } - $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else @@ -1797,7 +1813,8 @@ private static $encoding = 'UTF-8'; return($data) ; } -function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { +function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { + global $lv ; /* NAME blockcommit - Start a block commit operation. @@ -1825,7 +1842,65 @@ function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { --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) ; + #var_dump($disks) ; + foreach($disks as $disk) { + $path = $disk['file'] ; + $cmdstr = "virsh blockcommit '$vm' --path '$path' --verbose --pivot --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' "; + } + if ($action) $cmdstr .= " $action "; + + + $test = false ; + if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + #var_dump($cmdstr,$output) ; + if (strpos(" ".$output[0],"error") ) { + $arrResponse = ['error' => substr($output[0],6) ] ; + #return($arrResponse) ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + #Error Check + } + #If complete ok remove meta data for snapshots. + if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + 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) { diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 0654c87ac..940afe673 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -106,6 +106,11 @@ 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,7 +146,8 @@ 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);}}); @@ -198,10 +204,11 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ // e.preventDefault(); // ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); // }}); -// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); + 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 Copy"), icon:"fa-stop", action:function(e) { // e.preventDefault(); // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); @@ -212,7 +219,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ $('#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(Delete Disabled)"), icon:"fa-trash", action:function(e) { + 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) ; diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index f556fe7a3..996ba3281 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -1124,8 +1124,8 @@ function sortTables() { }); } function addProperties() { - $('tbody.system').addClass('sortable').attr('sort','_system_information_'); - $('tbody').not('.system').each(function(){ + $('div.frame').find('tbody.system').addClass('sortable').attr('sort','_system_information_'); + $('div.frame').find('tbody').not('.system').each(function(){ $(this).addClass('sortable').attr('sort',$(this).attr('title').marker()); $(this).find('td:first').prepend(""); }); @@ -1425,16 +1425,17 @@ $(function() {
-_(VM Name)_: - -
-_(Snapshot Name)_: - - _(Check free space)_: -
-_(Description )_: - +

+ + +
_(VM Name)_: +
_(Snapshot Name)_: + +_(Check free space)_: +
_(Description )_: +
+ From c7b88354db031f1c896f6f633f7936098a337db6 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 24 May 2023 07:27:12 +0100 Subject: [PATCH 013/212] Make Script executable --- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 77 ++++++++++++------- 1 file changed, 51 insertions(+), 26 deletions(-) mode change 100644 => 100755 plugins/dynamix.vm.manager/scripts/VMAjaxCall.php diff --git a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php old mode 100644 new mode 100755 index 9e40bf79e..7fc64bf76 --- a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php +++ b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php @@ -12,31 +12,39 @@ ?> 'http://localhost/pub/plugins?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); + $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); } - curl_close($com); -} function execCommand_nchan($command) { $waitID = mt_rand(); [$cmd,$args] = explode(' ',$command,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + write("addToID\0 99\0 $action") ; $proc = popen("$command 2>&1",'r'); while ($out = fgets($proc)) { $out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out); - write("addLog\0".htmlspecialchars($out)); + write("addToID\0 99\0".htmlspecialchars($out)); + sleep(5) ; } $retval = pclose($proc); $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; @@ -46,31 +54,48 @@ function execCommand_nchan($command) { #{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} $url = rawurldecode($argv[1]??''); -$waitID = "bob" ; +$waitID = mt_rand(); $style = [""; -$path = "--current" ; $pivot = "yes" ; $action = " " ; -write(implode($style)."

"); -write("

","addLog\0
"._('Block Commit').": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); - +$path = "--current" ; $pivot = "yes" ; foreach (explode('&', $url) as $chunk) { $param = explode("=", $chunk); if ($param) { -# write("addLog\0Parm" . sprintf("Value for parameter \"%s\" is \"%s\"
\n", urldecode($param[0]), urldecode($param[1]))); ${urldecode($param[0])} = urldecode($param[1]) ; } } +$id = 1 ; +write(implode($style)."

"); +write("addLog\0".htmlspecialchars("VMName $name ")); +write("addLog\0".htmlspecialchars("SNAP $snapshotname ")); +write("addLog\0".htmlspecialchars("Base $targetbase ")); +write("addLog\0".htmlspecialchars("Top $targettop ")); + sleep(3) ; + write("stop_Wait\0$waitID") ; +write("

","addLog\0
"._("Block $action").": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); -write("VMName $name "); -write("SNAP $snapshotname "); -write("Base $targetbase "); -write("Top $targettop "); -$path = "--current" ; $pivot = "yes" ; $action = " " ; -vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,$action) ; -#execCommand_nchan("ls /root") ; + + sleep(3) ; + write("stop_Wait\0$waitID") ; +$path = "--current" ; $pivot = "yes" ; +write("addToID\0 99\0 $action") ; +switch ($action) { + case "commit": + # vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ; + 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_',''); ?> \ No newline at end of file From 0eec9f2ad72482dd478392654f1d6bf36da36c6f Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 24 May 2023 07:44:29 +0100 Subject: [PATCH 014/212] WIP Update --- plugins/dynamix.vm.manager/VMMachines.page | 4 +- .../include/libvirt_helpers.php | 60 +++++++++++++++- .../javascript/vmmanager.js | 5 ++ plugins/dynamix/include/DefaultPageLayout.php | 72 ++++++++++++++++++- 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 0290b97fc..9b06fe820 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -389,8 +389,8 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ var targetbase = $('#targetblockbase').val() var targettop = $('#targetblocktop').val() //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); - Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:"snap-commit", name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; - openPlugin((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; + 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, free:free ,removemeta:removemeta ,keep:keep})) ; + openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); }, "_(Cancel)_": function(){ diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index b19b97fe3..d9f08e203 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1939,7 +1939,8 @@ function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { */ } -function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { +function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { + global $lv ; /* NAME blockpull - Populate a disk from its backing image. @@ -1964,6 +1965,63 @@ function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { */ + $snapslist= getvmsnapshots($vm) ; + $disks =$lv->get_disk_stats($vm) ; + #var_dump($disks) ; + 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 ($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' "; + } + if ($action) $cmdstr .= " $action "; + + + $test = false ; + if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + #var_dump($cmdstr,$output) ; + if (strpos(" ".$output[0],"error") ) { + $arrResponse = ['error' => substr($output[0],6) ] ; + #return($arrResponse) ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + #Error Check + } + #If complete ok remove meta data for snapshots. + #if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + if($ret) + $data = ["error" => "Unable to remove snap metadata $snap"] ; + else + $data = ["success => 'true"] ; + + return $data ; + } + + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 940afe673..133dcbf35 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -209,6 +209,11 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ 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) ; + }}); // opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { // e.preventDefault(); // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); diff --git a/plugins/dynamix/include/DefaultPageLayout.php b/plugins/dynamix/include/DefaultPageLayout.php index 9d236569c..80aedd800 100644 --- a/plugins/dynamix/include/DefaultPageLayout.php +++ b/plugins/dynamix/include/DefaultPageLayout.php @@ -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_docker.stop(); + $('div.spinner.fixed').hide(); + $(".upgrade_notice").addClass('alert'); + return; + } + swal({title:title,text:"

",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:""},function(close){ + nchan_vmaction.stop(); + $('div.spinner.fixed').hide(); + $('.sweet-alert').hide('fast').removeClass('nchan'); + setTimeout(function(){bannerAlert(" ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'>",cmd,plg,func,start);}); + }); + $('.sweet-alert').addClass('nchan'); + $('button.confirm').prop('disabled',button==0); + }); +} function abortOperation(pid) { swal({title:"",text:"",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"",cancelButtonText:""},function(){ $.post('/webGui/include/StartCommand.php',{kill:pid},function() { @@ -915,7 +938,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)) 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]+'
'; + } + 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 += 'VM ID ['+data[1]+']: '+data[2]+'.
'; + } + } else { + var rows_content = rows.getElementsByClassName('content'); + if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) { + rows.innerHTML += ''+data[2]+'.'; + } + } + 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() { From ea5cc331356ef75e813990dca3b442903167a432 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 28 May 2023 18:46:51 +0100 Subject: [PATCH 015/212] WIP Update --- plugins/dynamix.vm.manager/VMMachines.page | 30 ++- .../dynamix.vm.manager/include/VMMachines.php | 5 +- plugins/dynamix.vm.manager/include/VMajax.php | 6 - .../include/libvirt_helpers.php | 206 +++++++++--------- .../javascript/vmmanager.js | 54 ++--- plugins/dynamix/include/DefaultPageLayout.php | 4 +- 6 files changed, 148 insertions(+), 157 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 9b06fe820..6a76fbd76 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -196,8 +196,7 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ "_(Update)_": function(){ var target = box.find('#target'); if (target.length) { - target = target.val(); - + target = target.val(); } else target = ''; var target2 = box.find('#target2'); if (target2.length) { @@ -359,6 +358,15 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ 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+ ")_", @@ -379,6 +387,8 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ 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") { @@ -388,7 +398,10 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ } var targetbase = $('#targetblockbase').val() var targettop = $('#targetblocktop').val() - //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); + 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, free:free ,removemeta:removemeta ,keep:keep})) ; openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); @@ -401,9 +414,6 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ dialogStyle(); } -// ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); - // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); - function dialogStyle() { $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title'); $('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'}); @@ -571,13 +581,15 @@ _(Snapshot Name)_: _(Base Image)_: -_(Top Image )_: +_(Top Image )_: +_(Pivot)_: + +_(Delete)_: + -

-
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index d269f27a8..68c075827 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -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 ''._('No Virtual Machines installed').''; @@ -103,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': @@ -254,7 +255,7 @@ foreach ($vms as $vm) { $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; - $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$vmpreview); echo "$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; $tab .="    " ; } diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 5c1fa042a..b57025ace 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -339,12 +339,6 @@ case 'snap-create-external': $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; -case 'snap-blockcommit': - requireLibvirt(); - echo "Hello" ; - #$arrResponse = vm_snapshot($domName,'',$_REQUEST['base'],$_REQUEST['top'],"","") ; - break; - case 'snap-images': requireLibvirt(); $html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ; diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index d9f08e203..4d326c0a1 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1580,9 +1580,9 @@ private static $encoding = 'UTF-8'; $capacity = $capacity + $disk["capacity"] ; } - switch ($snapslist[$snap]['state']) { + switch ($snapslist[$snap]['state']) { case "shutoff": - case "running": + case "running": #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); @@ -1650,7 +1650,7 @@ private static $encoding = 'UTF-8'; # Remove running XML and memory. $xmlfile = $primarypath."/$name.running" ; $memoryfile = $primarypath."/memory$name.mem" ; - #var_dump(is_file($xmlfile), is_file($memoryfile)) ; + if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ; if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ; if ($s['name'] == $snap) break ; @@ -1778,14 +1778,13 @@ private static $encoding = 'UTF-8'; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks. - $disks=($snapslist[$snap]['disks']) ; - var_dump($disks,$snaps) ; + $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) { + if ($item!==false) { $data = ["error" => "Image currently active for this domain."] ; return ($data) ; } @@ -1797,15 +1796,16 @@ private static $encoding = 'UTF-8'; 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) ; - #} + if(!unlink("$path")) { + $data = ["error" => "Unable to remove image file $path"] ; + return ($data) ; + } } } - #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; - if($ret) + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + #var_dump($ret) ; + if(!$ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else $data = ["success => 'true"] ; @@ -1879,13 +1879,14 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } if ($action) $cmdstr .= " $action "; - $test = false ; - if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + if ($test) $cmdstr .= " --print-xml " ; + + $error = execCommand_nchan($cmdstr,$path) ; #var_dump($cmdstr,$output) ; - if (strpos(" ".$output[0],"error") ) { + if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; - #return($arrResponse) ; + return($arrResponse) ; } else { # Remove nvram snapshot $arrResponse = ['success' => true] ; @@ -1903,6 +1904,95 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } +function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { + global $lv ; + /* + NAME + blockpull - Populate a disk from its backing image. + + SYNOPSIS + blockpull [--bandwidth ] [--base ] [--wait] [--verbose] [--timeout ] [--async] [--keep-relative] [--bytes] + + DESCRIPTION + Populate a disk from its backing image. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of disk + --bandwidth bandwidth limit in MiB/s + --base path of backing file in chain for a partial pull + --wait wait for job to finish + --verbose with --wait, display the progress + --timeout 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) ; + file_put_contents("/tmp/snaps",$snaps_json) ; + #var_dump($disks) ; + 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 "; + + $test = false ; + if ($test) $cmdstr .= " --print-xml " ; + + $error = execCommand_nchan($cmdstr,$path) ; + + if (!$error) { + $arrResponse = ['error' => substr($output[0],6) ] ; + return($arrResponse) ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + #Error Check + } + #If complete ok remove meta data for snapshots. + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + 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 @@ -1939,89 +2029,5 @@ function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { */ } -function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { - global $lv ; - /* - NAME - blockpull - Populate a disk from its backing image. - - SYNOPSIS - blockpull [--bandwidth ] [--base ] [--wait] [--verbose] [--timeout ] [--async] [--keep-relative] [--bytes] - - DESCRIPTION - Populate a disk from its backing image. - - OPTIONS - [--domain] domain name, id or uuid - [--path] fully-qualified path of disk - --bandwidth bandwidth limit in MiB/s - --base path of backing file in chain for a partial pull - --wait wait for job to finish - --verbose with --wait, display the progress - --timeout 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) ; - #var_dump($disks) ; - 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 ($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' "; - } - if ($action) $cmdstr .= " $action "; - - - $test = false ; - if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; - #var_dump($cmdstr,$output) ; - if (strpos(" ".$output[0],"error") ) { - $arrResponse = ['error' => substr($output[0],6) ] ; - #return($arrResponse) ; - } else { - # Remove nvram snapshot - $arrResponse = ['success' => true] ; - } - #Error Check - } - #If complete ok remove meta data for snapshots. - #if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; - if($ret) - $data = ["error" => "Unable to remove snap metadata $snap"] ; - else - $data = ["success => 'true"] ; - - return $data ; - -} - - ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 133dcbf35..41fe241bb 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -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("?"); @@ -107,10 +107,11 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist"); }}); opts.push({divider:true}); + if (preview) { 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(); @@ -192,7 +193,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } context.attach('#vm-'+uuid, opts); } -function addVMSnapContext(name, uuid, template, state, snapshotname){ +function addVMSnapContext(name, uuid, template, state, snapshotname, preview=false){ var opts = []; var path = location.pathname; var x = path.indexOf("?"); @@ -200,24 +201,28 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ context.settings({right:false,above:false}); if (state == "running") { -// opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); -// }}); + if (preview) { + opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + e.preventDefault(); + ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); + }}); + } 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) ; }}); + if (preview) { 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) ; }}); -// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); + + 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(); @@ -229,34 +234,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); selectsnapshot(uuid, name, snapshotname, "remove",true) ; }}); -// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); -// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); } - - if (state == "shutoff") { - opts.push({divider:true}); -// opts.push({text:_("Remove Snapshot"), icon:"fa-minus", action:function(e) { -// e.preventDefault(); -// snapname = "test" ; -// swal({ -// title:_("Are you sure?"), -// text:_("Remove Snapshot:") + snapname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be become invalid.", -// type:"warning", -// showCancelButton:true, -// confirmButtonText:_('Proceed'), -// cancelButtonText:_('Cancel') -// },function(){ -// $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); -// ajaxVMDispatch({action:"snap-remove-external", uuid:uuid,snapshotname:snapshotname}, "loadlist"); -// }); -// }}); - } context.attach('#vmsnap-'+uuid, opts); } function startAll() { diff --git a/plugins/dynamix/include/DefaultPageLayout.php b/plugins/dynamix/include/DefaultPageLayout.php index 80aedd800..53aaf4205 100644 --- a/plugins/dynamix/include/DefaultPageLayout.php +++ b/plugins/dynamix/include/DefaultPageLayout.php @@ -350,7 +350,7 @@ function openVMAction(cmd,title,plg,func,start=0,button=0) { nchan_vmaction.start(); $.post('/webGui/include/StartCommand.php',{cmd:cmd,start:start},function(pid) { if (pid==0) { - nchan_docker.stop(); + nchan_vmaction.stop(); $('div.spinner.fixed').hide(); $(".upgrade_notice").addClass('alert'); return; @@ -963,7 +963,7 @@ nchan_vmaction.on('message', function(data) { rows = document.getElementsByClassName('logLine'); if (rows.length) { var row = rows[rows.length-1]; - row.innerHTML += 'VM ID ['+data[1]+']: '+data[2]+'.
'; + row.innerHTML += ''+data[1]+': '+data[2]+'.
'; } } else { var rows_content = rows.getElementsByClassName('content'); From 67f67430601af0ca1b9e243804760334feb59251 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Mon, 29 May 2023 17:07:04 +0100 Subject: [PATCH 016/212] WIP Update Remove using snapshot metadata and create own record. --- plugins/dynamix.vm.manager/VMMachines.page | 9 +- plugins/dynamix.vm.manager/include/VMajax.php | 4 +- .../include/libvirt_helpers.php | 163 ++++++++++++------ .../javascript/vmmanager.js | 12 +- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 35 ++-- 5 files changed, 138 insertions(+), 85 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 6a76fbd76..6d32b5643 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -396,13 +396,14 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ if (x) free = 'yes' ; else free = 'no' ; var desc = box.find("#targetsnapdesc").prop('value') ; } - var targetbase = $('#targetblockbase').val() - var targettop = $('#targetblocktop').val() + 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, free:free ,removemeta:removemeta ,keep:keep})) ; + 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'); }, @@ -579,7 +580,7 @@ _(Snapshot Name)_:



"; /* Display VM Snapshots */ if ($snapshots != null) { - + $i=0 ; + foreach($snapshots as $snap) { + if ($snap['parent'] == "" || $snap['parent'] == "Base") $i++; + $steps[$i] .= $snap['name'].';' ; + } echo ""; echo ""; - $tab = "    " ; - foreach($snapshots as $snapshotname => $snapshot) { - $snapshotstate = _(ucfirst($snapshot["state"])) ; - $snapshotdesc = $snapshot["desc"] ; - $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; - $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; - $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".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 ""; - $tab .="    " ; + 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" )."
".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 ""; + $tab .="    " ; + } + echo ""; + } } - echo ""; -} echo "
_(Base Image)_: -
_(Top Image )_:
"._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory')."
$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory
$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory
"; - echo ""; } + echo "\0".implode($kvm); ?> diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index bbf5bbac4..b476349d1 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -770,7 +770,7 @@ $vmrc = " - + $vmrcmousemode diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 721f4fbc2..bbbe272ba 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1515,11 +1515,34 @@ private static $encoding = 'UTF-8'; $b= json_decode($a, TRUE); $vmsnap = $b["name"] ; $snaps[$vmsnap]["name"]= $b["name"]; - if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + #if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + $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) ; @@ -1527,6 +1550,45 @@ private static $encoding = 'UTF-8'; file_put_contents($dbpath."/snapshots.db",$value) ; } + function refresh_snapshots_database($vm) { + global $lv ; + $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; + #var_dump($dbpath) ; + 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) + #var_dump($snaps_json) ; + + + $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"]; + var_dump($snaps) ; + $value = json_encode($snaps,JSON_PRETTY_PRINT) ; + + file_put_contents($dbpath."/snapshots.db",$value) ; + } + function delete_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; @@ -1944,6 +2006,7 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { #Remove NVRAMs #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; #If complete ok remove meta data for snapshots. + refresh_snapshots_database($vm) ; $ret = $ret = delete_snapshots_database("$vm","$snap") ; ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; @@ -2033,6 +2096,7 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { } #If complete ok remove meta data for snapshots. #$ret = delete_snapshots_database("$vm","$snap") ; + refresh_snapshots_database($vm) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else From 8f69ed880d02321e3d26a354d615c53c65be9af0 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 3 Jun 2023 07:47:48 +0100 Subject: [PATCH 018/212] Add snapshot directory setup to rc.libvirt --- etc/rc.d/rc.libvirt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt index e19f7790f..b2e4e11f0 100755 --- a/etc/rc.d/rc.libvirt +++ b/etc/rc.d/rc.libvirt @@ -159,6 +159,10 @@ start_libvirtd() { cp -n /etc/libvirt-/*.conf /etc/libvirt &> /dev/null # ensure tpm-states path exists mkdir -p /etc/libvirt/qemu/swtpm/tpm-states + # Setup Snapshot persistance. + mkdir -p /etc/libvirt/qemu/snapshot/ + rm -r /var/lib/libvirt/qemu/snapshot/ + ln -fs /etc/libvirt/qemu/snapshot/ /var/lib/libvirt/qemu/ echo "Starting libvirtd..." mkdir -p $(dirname $LIBVIRTD_PIDFILE) check_processor From c74236612eec7c7b1124e17536551e7770929716 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 4 Jun 2023 12:09:36 +0100 Subject: [PATCH 019/212] WIP Update --- .../dynamix.vm.manager/VMMachines.page | 4 +- .../dynamix.vm.manager/include/VMMachines.php | 11 +- .../dynamix.vm.manager/include/libvirt.php | 16 +- .../include/libvirt_helpers.php | 185 +++++++----------- .../javascript/vmmanager.js | 16 +- 5 files changed, 102 insertions(+), 130 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 0fdab5663..1391c5438 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -278,7 +278,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ document.getElementById("targetsnaprmv").checked = true ; document.getElementById("targetsnaprmvmeta").checked = true ; document.getElementById("targetsnapkeep").checked = true ; - document.getElementById("targetsnapfspc").checked = false ; + document.getElementById("targetsnapfspc").checked = true ; box.dialog({ title: "_("+optiontext+ ")_", @@ -533,7 +533,7 @@ _(CD2 ISO Image)_: _(Snapshot Name)_: _(Check free space)_: - + _(Description )_: diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php index e88ce6c8f..c79cbdba8 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php @@ -245,13 +245,14 @@ foreach ($vms as $vm) { echo ""; /* Display VM Snapshots */ if ($snapshots != null) { - $i=0 ; + $j=0 ; + $steps = array() ; foreach($snapshots as $snap) { - if ($snap['parent'] == "" || $snap['parent'] == "Base") $i++; - $steps[$i] .= $snap['name'].';' ; + if ($snap['parent'] == "" || $snap['parent'] == "Base") $j++; + $steps[$j] .= $snap['name'].';' ; } - echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; - echo ""; + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; + echo ""; foreach($steps as $stepsline) { $snapshotlist = explode(";",$stepsline) ; diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index b476349d1..05b272cb5 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -1446,7 +1446,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(); @@ -1874,6 +1874,20 @@ 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); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index bbbe272ba..4e5f13f4f 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1463,66 +1463,37 @@ private static $encoding = 'UTF-8'; 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 getvmsnapshots2($vm) { - global $lv ; - $vmsnaps = $lv->domain_snapshots_list($lv->get_domain_object($vm)) ; - $snaps=array() ; - foreach($vmsnaps as $vmsnap) { - $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$vmsnap) ; - $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; - $a = simplexml_load_string($snapshot_xml) ; - if($a == false) continue ; - $a = json_encode($a) ; - $b= json_decode($a, TRUE); - $vmsnap = $b["name"] ; - $snaps[$vmsnap]["name"]= $b["name"]; - if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; - $snaps[$vmsnap]["state"]= $b["state"]; - $snaps[$vmsnap]["desc"]= $b["description"]; - $snaps[$vmsnap]["memory"]= $b["memory"]; - $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; - if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; - } - - if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; - return $snaps ; - } + function write_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; - #var_dump($dbpath) ; if (!is_dir($dbpath)) mkdir($dbpath) ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; - #var_dump($snaps_json) ; - $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) ; - #if($a == false) continue ; - $a = json_encode($a) ; - - $b= json_decode($a, TRUE); - $vmsnap = $b["name"] ; - $snaps[$vmsnap]["name"]= $b["name"]; - #if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; - $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"]; + $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) ; + $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; @@ -1544,23 +1515,20 @@ private static $encoding = 'UTF-8'; 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) ; - #var_dump($value) ; file_put_contents($dbpath."/snapshots.db",$value) ; } function refresh_snapshots_database($vm) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; - #var_dump($dbpath) ; 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) - #var_dump($snaps_json) ; - - + $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; @@ -1582,9 +1550,29 @@ private static $encoding = 'UTF-8'; $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"]; - var_dump($snaps) ; $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) ; } @@ -1596,7 +1584,6 @@ private static $encoding = 'UTF-8'; $snaps = json_decode($snaps_json,true) ; unset($snaps[$name]) ; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; - #var_dump($value) ; file_put_contents($dbpath."/snapshots.db",$value) ; return true ; } @@ -1675,25 +1662,6 @@ private static $encoding = 'UTF-8'; $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 ; - $pathinfo = pathinfo($file) ; - $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; - $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; - $capacity = $capacity + $disk["capacity"] ; - } - switch ($snapslist[$snap]['state']) { case "shutoff": case "running": @@ -1718,8 +1686,8 @@ private static $encoding = 'UTF-8'; $primarypathinfo = pathinfo($path) ; $primarypath = $primarypathinfo['dirname'] ; } - $item = array_search($path,$snaps[$vm][$diskname]) ; - $newpath = $snaps[$vm][$diskname][$item + 1]; + $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) { @@ -1741,66 +1709,53 @@ private static $encoding = 'UTF-8'; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path) && $action == "yes") unlink("$path") ; - $item = array_search($path,$snaps[$vm]["r".$diskname]) ; + $item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ; $item++ ; while($item > 0) { - if (!isset($snaps[$vm]["r".$diskname][$item])) break ; - $newpath = $snaps[$vm]["r".$diskname][$item] ; + 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) ; + + # Resotre Memeory. + + $makerun = true ; + if ($makerun == true) exec("virsh restore $memoryfile") ; + #exec("virsh restore $memoryfile") ; + } #Delete Metadata only. if ($actionmeta == "yes") { $ret = delete_snapshots_database("$vm","$name") ; } - # Remove running XML and memory. - $xmlfile = $primarypath."/$name.running" ; - $memoryfile = $primarypath."/memory$name.mem" ; - 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') { + 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 ; - - case "other": - break ; - #VM must be shutdown. - # if VM running shutdown. Record was running. - # Replace disk paths - # remove snapshot meta data and images for all snpahots. - # if VM was started restart. - - - #type running - #Non Live restores - #VM must be shutdown. - # if VM running shutdown. Record was running. - # Replace disk paths - # remove snapshot meta data and images for all snpahots. - # if VM was started restart. - - #Live restore(currently not supported.) - # Freeze VM - # Replace disk paths - # Replace Mem - # remove snapshot meta data and images for all snpahots. - # Unfreeze VM } $arrResponse = ['success' => true] ; return($arrResponse) ; @@ -1917,6 +1872,9 @@ private static $encoding = 'UTF-8'; } } + # 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) @@ -2003,9 +1961,9 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } } - #Remove NVRAMs - #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; - #If complete ok remove meta data for snapshots. + # 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) @@ -2092,10 +2050,9 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { # Remove nvram snapshot $arrResponse = ['success' => true] ; } - #Error Check + } - #If complete ok remove meta data for snapshots. - #$ret = delete_snapshots_database("$vm","$snap") ; + refresh_snapshots_database($vm) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index 2a5929f43..a3907ffe0 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -107,11 +107,11 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist"); }}); opts.push({divider:true}); - if (preview) { + 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(); @@ -201,24 +201,24 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal context.settings({right:false,above:false}); if (state == "running") { - if (preview) { - opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + + opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); - ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); + 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) ; }}); - if (preview) { + 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"); From 144c456980d755dd735429a5ef18f78bd3a6beb1 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 25 Jun 2023 18:50:18 +0100 Subject: [PATCH 020/212] WIP Update. --- .../plugins/dynamix.vm.manager/CloneVM.page | 111 ------- .../dynamix.vm.manager/VMMachines.page | 38 +++ .../dynamix.vm.manager/include/VMclone.php | 272 ------------------ .../include/libvirt_helpers.php | 111 +++++++ .../javascript/vmmanager.js | 142 +++++---- .../dynamix.vm.manager/scripts/VMClone.php | 90 ++++++ .../dynamix/include/DefaultPageLayout.php | 71 +++++ 7 files changed, 403 insertions(+), 432 deletions(-) delete mode 100644 emhttp/plugins/dynamix.vm.manager/CloneVM.page delete mode 100644 emhttp/plugins/dynamix.vm.manager/include/VMclone.php create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php diff --git a/emhttp/plugins/dynamix.vm.manager/CloneVM.page b/emhttp/plugins/dynamix.vm.manager/CloneVM.page deleted file mode 100644 index b74cbccbc..000000000 --- a/emhttp/plugins/dynamix.vm.manager/CloneVM.page +++ /dev/null @@ -1,111 +0,0 @@ -Title="Clone VM" -Tag="clipboard" -Cond="(pgrep('libvirtd')!==false)" -Markdown="false" ---- - - - - - - -
-
-lang=""> - - - - - - - -"> - - -_(VM Being Cloned)_: -
- -_(New VM)_: -
- -_(Overwrite)_: -
- -_(Start Cloned VM)_: -
- -_(Edit VM after clone)_: -
- - - -
\ No newline at end of file diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index e53dbacea..06940322d 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -305,4 +305,42 @@ $(function() { _(ISO Image)_: : +
+ +
+
+lang=""> + + + + + + + +"> + + +
+ + + + + + + + + + + + + + + + + + + +
_(VM Being Cloned)_:
_(New VM)_:
_(Overwrite)_:
_(Start Cloned VM)_:
_(Edit VM after clone)_:
_(Check Free Space)_:
+ +
\ No newline at end of file diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMclone.php b/emhttp/plugins/dynamix.vm.manager/include/VMclone.php deleted file mode 100644 index 233b74256..000000000 --- a/emhttp/plugins/dynamix.vm.manager/include/VMclone.php +++ /dev/null @@ -1,272 +0,0 @@ - - '', - 'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'], - 'autostart' => false, - 'form' => $arrAllTemplates[$strSelectedTemplate]['form'], - 'state' => 'shutoff' -]; -$strIconURL = '/plugins/dynamix.vm.manager/templates/images/'.$arrLoad['icon']; - -if (isset($_GET['uuid'])) { - // Edit VM mode - $res = $lv->domain_get_domain_by_uuid(unscript($_GET['uuid'])); - - if ($res === false) { - echo "

"._('Invalid VM to edit').".

"; - return; - } - - $strIconURL = $lv->domain_get_icon_url($res); - $arrLoad = [ - 'name' => $lv->domain_get_name($res), - 'icon' => basename($strIconURL), - 'autostart' => $lv->domain_get_autostart($res), - 'form' => $arrAllTemplates[$strSelectedTemplate]['form'], - 'state' => $lv->domain_get_state($res) - ]; - - if (empty($_GET['template'])) { - // read vm-template attribute - $strTemplateOS = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@os'); - $strLibreELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@libreelec'); - $strOpenELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@openelec'); - if ($strLibreELEC) $strSelectedTemplate = 'LibreELEC'; - elseif ($strOpenELEC) $strSelectedTemplate = 'OpenELEC'; - elseif ($strTemplateOS) { - $strSelectedTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name'); - } else { - // legacy VM support for <6.2 but need it going forward too - foreach ($arrAllTemplates as $strName => $arrTemplate) { - if (!empty($arrTemplate) && !empty($arrTemplate['os']) && $arrTemplate['os'] == $strTemplateOS) { - $strSelectedTemplate = $strName; - break; - } - } - } - if (empty($strSelectedTemplate) || empty($arrAllTemplates[$strSelectedTemplate])) { - $strSelectedTemplate = 'Windows 10'; //default to Windows 10 - } - } - $arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form']; -} -?> - - - - - - - -
-
- - - - - - - - -
_(Icon)_: - - -
-
- '/plugins/dynamix.vm.manager/templates/images/', - "$docroot/boot/config/plugins/dynamix.vm.manager/templates/images/*.png" => '/boot/config/plugins/dynamix.vm.manager/templates/images/' - ]; - foreach ($arrImagePaths as $strGlob => $strIconURLBase) { - foreach (glob($strGlob) as $png_file) { - echo '

'.basename($png_file,'.png').'

'; - } - } - ?> -
-
-
- - - - - - -
_(Autostart)_:
>
-
-

If you want this VM to start with the array, set this to yes.

-
- -
'.parse_file("$docroot/plugins/dynamix.vm.manager/templates/{$arrLoad['form']}",false))?>
- -
-
- - - - - - diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 7bf82d5cd..3a2efc8ee 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1454,4 +1454,115 @@ 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 + */ + + #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? + + + + $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' ; + $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; + $capacity = $capacity + $disk["capacity"] ; + } + $dirpath = $pathinfo["dirname"] ; + + #Check free space. + write("addLog\0".htmlspecialchars("Checking for free space")); + $dirfree = disk_free_space($pathinfo["dirname"]) ; + + $capacity *= 1 ; + + #if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Clone")]; return $arrResponse ;} + + #Clone XML + $res = $lv->domain_get_domain_by_uuid($uuid); + + $strXML = $lv->domain_get_xml($res); + $uuid = $lv->domain_get_uuid($vm) ; + var_dump($uuid) ; + $config=domain_to_config($uuid) ; + + + #$config = array_replace_recursive($arrConfigDefaults, domain_to_config($uuid)); + #$config = domain_to_config($uuid); + $config["domain"]["name"] = $clone ; + $config["domain"]["uuid"] = $lv->domain_generate_uuid() ; + $config["nic"]["0"]["mac"] = $lv->generate_random_mac_addr() ; + $config["domain"]["type"] = "kvm"; + + + $files_exist = false ; + foreach ($config["disk"] as $diskid => $disk) { + $config["disk"][$diskid]["new"] = str_replace($name,$clonename,$config["disk"][$diskid]["new"]) ; + #var_dump(pathinfo($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 ; + #var_dump($isdir,$pi['dirname']) ; + } + + $clonedir = $domain_cfg['DOMAINDIR'].$clone ; + #write("addLog\0".htmlspecialchars("Overwrite $overwrite Start $start Edit $edit Check Freespace $free")); + write("addLog\0".htmlspecialchars("Checking for image files")); + #if ($file_exists && $overwrite != "yes") { $arrResponse = ['error' => _("New image file names exist and Overwrite is no")]; return $arrResponse ;} + + write("addLog\0".htmlspecialchars("Creating new XML $clone")); + $xml = $lv->config_to_xml($config) ; + file_put_contents("/tmp/xml" ,$xml) ; + + foreach($disks as $disk) { + $cmdstr = "ls" ; + $error = execCommand_nchan($cmdstr,$path) ; + if (!$error) { + $arrResponse = ['error' => substr($output[0],6) ] ; + return($arrResponse) ; + } else { + $arrResponse = ['success' => true] ; + } + + } + $arrResponse = ['error' => _("Insufficent Storage for Clone")]; + return$arrResponse ; + + } + ?> diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index 15706b2c7..ab393079c 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -2,41 +2,6 @@ function displayconsole(url) { window.open(url, '_blank', 'scrollbars=yes,resizable=yes'); } -function getCloneName(name){ - var root = "" ; - var match= ".iso" ; - var box = $("#dialogWindow"); - box.html($("#templateClone").html()); - - box.find('#VMBeingCloned').html(name).change() ; - - var height = 100; - box.dialog({ - title: "Enter Clone Name", - resizable: false, - width: 600, - height: 300, - modal: true, - show: {effect:'fade', duration:250}, - hide: {effect:'fade', duration:250}, - buttons: { - "_(Insert)_": function(){ - var target = box.find('#target'); - if (target.length) { - target = target.val(); - if (!target ) {errorTarget(); return;} - } else target = ''; - box.find('#target').prop('disabled',true); - ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:target}, "loadlist"); ; - box.dialog('close'); - }, - "_(Cancel)_": function(){ - box.dialog('close'); - } - } - }); - dialogStyle(); -} function downloadFile(source) { var a = document.createElement('a'); a.setAttribute('href',source); @@ -182,25 +147,12 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c 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}); - opts.push({text:_("Clone2"), icon:"fa-clone", href:path+'/CloneVM?uuid='+uuid}); if (state == "shutoff") { opts.push({text:_("Clone"), icon:"fa-clone", action:function(e) { e.preventDefault(); - var clonename = getCloneName(name) ; - /*swal({ - title:_("Enter"), - text:_("From VM:")+name, - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); - ajaxVMDispatch({action:"domain-clone", uuid:uuid }, "loadlist"); - }) ;*/ - + var clonename = VMClone(uuid,name) ; }}); opts.push({divider:true}); @@ -280,3 +232,95 @@ function addVM() { if (x!=-1) path = path.substring(0,x); location = path+"/VMTemplates"; } + + +function getCloneName(uuid,name){ + var root = "" ; + var match= ".iso" ; + var box = $("#dialogWindow"); + box.html($("#templateClone").html()); + + box.find('#VMBeingCloned').html(name).change() ; + + var height = 100; + box.dialog({ + title: "Enter Clone Name", + resizable: false, + width: 600, + height: 300, + 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 = ''; + box.find('#target').prop('disabled',true); + ajaxVMDispatch({action:"domain-clone", uuid:uuid , clone:target}, "loadlist"); ; + box.dialog('close'); + }, + "Cancel": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + +function VMClone(uuid, name){ + + //var root = ; + var match= ".iso"; + var box = $("#dialogWindow"); + box.html($("#templateblock").html()); + var height = 200; + box.html($("#templateClone").html()); + + box.find('#VMBeingCloned').html(name).change() ; + + //document.getElementById("targetsnaprmv").checked = true ; + //document.getElementById("targetsnaprmvmeta").checked = true ; + //document.getElementById("targetsnapkeep").checked = true ; + //document.getElementById("targetsnapfspc").checked = true ; + + 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(); +} \ No newline at end of file diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php new file mode 100644 index 000000000..50d32782d --- /dev/null +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -0,0 +1,90 @@ +#!/usr/bin/php -q + + '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("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + write("addLog\0
") ; + 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
$out"); + 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 = [""; + +foreach (explode('&', $url) as $chunk) { + $param = explode("=", $chunk); + if ($param) { + ${urldecode($param[0])} = urldecode($param[1]) ; + } +} +$id = 1 ; +write(implode($style)."

"); +$process = " " ; +write("

","addLog\0
"._("Options for Block $action").":

"._('Please wait')."
"); +write("addLog\0".htmlspecialchars("VMName $name ")); +write("addLog\0".htmlspecialchars("Clone $clone ")); +write("addLog\0".htmlspecialchars("Overwrite $overwrite Start $start Edit $edit Check Freespace $free")); + +switch ($action) { + case "clone": + vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID) ; + break ; + } +#execCommand_nchan("ls /") ; +write("stop_Wait\0$waitID") ; +write('_DONE_',''); + +?> \ No newline at end of file diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout.php b/emhttp/plugins/dynamix/include/DefaultPageLayout.php index 1bf2f94dc..f3e67955f 100644 --- a/emhttp/plugins/dynamix/include/DefaultPageLayout.php +++ b/emhttp/plugins/dynamix/include/DefaultPageLayout.php @@ -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:"

",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:""},function(close){ + nchan_vmaction.stop(); + $('div.spinner.fixed').hide(); + $('.sweet-alert').hide('fast').removeClass('nchan'); + setTimeout(function(){bannerAlert(" ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'>",cmd,plg,func,start);}); + }); + $('.sweet-alert').addClass('nchan'); + $('button.confirm').prop('disabled',button==0); + }); +} function abortOperation(pid) { swal({title:"",text:"",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"",cancelButtonText:""},function(){ $.post('/webGui/include/StartCommand.php',{kill:pid},function() { @@ -915,6 +938,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)) 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]+'
'; + } + 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 += ''+data[1]+': '+data[2]+'.
'; + } + } else { + var rows_content = rows.getElementsByClassName('content'); + if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) { + rows.innerHTML += ''+data[2]+'.'; + } + } + 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; From 276406c830de993ad5147d5e6bea25c07e2cf0de Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 25 Jun 2023 20:51:00 +0100 Subject: [PATCH 021/212] WIP Update --- .../dynamix.vm.manager/VMMachines.page | 2 +- .../include/libvirt_helpers.php | 49 +++++++++---------- .../javascript/vmmanager.js | 13 ++--- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 579b1a05b..138664951 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -204,7 +204,7 @@ function getisoimage(uuid,dev,bus,file){ dialogStyle(); } function dialogStyle() { - $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').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('').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'}); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 3a2efc8ee..e80ada8a7 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1490,8 +1490,6 @@ private static $encoding = 'UTF-8'; if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ; # Wait for shutdown? - - $disks =$lv->get_disk_stats($vm) ; $capacity = 0 ; @@ -1511,55 +1509,52 @@ private static $encoding = 'UTF-8'; $capacity *= 1 ; - #if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Clone")]; return $arrResponse ;} + if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Clone")]; return $arrResponse ;} #Clone XML - $res = $lv->domain_get_domain_by_uuid($uuid); - - $strXML = $lv->domain_get_xml($res); $uuid = $lv->domain_get_uuid($vm) ; - var_dump($uuid) ; $config=domain_to_config($uuid) ; - - #$config = array_replace_recursive($arrConfigDefaults, domain_to_config($uuid)); - #$config = domain_to_config($uuid); $config["domain"]["name"] = $clone ; $config["domain"]["uuid"] = $lv->domain_generate_uuid() ; - $config["nic"]["0"]["mac"] = $lv->generate_random_mac_addr() ; + foreach($config["nic"] as $index => $detail) { + $config["nic"][$index]["mac"] = $lv->generate_random_mac_addr() ; + } $config["domain"]["type"] = "kvm"; - $files_exist = false ; foreach ($config["disk"] as $diskid => $disk) { - $config["disk"][$diskid]["new"] = str_replace($name,$clonename,$config["disk"][$diskid]["new"]) ; - #var_dump(pathinfo($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 ; - #var_dump($isdir,$pi['dirname']) ; + } $clonedir = $domain_cfg['DOMAINDIR'].$clone ; + if (!is_dir($clonedir)) mkdir($clonedir) ; #write("addLog\0".htmlspecialchars("Overwrite $overwrite Start $start Edit $edit Check Freespace $free")); write("addLog\0".htmlspecialchars("Checking for image files")); - #if ($file_exists && $overwrite != "yes") { $arrResponse = ['error' => _("New image file names exist and Overwrite is no")]; return $arrResponse ;} + if ($file_exists && $overwrite != "yes") { $arrResponse = ['error' => _("New image file names exist and Overwrite is no")]; return $arrResponse ;} + #Create duplicate files. + foreach($config["disk"] as $diskid => $disk) { + $cmdstr = "touch {$config['disk'][$diskid]['new']}" ; + $error = execCommand_nchan($cmdstr,$path) ; + if (!$error) { + $arrResponse = ['error' => substr($output[0],6) ] ; + return($arrResponse) ; + } else { + $arrResponse = ['success' => true] ; + } + + } + write("

","addLog\0
"._("Options for Block $action").":

"._('Please wait')."
"); write("addLog\0".htmlspecialchars("Creating new XML $clone")); $xml = $lv->config_to_xml($config) ; file_put_contents("/tmp/xml" ,$xml) ; - foreach($disks as $disk) { - $cmdstr = "ls" ; - $error = execCommand_nchan($cmdstr,$path) ; - if (!$error) { - $arrResponse = ['error' => substr($output[0],6) ] ; - return($arrResponse) ; - } else { - $arrResponse = ['success' => true] ; - } - - } + $arrResponse = ['error' => _("Insufficent Storage for Clone")]; return$arrResponse ; diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index ab393079c..fed59144e 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -275,19 +275,16 @@ function VMClone(uuid, name){ //var root = ; var match= ".iso"; var box = $("#dialogWindow"); - box.html($("#templateblock").html()); var height = 200; box.html($("#templateClone").html()); - box.find('#VMBeingCloned').html(name).change() ; - - //document.getElementById("targetsnaprmv").checked = true ; - //document.getElementById("targetsnaprmvmeta").checked = true ; + document.getElementById("Free").checked = true ; + document.getElementById("Overwrite").checked = true ; //document.getElementById("targetsnapkeep").checked = true ; //document.getElementById("targetsnapfspc").checked = true ; box.dialog({ - title: "_(VM Clone)_", + title: "VM Clone", resizable: false, width: 600, height: 500, @@ -299,7 +296,7 @@ function VMClone(uuid, name){ var target = box.find('#target'); if (target.length) { target = target.val(); - if (!target ) {errorTarget(); return;} + //if (!target ) {errorTarget(); return;} } else target = ''; var clone = box.find("#target").prop('value') ; @@ -317,7 +314,7 @@ function VMClone(uuid, name){ openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); }, - "_(Cancel)_": function(){ + "Cancel": function(){ box.dialog('close'); } } From a39102314833c3c9a3998ccce6f3aee8938b5f34 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:51:23 +0100 Subject: [PATCH 022/212] Make script executable --- emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php old mode 100644 new mode 100755 From 5c76b3c850534089f5dee7947a7632d7aaaf30f0 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 2 Jul 2023 08:45:21 +0100 Subject: [PATCH 023/212] WIP Update. --- .../dynamix.vm.manager/VMMachines.page | 6 +-- .../include/libvirt_helpers.php | 35 ++++++++------- .../javascript/vmmanager.js | 5 +-- .../dynamix.vm.manager/scripts/VMClone.php | 28 ++++++------ emhttp/plugins/dynamix/DashStats.page | 44 +++++++++++++++++++ .../dynamix/include/DefaultPageLayout.php | 10 ++++- 6 files changed, 90 insertions(+), 38 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 138664951..f2374e4c1 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -326,15 +326,15 @@ _(ISO Image)_: _(New VM)_: - + _(Overwrite)_: -_(Start Cloned VM)_: +_(Start Cloned VM)_: -_(Edit VM after clone)_: +_(Edit VM after clone)_: _(Check Free Space)_: diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index e80ada8a7..d942c3d60 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1481,13 +1481,16 @@ private static $encoding = 'UTF-8'; If option to edit, show VMUpdate */ - + $uuid = $lv->domain_get_uuid($clone) ; + write("addLog\0".htmlspecialchars("checking if Clone UUID exists $uuid")); + if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return $arrResponse ;} #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); $state = $lv->domain_state_translate($dom['state']); + file_put_contents("/tmp/cloningxml" ,$lv->domain_get_xml($res)) ; # if VM running shutdown. Record was running. - if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ; + 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) ; @@ -1498,7 +1501,6 @@ private static $encoding = 'UTF-8'; $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"] ; @@ -1509,7 +1511,7 @@ private static $encoding = 'UTF-8'; $capacity *= 1 ; - if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Clone")]; return $arrResponse ;} + if ($free == "yes" && $dirfree < $capacity) { write("addLog\0".htmlspecialchars("Insufficent Storage for Clone")); return false ;} #Clone XML $uuid = $lv->domain_get_uuid($vm) ; @@ -1523,23 +1525,28 @@ private static $encoding = 'UTF-8'; $config["domain"]["type"] = "kvm"; $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) ; - #write("addLog\0".htmlspecialchars("Overwrite $overwrite Start $start Edit $edit Check Freespace $free")); write("addLog\0".htmlspecialchars("Checking for image files")); - if ($file_exists && $overwrite != "yes") { $arrResponse = ['error' => _("New image file names exist and Overwrite is no")]; return $arrResponse ;} + if ($file_exists && $overwrite != "yes") { write("addLog\0".htmlspecialchars("New image file names exist and Overwrite is set to No")); return( false) ; } #Create duplicate files. - foreach($config["disk"] as $diskid => $disk) { - $cmdstr = "touch {$config['disk'][$diskid]['new']}" ; + foreach($file_clone as $diskid => $disk) { + $cmdstr = "touch {$disk['target']}" ; + $sparse = "-S "; + $target = $disk['target'] ; + $source = $disk['source'] ; + $cmdstr = "rsync -ahPIX $sparse --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ; $error = execCommand_nchan($cmdstr,$path) ; if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; @@ -1549,14 +1556,12 @@ private static $encoding = 'UTF-8'; } } - write("

","addLog\0
"._("Options for Block $action").":

"._('Please wait')."
"); + write("

","addLog\0
"._("Options for Block $action").":

"); write("addLog\0".htmlspecialchars("Creating new XML $clone")); $xml = $lv->config_to_xml($config) ; - file_put_contents("/tmp/xml" ,$xml) ; - - - $arrResponse = ['error' => _("Insufficent Storage for Clone")]; - return$arrResponse ; + file_put_contents("/tmp/clonexml" ,$xml) ; + $rtn = $lv->domain_define($xml) ; + return($rtn) ; } diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index fed59144e..8b82d5c72 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -278,10 +278,9 @@ function VMClone(uuid, name){ 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 ; - //document.getElementById("targetsnapkeep").checked = true ; - //document.getElementById("targetsnapfspc").checked = true ; box.dialog({ title: "VM Clone", @@ -300,8 +299,6 @@ function VMClone(uuid, name){ } 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') ; diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index 50d32782d..342242ddc 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -39,14 +39,15 @@ function execCommand_nchan($command,$idx) { $waitID = mt_rand(); [$cmd,$args] = explode(' ',$command,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); - write("addLog\0
") ; + write("addToID\0$idx\0 $action") ; - $proc = popen("$command 2>&1",'r'); - while ($out = fgets($proc)) { + $proc = popen("$command 2>&1 &",'r'); + while ($out = fread($proc,100)) { $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)); + $out = trim($out) ; + $values = explode(' ',$out) ; + $string = "Data copied: ".$values[0]." Percentage: ".$values[1]." Transfer Rate: ".$values[2]." Time remaining: ".$values[3]." ".$values[4]." ".$values[5]; + write("progress\0$idx\0".htmlspecialchars($string)) ; } $retval = pclose($proc); $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; @@ -73,18 +74,15 @@ foreach (explode('&', $url) as $chunk) { $id = 1 ; write(implode($style)."

"); $process = " " ; -write("

","addLog\0
"._("Options for Block $action").":

"._('Please wait')."
"); -write("addLog\0".htmlspecialchars("VMName $name ")); -write("addLog\0".htmlspecialchars("Clone $clone ")); -write("addLog\0".htmlspecialchars("Overwrite $overwrite Start $start Edit $edit Check Freespace $free")); +$actiontxt = ucfirst($action) ; +write("

","addLog\0
"._("Options for $actiontxt").":

"); +write("addLog\0".htmlspecialchars("Cloning $name to $clone")); switch ($action) { case "clone": - vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID) ; - break ; + $rtn = vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID) ; + break ; } -#execCommand_nchan("ls /") ; write("stop_Wait\0$waitID") ; -write('_DONE_',''); - +if ($rtn) write('_DONE_',''); else write('_ERROR_',''); ?> \ No newline at end of file diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index 20e17c8d0..acb7a3c65 100644 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -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} @@ -1504,4 +1505,47 @@ $(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('').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'}); +} + +
+
+lang=""> + + + + + + + +"> + + +
+ + + + + + + + + + + + + + + + + + +
_(VM Being Cloned)_:
_(New VM)_:
_(Overwrite)_:
_(Check Free Space)_:
+ + +
\ No newline at end of file diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout.php b/emhttp/plugins/dynamix/include/DefaultPageLayout.php index f3e67955f..db36fda94 100644 --- a/emhttp/plugins/dynamix/include/DefaultPageLayout.php +++ b/emhttp/plugins/dynamix/include/DefaultPageLayout.php @@ -410,6 +410,14 @@ function openDone(data) { } return false; } +function openError(data) { + if (data == '_ERROR_') { + $('div.spinner.fixed').hide(); + $('button.confirm').text("").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);}); } @@ -940,7 +948,7 @@ nchan_docker.on('message', function(data) { }); var nchan_vmaction = new NchanSubscriber('/sub/vmaction',{subscriber:'websocket'}); nchan_vmaction.on('message', function(data) { - if (!data || openDone(data)) return; + if (!data || openDone(data) || openError(data)) return; var box = $('pre#swaltext'); data = data.split('\0'); switch (data[0]) { From a08452c59eb7ddc656dd0c5b0825feca8752fc04 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 2 Jul 2023 20:45:59 +0100 Subject: [PATCH 024/212] Wip update. --- .../dynamix.vm.manager/include/libvirt.php | 31 +++-- .../include/libvirt_helpers.php | 128 +++++++++--------- .../dynamix.vm.manager/scripts/VMClone.php | 7 +- 3 files changed, 91 insertions(+), 75 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index aa61c11bc..7b301426b 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -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,27 @@ $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 ($vmclone ) { + if ($v["startupPolicy"] == "optional" ) $startupPolicy = 'startupPolicy="optional"' ; else $startupPolicy = '' ; + #$startupPolicy = 'startupPolicy="optional"' ; + } + $usbstr .= " " ; - if (!empty($usbboot[$v])) { + if (!empty($usbboot[$v]) && !$vmclone ) { $usbstr .= "" ; - } + } + if ($vmclone ) { + if ($v["usbboot"] != NULL) $usbstr .= "" ; + } $usbstr .= ""; } } @@ -857,20 +865,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 .= "
" ; - if (!empty($pciboot[$pci_id])) { + if (!empty($pciboot[$pci_id]) && !$vmclone) { $pcidevs .= "" ; } + if (!empty($pci_id["boot"]) && $vmclone) { + $pcidevs .= "" ; + } $pcidevs .= ""; - $pcidevs_used[] = $pci_id; + if ($vmclone) $pcidevs_used[] = $pci_id['d']; else $pcidevs_used[] = $pci_id ; } } diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index d942c3d60..8fdbbf7c3 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1481,84 +1481,86 @@ private static $encoding = 'UTF-8'; If option to edit, show VMUpdate */ - $uuid = $lv->domain_get_uuid($clone) ; - write("addLog\0".htmlspecialchars("checking if Clone UUID exists $uuid")); - if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return $arrResponse ;} - #VM must be shutdown. - $res = $lv->get_domain_by_name($vm); - $dom = $lv->domain_get_info($res); - $state = $lv->domain_state_translate($dom['state']); - file_put_contents("/tmp/cloningxml" ,$lv->domain_get_xml($res)) ; - # 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? + $uuid = $lv->domain_get_uuid($clone) ; + write("addLog\0".htmlspecialchars(_("Checking if clone exists"))); + if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return $arrResponse ;} + #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) ; + $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"] ; + $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"]) ; + + $capacity *= 1 ; + + 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["domain"]["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"] ; } - $dirpath = $pathinfo["dirname"] ; - - #Check free space. - write("addLog\0".htmlspecialchars("Checking for free space")); - $dirfree = disk_free_space($pathinfo["dirname"]) ; - - $capacity *= 1 ; - - 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"; - - $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) ; 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 set to No")); return( false) ; } + 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) { - $cmdstr = "touch {$disk['target']}" ; - $sparse = "-S "; $target = $disk['target'] ; $source = $disk['source'] ; - $cmdstr = "rsync -ahPIX $sparse --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ; + $cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ; $error = execCommand_nchan($cmdstr,$path) ; - if (!$error) { - $arrResponse = ['error' => substr($output[0],6) ] ; - return($arrResponse) ; - } else { - $arrResponse = ['success' => true] ; - } - + if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; } } - write("

","addLog\0
"._("Options for Block $action").":

"); + + write("

","addLog\0
"._("Completing Clone").":

"); write("addLog\0".htmlspecialchars("Creating new XML $clone")); - $xml = $lv->config_to_xml($config) ; + + $xml = $lv->config_to_xml($config, true) ; file_put_contents("/tmp/clonexml" ,$xml) ; $rtn = $lv->domain_define($xml) ; return($rtn) ; diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index 342242ddc..a960befd5 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -21,6 +21,7 @@ $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,[ @@ -40,16 +41,18 @@ function execCommand_nchan($command,$idx) { [$cmd,$args] = explode(' ',$command,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); - write("addToID\0$idx\0 $action") ; + 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[3]." ".$values[4]." ".$values[5]; + $string = _("Data copied: ").$values[0]._(" Percentage: ").$values[1]._(" Transfer Rate: ").$values[2]._(" Time remaining: ").$values[3] ; 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
$out"); return $retval===0; From 2b3990922d54931b2e9318e3a77242ba13a7d95d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 2 Jul 2023 21:37:11 +0100 Subject: [PATCH 025/212] Fix usb updates. --- emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 5 ++--- .../plugins/dynamix.vm.manager/include/libvirt_helpers.php | 4 ++-- emhttp/plugins/dynamix/include/DefaultPageLayout.php | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index 7b301426b..d55422197 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -468,9 +468,8 @@ if (isset($usbopt[$v]) && !$vmclone ) { if (strpos($usbopt[$v], "#remove") == false) $startupPolicy = 'startupPolicy="optional"' ; else $startupPolicy = '' ; } - if ($vmclone ) { + if (isset($v["startupPolicy"]) && $vmclone ) { if ($v["startupPolicy"] == "optional" ) $startupPolicy = 'startupPolicy="optional"' ; else $startupPolicy = '' ; - #$startupPolicy = 'startupPolicy="optional"' ; } $usbstr .= " @@ -481,7 +480,7 @@ if (!empty($usbboot[$v]) && !$vmclone ) { $usbstr .= "" ; } - if ($vmclone ) { + if (isset($v["usbboot"]) && $vmclone ) { if ($v["usbboot"] != NULL) $usbstr .= "" ; } $usbstr .= ""; diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 8fdbbf7c3..5bea9798a 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1483,7 +1483,7 @@ private static $encoding = 'UTF-8'; */ $uuid = $lv->domain_get_uuid($clone) ; write("addLog\0".htmlspecialchars(_("Checking if clone exists"))); - if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return $arrResponse ;} + 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); @@ -1530,7 +1530,7 @@ private static $encoding = 'UTF-8'; if ($usb["checked"] == "checked") continue ; unset($usbs[$i]) ; } - $config["domain"]["usb"] = $usbs ; + $config["usb"] = $usbs ; $files_exist = false ; $files_clone = array() ; diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout.php b/emhttp/plugins/dynamix/include/DefaultPageLayout.php index db36fda94..e227908f8 100644 --- a/emhttp/plugins/dynamix/include/DefaultPageLayout.php +++ b/emhttp/plugins/dynamix/include/DefaultPageLayout.php @@ -971,12 +971,12 @@ nchan_vmaction.on('message', function(data) { rows = document.getElementsByClassName('logLine'); if (rows.length) { var row = rows[rows.length-1]; - row.innerHTML += ''+data[1]+': '+data[2]+'.
'; + row.innerHTML += ''+data[1]+' '+data[2]+'
'; } } else { var rows_content = rows.getElementsByClassName('content'); if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) { - rows.innerHTML += ''+data[2]+'.'; + rows.innerHTML += ''+data[2]+''; } } break; From cf13a33f00a53d0b8f6d5835a272f28fc7d560e7 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 2 Jul 2023 21:54:34 +0100 Subject: [PATCH 026/212] Fir directory permissions. --- .../plugins/dynamix.vm.manager/include/libvirt_helpers.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 5bea9798a..dab7136d1 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1544,7 +1544,11 @@ private static $encoding = 'UTF-8'; } $clonedir = $domain_cfg['DOMAINDIR'].$clone ; - if (!is_dir($clonedir)) mkdir($clonedir) ; + 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) ; } From b2ab922439cdba1a8081dac9a2b3a54aef0a615a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:46:50 +0100 Subject: [PATCH 027/212] Fix Multilanguage buttons --- .../dynamix.vm.manager/VMMachines.page | 48 +++++++++++ .../javascript/vmmanager.js | 84 ------------------- .../dynamix.vm.manager/scripts/VMClone.php | 2 +- emhttp/plugins/dynamix/DashStats.page | 48 +++++++++++ 4 files changed, 97 insertions(+), 85 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index f2374e4c1..946ebe37e 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -203,6 +203,54 @@ function getisoimage(uuid,dev,bus,file){ }); dialogStyle(); } +function VMClone(uuid, name){ + + //var root = ; + 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 dialogStyle() { $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title') ; $('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'}); diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index 8b82d5c72..3a80b979b 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -234,87 +234,3 @@ function addVM() { } -function getCloneName(uuid,name){ - var root = "" ; - var match= ".iso" ; - var box = $("#dialogWindow"); - box.html($("#templateClone").html()); - - box.find('#VMBeingCloned').html(name).change() ; - - var height = 100; - box.dialog({ - title: "Enter Clone Name", - resizable: false, - width: 600, - height: 300, - 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 = ''; - box.find('#target').prop('disabled',true); - ajaxVMDispatch({action:"domain-clone", uuid:uuid , clone:target}, "loadlist"); ; - box.dialog('close'); - }, - "Cancel": function(){ - box.dialog('close'); - } - } - }); - dialogStyle(); -} - -function VMClone(uuid, name){ - - //var root = ; - 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 ; - - 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(); -} \ No newline at end of file diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index a960befd5..05f350e4c 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -47,7 +47,7 @@ function execCommand_nchan($command,$idx) { $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[3] ; + $string = _("Data copied: ").$values[0]._(" Percentage: ").$values[1]._(" Transfer Rate: ").$values[3]._(" Time remaining: ").$values[4].$values[5] ; write("progress\0$idx\0".htmlspecialchars($string)) ; if ($out) $stringsave=$string ; } diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index acb7a3c65..10d693aaa 100644 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -1511,6 +1511,54 @@ $(function() { $('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'}); $('.ui-button-text').css({'padding':'0px 5px'}); } +function VMClone(uuid, name){ + + //var root = ; + 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(); +}
From ffe4f4a743b3277ed956e9413c1f8b21d2dfcda0 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Fri, 8 Sep 2023 09:13:03 -0700 Subject: [PATCH 028/212] Add readme and VS Code settings --- .vscode/extensions.json | 10 ++++++++++ .vscode/settings.json | 8 ++++++++ .vscode/sftp.template | 36 ++++++++++++++++++++++++++++++++++++ README.md | 21 +++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/sftp.template create mode 100644 README.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..8f6fb9c6a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "natizyskunk.sftp", + "davidanson.vscode-markdownlint", + "bmewburn.vscode-intelephense-client", + "foxundermoon.shell-format", + "timonwong.shellcheck", + "esbenp.prettier-vscode", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..8242fd34b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "*.page": "php" + }, + "prettier.tabWidth": 4, + "editor.tabSize": 4, + "editor.indentSize": "tabSize", +} diff --git a/.vscode/sftp.template b/.vscode/sftp.template new file mode 100644 index 000000000..2e103fd3c --- /dev/null +++ b/.vscode/sftp.template @@ -0,0 +1,36 @@ +{ + "_comment": "rename this file to .vscode/sftp.json and replace name/host/privateKeyPath for your system", + "name": "Tower", + "host": "Tower.local", + "protocol": "sftp", + "port": 22, + "username": "root", + "privateKeyPath": "C:/Users/username/.ssh/tower", + "remotePath": "/usr/local", + "uploadOnSave": true, + "useTempFile": false, + "openSsh": false, + "ignore": [ + "// comment: ignore dot files/dirs in root of repo", + ".vscode", + ".git", + ".DS_Store", + "// comment: ignore symlinks in repo", + "sbin/emcmd", + "sbin/plugin", + "sbin/language", + "sbin/newperms", + "sbin/inet", + "sbin/samba", + "sbin/diagnostics", + "emhttp/boot", + "emhttp/state", + "emhttp/mnt", + "emhttp/log", + "emhttp/plugins/dynamix/images/case-model.png", + "emhttp/webGui", + "// comment: ignore files distributed by Unraid Connect", + "emhttp/plugins/dynamix.my.servers", + "emhttp/plugins/dynamix/include/UpdateDNS.php" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 000000000..a735bd69e --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Unraid webgui repo + +## Structure + +The emhttp, etc, sbin, and src dirs in this repo are extracted to the /usr/local/ directory when an Unraid release is built. + +## Contributions + +Please be aware that there is a fairly high barrier to entry when working on the Unraid webgui. It has grown organically over the years and you will need to do quite a bit of reverse engineering to understand it. + +If you choose to proceed, clone this repo locally, test edits by copying the changes to your server, then submit a PR to when fully tested. + +Be sure to describe (in the Unraid forum, a GitHub issue, or the GitHub PR) the problem/feature you are working on and explain how the proposed change solves it. + +We recommend using VS Code with the following plugins: + +* Install [natizyskunk.sftp](https://marketplace.visualstudio.com/items?itemName=Natizyskunk.sftp) and use .vscode/sftp.template as a template for creating your personalized sftp.json file to have VS Code upload files to your server when they are saved. Your personalized sftp.json file is blocked by .gitignore so it should never be included in your PR. +* Install [bmewburn.vscode-intelephense-client](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client) and when reasonable, implement its recommendations for PHP code. +* Install [DavidAnson.vscode-markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) and when reasonable, implement its recommendations for markdown code. +* Install [foxundermoon.shell-format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format) and [timonwong.shellcheck](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck) and when reasonable, implement their recommendations when making changes to bash scripts. +* Install [esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and when reasonable, use it for formatting files. From c9c2a0a8be8dc0dbb9e23aed57b81c642d9a68d5 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Fri, 8 Sep 2023 11:17:53 -0700 Subject: [PATCH 029/212] Set bash shebang on scripts Any shell scripts that have been modified for Unraid should have the correct `#!/bin/bash` shebang to aid with linting. --- emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init | 2 +- etc/rc.d/rc.inet1 | 4 +++- etc/rc.d/rc.inet2 | 5 +++-- etc/rc.d/rc.library.source | 1 + etc/rc.d/rc.local_shutdown | 3 ++- etc/rc.d/rc.mcelog | 2 +- etc/rc.d/rc.nfsd | 4 +++- etc/rc.d/rc.ntpd | 2 +- etc/rc.d/rc.php-fpm | 4 +++- etc/rc.d/rc.rpc | 2 +- etc/rc.d/rc.samba | 2 +- etc/rc.d/rc.serial | 2 ++ etc/rc.d/rc.sshd | 4 +++- etc/rc.d/rc.udev | 2 +- sbin/create_network_ini | 2 +- sbin/mount_image | 2 +- sbin/vfio-pci | 2 +- 17 files changed, 29 insertions(+), 16 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init index 5b945b189..85622fdb8 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init +++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Intialize libvirt config storage # Invoked by emhttp after mounting libvirt loopback but before starting libvirt. diff --git a/etc/rc.d/rc.inet1 b/etc/rc.d/rc.inet1 index 670f59919..44da53556 100755 --- a/etc/rc.d/rc.inet1 +++ b/etc/rc.d/rc.inet1 @@ -1,8 +1,10 @@ -#!/bin/sh +#!/bin/bash # /etc/rc.d/rc.inet1 # This script is used to bring up the various network interfaces. # # @(#)/etc/rc.d/rc.inet1 10.2 Sun Jul 24 12:45:56 PDT 2005 (pjv) +# +# LimeTech - Modified for Unraid OS # Adapted by Bergware for use in unRAID - April 2016 # - improved interface configuration diff --git a/etc/rc.d/rc.inet2 b/etc/rc.d/rc.inet2 index d5291cc49..deedad8c1 100755 --- a/etc/rc.d/rc.inet2 +++ b/etc/rc.d/rc.inet2 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # rc.inet2 This shell script boots up the entire network system. # Note, that when this script is used to also fire @@ -11,7 +11,8 @@ # # Author: Fred N. van Kempen, # Modified for Slackware by Patrick Volkerding - +# +# LimeTech - Modified for Unraid OS # At this point, we are (almost) ready to talk to The World... diff --git a/etc/rc.d/rc.library.source b/etc/rc.d/rc.library.source index 647b977f0..460bfef01 100644 --- a/etc/rc.d/rc.library.source +++ b/etc/rc.d/rc.library.source @@ -2,6 +2,7 @@ # Used by nfsd, ntpd, rpc, samba, nginx, sshd, avahidaemon, show_interfaces # # bergware - updated for Unraid, June 2023 +# shellcheck shell=bash WIREGUARD="/etc/wireguard" NETWORK_INI="/var/local/emhttp/network.ini" diff --git a/etc/rc.d/rc.local_shutdown b/etc/rc.d/rc.local_shutdown index 63a3d937c..fffa64363 100755 --- a/etc/rc.d/rc.local_shutdown +++ b/etc/rc.d/rc.local_shutdown @@ -1,7 +1,8 @@ -#!/bin/sh +#!/bin/bash # # /etc/rc.d/rc.local_shutdown: Local system shutdown script. # +# LimeTech - Modified for Unraid OS # Beep the motherboard speaker beep -r 2 diff --git a/etc/rc.d/rc.mcelog b/etc/rc.d/rc.mcelog index 3b527b687..fc5fcf893 100755 --- a/etc/rc.d/rc.mcelog +++ b/etc/rc.d/rc.mcelog @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Startup script for mcelog # diff --git a/etc/rc.d/rc.nfsd b/etc/rc.d/rc.nfsd index 9b369a1a6..74850cd6b 100755 --- a/etc/rc.d/rc.nfsd +++ b/etc/rc.d/rc.nfsd @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Start/stop/restart the NFS server. # # This is an init script for the knfsd NFS daemons. @@ -7,6 +7,8 @@ # # Written for Slackware Linux by Patrick J. Volkerding . # +# LimeTech - Modified for Unraid OS +# # bergware - added interface bind functionality CALLER="nfs" diff --git a/etc/rc.d/rc.ntpd b/etc/rc.d/rc.ntpd index aec0cd440..c2a0ffd98 100755 --- a/etc/rc.d/rc.ntpd +++ b/etc/rc.d/rc.ntpd @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Start/stop/restart ntpd. # limetech - modified to initialize ntp.conf file from config diff --git a/etc/rc.d/rc.php-fpm b/etc/rc.d/rc.php-fpm index 48949abee..e1124187e 100755 --- a/etc/rc.d/rc.php-fpm +++ b/etc/rc.d/rc.php-fpm @@ -1,4 +1,6 @@ -#!/bin/sh +#!/bin/bash +# +# LimeTech - Modified for Unraid OS ### BEGIN INIT INFO # Provides: php-fpm diff --git a/etc/rc.d/rc.rpc b/etc/rc.d/rc.rpc index 8d8833afa..ea1845593 100755 --- a/etc/rc.d/rc.rpc +++ b/etc/rc.d/rc.rpc @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # rc.rpc: start/stop/restart RPC daemons needed to use NFS. # # You must run these daemons in order to mount NFS partitions diff --git a/etc/rc.d/rc.samba b/etc/rc.d/rc.samba index f0f53b9b2..a1a5b3e56 100755 --- a/etc/rc.d/rc.samba +++ b/etc/rc.d/rc.samba @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # /etc/rc.d/rc.samba # diff --git a/etc/rc.d/rc.serial b/etc/rc.d/rc.serial index 5f31c0194..31c3716c4 100755 --- a/etc/rc.d/rc.serial +++ b/etc/rc.d/rc.serial @@ -1,3 +1,4 @@ +#!/bin/bash # # /etc/rc.serial # Initializes the serial ports on your system @@ -15,6 +16,7 @@ # using the multiport feature; it doesn't save the multiport configuration # (for now). Autosave also doesn't work for the hayes devices. # +# LimeTech - Modified for Unraid OS RCLOCKFILE=/var/lock/subsys/serial DIRS="/lib/modules/`uname -r`/misc /lib/modules /usr/lib/modules ." diff --git a/etc/rc.d/rc.sshd b/etc/rc.d/rc.sshd index 3cecab880..ac09efca2 100755 --- a/etc/rc.d/rc.sshd +++ b/etc/rc.d/rc.sshd @@ -1,5 +1,7 @@ -#!/bin/sh +#!/bin/bash # Start/stop/restart the secure shell server: +# +# LimeTech - Modified for Unraid OS # bergware - added interface bind functionality CALLER="ssh" diff --git a/etc/rc.d/rc.udev b/etc/rc.d/rc.udev index e666f4f30..07b6d9a71 100755 --- a/etc/rc.d/rc.udev +++ b/etc/rc.d/rc.udev @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # This is a script to initialize udev, which populates the /dev # directory with device nodes, scans for devices, loads the # appropriate kernel modules, and configures the devices. diff --git a/sbin/create_network_ini b/sbin/create_network_ini index 22670a7a6..4fe604d42 100755 --- a/sbin/create_network_ini +++ b/sbin/create_network_ini @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright 2005-2023, Lime Technology # Copyright 2012-2023, Bergware International. # diff --git a/sbin/mount_image b/sbin/mount_image index 60731ea5c..d825ddba2 100755 --- a/sbin/mount_image +++ b/sbin/mount_image @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash #Copyright 2005-2021, Lime Technology #License: GPLv2 only diff --git a/sbin/vfio-pci b/sbin/vfio-pci index 34c355767..fc51056c7 100755 --- a/sbin/vfio-pci +++ b/sbin/vfio-pci @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # limetech - wrapper for Andre Ritchter's vfio-bind script: # https://github.com/andre-richter/vfio-pci-bind/blob/master/vfio-pci-bind.sh # additional changes by ljm42 From cb4a7d8735658b7292109956e39a34c427e98b19 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Fri, 8 Sep 2023 11:55:53 -0700 Subject: [PATCH 030/212] rename sftp-template.json --- .vscode/extensions.json | 8 ++++---- .vscode/settings.json | 2 +- .vscode/{sftp.template => sftp-template.json} | 0 README.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename .vscode/{sftp.template => sftp-template.json} (100%) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8f6fb9c6a..b3d3e58c9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,10 @@ { - "recommendations": [ - "natizyskunk.sftp", + "recommendations": [ + "natizyskunk.sftp", "davidanson.vscode-markdownlint", "bmewburn.vscode-intelephense-client", "foxundermoon.shell-format", "timonwong.shellcheck", - "esbenp.prettier-vscode", - ] + "esbenp.prettier-vscode" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 8242fd34b..efd806383 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,5 @@ }, "prettier.tabWidth": 4, "editor.tabSize": 4, - "editor.indentSize": "tabSize", + "editor.indentSize": "tabSize" } diff --git a/.vscode/sftp.template b/.vscode/sftp-template.json similarity index 100% rename from .vscode/sftp.template rename to .vscode/sftp-template.json diff --git a/README.md b/README.md index a735bd69e..17a862b7f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Be sure to describe (in the Unraid forum, a GitHub issue, or the GitHub PR) the We recommend using VS Code with the following plugins: -* Install [natizyskunk.sftp](https://marketplace.visualstudio.com/items?itemName=Natizyskunk.sftp) and use .vscode/sftp.template as a template for creating your personalized sftp.json file to have VS Code upload files to your server when they are saved. Your personalized sftp.json file is blocked by .gitignore so it should never be included in your PR. +* Install [natizyskunk.sftp](https://marketplace.visualstudio.com/items?itemName=Natizyskunk.sftp) and use .vscode/sftp-template.json as a template for creating your personalized sftp.json file to have VS Code upload files to your server when they are saved. Your personalized sftp.json file is blocked by .gitignore so it should never be included in your PR. * Install [bmewburn.vscode-intelephense-client](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client) and when reasonable, implement its recommendations for PHP code. * Install [DavidAnson.vscode-markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) and when reasonable, implement its recommendations for markdown code. * Install [foxundermoon.shell-format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format) and [timonwong.shellcheck](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck) and when reasonable, implement their recommendations when making changes to bash scripts. From fa0a965494ab9905316c55ad73b5bac572fa2585 Mon Sep 17 00:00:00 2001 From: Christoph Hummer Date: Mon, 11 Sep 2023 15:20:56 +0200 Subject: [PATCH 031/212] update LXC diagnostics - remove GitHub user and token from LXC diagnostics if they are set --- emhttp/plugins/dynamix/scripts/diagnostics | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emhttp/plugins/dynamix/scripts/diagnostics b/emhttp/plugins/dynamix/scripts/diagnostics index af637ea33..2a1c69d5b 100755 --- a/emhttp/plugins/dynamix/scripts/diagnostics +++ b/emhttp/plugins/dynamix/scripts/diagnostics @@ -720,6 +720,8 @@ if (is_dir("/boot/config/plugins/lxc")) { run("cat /boot/config/plugins/lxc/lxc.conf 2>/dev/null|todos >>".escapeshellarg("/$diag/lxc/lxc.conf")); run("cat /boot/config/plugins/lxc/default.conf 2>/dev/null|todos >>".escapeshellarg("/$diag/lxc/default.conf")); run("lxc-checkconfig 2>/dev/null|todos >>".escapeshellarg("/$diag/lxc/checkconfig.txt")); + // remove username and token + run("sed -i -e '/LXC_GITHUB_USER/c ***line removed***' -e '/LXC_GITHUB_TOKEN/c ***line removed***' ".escapeshellarg("/$diag/lxc/plugin.cfg")); } else { null; } From 4eea53584115037e27470447c2652d85f5c01fca Mon Sep 17 00:00:00 2001 From: ljm42 Date: Wed, 13 Sep 2023 15:32:27 -0700 Subject: [PATCH 032/212] Add more logging when stopping docker --- etc/rc.d/rc.docker | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker index 962696b3c..c1408ba28 100755 --- a/etc/rc.d/rc.docker +++ b/etc/rc.d/rc.docker @@ -488,6 +488,7 @@ start_network(){ # Remove custom networks stop_network(){ + echo "stopping $DOCKERD networks..." for NIC in $NICS; do if [[ -e $SYSTEM/${NIC/eth/br} ]]; then NIC=${NIC/eth/br} @@ -542,7 +543,7 @@ start_containers(){ # Stop containers stop_containers(){ - [[ ! -z $(running_containers) ]] && docker stop $(running_containers) >/dev/null + [[ ! -z $(running_containers) ]] && echo "stopping $DOCKERD containers..." && docker stop $(running_containers) >/dev/null } # Start docker @@ -572,25 +573,28 @@ start_docker(){ stop_docker(){ echo "stopping $DOCKERD ..." # If there is no PID file, ignore this request... - if [[ -r $DOCKER_PIDFILE ]]; then - kill $(docker_pid) 2>/dev/null - # must ensure daemon has exited - for n in {1..15}; do - sleep 1 - if [[ $(ps -p $(docker_pid) -o comm= 2>/dev/null) != $DOCKERD ]]; then - rm -f $DOCKER_PIDFILE - # tear down the bridge - if ip link show docker0 >/dev/null 2>&1; then - ip link set docker0 down - ip link del docker0 - fi - return - fi - echo "waiting for docker to die ..." - done - echo "docker will not die!" - exit 1 + if [[ ! -r $DOCKER_PIDFILE ]]; then + echo "$DOCKERD is not running..." + return 1 fi + kill $(docker_pid) 2>/dev/null + # must ensure daemon has exited + for n in {1..15}; do + sleep 1 + if [[ $(ps -p $(docker_pid) -o comm= 2>/dev/null) != $DOCKERD ]]; then + rm -f $DOCKER_PIDFILE + # tear down the bridge + if ip link show docker0 >/dev/null 2>&1; then + ip link set docker0 down + ip link del docker0 + fi + echo "$DOCKERD has stopped" + return + fi + echo "waiting for $DOCKERD to die ..." + done + echo "$DOCKERD will not die!" + exit 1 } case "$1" in From da3c3132546445515e22adac42d89f1240af1a4c Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 13 Sep 2023 19:50:21 -0400 Subject: [PATCH 033/212] Integrate GUI Search --- emhttp/plugins/dynamix/Credits.page | 2 + emhttp/plugins/dynamix/javascript/dynamix.js | 8 + .../plugins/dynamix/styles/dynamix-azure.css | 21 ++ .../plugins/dynamix/styles/dynamix-black.css | 20 ++ .../plugins/dynamix/styles/dynamix-gray.css | 21 ++ .../plugins/dynamix/styles/dynamix-white.css | 20 ++ emhttp/plugins/gui.search/exec.php | 146 ++++++++++++++ emhttp/plugins/gui.search/gui_search.page | 180 ++++++++++++++++++ .../gui.search/gui_search_post_hook.sh | 2 + 9 files changed, 420 insertions(+) create mode 100755 emhttp/plugins/gui.search/exec.php create mode 100755 emhttp/plugins/gui.search/gui_search.page create mode 100755 emhttp/plugins/gui.search/gui_search_post_hook.sh diff --git a/emhttp/plugins/dynamix/Credits.page b/emhttp/plugins/dynamix/Credits.page index bb7005e17..a56d6732d 100644 --- a/emhttp/plugins/dynamix/Credits.page +++ b/emhttp/plugins/dynamix/Credits.page @@ -19,6 +19,8 @@ Tag="trophy" **Community Applications** Copyright © 2015-2023, Andrew Zawadzki +**GUI Search** Copyright © 2021-2023, Andrew Zawadzki + The Software comprising the Unraid webGui, which is all files within this repository **except** for files listed below, is licensed under GPL version 2. diff --git a/emhttp/plugins/dynamix/javascript/dynamix.js b/emhttp/plugins/dynamix/javascript/dynamix.js index e30f48e1d..e315d668c 100644 --- a/emhttp/plugins/dynamix/javascript/dynamix.js +++ b/emhttp/plugins/dynamix/javascript/dynamix.js @@ -58,3 +58,11 @@ shortcut={all_shortcuts:{},add:function(l,p,i){var e={type:"keydown",propagate:! /* readmore.js - v2.0.0, copyright Jed Foster NOT UPDATED */ (function(c){function g(b,a){this.element=b;this.options=c.extend({},h,a);c(this.element).data("max-height",this.options.maxHeight);c(this.element).data("height-margin",this.options.heightMargin);delete this.options.maxHeight;if(this.options.embedCSS&&!k){var d=".readmore-js-toggle, .readmore-js-section { "+this.options.sectionCSS+" } .readmore-js-section { overflow: hidden; }",e=document.createElement("style");e.type="text/css";e.styleSheet?e.styleSheet.cssText=d:e.appendChild(document.createTextNode(d));document.getElementsByTagName("head")[0].appendChild(e);k=!0}this._defaults=h;this._name=f;this.init()}var f="readmore",h={speed:100,maxHeight:200,heightMargin:16,moreLink:'Read More',lessLink:'Close',embedCSS:!0,sectionCSS:"display: block; width: 100%;",startOpen:!1,expandedClass:"readmore-js-expanded",collapsedClass:"readmore-js-collapsed",beforeToggle:function(){},afterToggle:function(){}},k=!1;g.prototype={init:function(){var b=this;c(this.element).each(function(){var a=c(this),d=a.css("max-height").replace(/[^-\d\.]/g,"")>a.data("max-height")?a.css("max-height").replace(/[^-\d\.]/g,""):a.data("max-height"),e=a.data("height-margin");"none"!=a.css("max-height")&&a.css("max-height","none");b.setBoxHeight(a);if(a.outerHeight(!0)<=d+e)return!0;a.addClass("readmore-js-section "+b.options.collapsedClass).data("collapsedHeight",d);a.after(c(b.options.startOpen?b.options.lessLink:b.options.moreLink).on("click",function(c){b.toggleSlider(this,a,c)}).addClass("readmore-js-toggle"));b.options.startOpen||a.css({height:d})});c(window).on("resize",function(a){b.resizeBoxes()})},toggleSlider:function(b,a,d){d.preventDefault();var e=this;d=newLink=sectionClass="";var f=!1;d=c(a).data("collapsedHeight");c(a).height()<=d?(d=c(a).data("expandedHeight")+"px",newLink="lessLink",f=!0,sectionClass=e.options.expandedClass):(newLink="moreLink",sectionClass=e.options.collapsedClass);e.options.beforeToggle(b,a,f);c(a).animate({height:d},{duration:e.options.speed,complete:function(){e.options.afterToggle(b,a,f);c(b).replaceWith(c(e.options[newLink]).on("click",function(b){e.toggleSlider(this,a,b)}).addClass("readmore-js-toggle"));c(this).removeClass(e.options.collapsedClass+" "+e.options.expandedClass).addClass(sectionClass)}})},setBoxHeight:function(b){var a=b.clone().css({height:"auto",width:b.width(),overflow:"hidden"}).insertAfter(b),c=a.outerHeight(!0);a.remove();b.data("expandedHeight",c)},resizeBoxes:function(){var b=this;c(".readmore-js-section").each(function(){var a=c(this);b.setBoxHeight(a);(a.height()>a.data("expandedHeight")||a.hasClass(b.options.expandedClass)&&a.height()-1)this._list=t.split(/\s*,\s*/);else if((t=i(t))&&t.children){var e=[];o.apply(t.children).forEach(function(t){if(!t.disabled){var i=t.textContent.trim(),n=t.value||i,s=t.label||i;""!==n&&e.push({label:s,value:n})}}),this._list=e}document.activeElement===this.input&&this.evaluate()},get selected(){return this.index>-1},get opened(){return this.isOpened},close:function(t){this.opened&&(this.input.setAttribute("aria-expanded","false"),this.ul.setAttribute("hidden",""),this.isOpened=!1,this.index=-1,this.status.setAttribute("hidden",""),i.fire(this.input,"awesomplete-close",t||{}))},open:function(){this.input.setAttribute("aria-expanded","true"),this.ul.removeAttribute("hidden"),this.isOpened=!0,this.status.removeAttribute("hidden"),this.autoFirst&&-1===this.index&&this.goto(0),i.fire(this.input,"awesomplete-open")},destroy:function(){if(i.unbind(this.input,this._events.input),i.unbind(this.input.form,this._events.form),!this.options.container){var t=this.container.parentNode;t.insertBefore(this.input,this.container),t.removeChild(this.container)}this.input.removeAttribute("autocomplete"),this.input.removeAttribute("aria-autocomplete");var e=r.all.indexOf(this);-1!==e&&r.all.splice(e,1)},next:function(){var t=this.ul.children.length;this.goto(this.index-1&&e.length>0&&(e[t].setAttribute("aria-selected","true"),this.status.textContent=e[t].textContent+", list item "+(t+1)+" of "+e.length,this.input.setAttribute("aria-activedescendant",this.ul.id+"_item_"+this.index),this.ul.scrollTop=e[t].offsetTop-this.ul.clientHeight+e[t].clientHeight,i.fire(this.input,"awesomplete-highlight",{text:this.suggestions[this.index]}))},select:function(t,e,n){if(t?this.index=i.siblingIndex(t):t=this.ul.children[this.index],t){var s=this.suggestions[this.index];i.fire(this.input,"awesomplete-select",{text:s,origin:e||t,originalEvent:n})&&(this.replace(s),this.close({reason:"select"}),i.fire(this.input,"awesomplete-selectcomplete",{text:s,originalEvent:n}))}},evaluate:function(){var e=this,i=this.input.value;i.length>=this.minChars&&this._list&&this._list.length>0?(this.index=-1,this.ul.innerHTML="",this.suggestions=this._list.map(function(n){return new t(e.data(n,i))}).filter(function(t){return e.filter(t,i)}),!1!==this.sort&&(this.suggestions=this.suggestions.sort(this.sort)),this.suggestions=this.suggestions.slice(0,this.maxItems),this.suggestions.forEach(function(t,n){e.ul.appendChild(e.item(t,i,n))}),0===this.ul.children.length?(this.status.textContent="No results found",this.close({reason:"nomatches"})):(this.open(),this.status.textContent=this.ul.children.length+" results found")):(this.close({reason:"nomatches"}),this.status.textContent="No results found")}},r.all=[],r.FILTER_CONTAINS=function(t,e){return RegExp(i.regExpEscape(e.trim()),"i").test(t)},r.FILTER_STARTSWITH=function(t,e){return RegExp("^"+i.regExpEscape(e.trim()),"i").test(t)},r.SORT_BYLENGTH=function(t,e){return t.length!==e.length?t.length-e.length:t$&"),role:"option","aria-selected":"false",id:"awesomplete_list_"+this.count+"_item_"+n})},r.REPLACE=function(t){this.input.value=t.value},r.DATA=function(t){return t},Object.defineProperty(t.prototype=Object.create(String.prototype),"length",{get:function(){return this.label.length}}),t.prototype.toString=t.prototype.valueOf=function(){return""+this.label};var o=Array.prototype.slice;i.create=function(t,e){var n=document.createElement(t);for(var s in e){var r=e[s];if("inside"===s)i(r).appendChild(n);else if("around"===s){var o=i(r);o.parentNode.insertBefore(n,o),n.appendChild(o),null!=o.getAttribute("autofocus")&&o.focus()}else s in n?n[s]=r:n.setAttribute(s,r)}return n},i.bind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}},i.unbind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.removeEventListener(e,n)})}},i.fire=function(t,e,i){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0);for(var s in i)n[s]=i[s];return t.dispatchEvent(n)},i.regExpEscape=function(t){return t.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")},i.siblingIndex=function(t){for(var e=0;t=t.previousElementSibling;e++);return e},"undefined"!=typeof self&&(self.Awesomplete=r),"undefined"!=typeof Document&&("loading"!==document.readyState?s():document.addEventListener("DOMContentLoaded",s)),r.$=i,r.$$=n,"object"==typeof module&&module.exports&&(module.exports=r)}(); + diff --git a/emhttp/plugins/dynamix/styles/dynamix-azure.css b/emhttp/plugins/dynamix/styles/dynamix-azure.css index 4647bfab3..95a6ad462 100644 --- a/emhttp/plugins/dynamix/styles/dynamix-azure.css +++ b/emhttp/plugins/dynamix/styles/dynamix-azure.css @@ -266,3 +266,24 @@ div.icon-zip{background:url('../images/file-types.png') -32px -80px;width:16px;h .tooltipster-sidetip.tooltipster-left .tooltipster-box{border-right:3px solid #ff8c2f} .tooltipster-sidetip.tooltipster-right .tooltipster-box{border-left:3px solid #ff8c2f} .fileTree{background-color:#f2f2f2} +#guiSearchBox:placeholder-shown {caret-color:transparent;} +#guiSearchBox {position:fixed;top:55px;left:250px;} +.guiSearchBoxResults {position:fixed !important;top:90px !important; left:250px !important;} +.awesomplete [hidden] {display: none;} +.awesomplete .visually-hidden {position: absolute;clip: rect(0, 0, 0, 0);} +.awesomplete {display: inline-block;position: relative;color: red;} +.awesomplete > input {display: block;} +.awesomplete > ul {position: absolute;left: 0;z-index: 1;min-width: 100%;box-sizing: border-box;list-style: none;padding: 0;margin: 0;background: #fff;} +.awesomplete > ul:empty {display: none;} +.awesomplete > ul {border-radius: .3em;margin: .2em 0 0;background: hsla(0,0%,100%,.9);background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));border: 1px solid rgba(0,0,0,.3);box-shadow: .05em .2em .6em rgba(0,0,0,.2);text-shadow: none;} +@supports (transform: scale(0)) {.awesomplete > ul {transition: .3s cubic-bezier(.4,.2,.5,1.4);transform-origin: 1.43em -.43em;} + .awesomplete > ul[hidden],.awesomplete > ul:empty {opacity: 0;transform: scale(0);display: block;transition-timing-function: ease;} +} +/* Pointer */ +.awesomplete > ul:before {content: "";position: absolute;top: -.43em;left: 1em;width: 0; height: 0;padding: .4em;background: white;border: inherit;border-right: 0;border-bottom: 0;-webkit-transform: rotate(45deg);transform: rotate(45deg);} +.awesomplete > ul > li {position: relative;padding: .2em .5em;cursor: pointer;} +.awesomplete > ul > li:hover {background: hsl(200, 40%, 80%);color: black;} +.awesomplete > ul > li[aria-selected="true"] {background: hsl(205, 40%, 40%);color: white;} +.awesomplete mark {background: hsl(65, 100%, 50%);} +.awesomplete li:hover mark {background: hsl(68, 100%, 41%);} +.awesomplete li[aria-selected="true"] mark {background: hsl(86, 100%, 21%);color: inherit;} \ No newline at end of file diff --git a/emhttp/plugins/dynamix/styles/dynamix-black.css b/emhttp/plugins/dynamix/styles/dynamix-black.css index f20542757..d30366e6f 100644 --- a/emhttp/plugins/dynamix/styles/dynamix-black.css +++ b/emhttp/plugins/dynamix/styles/dynamix-black.css @@ -266,3 +266,23 @@ div.icon-zip{background:url('../images/file-types.png') -32px -80px;width:16px;h .tooltipster-sidetip.tooltipster-left .tooltipster-box{border-right:3px solid #ff8c2f} .tooltipster-sidetip.tooltipster-right .tooltipster-box{border-left:3px solid #ff8c2f} .fileTree{background-color:#f2f2f2} +#guiSearchBox:placeholder-shown {caret-color:transparent;} +.guiSearchBoxResults {width:450px;} +.awesomplete [hidden] {display: none;} +.awesomplete .visually-hidden {position: absolute;clip: rect(0, 0, 0, 0);} +.awesomplete {display: inline-block;position: relative;color: red;} +.awesomplete > input {display: block;} +.awesomplete > ul {position: absolute;left: 0;z-index: 1;min-width: 100%;box-sizing: border-box;list-style: none;padding: 0;margin: 0;background: #fff;} +.awesomplete > ul:empty {display: none;} +.awesomplete > ul {border-radius: .3em;margin: .2em 0 0;background: hsla(0,0%,100%,.9);background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));border: 1px solid rgba(0,0,0,.3);box-shadow: .05em .2em .6em rgba(0,0,0,.2);text-shadow: none;} +@supports (transform: scale(0)) {.awesomplete > ul {transition: .3s cubic-bezier(.4,.2,.5,1.4);transform-origin: 1.43em -.43em;} + .awesomplete > ul[hidden],.awesomplete > ul:empty {opacity: 0;transform: scale(0);display: block;transition-timing-function: ease;} +} +/* Pointer */ +.awesomplete > ul:before {content: "";position: absolute;top: -.43em;left: 1em;width: 0; height: 0;padding: .4em;background: white;border: inherit;border-right: 0;border-bottom: 0;-webkit-transform: rotate(45deg);transform: rotate(45deg);} +.awesomplete > ul > li {position: relative;padding: .2em .5em;cursor: pointer;} +.awesomplete > ul > li:hover {background: hsl(200, 40%, 80%);color: black;} +.awesomplete > ul > li[aria-selected="true"] {background: hsl(205, 40%, 40%);color: white;} +.awesomplete mark {background: hsl(65, 100%, 50%);} +.awesomplete li:hover mark {background: hsl(68, 100%, 41%);} +.awesomplete li[aria-selected="true"] mark {background: hsl(86, 100%, 21%);color: inherit;} \ No newline at end of file diff --git a/emhttp/plugins/dynamix/styles/dynamix-gray.css b/emhttp/plugins/dynamix/styles/dynamix-gray.css index 4ee534c5a..080fc84db 100644 --- a/emhttp/plugins/dynamix/styles/dynamix-gray.css +++ b/emhttp/plugins/dynamix/styles/dynamix-gray.css @@ -266,3 +266,24 @@ div.icon-zip{background:url('../images/file-types.png') -32px -80px;width:16px;h .tooltipster-sidetip.tooltipster-left .tooltipster-box{border-right:3px solid #ff8c2f} .tooltipster-sidetip.tooltipster-right .tooltipster-box{border-left:3px solid #ff8c2f} .fileTree{background-color:#f2f2f2} +#guiSearchBox:placeholder-shown {caret-color:transparent;} +#guiSearchBox {position:fixed;top:55px;left:250px;} +.guiSearchBoxResults {position:fixed !important;top:90px !important; left:250px !important;} +.awesomplete [hidden] {display: none;} +.awesomplete .visually-hidden {position: absolute;clip: rect(0, 0, 0, 0);} +.awesomplete {display: inline-block;position: relative;color: red;} +.awesomplete > input {display: block;} +.awesomplete > ul {position: absolute;left: 0;z-index: 1;min-width: 100%;box-sizing: border-box;list-style: none;padding: 0;margin: 0;background: #fff;} +.awesomplete > ul:empty {display: none;} +.awesomplete > ul {border-radius: .3em;margin: .2em 0 0;background: hsla(0,0%,100%,.9);background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));border: 1px solid rgba(0,0,0,.3);box-shadow: .05em .2em .6em rgba(0,0,0,.2);text-shadow: none;} +@supports (transform: scale(0)) {.awesomplete > ul {transition: .3s cubic-bezier(.4,.2,.5,1.4);transform-origin: 1.43em -.43em;} + .awesomplete > ul[hidden],.awesomplete > ul:empty {opacity: 0;transform: scale(0);display: block;transition-timing-function: ease;} +} +/* Pointer */ +.awesomplete > ul:before {content: "";position: absolute;top: -.43em;left: 1em;width: 0; height: 0;padding: .4em;background: white;border: inherit;border-right: 0;border-bottom: 0;-webkit-transform: rotate(45deg);transform: rotate(45deg);} +.awesomplete > ul > li {position: relative;padding: .2em .5em;cursor: pointer;} +.awesomplete > ul > li:hover {background: hsl(200, 40%, 80%);color: black;} +.awesomplete > ul > li[aria-selected="true"] {background: hsl(205, 40%, 40%);color: white;} +.awesomplete mark {background: hsl(65, 100%, 50%);} +.awesomplete li:hover mark {background: hsl(68, 100%, 41%);} +.awesomplete li[aria-selected="true"] mark {background: hsl(86, 100%, 21%);color: inherit;} \ No newline at end of file diff --git a/emhttp/plugins/dynamix/styles/dynamix-white.css b/emhttp/plugins/dynamix/styles/dynamix-white.css index 830f1295e..8db7a567c 100644 --- a/emhttp/plugins/dynamix/styles/dynamix-white.css +++ b/emhttp/plugins/dynamix/styles/dynamix-white.css @@ -266,3 +266,23 @@ div.icon-zip{background:url('../images/file-types.png') -32px -80px;width:16px;h .tooltipster-sidetip.tooltipster-left .tooltipster-box{border-right:3px solid #ff8c2f} .tooltipster-sidetip.tooltipster-right .tooltipster-box{border-left:3px solid #ff8c2f} .fileTree{background-color:#f2f2f2} +#guiSearchBox:placeholder-shown {caret-color:transparent;} +.guiSearchBoxResults {width:450px;} +.awesomplete [hidden] {display: none;} +.awesomplete .visually-hidden {position: absolute;clip: rect(0, 0, 0, 0);} +.awesomplete {display: inline-block;position: relative;color: red;} +.awesomplete > input {display: block;} +.awesomplete > ul {position: absolute;left: 0;z-index: 1;min-width: 100%;box-sizing: border-box;list-style: none;padding: 0;margin: 0;background: #fff;} +.awesomplete > ul:empty {display: none;} +.awesomplete > ul {border-radius: .3em;margin: .2em 0 0;background: hsla(0,0%,100%,.9);background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));border: 1px solid rgba(0,0,0,.3);box-shadow: .05em .2em .6em rgba(0,0,0,.2);text-shadow: none;} +@supports (transform: scale(0)) {.awesomplete > ul {transition: .3s cubic-bezier(.4,.2,.5,1.4);transform-origin: 1.43em -.43em;} + .awesomplete > ul[hidden],.awesomplete > ul:empty {opacity: 0;transform: scale(0);display: block;transition-timing-function: ease;} +} +/* Pointer */ +.awesomplete > ul:before {content: "";position: absolute;top: -.43em;left: 1em;width: 0; height: 0;padding: .4em;background: white;border: inherit;border-right: 0;border-bottom: 0;-webkit-transform: rotate(45deg);transform: rotate(45deg);} +.awesomplete > ul > li {position: relative;padding: .2em .5em;cursor: pointer;} +.awesomplete > ul > li:hover {background: hsl(200, 40%, 80%);color: black;} +.awesomplete > ul > li[aria-selected="true"] {background: hsl(205, 40%, 40%);color: white;} +.awesomplete mark {background: hsl(65, 100%, 50%);} +.awesomplete li:hover mark {background: hsl(68, 100%, 41%);} +.awesomplete li[aria-selected="true"] mark {background: hsl(86, 100%, 21%);color: inherit;} \ No newline at end of file diff --git a/emhttp/plugins/gui.search/exec.php b/emhttp/plugins/gui.search/exec.php new file mode 100755 index 000000000..1d7ef2e03 --- /dev/null +++ b/emhttp/plugins/gui.search/exec.php @@ -0,0 +1,146 @@ +sanitizeQuote(_($page[0])),"value"=>basename($page[1],".page")),$searchPages) ) + $searchPages[] = array("label"=>sanitizeQuote(_($page[0])),"value"=>basename($page[1],".page")); + } + file_put_contents("/tmp/gui.search/searchResults.json",serialize($searchPages)); +} +echo json_encode($searchPages); + +function getSettings() { + global $searchPages, $pageInfo, $page,$file,$locale; + + $bannedPages = array("ShareEdit","UserEdit","Device","community.applications","Selftest","DeviceInfo","EthX","CA_Notices","SecuritySMB","SecurityNFS"); + if (in_array(basename($page,".page"),$bannedPages) ) return; + foreach (explode("\n",$file[1]) as $line) { + $line = trim($line); + if ( startsWith($line,"_(") && (endsWith($line,")_:") || endsWith($line,")_):") ) ) { + preg_match("//i",$line,$extra,PREG_OFFSET_CAPTURE); + $string = str_replace(["_(",")_:",")_?",")_"],["","","",""],$line); + + $extraEng = ""; + $extraTra = ""; + if ( $extra ) { + $extrasearch = trim(str_replace([""],["",""],$extra[0][0])); + $string = str_replace($extra[0][0],"",$string); + foreach ( explode("|",$extrasearch) as $term ) { + $extraEng .= $term."|"; + $extraTra .= _($term)."|"; + } + $extraEng = " [".rtrim($extraEng,"|")."]"; + $extraTra = " [".rtrim($extraTra,"|")."]"; + } + $string = sanitizeQuote($string); + + $linkPage = basename($page,".page"); + if (strpos($linkPage,"WG") === 0) { + $linkPage = "VPNmanager"; + } + if ( stripos(str_replace(" ","",$line),"") ) + continue; + if ( ! in_array(array("label"=>"$string $extraTra (".sanitizeQuote($pageInfo['Title']).")","value"=>"$linkPage**"._($string)),$searchPages) ) { + $searchPages[] = array("label"=>_($string)." $extraTra (".sanitizeQuote(_($pageInfo['Title'])).")","value"=>"$linkPage**"._($string)); + if ( $locale ) { + if ( _($string) !== $string ) + $searchPages[] = array("label"=>($string)." $extraEng (".sanitizeQuote($pageInfo['Title']).")","value"=>"$linkPage**"._($string)); + } + } + } + } +} + +function sanitizeQuote($string) { + return str_replace("'","",str_replace('"',"",$string)); +} + +############################################## +# Determine if $haystack begins with $needle # +############################################## +function startsWith($haystack, $needle) { + if ( !is_string($haystack) || ! is_string($needle) ) return false; + return $needle === "" || strripos($haystack, $needle, -strlen($haystack)) !== FALSE; +} +############################################# +# Determine if $string ends with $endstring # +############################################# +function endsWith($string, $endString) { + $len = strlen($endString); + if ($len == 0) { + return true; + } + return (substr($string, -$len) === $endString); +} +?> \ No newline at end of file diff --git a/emhttp/plugins/gui.search/gui_search.page b/emhttp/plugins/gui.search/gui_search.page new file mode 100755 index 000000000..5f19685d7 --- /dev/null +++ b/emhttp/plugins/gui.search/gui_search.page @@ -0,0 +1,180 @@ +Menu='Buttons' +Icon='search' +Title='Search' +Code='f002' +--- + + \ No newline at end of file diff --git a/emhttp/plugins/gui.search/gui_search_post_hook.sh b/emhttp/plugins/gui.search/gui_search_post_hook.sh new file mode 100755 index 000000000..813e7b48e --- /dev/null +++ b/emhttp/plugins/gui.search/gui_search_post_hook.sh @@ -0,0 +1,2 @@ +#!/bin/bash +rm -rf /tmp/gui.search From 6ce02d1caacb8367b036c972c4327a2cf4cf847b Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 13 Sep 2023 19:57:17 -0400 Subject: [PATCH 034/212] Stop GUI search.plg from installing --- etc/rc.d/rc.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/rc.d/rc.local b/etc/rc.d/rc.local index 86f620e2a..8b52eeac5 100755 --- a/etc/rc.d/rc.local +++ b/etc/rc.d/rc.local @@ -67,7 +67,7 @@ rm -f /boot/plugins/unRAIDServer.plg rm -f $CONFIG/plugins/unRAIDServer.plg # These plugins are now integrated in the OS or obsolete and may interfere -Obsolete="vfio.pci dynamix.wireguard dynamix.ssd.trim" +Obsolete="vfio.pci dynamix.wireguard dynamix.ssd.trim gui.search" for Plugin in $Obsolete ; do if [[ -e $CONFIG/plugins/$Plugin.plg ]]; then logger "moving obsolete plugin $Plugin.plg to $CONFIG/plugins-error" From eda8d37c12f29fcd314f2cf9f0b926d6d6d360ba Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 15 Sep 2023 21:10:06 +0100 Subject: [PATCH 035/212] Add option to try reflink first. Revert to rsync if not supported. --- .../include/libvirt_helpers.php | 15 ++++++++-- .../dynamix.vm.manager/scripts/VMClone.php | 29 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 67e8962b3..f0321a8ee 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1509,9 +1509,13 @@ private static $encoding = 'UTF-8'; #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 @@ -1556,8 +1560,15 @@ private static $encoding = 'UTF-8'; foreach($file_clone as $diskid => $disk) { $target = $disk['target'] ; $source = $disk['source'] ; + $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) ; + $error = execCommand_nchan($cmdstr,$path,$refcmd) ; if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; } } diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index 05f350e4c..93a2fa7d1 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -36,27 +36,42 @@ function write(...$messages){ } curl_close($com); } -function execCommand_nchan($command,$idx) { + function execCommand_nchan($command,$idx,$refcmd=false) { $waitID = mt_rand(); + if ($refcmd) { + [$cmd,$args] = explode(' ',$refcmd,2); + write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + $rtn = exec("$refcmd 2>&1", $output,$return) ; + if ($return == 0) $reflinkok = true ; else $reflinkok = false ; + write("addLog\0
{$output[0]}"); + $out = $return ? _('The command failed revert to rsync')."." : _('The command finished successfully').'!'; + write("stop_Wait\0$waitID","addLog\0
$out"); + } + + if ($reflinkok) { + return true ; + } else { + $waitID = mt_rand(); [$cmd,$args] = explode(' ',$command,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","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[3]._(" Time remaining: ").$values[4].$values[5] ; + $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 ; + if ($out) $stringsave=$string ; } $retval = pclose($proc); - write("progress\0$idx\0".htmlspecialchars($stringsave)) ; + write("progress\0$idx\0".htmlspecialchars($stringsave)) ; $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; write("stop_Wait\0$waitID","addLog\0
$out"); 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. From 3aeba2f8717000fa291fd91084ad1bbe5f9a1a93 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 16 Sep 2023 00:27:23 +0100 Subject: [PATCH 036/212] Update reflink error display. --- emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index 93a2fa7d1..89e36bc6c 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -42,8 +42,10 @@ function write(...$messages){ [$cmd,$args] = explode(' ',$refcmd,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); $rtn = exec("$refcmd 2>&1", $output,$return) ; - if ($return == 0) $reflinkok = true ; else $reflinkok = false ; - write("addLog\0
{$output[0]}"); + if ($return == 0) $reflinkok = true ; else { + $reflinkok = false ; + write("addLog\0
{$output[0]}"); + } $out = $return ? _('The command failed revert to rsync')."." : _('The command finished successfully').'!'; write("stop_Wait\0$waitID","addLog\0
$out"); } From 5e5fb2e7b6f092b2167430035a07b982670dc1ee Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:22:50 +0100 Subject: [PATCH 037/212] PHP Fix and Spice Audio Fix Fix some PHP errors Fix changing from spice to vnc if