]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/lib/Bweb.pm
ebl Doesn't display running job more in job hist
[bacula/bacula] / gui / bweb / lib / Bweb.pm
index 336e63e286f98ca700a3bb997e01a063c90e4ff9..5b14313b5ef450d93e8b6068963f0773e82812c4 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
@@ -123,7 +134,7 @@ sub error
 
 =head2 EXAMPLE
 
-    $ref = { name => 'me', age => 26 };
+    $ref = { name => 'me', age => 26 };
     $self->display($ref, "people.tpl");
 
 =cut
@@ -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,8 @@ 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*)$!,
+             display_log_time => qr!^(on)?$!,
              );
 
 =head1 FUNCTION
@@ -221,7 +235,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 +862,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 +904,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 +976,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 +1025,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
@@ -1028,6 +1049,8 @@ our %sql_func = (
              STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
              STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
              STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
+             DB_SIZE => " SELECT pg_database_size(current_database()) ",
+             CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
          },
          mysql => {
              UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
@@ -1042,9 +1065,23 @@ our %sql_func = (
              STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
              STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
              STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
+             # with mysql < 5, you have to play with the ugly SHOW command
+             DB_SIZE => " SELECT 0 ",
+             # works only with mysql 5
+             # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
+             CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
          },
         );
 
+sub dbh_disconnect
+{
+    my ($self) = @_;
+    if ($self->{dbh}) {
+       $self->{dbh}->disconnect();
+       undef $self->{dbh};
+    }
+}
+
 sub dbh_selectall_arrayref
 {
     my ($self, $query) = @_;
@@ -1100,7 +1137,7 @@ sub dbh_selectrow_hashref
 # display Mb/Gb/Kb
 sub human_size
 {
-    my @unit = qw(b Kb Mb Gb Tb);
+    my @unit = qw(B KB MB GB TB);
     my $val = shift || 0;
     my $i=0;
     my $format = '%i %s';
@@ -1170,7 +1207,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';
@@ -1222,12 +1259,24 @@ sub display_clients
     my ($self) = @_;
 
     my $where='';
-    my $arg = $self->get_form("client", "qre_client");
+    my $arg = $self->get_form("client", "qre_client", "jclient_groups", "qnotingroup");
 
     if ($arg->{qre_client}) {
        $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
     } elsif ($arg->{client}) {
        $where = "WHERE Name = '$arg->{client}' ";
+    } elsif ($arg->{jclient_groups}) {
+       $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
+                  JOIN client_group USING (client_group_id)
+                  WHERE client_group_name IN ($arg->{jclient_groups})";
+    } elsif ($arg->{qnotingroup}) {
+       $where =   "
+  WHERE NOT EXISTS
+   (SELECT 1 FROM client_group_member
+     WHERE Client.ClientId = client_group_member.ClientId
+   )
+";
+   
     }
 
     my $query = "
@@ -1336,6 +1385,7 @@ sub get_form
                 maxvoljobs  => 0,
                 maxvolbytes => 0,
                 maxvolfiles => 0,
+                pathid => 1,
                 );
 
     my %opt_ss =(              # string with space
@@ -1366,6 +1416,7 @@ sub get_form
                 device => 1,
                 where  => 1,
                 );
+    my %opt_r = (regexwhere => 1);
 
     my %opt_d = (              # option with date
                 voluseduration=> 1,
@@ -1388,8 +1439,8 @@ sub get_form
            if ($value =~ /^([\w\d\.\-\s]+)$/) {
                $ret{$i} = $1;
            }
-       } elsif ($i =~ /^j(\w+)s$/) { # quote join args
-           my @value = CGI::param($1) ;
+       } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
+           my @value = grep { ! /^\s*$/ } CGI::param($1) ;
            if (@value) {
                $ret{$i} = $self->dbh_join(@value) ;
            }
@@ -1402,12 +1453,17 @@ 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:\@\-]+)$/) {
                $ret{$i} = $1;
            }
