]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/lib/Bweb.pm
ebl fix volstatus in display_media
[bacula/bacula] / gui / bweb / lib / Bweb.pm
index f9a95905e64374627ecb917173819de7afa05e0d..c3d7ee65f14d28db7a8349dbd25d136b70283565 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
 
@@ -219,16 +231,58 @@ sub load
 {
     my ($self) = @_ ;
 
+    unless (open(FP, $self->{config_file}))
+    {
+       return $self->error("$self->{config_file} : $!");
+    }
+    my $f=''; my $tmpbuffer;
+    while(read FP,$tmpbuffer,4096)
+    {
+       $f .= $tmpbuffer;
+    }
+    close(FP);
+
+    my $VAR1;
+
+    no strict; # I have no idea of the contents of the file
+    eval "$f" ;
+    use strict;
+
+    if ($f and $@) {
+       $self->load_old();
+       $self->save();
+       return $self->error("If you update from an old bweb install, your must reload this page and if it's fail again, you have to configure bweb again...") ;
+    }
+
+    foreach my $k (keys %$VAR1) {
+       $self->{$k} = $VAR1->{$k};
+    }
+
+    return 1;
+}
+
+=head1 FUNCTION
+
+    load_old - load old configuration format
+
+=cut
+
+sub load_old
+{
+    my ($self) = @_ ;
+
     unless (open(FP, $self->{config_file}))
     {
        return $self->error("$self->{config_file} : $!");
     }
 
-    while (my $line = <FP>) 
+    while (my $line = <FP>)
     {
        chomp($line);
        my ($k, $v) = split(/\s*=\s*/, $line, 2);
-       $self->{$k} = $v;
+       if ($k_re{$k}) {
+           $self->{$k} = $v;
+       }
     }
 
     close(FP);
@@ -245,17 +299,22 @@ sub save
 {
     my ($self) = @_ ;
 
-    unless (open(FP, ">$self->{config_file}"))
-    {
-       return $self->error("$self->{config_file} : $!");
+    if ($self->{ach_list}) {
+       # shortcut for display_begin
+       $self->{achs} = [ map {{ name => $_ }} 
+                         keys %{$self->{ach_list}}
+                       ];
     }
-    
-    foreach my $k (keys %$self)
+
+    unless (open(FP, ">$self->{config_file}"))
     {
-       next unless (exists $k_re{$k}) ;
-       print FP "$k = $self->{$k}\n";
+       return $self->error("$self->{config_file} : $!\n" .
+                           "You must add this to your config file\n" 
+                           . Data::Dumper::Dumper($self));
     }
 
+    print FP Data::Dumper::Dumper($self);
+    
     close(FP);       
     return 1;
 }
@@ -276,8 +335,7 @@ sub edit
 sub view
 {
     my ($self) = @_ ;
-
-    $self->display($self, "config_view.tpl");    
+    $self->display($self, "config_view.tpl");
 }
 
 sub modify
@@ -298,7 +356,7 @@ sub modify
        }
     }
 
-    $self->display($self, "config_view.tpl");
+    $self->view();
 
     if ($self->{error}) {      # an error as occured
        $self->display($self, 'error.tpl');
@@ -466,21 +524,6 @@ use base q/Bweb::Gui/;
 
 =cut
 
-# TODO : get autochanger definition from config/dump file
-my %ach_list ;
-
-sub get
-{
-    my ($name, $bweb) = @_;
-    my $a = new Bweb::Autochanger(debug => $bweb->{debug}, 
-                                 bweb => $bweb,
-                                 name => 'S1_L80',
-                                 precmd => 'sudo',
-                                 drive_name => ['S1_L80_SDLT0', 'S1_L80_SDLT1'],
-                                 );
-    return $a;
-}
-
 sub new
 {
     my ($class, %arg) = @_;
@@ -822,7 +865,7 @@ sub transfer
     
     if ($? == 0) {
        my $content = $self->get_slot($src);
-       print "content = $content<br/> $src => $dst<br/>";
+       print "$content ($src) => $dst<br/>";
        $self->{slot}->[$src] = 'empty';
        $self->set_slot($dst, $content);
        return 1;
@@ -832,6 +875,12 @@ sub transfer
     }
 }
 
+sub get_drive_name
+{
+    my ($self, $index) = @_;
+    return $self->{drive_name}->[$index];
+}
+
 # TODO : do a tapeinfo request to get informations
 sub tapeinfo
 {
@@ -915,8 +964,7 @@ WHERE Media.VolumeName IN ($media_list)
                }
                
                $all->{$vol}->{realslot} = $slot;
-               $all->{$vol}->{volbytes} = Bweb::human_size($all->{$vol}->{volbytes}) ;
-               
+
                push @{ $param }, $all->{$vol};
 
            } else {            # empty or no label
@@ -970,9 +1018,6 @@ use base q/Bweb::Gui/;
 use DBI;
 use POSIX qw/strftime/;
 
-our $bpath="/usr/local/bacula";
-our $bconsole="$bpath/sbin/bconsole -c $bpath/etc/bconsole.conf";
-
 our $cur_id=0;
 
 =head1 VARIABLE
@@ -982,21 +1027,35 @@ our $cur_id=0;
 =cut
 
 our %sql_func = ( 
-                 Pg => { 
-                     UNIX_TIMESTAMP => '',
-                     FROM_UNIXTIME => '',
-                     TO_SEC => " interval '1 second' * ",
-                     SEC_TO_INT => "SEC_TO_INT",
-                     SEC_TO_TIME => '',
-                 },
-                 mysql => {
-                     UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
-                     FROM_UNIXTIME => 'FROM_UNIXTIME',
-                     SEC_TO_INT => '',
-                     TO_SEC => '',
-                     SEC_TO_TIME => 'SEC_TO_TIME',
-                 },
-                );
+         Pg => { 
+             UNIX_TIMESTAMP => '',
+             FROM_UNIXTIME => '',
+             TO_SEC => " interval '1 second' * ",
+             SEC_TO_INT => "SEC_TO_INT",
+             SEC_TO_TIME => '',
+             MATCH => " ~ ",
+             STARTTIME_DAY  => " date_trunc('day', Job.StartTime) ",
+             STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
+             STARTTIME_MONTH  => " date_trunc('month', Job.StartTime) ",
+             STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
+             STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
+             STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
+         },
+         mysql => {
+             UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
+             FROM_UNIXTIME => 'FROM_UNIXTIME',
+             SEC_TO_INT => '',
+             TO_SEC => '',
+             SEC_TO_TIME => 'SEC_TO_TIME',
+             MATCH => " REGEXP ",
+             STARTTIME_DAY  => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
+             STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
+             STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
+             STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
+             STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
+             STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
+         },
+        );
 
 sub dbh_selectall_arrayref
 {
@@ -1127,6 +1186,10 @@ sub connect_db
            unless ($self->{dbh});
 
        $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
+
+       if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
+           $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
+       }
     }
 }
 
