WIP Update.

This commit is contained in:
SimonFair
2023-06-25 18:50:18 +01:00
parent 6ede3d55a1
commit 144c456980
7 changed files with 403 additions and 432 deletions
@@ -1,111 +0,0 @@
Title="Clone VM"
Tag="clipboard"
Cond="(pgrep('libvirtd')!==false)"
Markdown="false"
---
<?PHP
/* Copyright 2005-2020, Lime Technology
* Copyright 2015-2020, Derek Macias, Eric Schultz, Jon Panozzo.
* Copyright 2012-2020, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
// 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)));
}
?>
<style>
div.template,div#dialogWindow,input#upload{display:none}
.fileTree{background:<?=$bgcolor?>;width:500px;max-height:320px;overflow-y:scroll;overflow-x:hidden;position:absolute;z-index:100;display:none}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button[disabled]{cursor:default;color:#808080;background:-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#404040),to(#404040)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#808080),to(#808080)) 100% 100% no-repeat;background:linear-gradient(90deg,#404040 0,#808080) 0 0 no-repeat,linear-gradient(90deg,#404040 0,#808080) 0 100% no-repeat,linear-gradient(0deg,#404040 0,#404040) 0 100% no-repeat,linear-gradient(0deg,#808080 0,#808080) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
.dropdown-menu{z-index:10001;
</style>
<script>
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 dialogStyle() {
$('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('<i class="fa fa-times"></i>').prop('title',"_(Close)_").prop('onclick',null).off('click').click(function(){box.dialog('close');});
$('.ui-dialog-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'});
}
getCloneName("Test") ;
</script>
<div id="dialogWindow"></div>
<div markdown="1" id="templateClone" class="template">
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1600">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
</head>
<body>
_(VM Being Cloned)_:
<span id="VMBeingCloned"></span><br>
_(New VM)_:
<input type="text" id="target" autocomplete="off" spellcheck="false" value="" ><br>
<tabel>
<tr><td>_(Overwrite)_:</td>
<td><input type="checkbox" id="Overwrite" value="" ></td></tr><br>
<tr><td>_(Start Cloned VM)_:</td>
<td><input type="checkbox" id="Start" value="" ></td></tr><br>
_(Edit VM after clone)_:
<input type="checkbox" id="Edit" value="" ><br>
</table>
</body>
</html>
</div>
@@ -305,4 +305,42 @@ $(function() {
_(ISO Image)_:
: <input type="text" id="target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
</div>
<div id="dialogWindow"></div>
<div markdown="1" id="templateClone" class="template">
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1600">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
</head>
<body>
<br>
<table>
<tr><td>_(VM Being Cloned)_:</td>
<td><span id="VMBeingCloned"></span></td></tr>
<tr><td>_(New VM)_:</td>
<td><input type="text" id="target" autocomplete="off" spellcheck="false" value="" ></td></tr>
<tr><td>_(Overwrite)_:</td>
<td><input type="checkbox" id="Overwrite" value="" ></td></tr>
<tr><td>_(Start Cloned VM)_:</td>
<td><input type="checkbox" id="Start" value="" ></td></tr>
<tr><td>_(Edit VM after clone)_:</td>
<td><input type="checkbox" id="Edit" value="" ></td></tr>
<tr><td>_(Check Free Space)_:</td>
<td><input type="checkbox" id="Free" value="" ></td></tr>
</table>
</body>
</html>
</div>
@@ -1,272 +0,0 @@
<?PHP
/* Copyright 2005-2021, Lime Technology
* Copyright 2015-2021, Derek Macias, Eric Schultz, Jon Panozzo.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$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 "<p class='notice'>"._('Invalid VM to edit').".</p><input type='button' value=\""._('Done')."\" onclick='done()'>";
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'];
}
?>
<link type="text/css" rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css')?>">
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
<style>
body{-webkit-overflow-scrolling:touch}
.fileTree{background:<?=$bgcolor?>;width:300px;max-height:150px;overflow-y:scroll;overflow-x:hidden;position:relative;z-index:100;display:none}
#vmform table{margin-top:0}
#vmform div#title + table{margin-top:0}
#vmform table tr{vertical-align:top;line-height:40px}
#vmform table tr td:nth-child(odd){width:300px;text-align:right;padding-right:10px}
#vmform table tr td:nth-child(even){width:110px}
#vmform table tr td:first-child{padding-right:30px}
#vmform table tr td:last-child{width:inherit}
#vmform .multiple{position:relative}
#vmform .sectionbutton{position:absolute;left:2px;cursor:pointer;opacity:0.4;font-size:1.4rem;line-height:17px;z-index:10;transition-property:opacity,left;transition-duration:0.1s;transition-timing-function:linear}
#vmform .sectionbutton.remove{top:0;opacity:0.3}
#vmform .sectionbutton.add{bottom:0}
#vmform .sectionbutton:hover{opacity:1.0}
#vmform .sectiontab{position:absolute;top:2px;bottom:2px;left:0;width:6px;border-radius:3px;background-color:#DDDDDD;transition-property:background,width;transition-duration:0.1s;transition-timing-function:linear}
#vmform .multiple:hover .sectionbutton{opacity:0.7;left:4px}
#vmform .multiple:hover .sectionbutton.remove{opacity:0.6}
#vmform .multiple:hover .sectiontab{background-color:#CCCCCC;width:8px}
#vmform table.multiple{margin:10px 0;background:<?=$bgcolor?>;background-size:800px 100%;background-position:-800px;background-repeat:no-repeat;background-clip:content-box;transition:background 0.3s linear}
#vmform table.multiple:hover{background-position:0 0;}
#vmform table.multiple td{padding:5px 0}
span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
.basic{display:none}
.advanced{/*Empty placeholder*/}
.switch-button-label.off{color:inherit}
#template_img{cursor:pointer}
#template_img:hover{opacity:0.5}
#template_img:hover i{opacity:1.0}
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
.template_img_chooser_inner img{width:48px;height:48px}
.template_img_chooser_inner p{text-align:center;line-height:8px;}
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
#template_img_chooser_outer{position:absolute;display:none;border-radius:5px;border:1px solid <?=$border?>;background:<?=$bgcolor?>;z-index:10}
#form_content{display:none}
#vmform .four{overflow:hidden}
#vmform .four label{float:left;display:table-cell;width:15%;}
#vmform .four label:nth-child(4n+4){}
#vmform .four label.cpu1{width:28%;height:16px;line-height:16px}
#vmform .four label.cpu2{width:3%;height:16px;line-height:16px}
#vmform .mac_generate{cursor:pointer;margin-left:-5px;color:#08C;font-size:1.3rem;transform:translate(0px, 2px)}
#vmform .disk{display:none}
#vmform .disk_preview{display:inline-block;color:#BBB;transform:translate(0px, 1px)}
span#dropbox{border:1px solid <?=$border?>;background:<?=$bgcolor?>;padding:28px 12px;line-height:72px;margin-right:16px;}
i.fa-plus-circle,i.fa-minus-circle{margin-left:8px}
input[type=checkbox]{margin-left:0}
</style>
<span class="status advancedview_panel" style="margin-top:<?=$top?>px"><input type="checkbox" class="advancedview"></span>
<div class="domain">
<form id="vmform" method="POST">
<input type="hidden" name="domain[type]" value="kvm" />
<input type="hidden" name="template[name]" value="<?=htmlspecialchars($strSelectedTemplate)?>" />
<table>
<tr>
<td>_(Icon)_:</td>
<td>
<input type="hidden" name="template[icon]" id="template_icon" value="<?=htmlspecialchars($arrLoad['icon'])?>" />
<img id="template_img" src="<?=htmlspecialchars($strIconURL)?>" width="48" height="48" title="_(Change Icon)_..."/>
<div id="template_img_chooser_outer">
<div id="template_img_chooser">
<?
$arrImagePaths = [
"$docroot/plugins/dynamix.vm.manager/templates/images/*.png" => '/plugins/dynamix.vm.manager/templates/images/',
"$docroot/boot/config/plugins/dynamix.vm.manager/templates/images/*.png" => '/boot/config/plugins/dynamix.vm.manager/templates/images/'
];
foreach ($arrImagePaths as $strGlob => $strIconURLBase) {
foreach (glob($strGlob) as $png_file) {
echo '<div class="template_img_chooser_inner"><img src="'.$strIconURLBase.basename($png_file).'" basename="'.basename($png_file).'"><p>'.basename($png_file,'.png').'</p></div>';
}
}
?>
</div>
</div>
</td>
</tr>
</table>
<table>
<tr style="line-height: 16px; vertical-align: middle;">
<td>_(Autostart)_:</td>
<td><div style="margin-left:-10px;padding-top:6px"><input type="checkbox" id="domain_autostart" name="domain[autostart]" style="display:none" class="autostart" value="1" <?if ($arrLoad['autostart']) echo 'checked'?>></div></td>
</tr>
</table>
<blockquote class="inline_help">
<p>If you want this VM to start with the array, set this to yes.</p>
</blockquote>
<div id="form_content"><?eval('?>'.parse_file("$docroot/plugins/dynamix.vm.manager/templates/{$arrLoad['form']}",false))?></div>
</form>
</div>
<script src="<?autov('/webGui/javascript/jquery.filedrop.js')?>"></script>
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>" charset="utf-8"></script>
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/dynamix.vm.manager.js')?>"></script>
<script>
function isVMAdvancedMode() {
return true;
}
function isVMXMLMode() {
return ($.cookie('vmmanager_listview_mode') == 'xml');
}
$(function() {
$('.autostart').switchButton({
on_label: "_(Yes)_",
off_label: "_(No)_",
labels_placement: "right"
});
$('.autostart').change(function () {
$('#domain_autostart').prop('checked', $(this).is(':checked'));
});
$('.advancedview').switchButton({
labels_placement: "left",
on_label: "_(XML View)_",
off_label: "_(Form View)_",
checked: isVMXMLMode()
});
$('.advancedview').change(function () {
toggleRows('xmlview', $(this).is(':checked'), 'formview');
$.cookie('vmmanager_listview_mode', $(this).is(':checked') ? 'xml' : 'form', { expires: 3650 });
});
$('#template_img').click(function (){
var p = $(this).position();
p.left -= 4;
p.top -= 4;
$('#template_img_chooser_outer').css(p);
$('#template_img_chooser_outer').slideDown();
});
$('#template_img_chooser').on('click', 'div', function (){
$('#template_img').attr('src', $(this).find('img').attr('src'));
$('#template_icon').val($(this).find('img').attr('basename'));
$('#template_img_chooser_outer').slideUp();
});
$(document).keyup(function(e) {
if (e.which == 27) $('#template_img_chooser_outer').slideUp();
});
$("#vmform table[data-category]").each(function () {
var category = $(this).data('category');
updatePrefixLabels(category);
<?if ($arrLoad['state'] == 'shutoff'):?> bindSectionEvents(category); <?endif;?>
});
$("#vmform input[data-pickroot]").fileTreeAttach();
var $el = $('#form_content');
var $xmlview = $el.find('.xmlview');
var $formview = $el.find('.formview');
if ($xmlview.length || $formview.length) {
$('.advancedview_panel').fadeIn('fast');
if (isVMXMLMode()) {
$('.formview').hide();
$('.xmlview').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
} else {
$('.xmlview').hide();
$('.formview').filter(function() {
return (($(this).prop('style').display + '') === '');
}).show();
}
} else {
$('.advancedview_panel').fadeOut('fast');
}
$("#vmform #btnCancel").click(function (){
done();
});
$('#form_content').fadeIn('fast');
});
</script>
@@ -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 ;
}
?>
@@ -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();
}
@@ -0,0 +1,90 @@
#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/Wrappers.php";
// add translations
$_SERVER['REQUEST_URI'] = '';
$login_locale = _var($display,'locale');
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "$docroot/webGui/include/Translations.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
function write(...$messages){
$com = curl_init();
curl_setopt_array($com,[
CURLOPT_URL => 'http://localhost/pub/vmaction?buffer_length=1',
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
CURLOPT_POST => 1,
CURLOPT_RETURNTRANSFER => true
]);
foreach ($messages as $message) {
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
curl_exec($com);
}
curl_close($com);
}
function execCommand_nchan($command,$idx) {
$waitID = mt_rand();
[$cmd,$args] = explode(' ',$command,2);
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br>&nbsp;&nbsp;-",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
write("addLog\0<br>") ;
write("addToID\0$idx\0 $action") ;
$proc = popen("$command 2>&1",'r');
while ($out = fgets($proc)) {
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
if (substr($out,0,1) == "B") { ;
write("progress\0$idx\0".htmlspecialchars(substr($out,strrpos($out,"Block Pull")))) ;
} else echo write("addToID\0$idx\0 ".htmlspecialchars($out));
}
$retval = pclose($proc);
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
return $retval===0;
}
#{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc}
#VM ID [ 99]: pull. .Block Pull: [ 0 %]Block Pull: [100 %].Pull complete.
$url = rawurldecode($argv[1]??'');
$waitID = mt_rand();
$style = ["<style>"];
$style[] = ".logLine{font-family:bitstream!important;font-size:1.2rem!important;margin:0;padding:0}";
$style[] = "fieldset.docker{border:solid thin;margin-top:8px}";
$style[] = "legend{font-size:1.1rem!important;font-weight:bold}";
$style[] = "</style>";
foreach (explode('&', $url) as $chunk) {
$param = explode("=", $chunk);
if ($param) {
${urldecode($param[0])} = urldecode($param[1]) ;
}
}
$id = 1 ;
write(implode($style)."<p class='logLine'></p>");
$process = " " ;
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Options for Block $action").": </legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>");
write("addLog\0".htmlspecialchars("VMName $name "));
write("addLog\0".htmlspecialchars("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_','');
?>
@@ -342,6 +342,29 @@ function openDocker(cmd,title,plg,func,start=0,button=0) {
$('button.confirm').prop('disabled',button==0);
});
}
function openVMAction(cmd,title,plg,func,start=0,button=0) {
// start = 0 : run command only when not already running (default)
// start = 1 : run command unconditionally
// button = 0 : hide CLOSE button (default)
// button = 1 : show CLOSE button
nchan_vmaction.start();
$.post('/webGui/include/StartCommand.php',{cmd:cmd,start:start},function(pid) {
if (pid==0) {
nchan_vmaction.stop();
$('div.spinner.fixed').hide();
$(".upgrade_notice").addClass('alert');
return;
}
swal({title:title,text:"<pre id='swaltext'></pre><hr>",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:"<?=_('Close')?>"},function(close){
nchan_vmaction.stop();
$('div.spinner.fixed').hide();
$('.sweet-alert').hide('fast').removeClass('nchan');
setTimeout(function(){bannerAlert("<?=_('Attention - operation continues in background')?> ["+pid.toString().padStart(8,'0')+"]<i class='fa fa-bomb fa-fw abortOps' title=\"<?=_('Abort background process')?>\" onclick='abortOperation("+pid+")'></i>",cmd,plg,func,start);});
});
$('.sweet-alert').addClass('nchan');
$('button.confirm').prop('disabled',button==0);
});
}
function abortOperation(pid) {
swal({title:"<?=_('Abort background operation')?>",text:"<?=_('This may leave an unknown state')?>",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"<?=_('Proceed')?>",cancelButtonText:"<?=_('Cancel')?>"},function(){
$.post('/webGui/include/StartCommand.php',{kill:pid},function() {
@@ -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]+'<br>';
}
break;
case 'progress':
var rows = document.getElementsByClassName('progress-'+data[1]);
if (rows.length) {
rows[rows.length-1].textContent = data[2];
}
break;
case 'addToID':
var rows = document.getElementById(data[1]);
if (rows === null) {
rows = document.getElementsByClassName('logLine');
if (rows.length) {
var row = rows[rows.length-1];
row.innerHTML += '<span id="'+data[1]+'">'+data[1]+': <span class="content">'+data[2]+'</span><span class="progress-'+data[1]+'"></span>.</span><br>';
}
} else {
var rows_content = rows.getElementsByClassName('content');
if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) {
rows.innerHTML += '<span class="content">'+data[2]+'</span><span class="progress-'+data[1]+'"></span>.';
}
}
break;
case 'show_Wait':
progress_span[data[1]] = document.getElementById('wait-'+data[1]);
progress_dots[data[1]] = setInterval(function(){if (((progress_span[data[1]].innerHTML += '.').match(/\./g)||[]).length > 9) progress_span[data[1]].innerHTML = progress_span[data[1]].innerHTML.replace(/\.+$/,'');},500);
break;
case 'stop_Wait':
clearInterval(progress_dots[data[1]]);
progress_span[data[1]].innerHTML = '';
break;
default:
box.html(box.html()+data[0]);
break;
}
box.scrollTop(box[0].scrollHeight);
});
var backtotopoffset = 250;
var backtotopduration = 500;