]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/lib/Bweb.pm
ebl fix
[bacula/bacula] / gui / bweb / lib / Bweb.pm
index c9f4ee821a6f560c31935c735f14252224eae87c..1e69b486e549184840a386c72d4a6f86b17b7fd9 100644 (file)
@@ -3,22 +3,34 @@ use strict;
 
 =head1 LICENSE
 
-    Copyright (C) 2006 Eric Bollengier
-        All rights reserved.
+   Bweb - A Bacula web interface
+   Bacula® - The Network Backup Solution
 
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    any later version.
+   Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
 
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
+   The main author of Bweb is Eric Bollengier.
+   The main author of Bacula is Kern Sibbald, with contributions from
+   many others, a complete list can be found in the file AUTHORS.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+   This program is Free Software; you can redistribute it and/or
+   modify it under the terms of version two of the GNU General Public
+   License as published by the Free Software Foundation plus additions
+   that are listed in the file LICENSE.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.
+
+   Bacula® is a registered trademark of John Walker.
+   The licensor of Bacula is the Free Software Foundation Europe
+   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
+   Switzerland, email:ftf@fsfeurope.org.
 
 =head1 VERSION
 
@@ -41,7 +53,6 @@ package Bweb::Gui;
 use HTML::Template;
 our $template_dir='/usr/share/bweb/tpl';
 
-
 =head1 FUNCTION
 
     new - creation a of new Bweb object
@@ -196,6 +207,7 @@ use CGI;
 our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
              user     => qr/^([\w\d\.-]+)$/i,
              password => qr/^(.*)$/i,
+             fv_write_path => qr!^([/\w\d\.-]*)$!,
              template_dir => qr!^([/\w\d\.-]+)$!,
              debug    => qr/^(on)?$/,
              email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
@@ -203,6 +215,7 @@ our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
              bconsole    => qr!^(.+)?$!,
              syslog_file => qr!^(.+)?$!,
              log_dir     => qr!^(.+)?$!,
+             stat_job_table => qr!^(\w*)$!,
              );
 
 =head1 FUNCTION
@@ -221,7 +234,7 @@ sub load
 
     unless (open(FP, $self->{config_file}))
     {
-       return $self->error("$self->{config_file} : $!");
+       return $self->error("can't load config_file $self->{config_file} : $!");
     }
     my $f=''; my $tmpbuffer;
     while(read FP,$tmpbuffer,4096)
@@ -848,12 +861,13 @@ sub send_to_io
 sub transfer
 {
     my ($self, $src, $dst) = @_ ;
-    print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
+    if ($self->{debug}) {
+       print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
+    }
     my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
     
     if ($? == 0) {
        my $content = $self->get_slot($src);
-       print "$content ($src) => $dst<br/>";
        $self->{slot}->[$src] = 'empty';
        $self->set_slot($dst, $content);
        return 1;
@@ -889,13 +903,17 @@ sub clear_io
        if ($self->slot_is_full($slot))
        {
            my $free = $self->slot_get_first_free() ;
-           print "want to move $slot to $free\n";
+           print "move $slot to $free :\n";
 
            if ($free) {
-               $self->transfer($slot, $free) || print "$self->{error}\n";
+               if ($self->transfer($slot, $free)) {
+                   print "<img src='/bweb/T.png' alt='ok'><br/>\n";
+               } else {
+                   print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
+               }
                
            } else {
-               $self->{error} = "E : Can't find free slot";
+               $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
            }
        }
     }
@@ -957,7 +975,7 @@ WHERE Media.VolumeName IN ($media_list)
 
            } else {            # empty or no label
                push @{ $param }, {realslot => $slot,
-                                  volstatus => 'Unknow',
+                                  volstatus => 'Unknown',
                                   volumename => $self->{slot}->[$slot]} ;
            }
        } else {                # empty
@@ -1006,6 +1024,8 @@ use base q/Bweb::Gui/;
 use DBI;
 use POSIX qw/strftime/;
 
+our $config_file='/etc/bacula/bweb.conf';
+
 our $cur_id=0;
 
 =head1 VARIABLE
@@ -1170,7 +1190,7 @@ sub connect_db
                                    $self->{info}->{user},
                                    $self->{info}->{password});
 