+       } elsif (exists $opt_r{$i}) {
+           my $value = CGI::param($i) || '';
+           if ($value =~ /^([^'"']+)$/) {
+               $ret{$i} = $1;
+           }
        } elsif (exists $opt_d{$i}) {
            my $value = CGI::param($i) || '';
            if ($value =~ /^\s*(\d+\s+\w+)$/) {
@@ -1424,10 +1480,17 @@ 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
-FROM Client
+  FROM Client
 ";
 
        my $clients = $self->dbh_selectall_hashref($query, 'clientname');
@@ -1435,10 +1498,21 @@ FROM Client
                              values %$clients] ;
     }
 
+    if ($what{db_client_groups}) {
+       my $query = "
+SELECT client_group_name AS name 
+  FROM client_group
+";
+
+       my $grps = $self->dbh_selectall_hashref($query, 'name');
+       $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} } 
+                                 values %$grps] ;
+    }
+
     if ($what{db_mediatypes}) {
        my $query = "
 SELECT MediaType as mediatype
-FROM MediaType
+  FROM MediaType
 ";
 
        my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
@@ -1448,7 +1522,8 @@ FROM MediaType
 
     if ($what{db_locations}) {
        my $query = "
-SELECT Location as location, Cost as cost FROM Location
+SELECT Location as location, Cost as cost 
+  FROM Location
 ";
        my $loc = $self->dbh_selectall_hashref($query, 'location');
        $ret{db_locations} = [ sort { $a->{location} 
@@ -1467,7 +1542,7 @@ SELECT Location as location, Cost as cost FROM Location
     if ($what{db_filesets}) {
        my $query = "
 SELECT FileSet.FileSet AS fileset 
-FROM FileSet
+  FROM FileSet
 ";
 
        my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
@@ -1479,7 +1554,7 @@ FROM FileSet
     if ($what{db_jobnames}) {
        my $query = "
 SELECT DISTINCT Job.Name AS jobname 
-FROM Job
+  FROM Job
 ";
 
        my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
@@ -1491,7 +1566,7 @@ FROM Job
     if ($what{db_devices}) {
        my $query = "
 SELECT Device.Name AS name
-FROM Device
+  FROM Device
 ";
 
        my $devices = $self->dbh_selectall_hashref($query, 'name');
@@ -1730,6 +1805,7 @@ SELECT
     (SELECT count(Media.MediaId) FROM Media)  AS nb_media,
     (SELECT count(Job.JobId)     FROM Job)    AS nb_job,
     (SELECT sum(VolBytes)        FROM Media)  AS nb_bytes,
+    ($self->{sql}->{DB_SIZE})                 AS db_size,
     (SELECT count(Job.JobId)
       FROM Job
       WHERE Job.JobStatus IN ('E','e','f','A')
@@ -1742,7 +1818,7 @@ SELECT
 
     $row->{nb_bytes} = human_size($row->{nb_bytes});
 
-    $row->{db_size} = '???';
+    $row->{db_size} = human_size($row->{db_size});
     $row->{label} = $label;
 
     $self->display($row, "general.tpl");
@@ -1757,7 +1833,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);
@@ -1765,8 +1841,17 @@ sub get_param
        }
     }
 
+    if ($elt{client_groups}) {
+       my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
+       if (@clients) {
+           $ret{client_groups} = \@clients;
+           my $str = $self->dbh_join(@clients);
+           $limit .= "AND client_group_name IN ($str) ";
+       }
+    }
+
     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);
@@ -1775,7 +1860,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);
@@ -1813,14 +1898,24 @@ sub get_param
            $ret{status} = $1;
            if ($1 eq 'f') {
                $limit .= "AND Job.JobStatus IN ('f','E') ";            
-           } else {
+           } elsif ($1 eq 'W') {
+               $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";             
+            } else {
                $limit .= "AND Job.JobStatus = '$1' ";          
            }
        }
     }
 
+    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);
@@ -1829,7 +1924,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);
@@ -1880,12 +1975,22 @@ sub display_job
 
     my ($limit, $label) = $self->get_limit(%arg);
     my ($where, undef) = $self->get_param('clients',
+                                         'client_groups',
                                          'level',
                                          'filesets',
                                          'jobtype',
+                                         'pools',
                                          'jobid',
                                          'status');
 
+    my $cgq = '';
+    if (CGI::param('client_group')) {
+       $cgq = "
+LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
+LEFT JOIN client_group USING (client_group_id)
+";
+    }
+
     my $query="
 SELECT  Job.JobId       AS jobid,
         Client.Name     AS client,
@@ -1893,6 +1998,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,
@@ -1906,8 +2012,9 @@ SELECT  Job.JobId       AS jobid,
  FROM Client, 
       Job LEFT JOIN Pool     ON (Job.PoolId    = Pool.PoolId)
           LEFT JOIN FileSet  ON (Job.FileSetId = FileSet.FileSetId)
+          $cgq
  WHERE Client.ClientId=Job.ClientId
-   AND Job.JobStatus != 'R'
+   AND Job.JobStatus NOT IN ('R', 'C')
  $where
  $limit
 ";
@@ -1972,13 +2079,73 @@ WHERE Job.JobId = $jobid
     $self->display($row, "display_job_zoom.tpl");
 }
 
+sub display_job_group
+{
+    my ($self, %arg) = @_;
+
+    my ($limit, $label) = $self->get_limit(groupby => 'client_group_name',  %arg);
+
+    my ($where, undef) = $self->get_param('client_groups',
+                                         'level',
+                                         'pools');
+    
+    my $query = 
+"
+SELECT client_group_name AS client_group_name,
+       COALESCE(jobok.jobfiles,0)  + COALESCE(joberr.jobfiles,0)  AS jobfiles,
+       COALESCE(jobok.jobbytes,0)  + COALESCE(joberr.jobbytes,0)  AS jobbytes,
+       COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
+       COALESCE(jobok.nbjobs,0)  AS nbjobok,
+       COALESCE(joberr.nbjobs,0) AS nbjoberr,
+       COALESCE(jobok.duration, '0:0:0') AS duration
+
+FROM client_group LEFT JOIN (
+    SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs, 
+           SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes, 
+           SUM(JobErrors) AS joberrors,
+           SUM($self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
+                              - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
+                        AS duration
+
+    FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
+             JOIN client_group USING (client_group_id)
+    
+    WHERE JobStatus = 'T'
+    $where
+    $limit
+) AS jobok USING (client_group_name) LEFT JOIN
+
+(
+    SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs, 
+           SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes, 
+           SUM(JobErrors) AS joberrors
+    FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
+             JOIN client_group USING (client_group_id)
+    
+    WHERE JobStatus IN ('f','E', 'A')
+    $where
+    $limit
+) AS joberr USING (client_group_name)
+
+    ";
+
+    my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
+
+    my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
+                
+    $self->debug($rep);
+    $self->display($rep, "display_job_group.tpl");
+}
+
 sub display_media
 {
-    my ($self) = @_ ;
+    my ($self, %arg) = @_ ;
 
-    my ($where, %elt) = $self->get_param('pool',
-                                        'mediatype',
-                                        'location');
+    my ($limit, $label) = $self->get_limit(%arg);    
+    my ($where, %elt) = $self->get_param('pools',
+                                        'mediatypes',
+                                        'volstatus',
+                                        'locations');
 
     my $arg = $self->get_form('jmedias', 'qre_media');
 
@@ -2014,6 +2181,7 @@ LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
 
 WHERE Media.PoolId=Pool.PoolId
 $where
+$limit
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
@@ -2173,7 +2341,7 @@ WHERE Location.Location = $arg->{qlocation}
 
     $self->dbh_do($query);
 
-    $self->display_location();
+    $self->location_display();
 }
 
 sub location_del
@@ -2203,7 +2371,7 @@ DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
 
     $self->dbh_do($query);
 
-    $self->display_location();
+    $self->location_display();
 }
 
 
