* Admin/Setup: logging all DB backup operations to a text file db_backup.log in backup-directory

This commit is contained in:
ralf 2023-03-10 08:33:07 +01:00
parent 09ffa47041
commit fb9c214182
4 changed files with 142 additions and 47 deletions

View File

@ -189,9 +189,9 @@ class Backup
if (!$name) if (!$name)
{ {
//echo '-> !$name<br>'; // ! //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); return lang("backupdir '%1' is not writeable by the webserver",$this->backup_dir);
} }
$name = $this->backup_dir.'/db_backup-'.date('YmdHi'); $name = $this->backup_dir.'/db_backup-'.date('YmdHi');
@ -209,37 +209,40 @@ class Backup
if(!class_exists('ZipArchive', false)) if(!class_exists('ZipArchive', false))
{ {
$this->backup_files = 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"; 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>'; // ! //echo ' -> !($f = fopen($name, $mode))<br>'; // !
$lang_mode = $reading ? lang("reading") : lang("writing"); $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 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>'; // ! //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"); $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 lang("Cant open '%1' for %2", $name, $lang_mode)."<br>";
} }
return $f;
} }
if(!($f = fopen("compress.bzip2://$name.bz2", $mode)) && elseif (!($f = fopen('compress.bzip2://'.($path=$name.'.bz2'), $mode)) &&
!($f = fopen("compress.zlib://$name.gz",$mode)) && !($f = fopen('compress.zlib://'.($path=$name.'.gz'),$mode)) &&
!($f = fopen($name,$mode)) !($f = fopen($path=$name,$mode))
) )
{ {
//echo '-> !($f = fopen("compress.bzip2://$name.bz2", $mode))<br>'; // !
$lang_mode = $reading ? lang("reading") : lang("writing"); $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 lang("Cant open '%1' for %2", $name, $lang_mode)."<br>";
} }
// Log start of backup/restore
$this->log($path, $reading, true);
return $f; return $f;
} }
@ -280,7 +283,9 @@ class Backup
{ {
if ($count >= $this->backup_mincount)// if ($count >= $this->backup_mincount)//
{ {
$this->log($file, lang('Housekeeping removed'));
$ret = unlink($this->backup_dir.'/'.$file); $ret = unlink($this->backup_dir.'/'.$file);
if (!$ret) $this->log($file, 'remove', null, "Failed to remove $file");
if (($ret) && (is_array($files_return))) if (($ret) && (is_array($files_return)))
{ {
array_push($files_return, $file); array_push($files_return, $file);
@ -414,6 +419,7 @@ class Backup
$name = $dir.'/database_backup/'.basename($list[0]); $name = $dir.'/database_backup/'.basename($list[0]);
if(!($f = fopen($name, 'rb'))) 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"; return lang("Cant open '%1' for %2", $filename, lang("reading"))."<br>\n";
} }
} }
@ -476,6 +482,7 @@ class Backup
{ {
if (!$this->db->transaction_commit()) if (!$this->db->transaction_commit())
{ {
$this->log($filename, true, false, lang('Restore failed'));
return lang('Restore failed'); return lang('Restore failed');
} }
} }
@ -497,6 +504,9 @@ class Backup
// search-and-register-hooks // search-and-register-hooks
Api\Hooks::read(true); Api\Hooks::read(true);
// log end of successful restore
$this->log($filename, true, false);
return ''; return '';
} }
@ -907,7 +917,7 @@ class Backup
$res = $zip->open($filename, ZipArchive::CREATE); $res = $zip->open($filename, ZipArchive::CREATE);
if($res !== TRUE) 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"; return lang("Cant open '%1' for %2", $filename, lang("writing"))."<br>\n";
} }
$file_list = $this->get_file_list($dir); $file_list = $this->get_file_list($dir);
@ -975,11 +985,19 @@ class Backup
{ {
if ($this->backup_files && !$skip_files_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>'; echo '<center>'.lang("Cant open %1, needs ZipArchive", $name)."<br>\n".'</center>';
} }
fclose($f); // get actual filename from stream
if (file_exists($name)) unlink($name); $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; return TRUE;
} }
// save files .... // save files ....
@ -1002,6 +1020,10 @@ class Backup
$zip->close(); $zip->close();
fclose($f); fclose($f);
unlink($name); unlink($name);
// log successful end of backup
$this->log($filename, false, false);
return true; return true;
} }
@ -1150,6 +1172,62 @@ class Backup
return $def; 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);
}
}
} }
/* /*

View File

@ -1101,6 +1101,8 @@ class Schema
echo '<p>'.$msg."<br>\n".($backtrace ? 'Backtrace: '.function_backtrace(1)."</p>\n" : ''); 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 * Converts an eGW table-definition array into an ADOdb column-definition string
* *
@ -1155,7 +1157,7 @@ class Schema
case 'date': case 'date':
$ado_col = 'D'; $ado_col = 'D';
// allow to use now() beside current_date, as Postgres backups contain it and it's easier to remember anyway // 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'; $ado_col .= ' DEFDATE';
unset($col_data['default']); unset($col_data['default']);
@ -1190,7 +1192,7 @@ class Schema
case 'timestamp': case 'timestamp':
$ado_col = 'T'; $ado_col = 'T';
// allow to use now() beside current_timestamp, as Postgres backups contain it and it's easier to remember anyway // 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'; $ado_col .= ' DEFTIMESTAMP';
unset($col_data['default']); unset($col_data['default']);

View File

@ -41,7 +41,7 @@ $asyncservice = new Api\Asyncservice();
if (!empty($_POST['download'])) if (!empty($_POST['download']))
{ {
$file = key($_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 // FIRST: switch off zlib.output_compression, as this would limit downloads in size to memory_limit
ini_set('zlib.output_compression',0); ini_set('zlib.output_compression',0);
@ -50,6 +50,7 @@ if (!empty($_POST['download']))
Api\Header\Content::type(basename($file)); Api\Header\Content::type(basename($file));
readfile($file); readfile($file);
$db_backup->log($file, 'Downloaded');
exit; exit;
} }
$setup_tpl = new Framework\Template($tpl_root); $setup_tpl = new Framework\Template($tpl_root);
@ -88,7 +89,7 @@ else
$run_in_egw = true; $run_in_egw = true;
} }
// save backup housekeeping settings // save backup housekeeping settings
if ($_POST['save_backup_settings']) if (!empty($_POST['save_backup_settings']))
{ {
$matches = array(); $matches = array();
preg_match('/^\d*$/', $_POST['backup_mincount'], $matches); 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; Vfs::$is_root = true;
echo '<div align="center">'. echo '<div align="center">'.
@ -123,11 +124,10 @@ if ($_POST['mount'])
Vfs::$is_root = false; Vfs::$is_root = false;
} }
// create a backup now // create a backup now
if($_POST['backup']) if (!empty($_POST['backup']))
{ {
if (is_resource($f = $db_backup->fopen_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); $starttime = microtime(true);
$db_backup->backup($f); $db_backup->backup($f);
if(is_resource($f)) if(is_resource($f))
@ -147,7 +147,9 @@ if($_POST['backup'])
$setup_tpl->set_var('error_msg',$f); $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" /> &nbsp;'. $setup_tpl->set_var('upload','<input type="file" name="uploaded" /> &nbsp;'.
'<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")).'" />'); '<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"/>'); $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_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')).'" />'); $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'])) 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 = ', md5='.md5_file($filename).', sha1='.sha1_file($filename);
$md5 .= ', sha1='.sha1_file($db_backup->backup_dir.'/'.$_FILES['uploaded']['name']);
$setup_tpl->set_var('error_msg',lang("succesfully uploaded file %1",$_FILES['uploaded']['name'].', '. $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)); sprintf('%3.1f MB (%d)',$_FILES['uploaded']['size']/(1024*1024),$_FILES['uploaded']['size']))).$md5);
$db_backup->log($filename, $msg);
} }
// delete a backup // delete a backup
if (!empty($_POST['delete'])) if (!empty($_POST['delete']))
{ {
$file = key($_POST['delete']); $file = $db_backup->backup_dir.'/'.basename(key($_POST['delete'])); // basename to not allow to change the dir
$file = $db_backup->backup_dir.'/'.basename($file); // 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)); 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 $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] : ''; $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; $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 // restore a backup
@ -201,7 +207,6 @@ if (!empty($_POST['restore']))
if (is_resource($f = $db_backup->fopen_backup($file,true))) 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(); $start = time();
$db_backup->restore($f, true, $file); // allways convert to current system charset on restore $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)'); $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), 'mod' => date('Y-m-d H:i',$ctime),
'size' => sprintf('%3.1f MB (%d)',$size/(1024*1024),$size), 'size' => sprintf('%3.1f MB (%d)',$size/(1024*1024),$size),
'actions' => '<input type="submit" name="download['.$file.']" value="'.htmlspecialchars(lang('download')).'" />&nbsp;'."\n". 'actions' => '<input type="submit" name="download['.$file.']" value="'.htmlspecialchars(lang('download')).'" />&nbsp;'."\n".
($file === Api\Db\Backup::LOG_FILE ? '' :
'<input type="submit" name="delete['.$file.']" value="'.htmlspecialchars(lang('delete')).'" onclick="return confirm(\''. '<input type="submit" name="delete['.$file.']" value="'.htmlspecialchars(lang('delete')).'" onclick="return confirm(\''.
htmlspecialchars(lang('Confirm to delete this backup?')).'\');" />&nbsp;'."\n". htmlspecialchars(lang('Confirm to delete this backup?')).'\');" />&nbsp;'."\n".
'<input name="new_name['.$file.']" value="" size="15" /><input type="submit" name="rename['.$file.']" value="'.htmlspecialchars(lang('rename')).'" />&nbsp;'."\n". '<input name="new_name['.$file.']" value="" size="15" /><input type="submit" name="rename['.$file.']" value="'.htmlspecialchars(lang('rename')).'" />&nbsp;'."\n".
'<input type="submit" name="restore['.$file.']" value="'.htmlspecialchars(lang('restore')).'" onclick="return confirm(\''. '<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?')).'\');" />', 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); $setup_tpl->parse('set_rows','set_row',true);
} }

View File

@ -1,7 +1,14 @@
<!-- begin db_backup.tpl --> <!-- begin db_backup.tpl -->
<p align="center"><font color="red">{error_msg}</font></p> <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) Array.prototype.contains = function(value)
{ {
@ -74,18 +81,18 @@ function sort_table(id)
<input name="sortedby" id="sortedby" type="hidden" /> <input name="sortedby" id="sortedby" type="hidden" />
<table border="0" align="center" width="98%" cellpadding="5"> <table border="0" align="center" width="98%" cellpadding="5">
<!-- BEGIN setup_header --> <!-- BEGIN setup_header -->
<tr bgcolor="#486591"> <tr>
<td colspan="2"> <td colspan="2">
&nbsp;<font color="#fefefe"><b>{stage_title}</b></font> &nbsp;<font color="#fefefe"><b>{stage_title}</b></font>
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6e6"> <tr>
<td colspan="2"> <td colspan="2">
{stage_desc} {stage_desc}
</td> </td>
</tr> </tr>
<!-- END setup_header --> <!-- END setup_header -->
<tr bgcolor="#e6e6e6"> <tr>
<td> <td>
<b>{lang_scheduled_backups}</b> <b>{lang_scheduled_backups}</b>
</td> </td>
@ -93,7 +100,7 @@ function sort_table(id)
{backup_now_button} {backup_now_button}
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6e6"> <tr>
<td colspan="2"> <td colspan="2">
<table style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%"> <table style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%">
<tr align="center"> <tr align="center">
@ -121,7 +128,7 @@ function sort_table(id)
</table> </table>
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6e6"> <tr>
<td> <td>
<b>{lang_backup_sets}</b> {backup_dir} <b>{lang_backup_sets}</b> {backup_dir}
</td> </td>
@ -129,7 +136,7 @@ function sort_table(id)
{upload} {upload}
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6ee"> <tr>
<td> <td>
{lang_backup_cleanup} {lang_backup_cleanup}
</td> </td>
@ -137,7 +144,7 @@ function sort_table(id)
{lang_backup_mincount} {backup_mincount} {lang_backup_mincount} {backup_mincount}
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6e6"> <tr>
<td> <td>
{lang_backup_files_info} {lang_backup_files_info}
</td> </td>
@ -145,7 +152,7 @@ function sort_table(id)
{lang_backup_files} {backup_files} {lang_backup_files} {backup_files}
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6ee"> <tr>
<td> <td>
{backup_mount} {backup_mount}
</td> </td>
@ -153,7 +160,7 @@ function sort_table(id)
{backup_save_settings} {backup_save_settings}
</td> </td>
</tr> </tr>
<tr bgcolor="#e6e6e6"> <tr>
<td colspan="2"> <td colspan="2">
<table id="files_table" style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%"> <table id="files_table" style="border: 1px solid black; border-collapse: collapse;" border="1" width="100%">
<tr align="center"> <tr align="center">