@@ -1136,7 +1199,7 @@ sub new
     my $self = bless { 
        dbh => undef,           # connect_db();
        info => {
-           dbi   => 'DBI:Pg:database=bacula;host=127.0.0.1',
+           dbi   => '', # DBI:Pg:database=bacula;host=127.0.0.1
            user  => 'bacula',
            password => 'test', 
        },
@@ -1170,27 +1233,31 @@ sub display_clients
 {
     my ($self) = @_;
 
+    my $where='';
+    my $arg = $self->get_form("client", "qre_client");
+
+    if ($arg->{qre_client}) {
+       $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
+    } elsif ($arg->{client}) {
+       $where = "WHERE Name = '$arg->{client}' ";
+    }
+
     my $query = "
 SELECT Name   AS name,
        Uname  AS uname,
        AutoPrune AS autoprune,
        FileRetention AS fileretention,
        JobRetention  AS jobretention
-
 FROM Client
+$where
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'name') ;
 
-    foreach (values %$all) {
-       $_->{fileretention} = human_sec($_->{fileretention});
-       $_->{jobretention} = human_sec($_->{jobretention});
-    }
-
-    my $arg = { ID => $cur_id++,
+    my $dsp = { ID => $cur_id++,
                clients => [ values %$all] };
 
-    $self->display($arg, "client_list.tpl") ;
+    $self->display($dsp, "client_list.tpl") ;
 }
 
 sub get_limit
@@ -1212,6 +1279,10 @@ sub get_limit
        $label = "last " . human_sec($arg{age});
     }
 
+    if ($arg{groupby}) {
+       $limit .= " GROUP BY $arg{groupby} ";
+    }
+
     if ($arg{order}) {
        $limit .= " ORDER BY $arg{order} ";
     }
@@ -1270,23 +1341,49 @@ sub get_form
                 height => 480,
                 jobid  =>   0,
                 slot   =>   0,
-                drive  =>   undef,
+                drive  =>   0,
                 priority => 10,
                 age    => 60*60*24*7,
                 days   => 1,
+                maxvoljobs  => 0,
+                maxvolbytes => 0,
+                maxvolfiles => 0,
                 );
 
+    my %opt_ss =(              # string with space
+                job     => 1,
+                storage => 1,
+                );
     my %opt_s = (              # default to ''
                 ach    => 1,
                 status => 1,
+                volstatus => 1,
+                 inchanger => 1,
                  client => 1,
                 level  => 1,
                 pool   => 1,
                 media  => 1,
                  ach    => 1,
                  jobtype=> 1,
+                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
+                voluseduration=> 1,
+                volretention => 1,
+               );
+
     foreach my $i (@what) {
        if (exists $opt_i{$i}) {# integer param
            my $value = CGI::param($i) || $opt_i{$i} ;
@@ -1298,8 +1395,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) ;
            }
@@ -1312,7 +1414,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_d{$i}) {
+           my $value = CGI::param($i) || '';
+           if ($value =~ /^\s*(\d+\s+\w+)$/) {
+               $ret{$i} = $1;
+           }
        }
     }
 