@@ -2230,10 +2398,10 @@ INSERT INTO Location (Location, Cost, Enabled)
 
     $self->dbh_do($query);
 
-    $self->display_location();
+    $self->location_display();
 }
 
-sub display_location
+sub location_display
 {
     my ($self) = @_ ;
 
@@ -2273,6 +2441,149 @@ sub update_location
                   "update_location.tpl");
 }
 
+###########################################################
+
+sub groups_edit
+{
+    my ($self) = @_;
+
+    my $grp = $self->get_form(qw/qclient_group db_clients/);
+    $self->debug($grp);
+
+    unless ($grp->{qclient_group}) {
+       return $self->error("Can't get group");
+    }
+
+    my $query = "
+SELECT Name AS name 
+  FROM Client JOIN client_group_member using (clientid)
+              JOIN client_group using (client_group_id)
+WHERE client_group_name = $grp->{qclient_group}
+";
+
+    my $row = $self->dbh_selectall_hashref($query, "name");
+    $self->debug($row);
+    $self->display({ ID => $cur_id++,
+                    client_group => $grp->{qclient_group},
+                    %$grp,
+                    client_group_member => [ values %$row]}, 
+                  "groups_edit.tpl");
+}
+
+sub groups_save
+{
+    my ($self) = @_;
+
+    my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
+    unless ($arg->{qclient_group}) {
+       return $self->error("Can't get groups");
+    }
+    
+    $self->{dbh}->begin_work();
+
+    my $query = "
+DELETE FROM client_group_member 
+      WHERE client_group_id IN 
+           (SELECT client_group_id 
+              FROM client_group 
+             WHERE client_group_name = $arg->{qclient_group})
+";
+    $self->dbh_do($query);
+
+    $query = "
+    INSERT INTO client_group_member (clientid, client_group_id) 
+       (SELECT  Clientid, 
+                (SELECT client_group_id 
+                   FROM client_group 
+                  WHERE client_group_name = $arg->{qclient_group})
+          FROM Client WHERE Name IN ($arg->{jclients})
+       )
+";
+    $self->dbh_do($query);
+
+    if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
+       $query = "
+UPDATE client_group 
+   SET client_group_name = $arg->{qnewgroup}
+ WHERE client_group_name = $arg->{qclient_group}
+";
+
+       $self->dbh_do($query);
+    }
+
+    $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
+
+    $self->display_groups();
+}
+
+sub groups_del
+{
+    my ($self) = @_;
+    my $arg = $self->get_form(qw/qclient_group/);
+
+    unless ($arg->{qclient_group}) {
+       return $self->error("Can't get groups");
+    }
+
+    $self->{dbh}->begin_work();
+
+    my $query = "
+DELETE FROM client_group_member 
+      WHERE client_group_id IN 
+           (SELECT client_group_id 
+              FROM client_group 
+             WHERE client_group_name = $arg->{qclient_group});
+
+DELETE FROM client_group
+      WHERE client_group_name = $arg->{qclient_group};
+";
+    $self->dbh_do($query);
+
+    $self->{dbh}->commit();
+    
+    $self->display_groups();
+}
+
+
+sub groups_add
+{
+    my ($self) = @_;
+    my $arg = $self->get_form(qw/qclient_group/) ;
+
+    unless ($arg->{qclient_group}) {
+       $self->display({}, "groups_add.tpl");
+       return 1;
+    }
+
+    my $query = "
+INSERT INTO client_group (client_group_name) 
+VALUES ($arg->{qclient_group})
+";
+
+    $self->dbh_do($query);
+
+    $self->display_groups();
+}
+
+sub display_groups
+{
+    my ($self) = @_;
+
+    my $arg = $self->get_form(qw/db_client_groups/) ;
+
+    if ($self->{dbh}->errstr) {
+       return $self->error("Can't use groups with bweb, read INSTALL to enable them");
+    }
+
+    $self->debug($arg);
+
+    $self->display({ ID => $cur_id++,
+                    %$arg},
+                  "display_groups.tpl");
+}
+
+###########################################################
+
 sub get_media_max_size
 {
     my ($self, $type) = @_;
@@ -2365,7 +2676,7 @@ sub save_location
     $self->display_media();
 }
 
