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"
+---
+
+
+// add vm translations (if needed)
+if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
+ $vms = "$docroot/languages/$locale/vms.dot";
+ if (file_exists($vms)) $language = array_merge($language,unserialize(file_get_contents($vms)));
+}
+
+?>
+
+
+
+
+
"._('Invalid VM to edit').".
@@ -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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
+ var match= ".iso";
+ var box = $("#dialogWindow2");
+ box.html($("#templatesnapshot"+opt).html());
+ var height = 200;
+ const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ;
+ var optiontext = Capopt + " Snapshot" ;
+ box.find('#VMName').html(name) ;
+ box.find('#targetsnap').val(snaps) ;
+ box.find('#targetsnapl').html(snaps) ;
+ if (getlist) {
+ var only = 1 ;
+ if (opt == "remove") only = 0;
+ $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid , snapshotname:snaps, only:only}, function(data) {
+ if (data.html) {
+ box.find('#targetsnapimages').html(data.html) ;
+ }
+ },'json');
+ }
+
+
+ document.getElementById("targetsnapfspc").checked = true ;
+
+ box.dialog({
+ title: "_("+optiontext+ ")_",
+ resizable: false,
+ width: 600,
+ height: 500,
+ modal: true,
+ show: {effect:'fade', duration:250},
+ hide: {effect:'fade', duration:250},
+
+ buttons: {
+ "_(Proceed)_": function(){
+ var target = box.find('#targetsnap');
+ if (target.length) {
+ target = target.val();
+ if (!target ) {errorTarget(); return;}
+ } else target = '';
+ var remove = 'yes'
+ var 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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
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)_:
@@ -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() {
+
+
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
+
+
+$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
+require_once "$docroot/webGui/include/Wrappers.php";
+require_once "$docroot/webGui/include/Translations.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+function write(...$messages){
+ $com = curl_init();
+ curl_setopt_array($com,[
+ CURLOPT_URL => 'http://localhost/pub/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"
*/
?>
+$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
$cpus = cpu_list();
@@ -278,7 +279,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 = true ;
+ document.getElementById("targetsnapfspc").checked = false ;
box.dialog({
title: "_("+optiontext+ ")_",
@@ -326,6 +327,80 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist,state){
dialogStyle();
}
+function selectblock(uuid, name ,snaps, opt, getlist,state){
+
+ var root = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
+ var match= ".iso";
+ var box = $("#dialogWindow2");
+ box.html($("#templateblock").html());
+ var height = 200;
+ const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ;
+ var optiontext = Capopt + " Block Devices" ;
+ box.find('#VMName').html(name) ;
+ box.find('#targetsnap').val(snaps) ;
+ box.find('#targetsnapl').html(snaps) ;
+
+ getlist = true ;
+ if (getlist) {
+ var only = 1 ;
+ if (opt == "remove") only = 0;
+ $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-list", uuid:uuid }, function(data) {
+ if (data.html) {
+ var targetbase = document.getElementById("targetblockbase") ;
+ htmlstrbase = "--base " + data.html + " "
+ htmlstrtop = "--top " + data.html + " "
+ $("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)_:
@@ -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 .= "$snap $snapshotdatetime " ;
+ }
+ $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() {
+
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 @@
?>
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
+
require_once "$docroot/webGui/include/Wrappers.php";
+
+// add translations
+$_SERVER['REQUEST_URI'] = '';
+$login_locale = _var($display,'locale');
+require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "$docroot/webGui/include/Translations.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
function write(...$messages){
- $com = curl_init();
- curl_setopt_array($com,[
- CURLOPT_URL => 'http://localhost/pub/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:"=_('Close')?>"},function(close){
+ nchan_vmaction.stop();
+ $('div.spinner.fixed').hide();
+ $('.sweet-alert').hide('fast').removeClass('nchan');
+ setTimeout(function(){bannerAlert("=_('Attention - operation continues in background')?> ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'> ",cmd,plg,func,start);});
+ });
+ $('.sweet-alert').addClass('nchan');
+ $('button.confirm').prop('disabled',button==0);
+ });
+}
function abortOperation(pid) {
swal({title:"=_('Abort background operation')?>",text:"=_('This may leave an unknown state')?>",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"=_('Proceed')?>",cancelButtonText:"=_('Cancel')?>"},function(){
$.post('/webGui/include/StartCommand.php',{kill:pid},function() {
@@ -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)_:
_(Base Image)_:
-
+
_(Top Image )_:
diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php
index b57025ace..ed28dffe0 100644
--- a/plugins/dynamix.vm.manager/include/VMajax.php
+++ b/plugins/dynamix.vm.manager/include/VMajax.php
@@ -121,6 +121,7 @@ case 'domain-start-consoleRV':
$vvarray[] = "type=$protocol\n";
$vvarray[] = "host="._var($_SERVER,'HTTP_HOST')."\n" ;
$vvarray[] = "port=$port\n" ;
+ $vvarray[] = "delete-this-file=1\n" ;
if (!is_dir("/mnt/user/system/remoteviewer")) mkdir("/mnt/user/system/remoteviewer") ;
$vvfile = "/mnt/user/system/remoteviewer/rv"._var($_SERVER,'HTTP_HOST').".$port.vv" ;
file_put_contents($vvfile,$vvarray) ;
@@ -139,6 +140,7 @@ case 'domain-consoleRV':
$vvarray[] = "type=$protocol\n";
$vvarray[] = "host="._var($_SERVER,'HTTP_HOST')."\n" ;
$vvarray[] = "port=$port\n" ;
+ $vvarray[] = "delete-this-file=1\n" ;
if (!is_dir("/mnt/user/system/remoteviewer")) mkdir("/mnt/user/system/remoteviewer") ;
$vvfile = "/mnt/user/system/remoteviewer/rv"._var($_SERVER,'HTTP_HOST').".$port.vv" ;
file_put_contents($vvfile,$vvarray) ;
@@ -353,7 +355,7 @@ case 'snap-list':
$datartn = "";
foreach($data as $snap=>$snapdetail) {
$snapshotdatetime = date("Y-m-d H:i:s",$snapdetail["creationtime"]) ;
- $datartn .= "$snap $snapshotdatetime " ;
+ $datartn .= "$snap $snapshotdatetime " ;
}
$arrResponse['html'] = $datartn ;
break;
diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index 4d326c0a1..721f4fbc2 100644
--- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1466,6 +1466,15 @@ private static $encoding = 'UTF-8';
}
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() ;
@@ -1489,6 +1498,47 @@ private static $encoding = 'UTF-8';
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]["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"];
+
+ $value = json_encode($snaps,JSON_PRETTY_PRINT) ;
+ #var_dump($value) ;
+ file_put_contents($dbpath."/snapshots.db",$value) ;
+ }
+
+ function delete_snapshots_database($vm,$name) {
+ global $lv ;
+ $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
+ $snaps_json = file_get_contents($dbpath."/snapshots.db") ;
+ $snaps = json_decode($snaps_json,true) ;
+ unset($snaps[$name]) ;
+ $value = json_encode($snaps,JSON_PRETTY_PRINT) ;
+ #var_dump($value) ;
+ file_put_contents($dbpath."/snapshots.db",$value) ;
+ return true ;
+ }
+
function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") {
global $lv ;
@@ -1512,7 +1562,7 @@ private static $encoding = 'UTF-8';
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ;
$capacity = $capacity + $disk["capacity"] ;
}
-
+ $dirpath = $pathinfo["dirname"] ;
#get memory
$mem = $lv->domain_get_memory_stats($vm) ;
$memory = $mem[6] ;
@@ -1551,7 +1601,9 @@ private static $encoding = 'UTF-8';
# Remove nvram snapshot
$arrResponse = ['success' => true] ;
}
-
+ write_snapshots_database("$vm","$name") ;
+ #remove meta data
+ $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
return $arrResponse ;
}
@@ -1645,7 +1697,7 @@ private static $encoding = 'UTF-8';
$name = $s['name'] ;
#Delete Metadata only.
if ($actionmeta == "yes") {
- $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
+ $ret = delete_snapshots_database("$vm","$name") ;
}
# Remove running XML and memory.
$xmlfile = $primarypath."/$name.running" ;
@@ -1717,7 +1769,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 ;
@@ -1803,8 +1855,8 @@ private static $encoding = 'UTF-8';
}
}
- $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ;
- #var_dump($ret) ;
+ $ret = delete_snapshots_database("$vm","$snap") ;
+
if(!$ret)
$data = ["error" => "Unable to remove snap metadata $snap"] ;
else
@@ -1849,57 +1901,54 @@ function vm_blockcommit($vm, $snap ,$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 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 " ;
-
- $error = execCommand_nchan($cmdstr,$path) ;
- #var_dump($cmdstr,$output) ;
- if (!$error) {
- $arrResponse = ['error' => substr($output[0],6) ] ;
- return($arrResponse) ;
- } else {
- # Remove nvram snapshot
- $arrResponse = ['success' => true] ;
- }
- #Error Check
+ foreach($disks as $disk) {
+ $path = $disk['file'] ;
+ $cmdstr = "virsh blockcommit '$vm' --path '$path' --verbose " ;
+ if ($pivot == "yes") $cmdstr .= " --pivot " ;
+ if ($action == "yes") $cmdstr .= " --delete " ;
+ # Process disks and update path.
+ $snapdisks=($snapslist[$snap]['disks']) ;
+ if ($base != "--base" && $base != "") {
+ #get file name from snapshot.
+ $snapdisks=($snapslist[$base]['disks']) ;
+ $basepath = "" ;
+ foreach ($snapdisks as $snapdisk) {
+ $diskname = $snapdisk["@attributes"]["name"] ;
+ if ($diskname != $disk['device']) continue ;
+ $basepath = $snapdisk["source"]["@attributes"]["file"] ;
+ }
+ if ($basepath != "") $cmdstr .= " --base '$basepath' ";
}
+ if ($top != "--top" && $top !="") {
+ #get file name from snapshot.
+ $snapdisks=($snapslist[$top]['disks']) ;
+ $toppath = "" ;
+ foreach ($snapdisks as $snapdisk) {
+ $diskname = $snapdisk["@attributes"]["name"] ;
+ if ($diskname != $disk['device']) continue ;
+ $toppath = $snapdisk["source"]["@attributes"]["file"] ;
+ }
+ if ($toppath != "") $cmdstr .= " --top '$toppath' ";
+ }
+
+ $error = execCommand_nchan($cmdstr,$path) ;
+ if (!$error) {
+ $arrResponse = ['error' => substr($output[0],6) ] ;
+ return($arrResponse) ;
+ } else {
+ $arrResponse = ['success' => true] ;
+ }
+
+ }
+ #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.
- if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ;
+ $ret = $ret = delete_snapshots_database("$vm","$snap") ; ;
if($ret)
$data = ["error" => "Unable to remove snap metadata $snap"] ;
else
$data = ["success => 'true"] ;
-
return $data ;
}
@@ -1945,9 +1994,11 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) {
$reversed = array_reverse($output) ;
$snaps[$vm][$rev] = $reversed ;
}
- $snaps_json=json_encode($snaps) ;
- file_put_contents("/tmp/snaps",$snaps_json) ;
- #var_dump($disks) ;
+ $snaps_json=json_encode($snaps,JSON_PRETTY_PRINT) ;
+ $pathinfo = pathinfo($file) ;
+ $dirpath = $pathinfo["dirname"] ;
+ file_put_contents("$dirpath/image.tracker",$snaps_json) ;
+
foreach($disks as $disk) {
$path = $disk['file'] ;
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ;
@@ -1968,9 +2019,7 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) {
if ($action) $cmdstr .= " $action ";
- $test = false ;
- if ($test) $cmdstr .= " --print-xml " ;
-
+
$error = execCommand_nchan($cmdstr,$path) ;
if (!$error) {
@@ -1983,7 +2032,7 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) {
#Error Check
}
#If complete ok remove meta data for snapshots.
- $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ;
+ #$ret = delete_snapshots_database("$vm","$snap") ;
if($ret)
$data = ["error" => "Unable to remove snap metadata $snap"] ;
else
diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js
index 41fe241bb..2a5929f43 100644
--- a/plugins/dynamix.vm.manager/javascript/vmmanager.js
+++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js
@@ -229,12 +229,12 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
selectsnapshot(uuid, name, snapshotname, "revert",true) ;
}});
- opts.push({text:_("Remove snapshot"), icon:"fa-trash", action:function(e) {
- e.preventDefault();
- $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
- selectsnapshot(uuid, name, snapshotname, "remove",true) ;
- }});
-}
+ }
+ opts.push({text:_("Remove snapshot"), icon:"fa-trash", action:function(e) {
+ e.preventDefault();
+ $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
+ selectsnapshot(uuid, name, snapshotname, "remove",true) ;
+ }});
context.attach('#vmsnap-'+uuid, opts);
}
function startAll() {
diff --git a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php
index 7fc64bf76..59b846f4d 100755
--- a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php
+++ b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php
@@ -35,16 +35,18 @@ function write(...$messages){
}
curl_close($com);
}
-function execCommand_nchan($command) {
+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("addToID\0 99\0 $action") ;
+ 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);
- write("addToID\0 99\0".htmlspecialchars($out));
- sleep(5) ;
+ $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').'!';
@@ -53,6 +55,7 @@ function execCommand_nchan($command) {
}
#{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 = ["";
-$path = "--current" ; $pivot = "yes" ;
+
foreach (explode('&', $url) as $chunk) {
$param = explode("=", $chunk);
if ($param) {
@@ -69,25 +72,23 @@ 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("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");
+if ($action == "commit") {
+ write("addLog\0".htmlspecialchars("Top $targettop "));
+ write("addLog\0".htmlspecialchars("Pivot $targetpivot "));
+ write("addLog\0".htmlspecialchars("Delete $targetdelete "));
+}
-
- 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,' ') ;
+ vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$targetpivot,$targetdelete) ;
break ;
case "copy":
- # vm_blockcopy($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ;
+ vm_blockcopy($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ;
break;
case "pull":
vm_blockpull($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ;
From b16b9c35d2d414170742ef7501a9db88c56f2b14 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Fri, 2 Jun 2023 16:32:48 +0100
Subject: [PATCH 017/212] WIP Update.
---
.../dynamix.vm.manager/include/VMMachines.php | 38 +++++++----
.../dynamix.vm.manager/include/libvirt.php | 2 +-
.../include/libvirt_helpers.php | 66 ++++++++++++++++++-
3 files changed, 90 insertions(+), 16 deletions(-)
diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php
index 68c075827..e88ce6c8f 100644
--- a/plugins/dynamix.vm.manager/include/VMMachines.php
+++ b/plugins/dynamix.vm.manager/include/VMMachines.php
@@ -245,24 +245,34 @@ foreach ($vms as $vm) {
echo "";
/* Display VM Snapshots */
if ($snapshots != null) {
-
+ $i=0 ;
+ foreach($snapshots as $snap) {
+ if ($snap['parent'] == "" || $snap['parent'] == "Base") $i++;
+ $steps[$i] .= $snap['name'].';' ;
+ }
echo " "._('Snapshots')." "._('Date/Time')." "._('Type')." "._('Parent')." "._('Memory')." ";
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|__ ".$snapshot["name"]."$snapshotdesc $snapshotdatetime $snapshotstate $snapshotparent $snapshotmemory ";
- $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|__ ".$snapshot["name"]."$snapshotdesc $snapshotdatetime $snapshotstate $snapshotparent $snapshotmemory ";
+ $tab .=" " ;
+ }
+ echo " ";
+ }
}
- echo "";
-}
echo "
";
- 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"
----
-
-
-// add vm translations (if needed)
-if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
- $vms = "$docroot/languages/$locale/vms.dot";
- if (file_exists($vms)) $language = array_merge($language,unserialize(file_get_contents($vms)));
-}
-
-?>
-
-
-
-
-
-
-lang="=strtok($locale,'_')?:'en'?>">
-
-
-
-
-
-
-
- ">
-
-
-_(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="=strtok($locale,'_')?:'en'?>">
+
+
+
+
+
+
+
+
">
+
+
+
+
+
+
\ 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 @@
-
-
-$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
-// add translations
-if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
- $_SERVER['REQUEST_URI'] = 'vms';
- require_once "$docroot/webGui/include/Translations.php";
-}
-require_once "$docroot/webGui/include/Helpers.php";
-require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
-
-switch ($display['theme']) {
- case 'gray' : $bgcolor = '#121510'; $border = '#606e7f'; $top = -44; break;
- case 'azure': $bgcolor = '#edeaef'; $border = '#606e7f'; $top = -44; break;
- case 'black': $bgcolor = '#212121'; $border = '#2b2b2b'; $top = -58; break;
- default : $bgcolor = '#ededed'; $border = '#e3e3e3'; $top = -58; break;
-}
-
-$strSelectedTemplate = array_keys($arrAllTemplates)[1];
-if (isset($_GET['template']) && isset($arrAllTemplates[unscript($_GET['template'])])) {
- $strSelectedTemplate = unscript($_GET['template']);
-}
-
-$arrLoad = [
- 'name' => '',
- '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'];
-}
-?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
+ 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
+
+
+$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
+
+require_once "$docroot/webGui/include/Wrappers.php";
+
+// add translations
+$_SERVER['REQUEST_URI'] = '';
+$login_locale = _var($display,'locale');
+require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
+require_once "$docroot/webGui/include/Translations.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+function write(...$messages){
+ $com = curl_init();
+ curl_setopt_array($com,[
+ CURLOPT_URL => 'http://localhost/pub/vmaction?buffer_length=1',
+ CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
+ CURLOPT_POST => 1,
+ CURLOPT_RETURNTRANSFER => true
+ ]);
+ foreach ($messages as $message) {
+ curl_setopt($com, CURLOPT_POSTFIELDS, $message);
+ curl_exec($com);
+ }
+ curl_close($com);
+ }
+function execCommand_nchan($command,$idx) {
+ $waitID = mt_rand();
+ [$cmd,$args] = explode(' ',$command,2);
+ write("
","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:"=_('Close')?>"},function(close){
+ nchan_vmaction.stop();
+ $('div.spinner.fixed').hide();
+ $('.sweet-alert').hide('fast').removeClass('nchan');
+ setTimeout(function(){bannerAlert("=_('Attention - operation continues in background')?> ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'> ",cmd,plg,func,start);});
+ });
+ $('.sweet-alert').addClass('nchan');
+ $('button.confirm').prop('disabled',button==0);
+ });
+}
function abortOperation(pid) {
swal({title:"=_('Abort background operation')?>",text:"=_('This may leave an unknown state')?>",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"=_('Proceed')?>",cancelButtonText:"=_('Cancel')?>"},function(){
$.post('/webGui/include/StartCommand.php',{kill:pid},function() {
@@ -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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
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="=strtok($locale,'_')?:'en'?>">
+
+
+
+
+
+
+
+
">
+
+
+
+
+
+
+
\ 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("=_('Error')?>").prop('disabled',false).show();
+ return true;
+ }
+ return false;
+}
function showStatus(name,plugin,job) {
$.post('/webGui/include/ProcessStatus.php',{name:name,plugin:plugin,job:job},function(status){$(".tabs").append(status);});
}
@@ -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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
+ var match= ".iso";
+ var box = $("#dialogWindow");
+ var height = 200;
+ box.html($("#templateClone").html());
+ box.find('#VMBeingCloned').html(name).change() ;
+ box.find('#target').val(name + "_clone") ;
+ document.getElementById("Free").checked = true ;
+ document.getElementById("Overwrite").checked = true ;
+ var Cancel = _("Cancel") ;
+ box.dialog({
+ title: "VM Clone",
+ resizable: false,
+ width: 600,
+ height: 500,
+ modal: true,
+ show: {effect:'fade', duration:250},
+ hide: {effect:'fade', duration:250},
+ buttons: {
+ "_(Clone)_" : function(){
+ var target = box.find('#target');
+ if (target.length) {
+ target = target.val();
+ //if (!target ) {errorTarget(); return;}
+ } else target = '';
+
+ var clone = box.find("#target").prop('value') ;
+ x = box.find('#Start').prop('checked') ;
+ if (x) start = 'yes' ; else start = 'no' ;
+ x = box.find('#Edit').prop('checked') ;
+ if (x) edit = 'yes' ; else edit = 'no' ;
+ x = box.find('#Overwrite').prop('checked') ;
+ if (x) overwrite = 'yes' ; else overwrite = 'no' ;
+ x = box.find('#Free').prop('checked') ;
+ if (x) free = 'yes' ; else free = 'no' ;
+ scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone" , name:name ,clone:clone, overwrite:overwrite , edit:edit, start,start, free:free})) ;
+ openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist") ;
+ box.dialog('close');
+ },
+ "_(Cancel)_": function(){
+ box.dialog('close');
+ }
+ }
+ });
+ dialogStyle();
+}
function 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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
- var match= ".iso";
- var box = $("#dialogWindow");
- var height = 200;
- box.html($("#templateClone").html());
- box.find('#VMBeingCloned').html(name).change() ;
- box.find('#target').val(name + "_clone") ;
- document.getElementById("Free").checked = true ;
- document.getElementById("Overwrite").checked = true ;
-
- 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 = = '"'.$domain_cfg["MEDIADIR"].'"';?>;
+ var match= ".iso";
+ var box = $("#dialogWindow");
+ var height = 200;
+ box.html($("#templateClone").html());
+ box.find('#VMBeingCloned').html(name).change() ;
+ box.find('#target').val(name + "_clone") ;
+ document.getElementById("Free").checked = true ;
+ document.getElementById("Overwrite").checked = true ;
+ var Cancel = _("Cancel") ;
+ box.dialog({
+ title: "VM Clone",
+ resizable: false,
+ width: 600,
+ height: 500,
+ modal: true,
+ show: {effect:'fade', duration:250},
+ hide: {effect:'fade', duration:250},
+ buttons: {
+ "_(Clone)_" : function(){
+ var target = box.find('#target');
+ if (target.length) {
+ target = target.val();
+ //if (!target ) {errorTarget(); return;}
+ } else target = '';
+
+ var clone = box.find("#target").prop('value') ;
+ x = box.find('#Start').prop('checked') ;
+ if (x) start = 'yes' ; else start = 'no' ;
+ x = box.find('#Edit').prop('checked') ;
+ if (x) edit = 'yes' ; else edit = 'no' ;
+ x = box.find('#Overwrite').prop('checked') ;
+ if (x) overwrite = 'yes' ; else overwrite = 'no' ;
+ x = box.find('#Free').prop('checked') ;
+ if (x) free = 'yes' ; else free = 'no' ;
+ scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone" , name:name ,clone:clone, overwrite:overwrite , edit:edit, start,start, free:free})) ;
+ openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist") ;
+ box.dialog('close');
+ },
+ "_(Cancel)_": function(){
+ box.dialog('close');
+ }
+ }
+ });
+ dialogStyle();
+}
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 @@
+
+###################################################
+# #
+# GUI Search copyright 2021-2023, Andrew Zawadzki #
+# Licenced under GPLv2 #
+# #
+###################################################
+@mkdir("/tmp/gui.search");
+
+extract(@parse_ini_file("/boot/config/plugins/dynamix/dynamix.cfg"));
+$locale = $locale ?: "";
+if ( $locale != @file_get_contents("/tmp/gui.search/locale") ) {
+ @unlink("/tmp/gui.search/searchResults.json");
+}
+if ( $locale == "en_US")
+ $locale = "";
+
+file_put_contents("/tmp/gui.search/locale",$locale);
+
+$uri = "";
+if ( $locale ) {
+ if ( is_dir("/usr/local/emhttp/languages/$locale") ) {
+ $dotFiles = glob("/usr/local/emhttp/languages/$locale/*.txt");
+ foreach ($dotFiles as $dot) {
+ $uri .= basename($dot,".txt")."/";
+ }
+ $uri = rtrim($uri,"/");
+ }
+}
+
+$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: "/usr/local/emhttp";
+
+$_SERVER['REQUEST_URI'] = $uri;
+$_SESSION['locale'] = $locale;
+require_once "$docroot/plugins/dynamix/include/Translations.php";
+
+$searchPages = array();
+$pageFiles = glob("/usr/local/emhttp/plugins/*/*.page");
+if ( is_file("/tmp/gui.search/searchResults.json") ) {
+ $searchPages = unserialize(file_get_contents("/tmp/gui.search/searchResults.json"));
+}
+if ( ! $searchPages ) {
+ $MainPages = array("About","WebGui","UNRAID-OS","SystemInformation","OtherSettings","Settings","NetworkServices","Utilities","DiskUtilities","UserPreferences","WGX");
+ // pass 1 - get all the "main" pages
+ foreach ($pageFiles as $page) {
+ if ( $page == "/usr/local/emhttp/plugins/dynamix/WGX.page") continue;
+ $file = explode("---",file_get_contents($page));
+ $pageInfo = parse_ini_string($file[0],true);
+
+ if ( isset($pageInfo['Menu']) && $pageInfo['Menu'] && in_array(explode(":",$pageInfo['Menu'])[0],$MainPages) && ! in_array(basename($page,".page"),$MainPages) ) {
+ $newPage[basename($page,".page")] = array(sanitizeQuote(_($pageInfo['Title'])),basename($page));
+ if ( $locale )
+ $newPage[basename($page,".page")] = array(sanitizeQuote($pageInfo['Title']),basename($page));
+ }
+ }
+
+ //pass 2 - link back any sub-pages
+ foreach ($pageFiles as $page) {
+ $file = explode("---",file_get_contents($page));
+ if ( ! isset($file[1]) ) continue;
+ $pageInfo = parse_ini_string($file[0],true);
+
+ if (isset($pageInfo['Menu']) && $pageInfo['Menu']) {
+ $pageLinked = explode(":",$pageInfo['Menu'])[0];
+ if (isset($newPage[$pageLinked]) && ! in_array($pageLinked,$MainPages) ) {
+ $newPage[] = array(sanitizeQuote(_($pageInfo['Title']))." ("._($newPage[$pageLinked][0]).")",$pageLinked);
+ if ( $locale )
+ $newPage[] = array(sanitizeQuote(($pageInfo['Title']))." (".($newPage[$pageLinked][0]).")",$pageLinked);
+
+ }
+ getSettings();
+ }
+ }
+ //pass 3 - cleanup
+ foreach ($newPage as $page) {
+ if ( ! in_array(array("label"=>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'
+---
+
+###################################################
+# #
+# GUI Search copyright 2021-2023, Andrew Zawadzki #
+# Licenced under GPLv2 #
+# #
+###################################################
+
+$currentUnraidPage = str_replace("Browse","Main",basename(explode("?",$_SERVER['REQUEST_URI'])[0]));
+
+$altGUI = ($theme == "azure" || $theme == "gray");
+?>
+
\ 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 is set,
---
.../dynamix.vm.manager/include/VMMachines.php | 4 +-
.../dynamix.vm.manager/include/libvirt.php | 84 ++++++++++---------
.../include/libvirt_helpers.php | 4 +-
3 files changed, 49 insertions(+), 43 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php
index 54bdbd15b..8348998de 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php
@@ -171,10 +171,10 @@ foreach ($vms as $vm) {
foreach ($lv->get_cdrom_stats($res) as $arrCD) {
$capacity = $lv->format_size($arrCD['capacity'], 0);
$allocation = $lv->format_size($arrCD['allocation'], 0);
- $disk = $arrCD['file'] ?? $arrCD['partition'];
+ $disk = $arrCD['file'] ?? $arrCD['partition'] ?? "" ;
$dev = $arrCD['device'];
$bus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
- $boot= $arrCD["boot order"];
+ $boot= $arrCD["boot order"] ?? "" ;
if ($boot < 1) $boot="Not set";
if ($disk != "" ) {
$title = _("Eject CD Drive").".";
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index a9d938654..44e9449fe 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -781,6 +781,7 @@
if ($strAutoport == "yes") $strPort = $strWSport = "-1" ;
if (($gpu['copypaste'] == "yes") && ($strProtocol == "spice")) $vmrcmousemode = " " ; else $vmrcmousemode = "" ;
+ if ($strProtocol == "spice") $virtualaudio = "spice" ; else $virtualaudio = "none" ;
$vmrc = "
@@ -791,7 +792,9 @@
- ";
+
+ ";
+
if ($gpu['copypaste'] == "yes") {
if ($strProtocol == "spice") {
@@ -1154,7 +1157,7 @@
$tmp = libvirt_domain_get_block_info($dom, $disks[$i]);
if ($tmp) {
$tmp['bus'] = $buses[$i];
- $tmp["boot order"] = $boot[$i] ;
+ $tmp["boot order"] = $boot[$i] ?? "";
$ret[] = $tmp;
}
else {
@@ -1205,7 +1208,7 @@
if ($tmp) {
$tmp['bus'] = $disk->target->attributes()->bus->__toString();
- $tmp["boot order"] = $disk->boot->attributes()->order ;
+ $tmp["boot order"] = $disk->boot->attributes()->order ?? "";
$tmp['serial'] = $disk->serial ;
// Libvirt reports 0 bytes for raw disk images that haven't been
@@ -1605,7 +1608,7 @@
if (!$dom)
return false;
- $tmp = libvirt_domain_get_xml_desc($dom, $xpath);
+ $tmp = libvirt_domain_get_xml_desc($dom, 0);
return ($tmp) ? $tmp : $this->_set_last_error();
}
@@ -2007,7 +2010,7 @@
function domain_get_description($domain) {
$tmp = $this->get_xpath($domain, '//domain/description', false);
- $var = $tmp[0];
+ $var = $tmp[0] ?? "";
unset($tmp);
return $var;
@@ -2209,33 +2212,34 @@
// Get any pci devices contained in the qemu args
$args = $this->get_xpath($domain, '//domain/*[name()=\'qemu:commandline\']/*[name()=\'qemu:arg\']/@value', false);
+ if (isset($args['num'])) {
+ for ($i = 0; $i < $args['num']; $i++) {
+ if (strpos($args[$i], 'vfio-pci') !== 0) {
+ continue;
+ }
- for ($i = 0; $i < $args['num']; $i++) {
- if (strpos($args[$i], 'vfio-pci') !== 0) {
- continue;
- }
+ $arg_list = explode(',', $args[$i]);
- $arg_list = explode(',', $args[$i]);
+ foreach ($arg_list as $arg) {
+ $keypair = explode('=', $arg);
- foreach ($arg_list as $arg) {
- $keypair = explode('=', $arg);
-
- if ($keypair[0] == 'host' && !empty($keypair[1])) {
- $devid = 'pci_0000_' . str_replace([':', '.'], '_', $keypair[1]);
- $tmp2 = $this->get_node_device_information($devid);
- [$bus, $slot, $func] = my_explode(":", str_replace('.', ':', $keypair[1]), 3);
- $devs[] = [
- 'domain' => '0x0000',
- 'bus' => '0x' . $bus,
- 'slot' => '0x' . $slot,
- 'func' => '0x' . $func,
- 'id' => $keypair[1],
- 'vendor' => $tmp2['vendor_name'],
- 'vendor_id' => $tmp2['vendor_id'],
- 'product' => $tmp2['product_name'],
- 'product_id' => $tmp2['product_id']
- ];
- break;
+ if ($keypair[0] == 'host' && !empty($keypair[1])) {
+ $devid = 'pci_0000_' . str_replace([':', '.'], '_', $keypair[1]);
+ $tmp2 = $this->get_node_device_information($devid);
+ [$bus, $slot, $func] = my_explode(":", str_replace('.', ':', $keypair[1]), 3);
+ $devs[] = [
+ 'domain' => '0x0000',
+ 'bus' => '0x' . $bus,
+ 'slot' => '0x' . $slot,
+ 'func' => '0x' . $func,
+ 'id' => $keypair[1],
+ 'vendor' => $tmp2['vendor_name'],
+ 'vendor_id' => $tmp2['vendor_id'],
+ 'product' => $tmp2['product_name'],
+ 'product_id' => $tmp2['product_id']
+ ];
+ break;
+ }
}
}
}
@@ -2264,15 +2268,17 @@
$pid = $this->get_xpath($domain, $xpath.'product/@id', false);
$devs = [];
- for ($i = 0; $i < $vid['num']; $i++) {
- $dev = $this->_lookup_device_usb($vid[$i], $pid[$i]);
- $devs[] = [
- 'id' => str_replace('0x', '', $vid[$i] . ':' . $pid[$i]),
- 'vendor_id' => $vid[$i],
- 'product_id' => $pid[$i],
- 'product' => $dev['product_name'],
- 'vendor' => $dev['vendor_name']
- ];
+ if (isset($vid['num'])) {
+ for ($i = 0; $i < $vid['num']; $i++) {
+ $dev = $this->_lookup_device_usb($vid[$i], $pid[$i]);
+ $devs[] = [
+ 'id' => str_replace('0x', '', $vid[$i] . ':' . $pid[$i]),
+ 'vendor_id' => $vid[$i],
+ 'product_id' => $pid[$i],
+ 'product' => $dev['product_name'],
+ 'vendor' => $dev['vendor_name']
+ ];
+ }
}
return $devs;
@@ -2306,7 +2312,7 @@
'mac' => $macs[$i],
'network' => $net[0],
'model' => $model[0],
- 'boot' => $boot[0]
+ 'boot' => $boot[0] ?? ""
];
}
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index f0321a8ee..1f18b725c 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1370,7 +1370,7 @@ private static $encoding = 'UTF-8';
$vendor=$USB->source->vendor->attributes()->id ;
$product=$USB->source->product->attributes()->id ;
$startupPolicy=$USB->source->attributes()->startupPolicy ;
- $usbboot= $USB->boot->attributes()->order ;
+ $usbboot= $USB->boot->attributes()->order ?? "" ;
$id = str_replace('0x', '', $vendor . ':' . $product) ;
$found = false ;
foreach($arrValidUSBDevices as $key => $data) {
@@ -1420,7 +1420,7 @@ private static $encoding = 'UTF-8';
global $lv ;
$xml = new SimpleXMLElement($lv->domain_get_xml($res)) ;
$data = $xml->xpath('//channel/target[@name="org.qemu.guest_agent.0"]/@state') ;
- $data = $data[0]->state ;
+ $data = $data[0]->state ?? null ;
return $data ;
}
From e44851de92f9a4fe0d3043e85c413560692dd7b3 Mon Sep 17 00:00:00 2001
From: Squidly271
Date: Tue, 19 Sep 2023 07:50:04 -0400
Subject: [PATCH 038/212] PHP8 error if dynamix.cfg doesn't exist
---
emhttp/plugins/gui.search/exec.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/gui.search/exec.php b/emhttp/plugins/gui.search/exec.php
index 1d7ef2e03..19591914b 100755
--- a/emhttp/plugins/gui.search/exec.php
+++ b/emhttp/plugins/gui.search/exec.php
@@ -8,7 +8,7 @@
@mkdir("/tmp/gui.search");
extract(@parse_ini_file("/boot/config/plugins/dynamix/dynamix.cfg"));
-$locale = $locale ?: "";
+$locale = $locale ?? "";
if ( $locale != @file_get_contents("/tmp/gui.search/locale") ) {
@unlink("/tmp/gui.search/searchResults.json");
}
@@ -143,4 +143,4 @@ function endsWith($string, $endString) {
}
return (substr($string, -$len) === $endString);
}
-?>
\ No newline at end of file
+?>
From e842121c3b9c9f3da21b2195898e10d7f33af694 Mon Sep 17 00:00:00 2001
From: ljm42
Date: Tue, 19 Sep 2023 16:49:03 -0700
Subject: [PATCH 039/212] Improve Downgrade process
---
emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php | 3 +++
sbin/upgrade | 2 ++
2 files changed, 5 insertions(+)
diff --git a/emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php b/emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
index ecf97df96..a3a92ba2a 100644
--- a/emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
+++ b/emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
@@ -20,6 +20,9 @@ require_once "$docroot/webGui/include/Secure.php";
$_SERVER['REQUEST_URI'] = 'plugins';
require_once "$docroot/webGui/include/Translations.php";
+$tmpdir="/boot/deletemedowngrade.".uniqid();
+mkdir($tmpdir);
+exec("mv -f /boot/bz* $tmpdir");
exec("mv -f /boot/previous/* /boot");
$version = unscript(_var($_GET,'version'));
file_put_contents("$docroot/plugins/unRAIDServer/README.md","**"._('DOWNGRADE TO VERSION')." $version**");
diff --git a/sbin/upgrade b/sbin/upgrade
index 17c36c3e3..b22c109e7 100755
--- a/sbin/upgrade
+++ b/sbin/upgrade
@@ -38,3 +38,5 @@ mkdir -p "$CFG_NEW"
# stating with 6.10 'USE_SSL="auto"' without a LE cert is invalid
[[ ! -f /boot/config/ssl/certs/certificate_bundle.pem ]] && sed -i s/USE_SSL=\"auto\"/USE_SSL=\"no\"/ /boot/config/ident.cfg
+# delete any temp dir left over from a version downgrade (see dynamix.plugin.manager/include/Downgrade.php)
+rm -rf /boot/deletemedowngrade.*
From 051e603384958374cfb5aebfc99d8dd2772558ee Mon Sep 17 00:00:00 2001
From: Squidly271
Date: Sat, 23 Sep 2023 10:07:04 -0400
Subject: [PATCH 040/212] Minor logic error if locale is "en_US" instead of ""
(unlikely)
---
emhttp/plugins/gui.search/exec.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/gui.search/exec.php b/emhttp/plugins/gui.search/exec.php
index 19591914b..c55108aaf 100755
--- a/emhttp/plugins/gui.search/exec.php
+++ b/emhttp/plugins/gui.search/exec.php
@@ -9,11 +9,12 @@
extract(@parse_ini_file("/boot/config/plugins/dynamix/dynamix.cfg"));
$locale = $locale ?? "";
+if ( $locale == "en_US")
+ $locale = "";
+
if ( $locale != @file_get_contents("/tmp/gui.search/locale") ) {
@unlink("/tmp/gui.search/searchResults.json");
}
-if ( $locale == "en_US")
- $locale = "";
file_put_contents("/tmp/gui.search/locale",$locale);
From a039c2dd494f406b3f8d49c4654337b235718137 Mon Sep 17 00:00:00 2001
From: Squidly271
Date: Sat, 23 Sep 2023 10:20:14 -0400
Subject: [PATCH 041/212] Fatal PHP error if dynamix.cfg corrupt or non
existent
---
emhttp/plugins/gui.search/exec.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/plugins/gui.search/exec.php b/emhttp/plugins/gui.search/exec.php
index c55108aaf..588f66fb6 100755
--- a/emhttp/plugins/gui.search/exec.php
+++ b/emhttp/plugins/gui.search/exec.php
@@ -7,7 +7,7 @@
###################################################
@mkdir("/tmp/gui.search");
-extract(@parse_ini_file("/boot/config/plugins/dynamix/dynamix.cfg"));
+extract(@parse_ini_file("/boot/config/plugins/dynamix/dynamix.cfg") ?: []);
$locale = $locale ?? "";
if ( $locale == "en_US")
$locale = "";
From 6b67f4a5891998a525ae95a64294fd43034f48f9 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 25 Sep 2023 15:05:02 +0100
Subject: [PATCH 042/212] Merge and spaces fixes
---
.../dynamix.vm.manager/VMMachines.page | 134 +++++++++---------
.../include/libvirt_helpers.php | 23 +--
2 files changed, 79 insertions(+), 78 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page
index 91ba453cb..f34aebf5b 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page
@@ -566,6 +566,7 @@ $(function() {
_(ISO Image)_:
:
+
_(CD1 ISO Image)_:
:
@@ -573,6 +574,71 @@ _(CD2 ISO Image)_:
:
+
+
+
+
+_(VM Name)_:
+
+
+_(Snapshot Name)_:
+
+
+_(Remove Images)_:
+
+_(Remove Meta)_:
+
+
+
+
+
+
+
+_(!! Warning removing Snapshots can break the chain !!)_
+
+_(VM Name)_:
+
+
+_(Snapshot Name)_:
+
+
+
+
+
+
+_(VM Name)_:
+
+
+_(Snapshot Name)_:
+
+
+
+
+
+
@@ -611,73 +677,7 @@ _(CD2 ISO Image)_: