mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 23:43:17 +01:00
* Admin/Setup: logging all DB backup operations to a text file db_backup.log in backup-directory
This commit is contained in:
parent
09ffa47041
commit
fb9c214182
@ -189,9 +189,9 @@ class Backup
|
||||
if (!$name)
|
||||
{
|
||||
//echo '-> !$name<br>'; // !
|
||||
if (!$this->backup_dir || !is_writable($this->backup_dir))
|
||||
if (empty($this->backup_dir) || !is_writable($this->backup_dir))
|
||||
{
|
||||
//echo ' -> !$this->backup_dir || !is_writable($this->backup_dir)<br>'; // !
|
||||
$this->log($name, $reading, null, lang("backupdir '%1' is not writeable by the webserver", $this->backup_dir));
|
||||
return lang("backupdir '%1' is not writeable by the webserver",$this->backup_dir);
|
||||
}
|
||||
$name = $this->backup_dir.'/db_backup-'.date('YmdHi');
|
||||
@ -209,37 +209,40 @@ class Backup
|
||||
if(!class_exists('ZipArchive', false))
|
||||
{
|
||||
$this->backup_files = false;
|
||||
//echo ' -> (new ZipArchive) == NULL<br>'; // !
|
||||
$this->log($name, $reading, null, lang("Cant open %1, needs ZipArchive", $name));
|
||||
return lang("Cant open %1, needs ZipArchive", $name)."<br>\n";
|
||||
}
|
||||
if(!($f = fopen($name, $mode)))
|
||||
if(!($f = fopen($path=$name, $mode)))
|
||||
{
|
||||
//echo ' -> !($f = fopen($name, $mode))<br>'; // !
|
||||
$lang_mode = $reading ? lang("reading") : lang("writing");
|
||||
$this->log($name, $reading, null, lang("Cant open '%1' for %2", $name, $lang_mode));
|
||||
return lang("Cant open '%1' for %2", $name, $lang_mode)."<br>";
|
||||
}
|
||||
return $f;
|
||||
}
|
||||
if(class_exists('ZipArchive', false) && !$reading && $this->backup_files)
|
||||
elseif (class_exists('ZipArchive', false) && !$reading && $this->backup_files)
|
||||
{
|
||||
//echo '-> (new ZipArchive) != NULL && !$reading; '.$name.'<br>'; // !
|
||||
if(!($f = fopen($name, $mode)))
|
||||
if (!($f = fopen($path=$name, $mode)))
|
||||
{
|
||||
//echo ' -> !($f = fopen($name, $mode))<br>'; // !
|
||||
$lang_mode = $reading ? lang("reading") : lang("writing");
|
||||
$this->log($name, $reading, null, lang("Cant open '%1' for %2", $name, $lang_mode));
|
||||
return lang("Cant open '%1' for %2", $name, $lang_mode)."<br>";
|
||||
}
|
||||
return $f;
|
||||
}
|
||||
if(!($f = fopen("compress.bzip2://$name.bz2", $mode)) &&
|
||||
!($f = fopen("compress.zlib://$name.gz",$mode)) &&
|
||||
!($f = fopen($name,$mode))
|
||||
elseif (!($f = fopen('compress.bzip2://'.($path=$name.'.bz2'), $mode)) &&
|
||||
!($f = fopen('compress.zlib://'.($path=$name.'.gz'),$mode)) &&
|
||||
!($f = fopen($path=$name,$mode))
|
||||
)
|
||||
{
|
||||
//echo '-> !($f = fopen("compress.bzip2://$name.bz2", $mode))<br>'; // !
|
||||
$lang_mode = $reading ? lang("reading") : lang("writing");
|
||||
$this->log($name, $reading, null, lang("Cant open '%1' for %2", $name, $lang_mode));
|
||||
return lang("Cant open '%1' for %2", $name, $lang_mode)."<br>";
|
||||
}
|
||||
|
||||
// Log start of backup/restore
|
||||
$this->log($path, $reading, true);
|
||||
|
||||
return $f;
|
||||
}
|
||||
|
||||
@ -280,7 +283,9 @@ class Backup
|
||||
{
|
||||
if ($count >= $this->backup_mincount)//
|
||||
{
|
||||
$this->log($file, lang('Housekeeping removed'));
|
||||
$ret = unlink($this->backup_dir.'/'.$file);
|
||||
if (!$ret) $this->log($file, 'remove', null, "Failed to remove $file");
|
||||
if (($ret) && (is_array($files_return)))
|
||||
{
|
||||
array_push($files_return, $file);
|
||||
@ -414,6 +419,7 @@ class Backup
|
||||
$name = $dir.'/database_backup/'.basename($list[0]);
|
||||
if(!($f = fopen($name, 'rb')))
|
||||
{
|
||||
$this->log($name, true, null, lang("Cant open '%1' for %2", $filename, lang("reading")));
|
||||
return lang("Cant open '%1' for %2", $filename, lang("reading"))."<br>\n";
|
||||
}
|
||||
}
|
||||
@ -476,6 +482,7 @@ class Backup
|
||||
{
|
||||
if (!$this->db->transaction_commit())
|
||||
{
|
||||
$this->log($filename, true, false, lang('Restore failed'));
|
||||
return lang('Restore failed');
|
||||
}
|
||||
}
|
||||
@ -497,6 +504,9 @@ class Backup
|
||||
// search-and-register-hooks
|
||||
Api\Hooks::read(true);
|
||||
|
||||
// log end of successful restore
|
||||
$this->log($filename, true, false);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -907,7 +917,7 @@ class Backup
|
||||
$res = $zip->open($filename, ZipArchive::CREATE);
|
||||
if($res !== TRUE)
|
||||
{
|
||||
//echo ' -> !$res<br>'; // !
|
||||
$this->log($filename, false, null, lang("Cant open '%1' for %2", $filename, lang("writing")));
|
||||
return lang("Cant open '%1' for %2", $filename, lang("writing"))."<br>\n";
|
||||
}
|
||||
$file_list = $this->get_file_list($dir);
|
||||
@ -975,11 +985,19 @@ class Backup
|
||||
{
|
||||
if ($this->backup_files && !$skip_files_backup)
|
||||
{
|
||||
$this->log($name, false, null, lang("Cant open %1, needs ZipArchive", $name));
|
||||
echo '<center>'.lang("Cant open %1, needs ZipArchive", $name)."<br>\n".'</center>';
|
||||
}
|
||||
|
||||
fclose($f);
|
||||
if (file_exists($name)) unlink($name);
|
||||
// get actual filename from stream
|
||||
$filename_parts = explode('://', stream_get_meta_data($f)['uri'] ?? $name);
|
||||
|
||||
fclose($f);
|
||||
//if (file_exists($name)) unlink($name);
|
||||
|
||||
// log successful end of backup
|
||||
$this->log(array_pop($filename_parts), false, false);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
// save files ....
|
||||
@ -1002,6 +1020,10 @@ class Backup
|
||||
$zip->close();
|
||||
fclose($f);
|
||||
unlink($name);
|
||||
|
||||
// log successful end of backup
|
||||
$this->log($filename, false, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1150,6 +1172,62 @@ class Backup
|
||||
|
||||
return $def;
|
||||
}
|
||||
|
||||
const LOG_FILE = 'db_backup.log';
|
||||
|
||||
/**
|
||||
* Log backup and restore start- and stop-time, plus file-name, -size and sha1 hash
|
||||
*
|
||||
* @param string $file filename
|
||||
* @param bool|string $restore true: 'Restore', false: 'Backup', string with custom label
|
||||
* @param bool $start true: start of operation, false: end, null: neither
|
||||
* @return void
|
||||
*/
|
||||
public function log(string $file, $restore, bool $start=null, string $error_msg=null)
|
||||
{
|
||||
$msg = (is_string($restore) ? $restore : ($restore ? 'Restore' : 'Backup')).' ';
|
||||
if (isset($start))
|
||||
{
|
||||
$msg .= $start ? 'started ' : 'finished ';
|
||||
}
|
||||
$msg .= 'at '.date('Y-m-d H:i:s e').' ';
|
||||
if (!empty($GLOBALS['egw_setup']))
|
||||
{
|
||||
$msg .= 'from setup: ';
|
||||
}
|
||||
elseif (!empty($GLOBALS['egw_info']['user']['account_lid']))
|
||||
{
|
||||
$msg .= 'by user '.$GLOBALS['egw_info']['user']['account_lid'].': ';
|
||||
}
|
||||
$msg .= ($path = $file[0] === '/' ? $file : $this->backup_dir.'/'.$file).' ';
|
||||
if (empty($this->backup_dir) || !file_exists($path))
|
||||
{
|
||||
if (empty($error_msg) && (!$start || $restore))
|
||||
{
|
||||
$msg .= 'NOT existing!';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$msg .= sprintf('%3.1f MB (%d)',filesize($path)/(1024*1024), filesize($path)).' sha1: '.sha1_file($path);
|
||||
}
|
||||
if (!empty($error_msg))
|
||||
{
|
||||
$msg .= ' ERROR: '.$error_msg;
|
||||
}
|
||||
|
||||
// try opening log-file in backup-dir, or /var/lib/egroupware
|
||||
if (!empty($this->backup_dir) && ($f = fopen($this->backup_dir.'/'.self::LOG_FILE, 'a')) ||
|
||||
($f = fopen('/var/lib/egroupware/'.self::LOG_FILE, 'a')))
|
||||
{
|
||||
fwrite($f, $msg."\n".(!$start ? "\n" : ''));
|
||||
fclose($f);
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log("Could NOT open ".self::LOG_FILE.': '.$msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1101,6 +1101,8 @@ class Schema
|
||||
echo '<p>'.$msg."<br>\n".($backtrace ? 'Backtrace: '.function_backtrace(1)."</p>\n" : '');
|
||||
}
|
||||
|
||||
const DEFAULT_TIMESTAMPS = ['current_date', 'current_timestamp', 'now()', 'curdate()'];
|
||||
|
||||
/**
|
||||
* Converts an eGW table-definition array into an ADOdb column-definition string
|
||||
*
|
||||
@ -1155,7 +1157,7 @@ class Schema
|
||||
case 'date':
|
||||
$ado_col = 'D';
|
||||
// allow to use now() beside current_date, as Postgres backups contain it and it's easier to remember anyway
|
||||
if (in_array($col_data['default'],array('current_date','now()')))
|
||||
if (in_array(strtolower($col_data['default']), self::DEFAULT_TIMESTAMPS))
|
||||
{
|
||||
$ado_col .= ' DEFDATE';
|
||||
unset($col_data['default']);
|
||||
@ -1190,7 +1192,7 @@ class Schema
|
||||
case 'timestamp':
|
||||
$ado_col = 'T';
|
||||
// allow to use now() beside current_timestamp, as Postgres backups contain it and it's easier to remember anyway
|
||||
if (in_array($col_data['default'],array('current_timestamp','now()')))
|
||||
if (in_array(strtolower($col_data['default']) , self::DEFAULT_TIMESTAMPS))
|
||||
{
|
||||
$ado_col .= ' DEFTIMESTAMP';
|
||||
unset($col_data['default']);
|
||||
|
@ -41,7 +41,7 @@ $asyncservice = new Api\Asyncservice();
|
||||
if (!empty($_POST['download']))
|
||||
{
|
||||
$file = key($_POST['download']);
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to now allow to change the dir
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to not allow to change the dir
|
||||
|
||||
// FIRST: switch off zlib.output_compression, as this would limit downloads in size to memory_limit
|
||||
ini_set('zlib.output_compression',0);
|
||||
@ -50,6 +50,7 @@ if (!empty($_POST['download']))
|
||||
|
||||
Api\Header\Content::type(basename($file));
|
||||
readfile($file);
|
||||
$db_backup->log($file, 'Downloaded');
|
||||
exit;
|
||||
}
|
||||
$setup_tpl = new Framework\Template($tpl_root);
|
||||
@ -88,7 +89,7 @@ else
|
||||
$run_in_egw = true;
|
||||
}
|
||||
// save backup housekeeping settings
|
||||
if ($_POST['save_backup_settings'])
|
||||
if (!empty($_POST['save_backup_settings']))
|
||||
{
|
||||
$matches = array();
|
||||
preg_match('/^\d*$/', $_POST['backup_mincount'], $matches);
|
||||
@ -112,7 +113,7 @@ if ($_POST['save_backup_settings'])
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($_POST['mount'])
|
||||
if (!empty($_POST['mount']))
|
||||
{
|
||||
Vfs::$is_root = true;
|
||||
echo '<div align="center">'.
|
||||
@ -123,11 +124,10 @@ if ($_POST['mount'])
|
||||
Vfs::$is_root = false;
|
||||
}
|
||||
// create a backup now
|
||||
if($_POST['backup'])
|
||||
if (!empty($_POST['backup']))
|
||||
{
|
||||
if (is_resource($f = $db_backup->fopen_backup()))
|
||||
{
|
||||
echo '<p align="center">'.lang('backup started, this might take a few minutes ...')."</p>\n";
|
||||
$starttime = microtime(true);
|
||||
$db_backup->backup($f);
|
||||
if(is_resource($f))
|
||||
@ -147,7 +147,9 @@ if($_POST['backup'])
|
||||
$setup_tpl->set_var('error_msg',$f);
|
||||
}
|
||||
}
|
||||
$setup_tpl->set_var('backup_now_button','<input type="submit" name="backup" title="'.htmlspecialchars(lang("back's up your DB now, this might take a few minutes")).'" value="'.htmlspecialchars(lang('backup now')).'" />');
|
||||
$setup_tpl->set_var('backup_now_button','<input type="submit" name="backup" title="'.
|
||||
htmlspecialchars(lang("back's up your DB now, this might take a few minutes")).'" value="'.htmlspecialchars(lang('backup now')).
|
||||
'" onclick="if (egw && egw.loading_prompt) egw.loading_prompt(\'db_backup\', true, \''.htmlspecialchars(lang('backup started, this might take a few minutes ...')).'\'); return true;" />');
|
||||
$setup_tpl->set_var('upload','<input type="file" name="uploaded" /> '.
|
||||
'<input type="submit" name="upload" value="'.htmlspecialchars(lang('upload backup')).'" title="'.htmlspecialchars(lang("uploads a backup to the backup-dir, from where you can restore it")).'" />');
|
||||
$setup_tpl->set_var('backup_mincount','<input type="text" name="backup_mincount" value="'.$db_backup->backup_mincount.'" size="3" maxlength="3"/>');
|
||||
@ -158,22 +160,22 @@ $setup_tpl->set_var('backup_files','<input type="checkbox" name="backup_files" v
|
||||
$setup_tpl->set_var('backup_save_settings','<input type="submit" name="save_backup_settings" value="'.htmlspecialchars(lang('save')).'" />');
|
||||
$setup_tpl->set_var('backup_mount','<input type="submit" name="mount" value="'.htmlspecialchars(lang('Mount backup directory to %1','/backup')).'" />');
|
||||
|
||||
if ($_POST['upload'] && is_array($_FILES['uploaded']) && !$_FILES['uploaded']['error'] &&
|
||||
if (!empty($_POST['upload']) && is_array($_FILES['uploaded']) && !$_FILES['uploaded']['error'] &&
|
||||
is_uploaded_file($_FILES['uploaded']['tmp_name']))
|
||||
{
|
||||
move_uploaded_file($_FILES['uploaded']['tmp_name'],$db_backup->backup_dir.'/'.$_FILES['uploaded']['name']);
|
||||
move_uploaded_file($_FILES['uploaded']['tmp_name'], $filename=$db_backup->backup_dir.'/'.$_FILES['uploaded']['name']);
|
||||
|
||||
$md5 = ', md5='.md5_file($db_backup->backup_dir.'/'.$_FILES['uploaded']['name']);
|
||||
$md5 .= ', sha1='.sha1_file($db_backup->backup_dir.'/'.$_FILES['uploaded']['name']);
|
||||
$md5 = ', md5='.md5_file($filename).', sha1='.sha1_file($filename);
|
||||
|
||||
$setup_tpl->set_var('error_msg',lang("succesfully uploaded file %1",$_FILES['uploaded']['name'].', '.
|
||||
sprintf('%3.1f MB (%d)',$_FILES['uploaded']['size']/(1024*1024),$_FILES['uploaded']['size']).$md5));
|
||||
$setup_tpl->set_var('error_msg', ($msg=lang("succesfully uploaded file %1", $filename.', '.
|
||||
sprintf('%3.1f MB (%d)',$_FILES['uploaded']['size']/(1024*1024),$_FILES['uploaded']['size']))).$md5);
|
||||
$db_backup->log($filename, $msg);
|
||||
}
|
||||
// delete a backup
|
||||
if (!empty($_POST['delete']))
|
||||
{
|
||||
$file = key($_POST['delete']);
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to not allow to change the dir
|
||||
$file = $db_backup->backup_dir.'/'.basename(key($_POST['delete'])); // basename to not allow to change the dir
|
||||
$db_backup->log($file, lang("backup '%1' deleted", $file));
|
||||
|
||||
if (unlink($file)) $setup_tpl->set_var('error_msg',lang("backup '%1' deleted",$file));
|
||||
}
|
||||
@ -190,7 +192,11 @@ if (!empty($_POST['rename']))
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to not allow to change the dir
|
||||
$ext = preg_match('/(\.gz|\.bz2)+$/i',$file,$matches) ? $matches[1] : '';
|
||||
$new_file = $db_backup->backup_dir.'/'.preg_replace('/(\.gz|\.bz2)+$/i','',basename($new_name)).$ext;
|
||||
if (rename($file,$new_file)) $setup_tpl->set_var('error_msg',lang("backup '%1' renamed to '%2'",basename($file),basename($new_file)));
|
||||
if (rename($file,$new_file))
|
||||
{
|
||||
$setup_tpl->set_var('error_msg',lang("backup '%1' renamed to '%2'",basename($file),basename($new_file)));
|
||||
$db_backup->log($new_file, lang("backup '%1' renamed to '%2'",basename($file),basename($new_file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// restore a backup
|
||||
@ -201,7 +207,6 @@ if (!empty($_POST['restore']))
|
||||
|
||||
if (is_resource($f = $db_backup->fopen_backup($file,true)))
|
||||
{
|
||||
echo '<p align="center">'.lang('restore started, this might take a few minutes ...')."</p>\n".str_repeat(' ',4096);
|
||||
$start = time();
|
||||
$db_backup->restore($f, true, $file); // allways convert to current system charset on restore
|
||||
$setup_tpl->set_var('error_msg',lang("backup '%1' restored",$file).' ('.(time()-$start).' s)');
|
||||
@ -274,11 +279,14 @@ foreach($files as $file => $ctime)
|
||||
'mod' => date('Y-m-d H:i',$ctime),
|
||||
'size' => sprintf('%3.1f MB (%d)',$size/(1024*1024),$size),
|
||||
'actions' => '<input type="submit" name="download['.$file.']" value="'.htmlspecialchars(lang('download')).'" /> '."\n".
|
||||
($file === Api\Db\Backup::LOG_FILE ? '' :
|
||||
'<input type="submit" name="delete['.$file.']" value="'.htmlspecialchars(lang('delete')).'" onclick="return confirm(\''.
|
||||
htmlspecialchars(lang('Confirm to delete this backup?')).'\');" /> '."\n".
|
||||
'<input name="new_name['.$file.']" value="" size="15" /><input type="submit" name="rename['.$file.']" value="'.htmlspecialchars(lang('rename')).'" /> '."\n".
|
||||
'<input type="submit" name="restore['.$file.']" value="'.htmlspecialchars(lang('restore')).'" onclick="return confirm(\''.
|
||||
htmlspecialchars(lang('Restoring a backup will delete/replace all content in your database. Are you sure?')).'\');" />',
|
||||
'<input type="submit" name="restore['.$file.']" value="'.htmlspecialchars(lang('restore')).'" onclick="if (confirm(\''.
|
||||
htmlspecialchars(lang('Restoring a backup will delete/replace all content in your database. Are you sure?')).
|
||||
'\')) { if (egw && egw.loading_prompt) egw.loading_prompt(\'db_backup\', true, \''.htmlspecialchars(lang('restore started, this might take a few minutes ...')).
|
||||
'\'); return true; } else return false;" />'),
|
||||
));
|
||||
$setup_tpl->parse('set_rows','set_row',true);
|
||||
}
|
||||
|
@ -1,7 +1,14 @@
|
||||
<!-- begin db_backup.tpl -->
|
||||
<p align="center"><font color="red">{error_msg}</font></p>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
<script>
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (egw && egw.loading_prompt)
|
||||
{
|
||||
egw.loading_prompt('db_backup', false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
Array.prototype.contains = function(value)
|
||||
{
|
||||
@ -74,18 +81,18 @@ function sort_table(id)
|
||||
<input name="sortedby" id="sortedby" type="hidden" />
|
||||
<table border="0" align="center" width="98%" cellpadding="5">
|
||||
<!-- BEGIN setup_header -->
|
||||
<tr bgcolor="#486591">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<font color="#fefefe"><b>{stage_title}</b></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{stage_desc}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END setup_header -->
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td>
|
||||
<b>{lang_scheduled_backups}</b>
|
||||
</td>
|
||||
@ -93,7 +100,7 @@ function sort_table(id)
|
||||
{backup_now_button}
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<table style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%">
|
||||
<tr align="center">
|
||||
@ -121,7 +128,7 @@ function sort_table(id)
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td>
|
||||
<b>{lang_backup_sets}</b> {backup_dir}
|
||||
</td>
|
||||
@ -129,7 +136,7 @@ function sort_table(id)
|
||||
{upload}
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6ee">
|
||||
<tr>
|
||||
<td>
|
||||
{lang_backup_cleanup}
|
||||
</td>
|
||||
@ -137,7 +144,7 @@ function sort_table(id)
|
||||
{lang_backup_mincount} {backup_mincount}
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td>
|
||||
{lang_backup_files_info}
|
||||
</td>
|
||||
@ -145,7 +152,7 @@ function sort_table(id)
|
||||
{lang_backup_files} {backup_files}
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6ee">
|
||||
<tr>
|
||||
<td>
|
||||
{backup_mount}
|
||||
</td>
|
||||
@ -153,7 +160,7 @@ function sort_table(id)
|
||||
{backup_save_settings}
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#e6e6e6">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<table id="files_table" style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%">
|
||||
<tr align="center">
|
||||
@ -176,4 +183,4 @@ function sort_table(id)
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<!-- end db_backup.tpl -->
|
||||
<!-- end db_backup.tpl -->
|
Loading…
Reference in New Issue
Block a user