-sub change_location
+sub location_change
 {
     my ($self) = @_ ;
 
@@ -2375,7 +2686,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");
 
@@ -2401,7 +2712,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Â\81 },..]
                     medias => [ values %$medias ],
                   },
                   "change_location.tpl");
@@ -2413,6 +2724,7 @@ sub display_client_stats
     my ($self, %arg) = @_ ;
 
     my $client = $self->dbh_quote($arg{clientname});
+
     my ($limit, $label) = $self->get_limit(%arg);
 
     my $query = "
@@ -2422,7 +2734,7 @@ SELECT
     sum(Job.JobErrors)   AS nb_err,
     sum(Job.JobFiles)    AS nb_files,
     Client.Name          AS clientname
-FROM Job INNER JOIN Client USING (ClientId)
+FROM Job JOIN Client USING (ClientId)
 WHERE 
     Client.Name = $client
     $limit 
@@ -2433,6 +2745,45 @@ GROUP BY Client.Name
 
     $row->{ID} = $cur_id++;
     $row->{label} = $label;
+    $row->{grapharg} = "client";
+
+    $self->display($row, "display_client_stats.tpl");
+}
+
+
+sub display_group_stats
+{
+    my ($self, %arg) = @_ ;
+
+    my $carg = $self->get_form(qw/qclient_group/);
+
+    unless ($carg->{qclient_group}) {
+       return $self->error("Can't get group");
+    }
+
+    my ($limit, $label) = $self->get_limit(%arg);
+
+    my $query = "
+SELECT 
+    count(Job.JobId)     AS nb_jobs,
+    sum(Job.JobBytes)    AS nb_bytes,
+    sum(Job.JobErrors)   AS nb_err,
+    sum(Job.JobFiles)    AS nb_files,
+    client_group.client_group_name  AS clientname
+FROM Job JOIN Client USING (ClientId) 
+         JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
+         JOIN client_group USING (client_group_id)
+WHERE 
+    client_group.client_group_name = $carg->{qclient_group}
+    $limit 
+GROUP BY client_group.client_group_name
+";
+
+    my $row = $self->dbh_selectrow_hashref($query);
+
+    $row->{ID} = $cur_id++;
+    $row->{label} = $label;
+    $row->{grapharg} = "client_group";
 
     $self->display($row, "display_client_stats.tpl");
 }
@@ -2445,7 +2796,7 @@ sub display_pool
     my $whereW = '';
 
     my $arg = $self->get_form('jmediatypes', 'qmediatypes');
-    if ($arg->{jmediatypes}) {
+    if ($arg->{jmediatypes}) { 
        $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
        $whereA = "AND   MediaType IN ($arg->{jmediatypes}) ";
     }
@@ -2463,7 +2814,9 @@ SELECT subq.volmax        AS volmax,
        Pool.MaxVolJobs    AS maxvoljobs,
        Pool.MaxVolFiles   AS maxvolfiles,
        Pool.MaxVolBytes   AS maxvolbytes,
-       subq.PoolId        AS PoolId
+       subq.PoolId        AS PoolId,
+       subq.MediaType     AS mediatype,
+       $self->{sql}->{CAT_POOL_TYPE}  AS uniq
 FROM
   (
     SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
@@ -2484,7 +2837,7 @@ LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
 $whereW
 ";
 
-    my $all = $self->dbh_selectall_hashref($query, 'name') ;
+    my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
 
     $query = "
 SELECT Pool.Name AS name,
@@ -2510,7 +2863,8 @@ GROUP BY Pool.Name;
        $query = "
   SELECT VolStatus AS volstatus, count(MediaId) AS nb
     FROM Media 
-   WHERE PoolId=$p->{poolid} 
+   WHERE PoolId=$p->{poolid}
+     AND Media.MediaType = '$p->{mediatype}'
          $whereA
 GROUP BY VolStatus
 ";
@@ -2587,9 +2941,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}) {
@@ -2612,6 +2968,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();
@@ -2620,11 +2977,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
@@ -2681,7 +3039,8 @@ sub ach_get
        return undef;
     }
 