-       print "Can't connect to your database, see error log\n"
+       $self->error("Can't connect to your database:\n$DBI::errstr\n")
            unless ($self->{dbh});
 
        $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
@@ -1338,6 +1358,10 @@ sub get_form
                 maxvolfiles => 0,
                 );
 
+    my %opt_ss =(              # string with space
+                job     => 1,
+                storage => 1,
+                );
     my %opt_s = (              # default to ''
                 ach    => 1,
                 status => 1,
@@ -1352,12 +1376,15 @@ sub get_form
                 graph  => 1,
                  gtype  => 1,
                  type   => 1,
+                poolrecycle => 1,
+                replace => 1,
                 );
     my %opt_p = (              # option with path
                 fileset=> 1,
                 mtxcmd => 1,
                 precmd => 1,
                 device => 1,
+                where  => 1,
                 );
 
     my %opt_d = (              # option with date
@@ -1376,9 +1403,13 @@ sub get_form
            if ($value =~ /^([\w\d\.-]+)$/) {
                $ret{$i} = $1;
            }
-
+       } elsif ($opt_ss{$i}) { # simple string param (with space)
+           my $value = CGI::param($i) || '';
+           if ($value =~ /^([\w\d\.\-\s]+)$/) {
+               $ret{$i} = $1;
+           }
        } elsif ($i =~ /^j(\w+)s$/) { # quote join args
-           my @value = CGI::param($1) ;
+           my @value = grep { ! /^\s*$/ } CGI::param($1) ;
            if (@value) {
                $ret{$i} = $self->dbh_join(@value) ;
            }
@@ -1391,7 +1422,7 @@ sub get_form
 
        } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
            $ret{$i} = [ map { { name => $self->dbh_quote($_) } } 
-                                 CGI::param($1) ];
+                                          grep { ! /^\s*$/ } CGI::param($1) ];
        } elsif (exists $opt_p{$i}) {
            my $value = CGI::param($i) || '';
            if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
@@ -1413,6 +1444,13 @@ sub get_form
        }
     }
 