@@ -1324,6 +1436,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
@@ -1374,7 +1493,30 @@ FROM FileSet
 
        $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) } 
                               values %$filesets] ;
+    }
+
+    if ($what{db_jobnames}) {
+       my $query = "
+SELECT DISTINCT Job.Name AS jobname 
+FROM Job
+";
+
+       my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
+
+       $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) } 
+                              values %$jobnames] ;
+    }
+
+    if ($what{db_devices}) {
+       my $query = "
+SELECT Device.Name AS name
+FROM Device
+";
+
+       my $devices = $self->dbh_selectall_hashref($query, 'name');
 
+       $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) } 
+                              values %$devices] ;
     }
 
     return \%ret;
@@ -1385,8 +1527,9 @@ sub display_graph
     my ($self) = @_;
 
     my $fields = $self->get_form(qw/age level status clients filesets 
-                                  db_clients limit db_filesets width height
-                                  qclients qfilesets/);
+                                    graph gtype type
+                                   db_clients limit db_filesets width height
+                                   qclients qfilesets qjobnames db_jobnames/);
                                
 
     my $url = CGI::url(-full => 0,
@@ -1394,16 +1537,6 @@ sub display_graph
                       -query => 1);
     $url =~ s/^.+?\?//;        # http://path/to/bweb.pl?arg => arg
 
-    my $type = CGI::param('graph') || '';
-    if ($type =~ /^(\w+)$/) {
-       $fields->{graph} = $1;
-    }
-
-    my $gtype = CGI::param('gtype') || '';
-    if ($gtype =~ /^(\w+)$/) {
-       $fields->{gtype} = $1;
-    } 
-
 # this organisation is to keep user choice between 2 click
 # TODO : fileset and client selection doesn't work
 
@@ -1443,10 +1576,6 @@ SELECT DISTINCT Job.JobId       AS jobid,
 
     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
 
-    foreach (values %$all) {
-       $_->{jobbytes} = human_size($_->{jobbytes}) ;
-    }
-
     $self->display({ clientname => $arg{clientname},
                     Filter => $label,
                     ID => $cur_id++,
@@ -1615,16 +1744,16 @@ sub display_general
     my ($limit, $label) = $self->get_limit(%arg);
 
     my $query = "
-SELECT 
-    (SELECT count(Pool.PoolId)   FROM Pool)   AS nb_pool, 
-    (SELECT count(Media.MediaId) FROM Media)  AS nb_media, 
+SELECT
+    (SELECT count(Pool.PoolId)   FROM Pool)   AS nb_pool,
+    (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,
-    (SELECT count(Job.JobId)     
+    (SELECT count(Job.JobId)
       FROM Job
       WHERE Job.JobStatus IN ('E','e','f','A')
       $limit
-    )                                        AS nb_err,
+    )                                         AS nb_err,
     (SELECT count(Client.ClientId) FROM Client) AS nb_client
 ";
 
@@ -1647,7 +1776,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);
@@ -1656,7 +1785,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);
@@ -1665,7 +1794,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);
@@ -1701,12 +1830,24 @@ sub get_param
        my $status = CGI::param('status') || '';
        if ($status =~ /^(\w)$/) {
            $ret{status} = $1;
-           $limit .= "AND Job.JobStatus = '$1' ";
+           if ($1 eq 'f') {
+               $limit .= "AND Job.JobStatus IN ('f','E') ";            
+           } 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);
@@ -1715,7 +1856,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);
@@ -1756,25 +1897,6 @@ sub get_param
 
     get last backup
 
-SELECT DISTINCT Job.JobId       AS jobid,
-                Client.Name     AS client,
-                FileSet.FileSet AS fileset,
-               Job.Name        AS jobname,
-                Level           AS level,
-                StartTime       AS starttime,
-                JobFiles        AS jobfiles, 
-                JobBytes        AS jobbytes,
-                VolumeName      AS volumename,
-               JobStatus       AS jobstatus,
-                JobErrors      AS joberrors
-
- FROM Client,Job,JobMedia,Media,FileSet
- WHERE Client.ClientId=Job.ClientId
-   AND Job.FileSetId=FileSet.FileSetId
-   AND JobMedia.JobId=Job.JobId 
-   AND JobMedia.MediaId=Media.MediaId
- $limit
-
 =cut 
 
 sub display_job
@@ -1788,6 +1910,7 @@ sub display_job
                                          'level',
                                          'filesets',
                                          'jobtype',
+                                         'pools',
                                          'jobid',
                                          'status');
 
@@ -1812,16 +1935,13 @@ SELECT  Job.JobId       AS jobid,
       Job LEFT JOIN Pool     ON (Job.PoolId    = Pool.PoolId)
           LEFT JOIN FileSet  ON (Job.FileSetId = FileSet.FileSetId)
  WHERE Client.ClientId=Job.ClientId
+   AND Job.JobStatus != 'R'
  $where
  $limit
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
 