-    $a->{bweb} = $self;
+    $a->{bweb}  = $self;
+    $a->{debug} = $self->{debug};
 
     return $a;
 }
@@ -2837,28 +3196,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} " ;
     }    
 
@@ -2918,12 +3272,16 @@ sub get_job_log
 {
     my ($self) = @_;
 
-    my $arg = $self->get_form('jobid');
+    my $arg = $self->get_form('jobid', 'limit', 'offset');
     unless ($arg->{jobid}) {
        return $self->error("Can't get jobid");
     }
 
-    my $t = CGI::param('time') || '';
+    if ($arg->{limit} == 100) {
+        $arg->{limit} = 1000;
+    }
+
+    my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
 
     my $query = "
 SELECT Job.Name as name, Client.Name as clientname
@@ -2944,7 +3302,9 @@ SELECT Time AS time, LogText AS log
     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;
+ ORDER BY LogId
+ LIMIT $arg->{limit}
+ OFFSET $arg->{offset}
 ";
 
     my $log = $self->dbh_selectall_arrayref($query);
@@ -2964,6 +3324,8 @@ SELECT Time AS time, LogText AS log
                     jobid => $arg->{jobid},
                     name  => $row->{name},
                     client => $row->{clientname},
+                    offset => $arg->{offset},
+                    limit  => $arg->{limit},
                 }, 'display_log.tpl');
 }
 
@@ -2978,17 +3340,43 @@ 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 $slots_sql = '';
     my $t = 300 ;
     if ($arg->{slots}) {
        $slots = join(",", @{ $arg->{slots} });
+       $slots_sql = " AND Slot IN ($slots) ";
        $t += 60*scalar( @{ $arg->{slots} }) ;
     }
 
+    $self->dbh_do("
+  UPDATE Media 
+       SET LocationId =   (SELECT LocationId 
+                             FROM Location 
+                            WHERE Location = '$arg->{ach}'),
+
+           RecyclePoolId = (SELECT PoolId 
+                             FROM Pool
+                            WHERE Name = 'Scratch')
+
+     WHERE (LocationId = 0 OR LocationId IS NULL)
+       $slots_sql
+");
+
     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) ;
@@ -3159,8 +3547,12 @@ sub run_job_mod
     
     my $job = CGI::param('job') || '';
 
+    # we take informations from director, and we overwrite with user wish
     my $info = $b->send_cmd("show job=\"$job\"");
     my $attr = $self->run_parse_job($info);
+
+    my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
+    my %job_opt = (%$attr, %$arg);
     
     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
 
@@ -3175,7 +3567,7 @@ sub run_job_mod
        clients  => $clients,
        filesets => $filesets,
        storages => $storages,
-       %$attr,
+       %job_opt,
     }, "run_job_mod.tpl");
 }
 
@@ -3198,7 +3590,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', 'fileset');
     my $job = CGI::param('job') || '';
     my $storage = CGI::param('storage') || '';
 
@@ -3208,6 +3600,8 @@ sub run_job_now
                        level => $arg->{level},
                        storage => $storage,
                        pool => $arg->{pool},
+                       fileset => $arg->{fileset},
+                       when => $arg->{when},
                        );
 
     print $jobid, $b->{error};