+    if ($what{when}) {
+       my $when = CGI::param('when') || '';
+       if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
+           $ret{when} = $1;
+       }
+    }
+
     if ($what{db_clients}) {
        my $query = "
 SELECT Client.Name as clientname
@@ -1746,7 +1784,7 @@ sub get_param
     my $limit = '';
 
     if ($elt{clients}) {
-       my @clients = CGI::param('client');
+       my @clients = grep { ! /^\s*$/ } CGI::param('client');
        if (@clients) {
            $ret{clients} = \@clients;
            my $str = $self->dbh_join(@clients);
@@ -1755,7 +1793,7 @@ sub get_param
     }
 
     if ($elt{filesets}) {
-       my @filesets = CGI::param('fileset');
+       my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
        if (@filesets) {
            $ret{filesets} = \@filesets;
            my $str = $self->dbh_join(@filesets);
@@ -1764,7 +1802,7 @@ sub get_param
     }
 
     if ($elt{mediatypes}) {
-       my @medias = CGI::param('mediatype');
+       my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
        if (@medias) {
            $ret{mediatypes} = \@medias;
            my $str = $self->dbh_join(@medias);
@@ -1808,8 +1846,16 @@ sub get_param
        }
     }
 
+    if ($elt{volstatus}) {
+       my $status = CGI::param('volstatus') || '';
+       if ($status =~ /^(\w+)$/) {
+           $ret{status} = $1;
+           $limit .= "AND Media.VolStatus = '$1' ";            
+       }
+    }
+
     if ($elt{locations}) {
-       my @location = CGI::param('location') ;
+       my @location = grep { ! /^\s*$/ } CGI::param('location') ;
        if (@location) {
            $ret{locations} = \@location;           
            my $str = $self->dbh_join(@location);
@@ -1818,7 +1864,7 @@ sub get_param
     }
 
     if ($elt{pools}) {
-       my @pool = CGI::param('pool') ;
+       my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
        if (@pool) {
            $ret{pools} = \@pool; 
            my $str = $self->dbh_join(@pool);
@@ -1872,6 +1918,7 @@ sub display_job
                                          'level',
                                          'filesets',
                                          'jobtype',
+                                         'pools',
                                          'jobid',
                                          'status');
 
@@ -1882,6 +1929,7 @@ SELECT  Job.JobId       AS jobid,
        Job.Name        AS jobname,
         Level           AS level,
         StartTime       AS starttime,
+        EndTime         AS endtime,
         Pool.Name       AS poolname,
         JobFiles        AS jobfiles, 
         JobBytes        AS jobbytes,
@@ -1932,6 +1980,7 @@ SELECT DISTINCT Job.JobId       AS jobid,
                 JobFiles        AS jobfiles, 
                 JobBytes        AS jobbytes,
                JobStatus       AS jobstatus,
+                JobErrors       AS joberrors,
                 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
                                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
 
@@ -1964,8 +2013,10 @@ sub display_media
 {
     my ($self) = @_ ;
 
-    my ($where, %elt) = $self->get_param('pool',
-                                        'location');
+    my ($where, %elt) = $self->get_param('pools',
+                                        'mediatypes',
+                                        'volstatus',
+                                        'locations');
 
     my $arg = $self->get_form('jmedias', 'qre_media');
 
@@ -2053,6 +2104,8 @@ SELECT InChanger     AS online,
        Media.LastWritten  AS lastwritten,
        Media.VolReadTime/1000000  AS volreadtime,
        Media.VolWriteTime/1000000 AS volwritetime,
+       Media.RecycleCount AS recyclecount,
+       Media.Comment      AS comment,
        $self->{sql}->{FROM_UNIXTIME}(
           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
         + $self->{sql}->{TO_SEC}(Media.VolRetention)
@@ -2289,7 +2342,7 @@ sub update_media
 
     my $query = "
 SELECT Media.Slot         AS slot,
-       Pool.Name          AS poolname,
+       PoolMedia.Name     AS poolname,
        Media.VolStatus    AS volstatus,
        Media.InChanger    AS inchanger,
        Location.Location  AS location,
@@ -2298,9 +2351,12 @@ SELECT Media.Slot         AS slot,
        Media.MaxVolJobs   AS maxvoljobs,
        Media.MaxVolFiles  AS maxvolfiles,
        Media.VolUseDuration AS voluseduration,
-       Media.VolRetention AS volretention
+       Media.VolRetention AS volretention,
+       Media.Comment      AS comment,
+       PoolRecycle.Name   AS poolrecycle
 
-FROM Media INNER JOIN Pool ON (Media.PoolId = Pool.PoolId)
+FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
+           LEFT  JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
            LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
 
 WHERE Media.VolumeName = $media->{qmedia}
@@ -2357,7 +2413,7 @@ sub change_location
     }
     my $newloc = CGI::param('newlocation');
 
-    my $user = CGI::param('user') || 'unknow';
+    my $user = CGI::param('user') || 'unknown';
     my $comm = CGI::param('comment') || '';
     $comm = $self->dbh_quote("$user: $comm");
 
@@ -2383,7 +2439,7 @@ INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
     $self->display({ email  => $self->{info}->{email_media},
                     url => $url,
                     newlocation => $newloc,
-                    # [ { volumename => 'vol1' }, { volumename => 'vol2' },..]
+                    # [ { volumename => 'vol1' }, { volumename => 'vol2'\81 },..]
                     medias => [ values %$medias ],
                   },
                   "change_location.tpl");
@@ -2423,6 +2479,14 @@ GROUP BY Client.Name
 sub display_pool
 {
     my ($self, $poolname) = @_ ;
+    my $whereA = '';
+    my $whereW = '';
+
+    my $arg = $self->get_form('jmediatypes', 'qmediatypes');
+    if ($arg->{jmediatypes}) {
+       $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
+       $whereA = "AND   MediaType IN ($arg->{jmediatypes}) ";
+    }
     
 # TODO : afficher les tailles et les dates
 
@@ -2455,12 +2519,27 @@ FROM
     GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
   ) AS subq
 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
+$whereW
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'name') ;
 
+    $query = "
+SELECT Pool.Name AS name,
+       sum(VolBytes) AS size
+FROM   Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
+WHERE  Media.VolStatus IN ('Recycled', 'Purged')
+       $whereA
+GROUP BY Pool.Name;
+";
+    my $empty = $self->dbh_selectall_hashref($query, 'name');
+
     foreach my $p (values %$all) {
        if ($p->{volmax} > 0) { # mysql returns 0.0000
+           # we remove Recycled/Purged media from pool usage
+           if (defined $empty->{$p->{name}}) {
+               $p->{voltotal} -= $empty->{$p->{name}}->{size};
+           }
            $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
        } else {
            $p->{poolusage} = 0;
@@ -2470,6 +2549,7 @@ LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
   SELECT VolStatus AS volstatus, count(MediaId) AS nb
     FROM Media 
    WHERE PoolId=$p->{poolid} 
+         $whereA
 GROUP BY VolStatus
 ";
        my $content = $self->dbh_selectall_hashref($query, 'volstatus');
@@ -2480,6 +2560,7 @@ GROUP BY VolStatus
 
     $self->debug($all);
     $self->display({ ID => $cur_id++,
+                    MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
                     Pools => [ values %$all ]},
                   "display_pool.tpl");
 }
@@ -2544,9 +2625,11 @@ WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
                   "running_job.tpl") ;
 }
 
+# return the autochanger list to update
 sub eject_media
 {
     my ($self) = @_;
+    my %ret; 
     my $arg = $self->get_form('jmedias');
 
     unless ($arg->{jmedias}) {
@@ -2569,6 +2652,7 @@ WHERE Media.VolumeName IN ($arg->{jmedias})
     foreach my $vol (values %$all) {
        my $a = $self->ach_get($vol->{location});
        next unless ($a) ;
+       $ret{$vol->{location}} = 1;
 
        unless ($a->{have_status}) {
            $a->status();
@@ -2577,11 +2661,12 @@ WHERE Media.VolumeName IN ($arg->{jmedias})
 
        print "eject $vol->{volumename} from $vol->{storage} : ";
        if ($a->send_to_io($vol->{slot})) {
-           print "ok</br>";
+           print "<img src='/bweb/T.png' alt='ok'><br/>";
        } else {
-           print "err</br>";
+           print "<img src='/bweb/E.png' alt='err'><br/>";
        }
     }
+    return keys %ret;
 }
 
 sub move_email
@@ -2638,7 +2723,8 @@ sub ach_get
        return undef;
     }
 
-    $a->{bweb} = $self;
+    $a->{bweb}  = $self;
+    $a->{debug} = $self->{debug};
 
     return $a;
 }
@@ -2768,6 +2854,7 @@ sub do_update_media
     my $arg = $self->get_form(qw/media volstatus inchanger pool
                                 slot volretention voluseduration 
                                 maxvoljobs maxvolfiles maxvolbytes
+                                qcomment poolrecycle
                              /);
 
     unless ($arg->{media}) {
@@ -2793,28 +2880,23 @@ sub do_update_media
        $update .= " pool=$arg->{pool} " ;
     }
 
-    $arg->{volretention} ||= 0 ; 
-    if ($arg->{volretention}) {
+    if (defined $arg->{volretention}) {
        $update .= " volretention=\"$arg->{volretention}\" " ;
     }
 
-    $arg->{voluseduration} ||= 0 ; 
-    if ($arg->{voluseduration}) {
+    if (defined $arg->{voluseduration}) {
        $update .= " voluse=\"$arg->{voluseduration}\" " ;
     }
 
-    $arg->{maxvoljobs} ||= 0;
-    if ($arg->{maxvoljobs}) {
+    if (defined $arg->{maxvoljobs}) {
        $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
     }
     
-    $arg->{maxvolfiles} ||= 0;
-    if ($arg->{maxvolfiles}) {
+    if (defined $arg->{maxvolfiles}) {
        $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
     }    
 
-    $arg->{maxvolbytes} ||= 0;
-    if ($arg->{maxvolbytes}) {
+    if (defined $arg->{maxvolbytes}) {
        $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
     }    
 
@@ -2827,17 +2909,29 @@ sub do_update_media
     }, "command.tpl"); 
 
 
+    my @q;
+    my $media = $self->dbh_quote($arg->{media});
+
     my $loc = CGI::param('location') || '';
     if ($loc) {
-       my $media = $self->dbh_quote($arg->{media});
        $loc = $self->dbh_quote($loc); # is checked by db
-       my $query = "
+       push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
+    }
+    if ($arg->{poolrecycle}) {
+       push @q, "RecyclePoolId=(SELECT PoolId FROM Pool WHERE Name='$arg->{poolrecycle}')";
+    }
+    if (!$arg->{qcomment}) {
+       $arg->{qcomment} = "''";
+    }
+    push @q, "Comment=$arg->{qcomment}";
+    
+
+    my $query = "
 UPDATE Media 
-   SET LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)
+   SET " . join (',', @q) . "
  WHERE Media.VolumeName = $media
 ";
-       $self->dbh_do($query);
-    }
+    $self->dbh_do($query);
 
     $self->update_media();
 }
@@ -2882,11 +2976,15 @@ SELECT Job.Name as name, Client.Name as clientname
     }
 
     $query = "
-SELECT Time AS time, LogText AS log
- FROM  Log
- WHERE JobId = $arg->{jobid}
- ORDER BY Time
+SELECT Time AS time, LogText AS log 
+  FROM  Log 
+ WHERE Log.JobId = $arg->{jobid} 
+    OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid}) 
+                      AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
+       )
+ ORDER BY LogId;
 ";