-    foreach (values %$all) {
-       $_->{jobbytes} = human_size($_->{jobbytes}) ;
-    }
-
     $self->display({ Filter => $label,
                     ID => $cur_id++,
                     Jobs => 
@@ -1851,6 +1971,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
 
@@ -1863,8 +1984,6 @@ SELECT DISTINCT Job.JobId       AS jobid,
 
     my $row = $self->dbh_selectrow_hashref($query) ;
 
-    $row->{jobbytes} = human_size($row->{jobbytes}) ;
-
     # display all volumes associate with this job
     $query="
 SELECT Media.VolumeName as volumename
@@ -1885,37 +2004,48 @@ 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');
+    my $arg = $self->get_form('jmedias', 'qre_media');
 
     if ($arg->{jmedias}) {
        $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where"; 
     }
+    if ($arg->{qre_media}) {
+       $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where"; 
+    }
 
     my $query="
-SELECT Media.VolumeName AS volumename, 
-       Media.VolBytes   AS volbytes,
-       Media.VolStatus  AS volstatus,
-       Media.MediaType  AS mediatype,
-       Media.InChanger  AS online,
+SELECT Media.VolumeName  AS volumename, 
+       Media.VolBytes    AS volbytes,
+       Media.VolStatus   AS volstatus,
+       Media.MediaType   AS mediatype,
+       Media.InChanger   AS online,
        Media.LastWritten AS lastwritten,
        Location.Location AS location,
+       (volbytes*100/COALESCE(media_avg_size.size,-1))  AS volusage,
        Pool.Name         AS poolname,
        $self->{sql}->{FROM_UNIXTIME}(
           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
         + $self->{sql}->{TO_SEC}(Media.VolRetention)
        ) AS expire
-FROM Pool, Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
+FROM      Pool, Media 
+LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
+LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
+                  Media.MediaType     AS MediaType
+           FROM Media 
+          WHERE Media.VolStatus = 'Full' 
+          GROUP BY Media.MediaType
+           ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
+
 WHERE Media.PoolId=Pool.PoolId
 $where
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
-    foreach (values %$all) {
-       $_->{volbytes} = human_size($_->{volbytes}) ;
-    }
 
     $self->display({ ID => $cur_id++,
                     Pool => $elt{pool},
@@ -1963,11 +2093,15 @@ SELECT InChanger     AS online,
        Media.Recycle AS recycle,
        Media.VolRetention AS volretention,
        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)
        ) AS expire
- FROM Job,Pool,
+ FROM Pool,
       Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
  WHERE Pool.PoolId = Media.PoolId
  AND VolumeName IN ($medias->{jmedias})
@@ -1976,9 +2110,6 @@ SELECT InChanger     AS online,
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
 
     foreach my $media (values %$all) {
-       $media->{nb_bytes} = human_size($media->{nb_bytes}) ;
-       $media->{voluseduration} = human_sec($media->{voluseduration});
-       $media->{volretention} = human_sec($media->{volretention});
        my $mq = $self->dbh_quote($media->{volumename});
 
        $query = "
@@ -1998,11 +2129,23 @@ SELECT DISTINCT Job.JobId AS jobid,
 
        my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
 
-       foreach (values %$jobs) {
-           $_->{bytes} = human_size($_->{bytes}) ;
+       $query = "
+SELECT LocationLog.Date    AS date,
+       Location.Location   AS location,
+       LocationLog.Comment AS comment
+ FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
+ WHERE Media.MediaId = LocationLog.MediaId
+   AND Media.VolumeName = $mq
+";
+
+       my $logtxt = '';
+       my $log = $self->dbh_selectall_arrayref($query) ;
+       if ($log) {
+           $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
        }
 
        $self->display({ jobs => [ values %$jobs ],
+                        LocationLog => $logtxt,
                         %$media },
                       "display_media_zoom.tpl");
     }
@@ -2062,6 +2205,37 @@ WHERE Location.Location = $arg->{qlocation}
     $self->display_location();
 }
 
+sub location_del
+{
+    my ($self) = @_ ;
+    my $arg = $self->get_form(qw/qlocation/) ;
+
+    unless ($arg->{qlocation}) {
+       return $self->error("Can't get location");
+    }
+
+    my $query = "
+SELECT count(Media.MediaId) AS nb 
+  FROM Media INNER JOIN Location USING (LocationID)
+WHERE Location = $arg->{qlocation}
+";
+
+    my $res = $self->dbh_selectrow_hashref($query);
+
+    if ($res->{nb}) {
+       return $self->error("Sorry, the location must be empty");
+    }
+
+    $query = "
+DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
+";
+
+    $self->dbh_do($query);
+
+    $self->display_location();
+}
+
+
 sub location_add
 {
     my ($self) = @_ ;
@@ -2128,79 +2302,22 @@ sub update_location
                   "update_location.tpl");
 }
 
-sub do_update_media
+sub get_media_max_size
 {
-    my ($self) = @_ ;
-
-    my $media = CGI::param('media');
-    unless ($media) {
-       return $self->error("Can't find media selection");
-    }
-
-    $media = $self->dbh_quote($media);
-
-    my $update = '';
-
-    my $volstatus = CGI::param('volstatus') || ''; 
-    $volstatus = $self->dbh_quote($volstatus); # is checked by db
-    $update .= " VolStatus=$volstatus, ";
+    my ($self, $type) = @_;
+    my $query = 
+"SELECT avg(VolBytes) AS size
+  FROM Media 
+ WHERE Media.VolStatus = 'Full' 
+   AND Media.MediaType = '$type'
+";
     
-    my $inchanger = CGI::param('inchanger') || '';
-    if ($inchanger) {
-       $update .= " InChanger=1, " ;
-       my $slot = CGI::param('slot') || '';
-       if ($slot =~ /^(\d+)$/) {
-           $update .= " Slot=$1, ";
-       } else {
-           $update .= " Slot=0, ";
-       }
-    } else {
-       $update = " Slot=0, InChanger=0, ";
-    }
+    my $res = $self->selectrow_hashref($query);
 
-    my $pool = CGI::param('pool') || '';
-    $pool = $self->dbh_quote($pool); # is checked by db
-    $update .= " PoolId=(SELECT PoolId FROM Pool WHERE Name=$pool), ";
-
-    my $volretention = CGI::param('volretention') || '';
-    $volretention = from_human_sec($volretention);
-    unless ($volretention) {
-       return $self->error("Can't get volume retention");
-    }
-
-    $update .= " VolRetention = $volretention, ";
-
-    my $loc = CGI::param('location') || '';
-    $loc = $self->dbh_quote($loc); # is checked by db
-    $update .= " LocationId=(SELECT LocationId FROM Location WHERE Location=$loc), ";
-
-    my $usedu = CGI::param('voluseduration') || '0';
-    $usedu = from_human_sec($usedu);
-    $update .= " VolUseDuration=$usedu, ";
-
-    my $maxj = CGI::param('maxvoljobs') || '0';
-    unless ($maxj =~ /^(\d+)$/) {
-       return $self->error("Can't get max jobs");
-    }
-    $update .= " MaxVolJobs=$1, " ;
-
-    my $maxf = CGI::param('maxvolfiles') || '0';
-    unless ($maxj =~ /^(\d+)$/) {
-       return $self->error("Can't get max files");
-    }
-    $update .= " MaxVolFiles=$1, " ;
-   
-    my $maxb = CGI::param('maxvolbytes') || '0';
-    unless ($maxb =~ /^(\d+)$/) {
-       return $self->error("Can't get max bytes");
-    }
-    $update .= " MaxVolBytes=$1 " ;
-    
-    my $row=$self->dbh_do("UPDATE Media SET $update WHERE VolumeName=$media");
-    
-    if ($row) {
-       print "Update Ok\n";
-       $self->update_media();
+    if ($res) {
+       return $res->{size};
+    } else {
+       return 0;
     }
 }
 
@@ -2216,7 +2333,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,
@@ -2225,9 +2342,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}
@@ -2242,8 +2362,7 @@ WHERE Media.VolumeName = $media->{qmedia}
     $self->display({
        %$elt,
         %$row,
-    },
-                  "update_media.tpl");
+    }, "update_media.tpl");
 }
 
 sub save_location
