1#============================================================= -*-perl-*- 2# 3# BackupPC::CGI::HostInfo package 4# 5# DESCRIPTION 6# 7# This module implements the HostInfo action for the CGI interface. 8# 9# AUTHOR 10# Craig Barratt <cbarratt@users.sourceforge.net> 11# 12# COPYRIGHT 13# Copyright (C) 2003-2018 Craig Barratt 14# 15# This program is free software: you can redistribute it and/or modify 16# it under the terms of the GNU General Public License as published by 17# the Free Software Foundation, either version 3 of the License, or 18# (at your option) any later version. 19# 20# This program is distributed in the hope that it will be useful, 21# but WITHOUT ANY WARRANTY; without even the implied warranty of 22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23# GNU General Public License for more details. 24# 25# You should have received a copy of the GNU General Public License 26# along with this program. If not, see <http://www.gnu.org/licenses/>. 27# 28#======================================================================== 29# 30# Version 4.2.2, released 21 Oct 2018. 31# 32# See http://backuppc.sourceforge.net. 33# 34#======================================================================== 35 36package BackupPC::CGI::HostInfo; 37 38use strict; 39use BackupPC::CGI::Lib qw(:all); 40 41sub action 42{ 43 my $host = $1 if ( $In{host} =~ /(.*)/ ); 44 my($statusStr, $startIncrStr); 45 46 $host =~ s/^\s+//; 47 $host =~ s/\s+$//; 48 if ( $host eq "" ) { 49 ErrorExit(eval("qq{$Lang->{Unknown_host_or_user}}")); 50 } 51 $host = lc($host) 52 if ( !-d "$TopDir/pc/$host" && -d "$TopDir/pc/" . lc($host) ); 53 if ( $host =~ /\.\./ || !-d "$TopDir/pc/$host" ) { 54 # 55 # try to lookup by user name 56 # 57 if ( $host eq "" || !defined($Hosts->{$host}) ) { 58 foreach my $h ( keys(%$Hosts) ) { 59 if ( $Hosts->{$h}{user} eq $host 60 || lc($Hosts->{$h}{user}) eq lc($host) ) { 61 $host = $h; 62 last; 63 } 64 } 65 CheckPermission(); 66 ErrorExit(eval("qq{$Lang->{Unknown_host_or_user}}")) 67 if ( !defined($Hosts->{$host}) ); 68 } 69 $In{host} = $host; 70 } 71 GetStatusInfo("host(${EscURI($host)})"); 72 $bpc->ConfigRead($host); 73 %Conf = $bpc->Conf(); 74 my $Privileged = CheckPermission($host); 75 if ( !$Privileged ) { 76 ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_view_information_about}}")); 77 } 78 my $deleteEnabled = $PrivAdmin || ($Conf{CgiUserDeleteBackupEnable} > 0 && $Privileged); 79 $deleteEnabled = 0 if ( $Conf{CgiUserDeleteBackupEnable} < 0 ); 80 ReadUserEmailInfo(); 81 82 if ( $Conf{XferMethod} eq "archive" ) { 83 my @Archives = $bpc->ArchiveInfoRead($host); 84 my ($ArchiveStr,$warnStr); 85 86 for ( my $i = 0 ; $i < @Archives ; $i++ ) { 87 my $startTime = timeStamp2($Archives[$i]{startTime}); 88 my $dur = $Archives[$i]{endTime} - $Archives[$i]{startTime}; 89 $dur = 1 if ( $dur <= 0 ); 90 my $duration = sprintf("%.1f", $dur / 60); 91 my $Archives_Result = $Lang->{failed}; 92 if ($Archives[$i]{result} ne "failed") { $Archives_Result = $Lang->{success}; } 93 $ArchiveStr .= <<EOF; 94<tr><td align="center"><a href="$MyURL?action=archiveInfo&num=$Archives[$i]{num}&host=${EscURI($host)}">$Archives[$i]{num}</a> </td> 95 <td align="center"> $Archives_Result </td> 96 <td align="right" data-date_format="$Conf{CgiDateFormatMMDD}"> $startTime </td> 97 <td align="right"> $duration </td> 98</tr> 99EOF 100 } 101 if ( $ArchiveStr ne "" ) { 102 $ArchiveStr = eval("qq{$Lang->{Archive_Summary}}"); 103 } 104 if ( @Archives == 0 ) { 105 $warnStr = $Lang->{There_have_been_no_archives}; 106 } 107 if ( $StatusHost{BgQueueOn} ) { 108 $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_background_queue_will_be_backed_up_soon}}"); 109 } 110 if ( $StatusHost{UserQueueOn} ) { 111 $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_user_queue__will_be_backed_up_soon}}"); 112 } 113 if ( $StatusHost{CmdQueueOn} ) { 114 $statusStr .= eval("qq{$Lang->{A_command_for_host_is_on_the_command_queue_will_run_soon}}"); 115 } 116 117 my $content = eval("qq{$Lang->{Host__host_Archive_Summary2}}"); 118 Header(eval("qq{$Lang->{Host__host_Archive_Summary}}"), $content, 1); 119 Trailer(); 120 return; 121 } 122 123 # 124 # Normal, non-archive case 125 # 126 my @Backups = $bpc->BackupInfoRead($host); 127 my(@bkpRows, @sizeRows, @compRows, @errRows, @warnRows, $deleteHdrStr); 128 $deleteHdrStr = '<td align="center"> </td>' if ( $deleteEnabled ); 129 for ( my $i = 0 ; $i < @Backups ; $i++ ) { 130 my($MBExistComp, $ExistComp, $MBNewComp, $NewComp); 131 my($dur, $duration, $MB, $MBperSec, $MBExist, $MBNew); 132 my $startTime = timeStamp2($Backups[$i]{startTime}); 133 134 # 135 # if a backup is active, but there is no job, then force it to be displayed 136 # as partial. this handles case of a forced exit of BackupPC without normal 137 # cleanup 138 # 139 $Backups[$i]{type} = "partial" if ( $Backups[$i]{type} eq "active" && !defined($StatusHost{Job}) ); 140 if ( $Backups[$i]{type} ne "active" ) { 141 $dur = $Backups[$i]{endTime} - $Backups[$i]{startTime}; 142 $dur = 1 if ( $dur <= 0 ); 143 $duration = sprintf("%.1f", $dur / 60); 144 $MB = sprintf("%.1f", $Backups[$i]{size} / (1024*1024)); 145 $MBperSec = sprintf("%.2f", $Backups[$i]{size} / (1024*1024*$dur)); 146 $MBExist = sprintf("%.1f", $Backups[$i]{sizeExist} / (1024*1024)); 147 $MBNew = sprintf("%.1f", $Backups[$i]{sizeNew} / (1024*1024)); 148 if ( $Backups[$i]{sizeExist} && $Backups[$i]{sizeExistComp} ) { 149 $MBExistComp = sprintf("%.1f", $Backups[$i]{sizeExistComp} 150 / (1024 * 1024)); 151 $ExistComp = sprintf("%.1f%%", 100 * 152 (1 - $Backups[$i]{sizeExistComp} / $Backups[$i]{sizeExist})); 153 } 154 if ( $Backups[$i]{sizeNew} && $Backups[$i]{sizeNewComp} ) { 155 $MBNewComp = sprintf("%.1f", $Backups[$i]{sizeNewComp} 156 / (1024 * 1024)); 157 $NewComp = sprintf("%.1f%%", 100 * 158 (1 - $Backups[$i]{sizeNewComp} / $Backups[$i]{sizeNew})); 159 } 160 } 161 my $age = sprintf("%.1f", (time - $Backups[$i]{startTime}) / (24*3600)); 162 my $browseURL = "$MyURL?action=browse&host=${EscURI($host)}&num=$Backups[$i]{num}"; 163 my $level = $Backups[$i]{level}; 164 my $filled = $Backups[$i]{noFill} ? $Lang->{No} : $Lang->{Yes}; 165 $filled .= " ($Backups[$i]{fillFromNum}) " 166 if ( $Backups[$i]{fillFromNum} ne "" ); 167 my $ltype = $Lang->{"backupType_$Backups[$i]{type}"}; 168 my $deleteStr; 169 if ( $deleteEnabled ) { 170 $deleteStr = <<EOF; 171 <td align="center" class="border"><form name="DeleteForm" action="$MyURL" method="get" style="margin-bottom: 0px;"> 172 <input type="hidden" name="action" value="deleteBackup"> 173 <input type="hidden" name="host" value="$host"> 174 <input type="hidden" name="num" value="$Backups[$i]{num}"> 175 <input type="hidden" name="nofill" value="$Backups[$i]{noFill}"> 176 <input type="hidden" name="type" value="$Backups[$i]{type}"> 177 <input type="submit" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"> 178 </form></td> 179EOF 180 } 181 push @bkpRows, <<EOF; 182<tr> 183 <td align="center" class="border"> <a href="$browseURL">$Backups[$i]{num}</a> </td> 184 <td align="center" class="border"> $ltype </td> 185 <td align="center" class="border"> $filled </td> 186 <td align="center" class="border"> $level </td> 187 <td align="right" class="border" data-date_format="$Conf{CgiDateFormatMMDD}"> $startTime </td> 188 <td align="right" class="border"> $duration </td> 189 <td align="right" class="border"> $age </td> 190 $deleteStr 191 <td align="left" class="border"> <tt>$TopDir/pc/$host/$Backups[$i]{num}</tt> </td></tr> 192EOF 193 push @sizeRows, <<EOF; 194<tr><td align="center" class="border"> <a href="$browseURL">$Backups[$i]{num}</a> </td> 195 <td align="center" class="border"> $ltype </td> 196 <td align="right" class="border"> $Backups[$i]{nFiles} </td> 197 <td align="right" class="border"> $MB </td> 198 <td align="right" class="border"> $MBperSec </td> 199 <td align="right" class="border"> $Backups[$i]{nFilesExist} </td> 200 <td align="right" class="border"> $MBExist </td> 201 <td align="right" class="border"> $Backups[$i]{nFilesNew} </td> 202 <td align="right" class="border"> $MBNew </td> 203</tr> 204EOF 205 my $is_compress = $Backups[$i]{compress} || $Lang->{off}; 206 if (! $ExistComp) { $ExistComp = " "; } 207 if (! $MBExistComp) { $MBExistComp = " "; } 208 push @compRows, <<EOF; 209<tr><td align="center" class="border"> <a href="$browseURL">$Backups[$i]{num}</a> </td> 210 <td align="center" class="border"> $ltype </td> 211 <td align="center" class="border"> $is_compress </td> 212 <td align="right" class="border"> $MBExist </td> 213 <td align="right" class="border"> $MBExistComp </td> 214 <td align="right" class="border"> $ExistComp </td> 215 <td align="right" class="border"> $MBNew </td> 216 <td align="right" class="border"> $MBNewComp </td> 217 <td align="right" class="border"> $NewComp </td> 218</tr> 219EOF 220 push @errRows, <<EOF; 221<tr><td align="center" class="border"> <a href="$browseURL">$Backups[$i]{num}</a> </td> 222 <td align="center" class="border"> $ltype </td> 223 <td align="center" class="border"> <a href="$MyURL?action=view&type=XferLOG&num=$Backups[$i]{num}&host=${EscURI($host)}">$Lang->{XferLOG}</a>, 224 <a href="$MyURL?action=view&type=XferErr&num=$Backups[$i]{num}&host=${EscURI($host)}">$Lang->{Errors}</a> </td> 225 <td align="right" class="border"> $Backups[$i]{xferErrs} </td> 226 <td align="right" class="border"> $Backups[$i]{xferBadFile} </td> 227 <td align="right" class="border"> $Backups[$i]{xferBadShare} </td> 228 <td align="right" class="border"> $Backups[$i]{tarErrs} </td></tr> 229EOF 230 } 231 my $str = join("\n", reverse(@bkpRows)); 232 my $sizeStr = join("\n", reverse(@sizeRows)); 233 my $compStr = join("\n", reverse(@compRows)); 234 my $errStr = join("\n", reverse(@errRows)); 235 my $warnStr = join("\n", reverse(@warnRows)); 236 237 my @Restores = $bpc->RestoreInfoRead($host); 238 my @restoreRows; 239 240 for ( my $i = 0 ; $i < @Restores ; $i++ ) { 241 my $startTime = timeStamp2($Restores[$i]{startTime}); 242 my $dur = $Restores[$i]{endTime} - $Restores[$i]{startTime}; 243 $dur = 1 if ( $dur <= 0 ); 244 my $duration = sprintf("%.1f", $dur / 60); 245 my $MB = sprintf("%.1f", $Restores[$i]{size} / (1024*1024)); 246 my $MBperSec = sprintf("%.2f", $Restores[$i]{size} / (1024*1024*$dur)); 247 my $Restores_Result = $Lang->{failed}; 248 if ($Restores[$i]{result} ne "failed") { $Restores_Result = $Lang->{success}; } 249 push @restoreRows, <<EOF; 250<tr><td align="center" class="border"><a href="$MyURL?action=restoreInfo&num=$Restores[$i]{num}&host=${EscURI($host)}">$Restores[$i]{num}</a> </td> 251 <td align="center" class="border"> $Restores_Result </td> 252 <td align="right" class="border" data-date_format="$Conf{CgiDateFormatMMDD}"> $startTime </td> 253 <td align="right" class="border"> $duration </td> 254 <td align="right" class="border"> $Restores[$i]{nFiles} </td> 255 <td align="right" class="border"> $MB </td> 256 <td align="right" class="border"> $Restores[$i]{tarCreateErrs} </td> 257 <td align="right" class="border"> $Restores[$i]{xferErrs} </td> 258</tr> 259EOF 260 } 261 my $restoreStr = join("\n", reverse(@restoreRows)); 262 if ( $restoreStr ne "" ) { 263 $restoreStr = eval("qq{$Lang->{Restore_Summary}}"); 264 } 265 if ( @Backups == 0 ) { 266 $warnStr = $Lang->{This_PC_has_never_been_backed_up}; 267 } 268 if ( defined($Hosts->{$host}) ) { 269 my $user = $Hosts->{$host}{user}; 270 my @moreUsers = sort(keys(%{$Hosts->{$host}{moreUsers}})); 271 my $moreUserStr; 272 foreach my $u ( sort(keys(%{$Hosts->{$host}{moreUsers}})) ) { 273 $moreUserStr .= ", " if ( $moreUserStr ne "" ); 274 $moreUserStr .= "${UserLink($u)}"; 275 } 276 if ( $moreUserStr ne "" ) { 277 $moreUserStr = " ($Lang->{and} $moreUserStr).\n"; 278 } else { 279 $moreUserStr = ".\n"; 280 } 281 if ( $user ne "" ) { 282 $statusStr .= eval("qq{$Lang->{This_PC_is_used_by}$moreUserStr}"); 283 } 284 if ( defined($UserEmailInfo{$user}) 285 && defined($UserEmailInfo{$user}{$host}) 286 && defined($UserEmailInfo{$user}{$host}{lastSubj}) ) { 287 my $mailTime = timeStamp2($UserEmailInfo{$user}{$host}{lastTime}); 288 my $subj = $UserEmailInfo{$user}{$host}{lastSubj}; 289 $statusStr .= eval("qq{$Lang->{Last_email_sent_to__was_at___subject}}"); 290 } elsif ( defined($UserEmailInfo{$user}) 291 && $UserEmailInfo{$user}{lastHost} eq $host 292 && defined($UserEmailInfo{$user}{lastSubj}) ) { 293 # 294 # Old format %UserEmailInfo - pre 3.2.0. 295 # 296 my $mailTime = timeStamp2($UserEmailInfo{$user}{lastTime}); 297 my $subj = $UserEmailInfo{$user}{lastSubj}; 298 $statusStr .= eval("qq{$Lang->{Last_email_sent_to__was_at___subject}}"); 299 } 300 } 301 if ( defined($StatusHost{Job}) ) { 302 my $startTime = timeStamp2($StatusHost{Job}{startTime}); 303 (my $cmd = $StatusHost{Job}{cmd}) =~ s/$BinDir\///g; 304 $statusStr .= eval("qq{$Lang->{The_command_cmd_is_currently_running_for_started}}"); 305 } 306 if ( $StatusHost{BgQueueOn} ) { 307 $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_background_queue_will_be_backed_up_soon}}"); 308 } 309 if ( $StatusHost{UserQueueOn} ) { 310 $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_user_queue__will_be_backed_up_soon}}"); 311 } 312 if ( $StatusHost{CmdQueueOn} ) { 313 $statusStr .= eval("qq{$Lang->{A_command_for_host_is_on_the_command_queue_will_run_soon}}"); 314 } 315 my $startTime = timeStamp2($StatusHost{endTime} == 0 ? 316 $StatusHost{startTime} : $StatusHost{endTime}); 317 my $reason = ""; 318 if ( $StatusHost{reason} ne "" ) { 319 $reason = " ($Lang->{$StatusHost{reason}})"; 320 } 321 $statusStr .= eval("qq{$Lang->{Last_status_is_state_StatusHost_state_reason_as_of_startTime}}"); 322 323 if ( $StatusHost{state} ne "Status_backup_in_progress" 324 && $StatusHost{state} ne "Status_restore_in_progress" 325 && $StatusHost{error} ne "" ) { 326 $statusStr .= eval("qq{$Lang->{Last_error_is____EscHTML_StatusHost_error}}"); 327 } 328 my $priorStr = "Pings"; 329 if ( $StatusHost{deadCnt} > 0 ) { 330 $statusStr .= eval("qq{$Lang->{Pings_to_host_have_failed_StatusHost_deadCnt__consecutive_times}}"); 331 $priorStr = $Lang->{Prior_to_that__pings}; 332 } 333 if ( $StatusHost{aliveCnt} > 0 ) { 334 $statusStr .= eval("qq{$Lang->{priorStr_to_host_have_succeeded_StatusHostaliveCnt_consecutive_times}}"); 335 336 if ( (@{$Conf{BlackoutPeriods}} || defined($Conf{BlackoutHourBegin})) 337 && $StatusHost{aliveCnt} >= $Conf{BlackoutGoodCnt} 338 && $Conf{BlackoutGoodCnt} >= 0 ) { 339 # 340 # Handle backward compatibility with original separate scalar 341 # blackout parameters. 342 # 343 if ( defined($Conf{BlackoutHourBegin}) ) { 344 push(@{$Conf{BlackoutPeriods}}, 345 { 346 hourBegin => $Conf{BlackoutHourBegin}, 347 hourEnd => $Conf{BlackoutHourEnd}, 348 weekDays => $Conf{BlackoutWeekDays}, 349 } 350 ); 351 } 352 353 # 354 # TODO: this string needs i18n. Also, comma-separated 355 # list with "and" for the last element might not translate 356 # correctly. 357 # 358 my(@days) = qw(Sun Mon Tue Wed Thu Fri Sat); 359 my $blackoutStr; 360 my $periodCnt = 0; 361 foreach my $p ( @{$Conf{BlackoutPeriods}} ) { 362 next if ( ref($p->{weekDays}) ne "ARRAY" 363 || !defined($p->{hourBegin}) 364 || !defined($p->{hourEnd}) 365 ); 366 my $days = join(", ", @days[@{$p->{weekDays}}]); 367 my $t0 = sprintf("%d:%02d", $p->{hourBegin}, 368 60 * ($p->{hourBegin} - int($p->{hourBegin}))); 369 my $t1 = sprintf("%d:%02d", $p->{hourEnd}, 370 60 * ($p->{hourEnd} - int($p->{hourEnd}))); 371 if ( $periodCnt ) { 372 $blackoutStr .= ", "; 373 if ( $periodCnt == @{$Conf{BlackoutPeriods}} - 1 ) { 374 $blackoutStr .= eval("qq{$Lang->{and}}"); 375 $blackoutStr .= " "; 376 } 377 } 378 $blackoutStr 379 .= eval("qq{$Lang->{__time0_to__time1_on__days}}"); 380 $periodCnt++; 381 } 382 $statusStr .= eval("qq{$Lang->{Because__host_has_been_on_the_network_at_least__Conf_BlackoutGoodCnt_consecutive_times___}}"); 383 } 384 } 385 if ( $StatusHost{backoffTime} > time ) { 386 my $hours = sprintf("%.1f", ($StatusHost{backoffTime} - time) / 3600); 387 $statusStr .= eval("qq{$Lang->{Backups_are_deferred_for_hours_hours_change_this_number}}"); 388 389 } 390 if ( length($Conf{ClientComment}) ) { 391 $statusStr .= "<li>${EscHTML($Conf{ClientComment})}\n"; 392 } 393 if ( @Backups ) { 394 # only allow incremental if there are already some backups 395 $startIncrStr = <<EOF; 396<input type="button" value="$Lang->{Start_Incr_Backup}" 397 onClick="document.StartStopForm.action.value='Start_Incr_Backup'; 398 document.StartStopForm.submit();"> 399EOF 400 } 401 402 $startIncrStr = eval("qq{$startIncrStr}"); 403 my $content = eval("qq{$Lang->{Host__host_Backup_Summary2}}"); 404 Header(eval("qq{$Lang->{Host__host_Backup_Summary}}"), $content); 405 Trailer(); 406} 407 4081; 409