+
     my $log = $self->dbh_selectall_arrayref($query);
     unless ($log) {
        return $self->error("Can't get log for jobid $arg->{jobid}");
@@ -2918,6 +3016,16 @@ sub label_barcodes
        return $self->error("Can't find autochanger name");
     }
 
+    my $a = $self->ach_get($arg->{ach});
+    unless ($a) {
+       return $self->error("Can't find autochanger name in configuration");
+    } 
+
+    my $storage = $a->get_drive_name($arg->{drive});
+    unless ($storage) {
+       return $self->error("Can't get your drive name");
+    }
+
     my $slots = '';
     my $t = 300 ;
     if ($arg->{slots}) {
@@ -2928,12 +3036,27 @@ sub label_barcodes
     my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
     print "<h1>This command can take long time, be patient...</h1>";
     print "<pre>" ;
-    $b->label_barcodes(storage => $arg->{ach},
+    $b->label_barcodes(storage => $storage,
                       drive => $arg->{drive},
                       pool  => 'Scratch',
                       slots => $slots) ;
     $b->close();
     print "</pre>";
+
+    $self->dbh_do("
+  UPDATE Media 
+       SET LocationId =   (SELECT LocationId 
+                             FROM Location 
+                            WHERE Location = '$arg->{ach}'),
+
+           RecyclePoolId = PoolId
+
+     WHERE Media.PoolId = (SELECT PoolId 
+                             FROM Pool
+                            WHERE Name = 'Scratch')
+       AND (LocationId = 0 OR LocationId IS NULL)
+");
+
 }
 
 sub purge
@@ -3138,7 +3261,7 @@ sub run_job_now
     
     # TODO: check input (don't use pool, level)
 
-    my $arg = $self->get_form('pool', 'level', 'client', 'priority');
+    my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when');
     my $job = CGI::param('job') || '';
     my $storage = CGI::param('storage') || '';
 
@@ -3148,6 +3271,7 @@ sub run_job_now
                        level => $arg->{level},
                        storage => $storage,
                        pool => $arg->{pool},
+                       when => $arg->{when},
                        );
 
     print $jobid, $b->{error};