@@ -2270,7 +2389,9 @@ sub save_location
 
     my $nb = $self->dbh_do($query);
 
-    print "$nb media updated";
+    print "$nb media updated, you may have to update your autochanger.";
+
+    $self->display_media();
 }
 
 sub change_location
@@ -2298,7 +2419,7 @@ INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
        (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
       )
 ";
-       
+       $self->dbh_do($query);
        $self->debug($query);
     }
 
@@ -2341,7 +2462,6 @@ GROUP BY Client.Name
 
     $row->{ID} = $cur_id++;
     $row->{label} = $label;
-    $row->{nb_bytes}    = human_size($row->{nb_bytes}) ;
 
     $self->display($row, "display_client_stats.tpl");
 }
@@ -2350,38 +2470,79 @@ 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
 
     my $query = "
-SELECT Pool.Name     AS name, 
-       Pool.Recycle  AS recycle,
-       Pool.VolRetention AS volretention,
+SELECT subq.volmax        AS volmax,
+       subq.volnum        AS volnum,
+       subq.voltotal      AS voltotal,
+       Pool.Name          AS name,
+       Pool.Recycle       AS recycle,
+       Pool.VolRetention  AS volretention,
        Pool.VolUseDuration AS voluseduration,
-       Pool.MaxVolJobs AS maxvoljobs,
-       Pool.MaxVolFiles AS maxvolfiles,
-       Pool.MaxVolBytes AS maxvolbytes,
-       Pool.PoolId      AS poolid,
-      (SELECT count(Media.MediaId) 
-         FROM Media 
-        WHERE Media.PoolId = Pool.PoolId
-      ) AS volnum
- FROM Pool
-";     
+       Pool.MaxVolJobs    AS maxvoljobs,
+       Pool.MaxVolFiles   AS maxvolfiles,
+       Pool.MaxVolBytes   AS maxvolbytes,
+       subq.PoolId        AS PoolId
+FROM
+  (
+    SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
+           count(Media.MediaId)  AS volnum,
+           sum(Media.VolBytes)   AS voltotal,
+           Media.PoolId          AS PoolId,
+           Media.MediaType       AS MediaType
+    FROM Media
+    LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
+                      Media.MediaType     AS MediaType
+               FROM Media 
+              WHERE Media.VolStatus = 'Full' 
+              GROUP BY Media.MediaType
+               ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
+    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) {
-       $p->{maxvolbytes}    = human_size($p->{maxvolbytes}) ;
-       $p->{volretention}   = human_sec($p->{volretention}) ;
-       $p->{voluseduration} = human_sec($p->{voluseduration}) ;
+       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;
+       }
 
        $query = "
-  SELECT VolStatus AS volstatus, count(MediaId) AS nb 
+  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');
        foreach my $t (values %$content) {
            $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
@@ -2390,6 +2551,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");
 }
@@ -2457,12 +2619,12 @@ WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
 sub eject_media
 {
     my ($self) = @_;
-    my $arg = $self->get_form('jmedias', 'slots', 'ach');
+    my $arg = $self->get_form('jmedias');
 
     unless ($arg->{jmedias}) {
        return $self->error("Can't get media selection");
     }
-    
+
     my $query = "
 SELECT Media.VolumeName  AS volumename,
        Storage.Name      AS storage,
@@ -2476,10 +2638,15 @@ WHERE Media.VolumeName IN ($arg->{jmedias})
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename');
 
-    my $a = Bweb::Autochanger::get('S1_L80', $self);
-
-    $a->status();
     foreach my $vol (values %$all) {
+       my $a = $self->ach_get($vol->{location});
+       next unless ($a) ;
+
+       unless ($a->{have_status}) {
+           $a->status();
+           $a->{have_status} = 1;
+       }
+
        print "eject $vol->{volumename} from $vol->{storage} : ";
        if ($a->send_to_io($vol->{slot})) {
            print "ok</br>";
@@ -2489,6 +2656,23 @@ WHERE Media.VolumeName IN ($arg->{jmedias})
     }
 }
 
+sub move_email
+{
+    my ($self) = @_;
+
+    my ($to, $subject, $content) = (CGI::param('email'),
+                                   CGI::param('subject'),
+                                   CGI::param('content'));
+    $to =~ s/[^\w\d\.\@<>,]//;
+    $subject =~ s/[^\w\d\.\[\]]/ /;    
+
+    open(MAIL, "|mail -s '$subject' '$to'") ;
+    print MAIL $content;
+    close(MAIL);
+
+    print "Mail sent";
+}
+
 sub restore
 {
     my ($self) = @_;
@@ -2498,6 +2682,7 @@ sub restore
     print CGI::header('text/brestore');
     print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
     print "client=$arg->{client}\n" if ($arg->{client});
+    print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
     print "\n";
 }
 
@@ -2505,34 +2690,257 @@ sub restore
 # TODO : make this internal to not eject tape ?
 use Bconsole;
 
+
+sub ach_get
+{
+    my ($self, $name) = @_;
+    
+    unless ($name) {
+       return $self->error("Can't get your autochanger name ach");
+    }
+
+    unless ($self->{info}->{ach_list}) {
+       return $self->error("Could not find any autochanger");
+    }
+    
+    my $a = $self->{info}->{ach_list}->{$name};
+
+    unless ($a) {
+       $self->error("Can't get your autochanger $name from your ach_list");
+       return undef;
+    }
+
+    $a->{bweb} = $self;
+
+    return $a;
+}
+
+sub ach_register
+{
+    my ($self, $ach) = @_;
+
+    $self->{info}->{ach_list}->{$ach->{name}} = $ach;
+
+    $self->{info}->save();
+    
+    return 1;
+}
+
+sub ach_edit
+{
+    my ($self) = @_;
+    my $arg = $self->get_form('ach');
+    if (!$arg->{ach} 
+       or !$self->{info}->{ach_list} 
+       or !$self->{info}->{ach_list}->{$arg->{ach}}) 
+    {
+       return $self->error("Can't get autochanger name");
+    }
+
+    my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
+
+    my $i=0;
+    $ach->{drives} = 
+       [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
+
+    my $b = $self->get_bconsole();
+
+    my @storages = $b->list_storage() ;
+
+    $ach->{devices} = [ map { { name => $_ } } @storages ];
+    
+    $self->display($ach, "ach_add.tpl");
+    delete $ach->{drives};
+    delete $ach->{devices};
+    return 1;
+}
+
+sub ach_del
+{
+    my ($self) = @_;
+    my $arg = $self->get_form('ach');
+
+    if (!$arg->{ach} 
+       or !$self->{info}->{ach_list} 
+       or !$self->{info}->{ach_list}->{$arg->{ach}}) 
+    {
+       return $self->error("Can't get autochanger name");
+    }
+   
+    delete $self->{info}->{ach_list}->{$arg->{ach}} ;
+   
+    $self->{info}->save();
+    $self->{info}->view();
+}
+
+sub ach_add
+{
+    my ($self) = @_;
+    my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
+
+    my $b = $self->get_bconsole();
+    my @storages = $b->list_storage() ;
+
+    unless ($arg->{ach}) {
+       $arg->{devices} = [ map { { name => $_ } } @storages ];
+       return $self->display($arg, "ach_add.tpl");
+    }
+
+    my @drives ;
+    foreach my $drive (CGI::param('drives'))
+    {
+       unless (grep(/^$drive$/,@storages)) {
+           return $self->error("Can't find $drive in storage list");
+       }
+
+       my $index = CGI::param("index_$drive");
+       unless (defined $index and $index =~ /^(\d+)$/) {
+           return $self->error("Can't get $drive index");
+       }
+
+       $drives[$index] = $drive;
+    }
+
+    unless (@drives) {
+       return $self->error("Can't get drives from Autochanger");
+    }
+
+    my $a = new Bweb::Autochanger(name   => $arg->{ach},
+                                 precmd => $arg->{precmd},
+                                 drive_name => \@drives,
+                                 device => $arg->{device},
+                                 mtxcmd => $arg->{mtxcmd});
+
+    $self->ach_register($a) ;
+    
+    $self->{info}->view();
+}
+
 sub delete
 {
     my ($self) = @_;
     my $arg = $self->get_form('jobid');
 
-    my $b = new Bconsole(pref => $self->{info});
-
     if ($arg->{jobid}) {
+       my $b = $self->get_bconsole();
        my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
+
        $self->display({
-           content => $b->send_cmd("delete jobid=\"$arg->{jobid}\""),
+           content => $ret,
            title => "Delete a job ",
            name => "delete jobid=$arg->{jobid}",
        }, "command.tpl");      
     }
 }
 
+sub do_update_media
+{
+    my ($self) = @_ ;
+
+    my $arg = $self->get_form(qw/media volstatus inchanger pool
+                                slot volretention voluseduration 
+                                maxvoljobs maxvolfiles maxvolbytes
+                                qcomment poolrecycle
+                             /);
+
+    unless ($arg->{media}) {
+       return $self->error("Can't find media selection");
+    }
+
+    my $update = "update volume=$arg->{media} ";
+
+    if ($arg->{volstatus}) {
+       $update .= " volstatus=$arg->{volstatus} ";
+    }
+    
+    if ($arg->{inchanger}) {
+       $update .= " inchanger=yes " ;
+       if ($arg->{slot}) {
+           $update .= " slot=$arg->{slot} ";
+       }
+    } else {
+       $update .= " slot=0 inchanger=no ";
+    }
+
+    if ($arg->{pool}) {
+       $update .= " pool=$arg->{pool} " ;
+    }
+
+    $arg->{volretention} ||= 0 ; 
+    if ($arg->{volretention}) {
+       $update .= " volretention=\"$arg->{volretention}\" " ;
+    }
+
+    $arg->{voluseduration} ||= 0 ; 
+    if ($arg->{voluseduration}) {
+       $update .= " voluse=\"$arg->{voluseduration}\" " ;
+    }
+
+    $arg->{maxvoljobs} ||= 0;
+    if ($arg->{maxvoljobs}) {
+       $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
+    }
+    
+    $arg->{maxvolfiles} ||= 0;
+    if ($arg->{maxvolfiles}) {
+       $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
+    }    
+
+    $arg->{maxvolbytes} ||= 0;
+    if ($arg->{maxvolbytes}) {
+       $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
+    }    
+
+    my $b = $self->get_bconsole();
+
+    $self->display({
+       content => $b->send_cmd($update),
+       title => "Update a volume ",
+       name => $update,
+    }, "command.tpl"); 
+
+
+    my @q;
+    my $media = $self->dbh_quote($arg->{media});
+
+    my $loc = CGI::param('location') || '';
+    if ($loc) {
+       $loc = $self->dbh_quote($loc); # is checked by db
+       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 " . join (',', @q) . "
+ WHERE Media.VolumeName = $media
+";
+    $self->dbh_do($query);
+
+    $self->update_media();
+}
+
 sub update_slots
 {
     my ($self) = @_;
 
     my $ach = CGI::param('ach') ;
-    unless ($ach =~ /^([\w\d\.-]+)$/) {
+    $ach = $self->ach_get($ach);
+    unless ($ach) {
        return $self->error("Bad autochanger name");
     }
 
-    my $b = new Bconsole(pref => $self->{info});
-    print "<pre>" . $b->update_slots($ach) . "</pre>";
+    print "<pre>";
+    my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
+    $b->update_slots($ach->{name});
+    print "</pre>\n" 
 }
 
 sub get_job_log
@@ -2557,14 +2965,17 @@ SELECT Job.Name as name, Client.Name as clientname
     unless ($row) {
        return $self->error("Can't find $arg->{jobid} in catalog");
     }
-    
 
     $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}");
@@ -2597,11 +3008,12 @@ sub label_barcodes
     }
 
     my $slots = '';
+    my $t = 300 ;
     if ($arg->{slots}) {
        $slots = join(",", @{ $arg->{slots} });
+       $t += 60*scalar( @{ $arg->{slots} }) ;
     }
 
-    my $t = 60*scalar( @{ $arg->{slots} });
     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>" ;
@@ -2609,6 +3021,7 @@ sub label_barcodes
                       drive => $arg->{drive},
                       pool  => 'Scratch',
                       slots => $slots) ;
+    $b->close();
     print "</pre>";
 }
 
@@ -2618,6 +3031,10 @@ sub purge
 
     my @volume = CGI::param('media');
 
+    unless (@volume) {
+       return $self->error("Can't get media selection");
+    }
+
     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
 
     $self->display({
@@ -2625,20 +3042,27 @@ sub purge
        title => "Purge media",
        name => "purge volume=" . join(' volume=', @volume),
     }, "command.tpl"); 
+    $b->close();
 }
 
 sub prune
 {
     my ($self) = @_;
 
+    my @volume = CGI::param('media');
+    unless (@volume) {
+       return $self->error("Can't get media selection");
+    }
+
     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
 
-    my @volume = CGI::param('media');
     $self->display({
        content => $b->prune_volume(@volume),
        title => "Prune media",
        name => "prune volume=" . join(' volume=', @volume),
     }, "command.tpl"); 
+
+    $b->close();
 }
 
 sub cancel_job
@@ -2647,10 +3071,10 @@ sub cancel_job
 
     my $arg = $self->get_form('jobid');
     unless ($arg->{jobid}) {
-       return $self->error('Bad jobid');
+       return $self->error("Can't get jobid");
     }
 
-    my $b = new Bconsole(pref => $self->{info});
+    my $b = $self->get_bconsole();
     $self->display({
        content => $b->cancel($arg->{jobid}),
        title => "Cancel job",
@@ -2658,14 +3082,31 @@ sub cancel_job
     }, "command.tpl"); 
 }
 
+sub fileset_view
+{
+    # Warning, we display current fileset
+    my ($self) = @_;
+
+    my $arg = $self->get_form('fileset');
+
+    if ($arg->{fileset}) {
+       my $b = $self->get_bconsole();
+       my $ret = $b->get_fileset($arg->{fileset});
+       $self->display({ fileset => $arg->{fileset},
+                        %$ret,
+                    }, "fileset_view.tpl");
+    } else {
+       $self->error("Can't get fileset name");
+    }
+}
+
 sub director_show_sched
 {
     my ($self) = @_ ;
 
     my $arg = $self->get_form('days');
 
-    my $b = new Bconsole(pref => $self->{info}) ;
-    
+    my $b = $self->get_bconsole();
     my $ret = $b->director_get_sched( $arg->{days} );
 
     $self->display({
@@ -2683,7 +3124,7 @@ sub enable_disable_job
        return $self->error("Can't find job name");
     }
 
-    my $b = new Bconsole(pref => $self->{info}) ;
+    my $b = $self->get_bconsole();
 
     my $cmd;
     if ($what) {
@@ -2699,12 +3140,18 @@ sub enable_disable_job
     }, "command.tpl"); 
 }
 
+sub get_bconsole
+{
+    my ($self) = @_;
+    return new Bconsole(pref => $self->{info});
+}
+
 sub run_job_select
 {
     my ($self) = @_;
-    $b = new Bconsole(pref => $self->{info});
+    my $b = $self->get_bconsole();
 
-    my $joblist = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".job")) ];
+    my $joblist = [ map { { name => $_ } } $b->list_job() ];
 
     $self->display({ Jobs => $joblist }, "run_job.tpl");
 }
@@ -2737,19 +3184,19 @@ sub run_parse_job
 sub run_job_mod
 {
     my ($self) = @_;
-    $b = new Bconsole(pref => $self->{info});
+    my $b = $self->get_bconsole();
     
     my $job = CGI::param('job') || '';
 
     my $info = $b->send_cmd("show job=\"$job\"");
     my $attr = $self->run_parse_job($info);
     
-    my $jobs   = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
+    my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
 
-    my $pools  = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".pool")) ];
-    my $clients = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".client")) ];
-    my $filesets= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".fileset")) ];
-    my $storages= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".storage")) ];
+    my $pools  = [ map { { name => $_ } } $b->list_pool() ];
+    my $clients = [ map { { name => $_ } }$b->list_client()];
+    my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
+    my $storages= [ map { { name => $_ } }$b->list_storage()];
 
     $self->display({
        jobs     => $jobs,
@@ -2764,9 +3211,9 @@ sub run_job_mod
 sub run_job
 {
     my ($self) = @_;
-    $b = new Bconsole(pref => $self->{info});
+    my $b = $self->get_bconsole();
     
-    my $jobs   = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
+    my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
 
     $self->display({
        jobs     => $jobs,
@@ -2776,11 +3223,11 @@ sub run_job
 sub run_job_now
 {
     my ($self) = @_;
-    $b = new Bconsole(pref => $self->{info});
+    my $b = $self->get_bconsole();
     
     # 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') || '';
 
@@ -2790,6 +3237,7 @@ sub run_job_now
                        level => $arg->{level},
                        storage => $storage,
                        pool => $arg->{pool},
+                       when => $arg->{when},
                        );
 
     print $jobid, $b->{error};