]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/lib/Bweb.pm
ebl Add role checks for media
[bacula/bacula] / gui / bweb / lib / Bweb.pm
index 8676a3f8a542ae39ed999eb2fcf9b7773a236aa8..24c2b60a88cc2ffbb5a9c90878c3faa7b332ee9d 100644 (file)
@@ -4,9 +4,9 @@ use strict;
 =head1 LICENSE
 
    Bweb - A Bacula web interface
-   Bacula® - The Network Backup Solution
+   Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
 
    The main author of Bweb is Eric Bollengier.
    The main author of Bacula is Kern Sibbald, with contributions from
@@ -27,7 +27,7 @@ use strict;
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   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.
@@ -134,7 +134,7 @@ sub error
 
 =head2 EXAMPLE
 
-    $ref = { name => 'me', age => 26 };
+    $ref = { name => 'me', age => 26 };
     $self->display($ref, "people.tpl");
 
 =cut
@@ -217,6 +217,8 @@ our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
              log_dir     => qr!^(.+)?$!,
              stat_job_table => qr!^(\w*)$!,
              display_log_time => qr!^(on)?$!,
+             enable_security => qr/^(on)?$/,
+             enable_security_acl => qr/^(on)?$/,
              );
 
 =head1 FUNCTION
@@ -345,7 +347,11 @@ sub modify
     my ($self) = @_;
     
     $self->{error} = '';
+    # we need to reset checkbox first
     $self->{debug} = 0;
+    $self->{display_log_time} = 0;
+    $self->{enable_security} = 0;
+    $self->{enable_security_acl} = 0;
 
     foreach my $k (CGI::param())
     {
@@ -1042,13 +1048,17 @@ our %sql_func = (
              TO_SEC => " interval '1 second' * ",
              SEC_TO_INT => "SEC_TO_INT",
              SEC_TO_TIME => '',
-             MATCH => " ~ ",
+             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) ",
+             STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
+             DB_SIZE => " SELECT pg_database_size(current_database()) ",
+             CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
+             CONCAT_SEP => "",
          },
          mysql => {
              UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
@@ -1063,9 +1073,31 @@ our %sql_func = (
              STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
              STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
              STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
+             STARTTIME_PWEEK => " DATE_FORMAT(StartTime, '%v') ",
+             # 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) ",
+             CONCAT_SEP => " SEPARATOR '' ",
          },
         );
 
+sub dbh_is_mysql
+{
+    my ($self) = @_;
+    return $self->{info}->{dbi} =~ /dbi:mysql/i;
+}
+
+sub dbh_disconnect
+{
+    my ($self) = @_;
+    if ($self->{dbh}) {
+       $self->{dbh}->disconnect();
+       undef $self->{dbh};
+    }
+}
+
 sub dbh_selectall_arrayref
 {
     my ($self, $query) = @_;
@@ -1118,10 +1150,27 @@ sub dbh_selectrow_hashref
     return $self->{dbh}->selectrow_hashref($query) ;
 }
 
+sub dbh_strcat
+{
+    my ($self, @what) = @_;
+    if ($self->dbh_is_mysql()) {
+       return 'CONCAT(' . join(',', @what) . ')' ;
+    } else {
+       return join(' || ', @what);
+    }
+}
+
+sub dbh_prepare
+{
+    my ($self, $query) = @_;
+    $self->debug($query, up => 1);
+    return $self->{dbh}->prepare($query);    
+}
+
 # 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';
@@ -1160,6 +1209,34 @@ sub human_sec
     return "$val years";   
 }
 
+# display Enabled
+sub human_enabled
+{
+    my $val = shift || 0;
+
+    if ($val eq '1' or $val eq "yes") {
+       return "yes";
+    } elsif ($val eq '2' or $val eq "archived") {
+       return "archived";
+    } else {
+       return  "no";
+    }
+}
+
+# display Enabled
+sub from_human_enabled
+{
+    my $val = shift || 0;
+
+    if ($val == 1 or $val eq "yes") {
+       return 1;
+    } elsif ($val == 2 or $val eq "archived") {
+       return 2;
+    } else {
+       return  0;
+    }
+}
+
 # get Day, Hour, Year
 sub from_human_sec
 {
@@ -1187,6 +1264,7 @@ sub connect_db
     my ($self) = @_;
 
     unless ($self->{dbh}) {
+
        $self->{dbh} = DBI->connect($self->{info}->{dbi}, 
                                    $self->{info}->{user},
                                    $self->{info}->{password});
@@ -1196,7 +1274,9 @@ sub connect_db
 
        $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
 
-       if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
+       if ($self->dbh_is_mysql()) {
+           $self->{dbh}->do("SET group_concat_max_len=1000000");
+       } else {
            $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
        }
     }
@@ -1205,14 +1285,14 @@ sub connect_db
 sub new
 {
     my ($class, %arg) = @_;
-    my $self = bless { 
+    my $self = bless (
        dbh => undef,           # connect_db();
        info => {
            dbi   => '', # DBI:Pg:database=bacula;host=127.0.0.1
            user  => 'bacula',
            password => 'test', 
        },
-    } ;
+    },$class) ;
 
     map { $self->{lc($_)} = $arg{$_} } keys %arg ;
 
@@ -1220,6 +1300,7 @@ sub new
        $self->{sql} = $sql_func{$1};
     }
 
+    $self->{loginname} = CGI::remote_user();
     $self->{debug} = $self->{info}->{debug};
     $Bweb::Gui::template_dir = $self->{info}->{template_dir};
 
@@ -1241,14 +1322,28 @@ sub display_end
 sub display_clients
 {
     my ($self) = @_;
+    my $where='';      # by default
 
-    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}) {
+       # $filter could already contains client_group_member 
+       $where = "
+ JOIN client_group_member USING (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 = "
@@ -1257,9 +1352,8 @@ SELECT Name   AS name,
        AutoPrune AS autoprune,
        FileRetention AS fileretention,
        JobRetention  AS jobretention
-FROM Client
-$where
-";
+FROM Client " . $self->get_client_filter() .
+$where ;
 
     my $all = $self->dbh_selectall_hashref($query, 'name') ;
 
@@ -1357,6 +1451,8 @@ sub get_form
                 maxvoljobs  => 0,
                 maxvolbytes => 0,
                 maxvolfiles => 0,
+                filenameid => 0,
+                pathid => 0,
                 );
 
     my %opt_ss =(              # string with space
@@ -1379,7 +1475,11 @@ sub get_form
                  type   => 1,
                 poolrecycle => 1,
                 replace => 1,
-                );
+                expired => 1,
+                enabled => 1,
+                 username => 1,
+                 rolename => 1,
+                 );
     my %opt_p = (              # option with path
                 fileset=> 1,
                 mtxcmd => 1,
@@ -1387,6 +1487,7 @@ sub get_form
                 device => 1,
                 where  => 1,
                 );
+    my %opt_r = (regexwhere => 1);
 
     my %opt_d = (              # option with date
                 voluseduration=> 1,
@@ -1409,7 +1510,7 @@ sub get_form
            if ($value =~ /^([\w\d\.\-\s]+)$/) {
                $ret{$i} = $1;
            }
-       } elsif ($i =~ /^j(\w+)s$/) { # quote join args
+       } 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) ;
@@ -1429,6 +1530,11 @@ sub get_form
            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+)$/) {
@@ -1453,9 +1559,15 @@ sub get_form
     }
 
     if ($what{db_clients}) {
+       my $filter='';
+       if ($what{filter}) {
+           # get security filter only if asked
+           $filter = $self->get_client_filter();
+       }
+
        my $query = "
 SELECT Client.Name as clientname
-FROM Client
+  FROM Client $filter
 ";
 
        my $clients = $self->dbh_selectall_hashref($query, 'clientname');
@@ -1463,20 +1575,60 @@ FROM Client
                              values %$clients] ;
     }
 
+    if ($what{db_client_groups}) {
+       my $filter='';
+       if ($what{filter}) {
+           # get security filter only if asked
+           $filter = $self->get_client_group_filter();
+       }
+
+       my $query = "
+SELECT client_group_name AS name 
+  FROM client_group $filter
+";
+
+       my $grps = $self->dbh_selectall_hashref($query, 'name');
+       $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} } 
+                                 values %$grps] ;
+    }
+
+    if ($what{db_usernames}) {
+       my $query = "
+SELECT username 
+  FROM bweb_user
+";
+
+       my $users = $self->dbh_selectall_hashref($query, 'username');
+       $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} } 
+                                 values %$users] ;
+    }
+
+    if ($what{db_roles}) {
+       my $query = "
+SELECT rolename 
+  FROM bweb_role
+";
+
+       my $r = $self->dbh_selectall_hashref($query, 'rolename');
+       $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} } 
+                                 values %$r] ;
+    }
+
     if ($what{db_mediatypes}) {
        my $query = "
 SELECT MediaType as mediatype
-FROM MediaType
+  FROM MediaType
 ";
 
-       my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
+       my $media = $self->dbh_selectall_hashref($query, 'mediatype');
        $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} } 
-                                 values %$medias] ;
+                                 values %$media] ;
     }
 
     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} 
@@ -1495,7 +1647,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');
@@ -1505,9 +1657,13 @@ FROM FileSet
     }
 
     if ($what{db_jobnames}) {
+       my $filter='';
+       if ($what{filter}) {
+           $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
+       }
        my $query = "
 SELECT DISTINCT Job.Name AS jobname 
-FROM Job
+  FROM Job $filter
 ";
 
        my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
@@ -1519,7 +1675,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');
@@ -1536,8 +1692,8 @@ sub display_graph
     my ($self) = @_;
 
     my $fields = $self->get_form(qw/age level status clients filesets 
-                                    graph gtype type
-                                   db_clients limit db_filesets width height
+                                    graph gtype type filter db_clients
+                                   limit db_filesets width height
                                    qclients qfilesets qjobnames db_jobnames/);
                                
 
@@ -1556,57 +1712,20 @@ sub display_graph
 
 }
 
-sub display_client_job
-{
-    my ($self, %arg) = @_ ;
-
-    $arg{order} = ' Job.JobId DESC ';
-    my ($limit, $label) = $self->get_limit(%arg);
-
-    my $clientname = $self->dbh_quote($arg{clientname});
-
-    my $query="
-SELECT DISTINCT Job.JobId       AS jobid,
-               Job.Name        AS jobname,
-                FileSet.FileSet AS fileset,
-                Level           AS level,
-                StartTime       AS starttime,
-                JobFiles        AS jobfiles, 
-                JobBytes        AS jobbytes,
-                JobStatus       AS jobstatus,
-               JobErrors       AS joberrors
-
- FROM Client,Job,FileSet
- WHERE Client.Name=$clientname
- AND Client.ClientId=Job.ClientId
- AND Job.FileSetId=FileSet.FileSetId
- $limit
-";
-
-    my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
-
-    $self->display({ clientname => $arg{clientname},
-                    Filter => $label,
-                    ID => $cur_id++,
-                    Jobs => [ values %$all ],
-                  },
-                  "display_client_job.tpl") ;
-}
-
 sub get_selected_media_location
 {
     my ($self) = @_ ;
 
-    my $medias = $self->get_form('jmedias');
+    my $media = $self->get_form('jmedias');
 
-    unless ($medias->{jmedias}) {
+    unless ($media->{jmedias}) {
        return undef;
     }
 
     my $query = "
 SELECT Media.VolumeName AS volumename, Location.Location AS location
 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
-WHERE Media.VolumeName IN ($medias->{jmedias})
+WHERE Media.VolumeName IN ($media->{jmedias})
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
@@ -1620,20 +1739,21 @@ WHERE Media.VolumeName IN ($medias->{jmedias})
 
 sub move_media
 {
-    my ($self) = @_ ;
+    my ($self, $in) = @_ ;
 
-    my $medias = $self->get_selected_media_location();
+    my $media = $self->get_selected_media_location();
 
-    unless ($medias) {
+    unless ($media) {
        return ;
     }
-    
+
     my $elt = $self->get_form('db_locations');
 
     $self->display({ ID => $cur_id++,
+                    enabled => human_enabled($in),
                     %$elt,     # db_locations
-                    medias => [ 
-            sort { $a->{volumename} cmp $b->{volumename} } values %$medias
+                    media => [ 
+            sort { $a->{volumename} cmp $b->{volumename} } values %$media
                               ],
                     },
                   "move_media.tpl");
@@ -1685,7 +1805,7 @@ LIMIT $number
     
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
 
-    $self->display({ Medias => [ values %$all ] },
+    $self->display({ Media => [ values %$all ] },
                   "help_extern_compute.tpl");
 }
 
@@ -1741,7 +1861,7 @@ LIMIT $number
     
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
 
-    $self->display({ Medias => [ values %$all ] },
+    $self->display({ Media => [ values %$all ] },
                   "help_intern_compute.tpl");
 
 }
@@ -1758,6 +1878,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')
@@ -1770,7 +1891,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");
@@ -1793,6 +1914,15 @@ 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 = grep { ! /^\s*$/ } CGI::param('fileset');
        if (@filesets) {
@@ -1803,10 +1933,10 @@ sub get_param
     }
 
     if ($elt{mediatypes}) {
-       my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
-       if (@medias) {
-           $ret{mediatypes} = \@medias;
-           my $str = $self->dbh_join(@medias);
+       my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
+       if (@media) {
+           $ret{mediatypes} = \@media;
+           my $str = $self->dbh_join(@media);
            $limit .= "AND Media.MediaType IN ($str) ";
        }
     }
@@ -1913,17 +2043,27 @@ sub get_param
 sub display_job
 {
     my ($self, %arg) = @_ ;
+    return if $self->cant_do('r_view_job');
 
     $arg{order} = ' Job.JobId DESC ';
 
     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 .= "
+JOIN client_group_member USING (ClientId)
+JOIN client_group USING (client_group_id)
+";
+    }
+    my $filter = $self->get_client_filter();
 
     my $query="
 SELECT  Job.JobId       AS jobid,
@@ -1943,11 +2083,11 @@ SELECT  Job.JobId       AS jobid,
 
         JobErrors      AS joberrors
 
- FROM Client, 
+ FROM Client $filter $cgq
       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'
+   AND Job.JobStatus NOT IN ('R', 'C')
  $where
  $limit
 ";
@@ -1969,9 +2109,13 @@ SELECT  Job.JobId       AS jobid,
 sub display_job_zoom
 {
     my ($self, $jobid) = @_ ;
+    $self->can_do('r_view_job');
 
     $jobid = $self->dbh_quote($jobid);
-    
+
+    # get security filter
+    my $filter = $self->get_client_filter();
+
     my $query="
 SELECT DISTINCT Job.JobId       AS jobid,
                 Client.Name     AS client,
@@ -1987,7 +2131,7 @@ SELECT DISTINCT Job.JobId       AS jobid,
                 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
                                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
 
- FROM Client,
+ FROM Client $filter,
       Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
           LEFT JOIN Pool    ON (Job.PoolId    = Pool.PoolId)
  WHERE Client.ClientId=Job.ClientId
@@ -2012,16 +2156,76 @@ WHERE Job.JobId = $jobid
     $self->display($row, "display_job_zoom.tpl");
 }
 
+sub display_job_group
+{
+    my ($self, %arg) = @_;
+    $self->can_do('r_view_job');
+
+    my ($limit, $label) = $self->get_limit(groupby => 'client_group_name',  %arg);
+
+    my ($where, undef) = $self->get_param('client_groups',
+                                         'level',
+                                         'pools');
+    my $filter = $self->get_client_group_filter();
+    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 $filter 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 ($limit, $label) = $self->get_limit(%arg);    
     my ($where, %elt) = $self->get_param('pools',
                                         'mediatypes',
                                         'volstatus',
                                         'locations');
 
-    my $arg = $self->get_form('jmedias', 'qre_media');
+    my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
 
     if ($arg->{jmedias}) {
        $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where"; 
@@ -2029,6 +2233,13 @@ sub display_media
     if ($arg->{qre_media}) {
        $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where"; 
     }
+    if ($arg->{expired}) {
+       $where = " 
+        AND VolStatus = 'Full'
+        AND (    $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+               + $self->{sql}->{TO_SEC}(Media.VolRetention)
+            ) < NOW()  " . $where ;
+    }
 
     my $query="
 SELECT Media.VolumeName  AS volumename, 
@@ -2055,6 +2266,7 @@ LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
 
 WHERE Media.PoolId=Pool.PoolId
 $where
+$limit
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
@@ -2062,12 +2274,12 @@ $where
     $self->display({ ID => $cur_id++,
                     Pool => $elt{pool},
                     Location => $elt{location},
-                    Medias => [ values %$all ]
+                    Media => [ values %$all ],
                   },
                   "display_media.tpl");
 }
 
-sub display_medias
+sub display_allmedia
 {
     my ($self) = @_ ;
 
@@ -2083,14 +2295,15 @@ sub display_media_zoom
 {
     my ($self) = @_ ;
 
-    my $medias = $self->get_form('jmedias');
+    my $media = $self->get_form('jmedias');
     
-    unless ($medias->{jmedias}) {
+    unless ($media->{jmedias}) {
        return $self->error("Can't get media selection");
     }
     
     my $query="
 SELECT InChanger     AS online,
+       Media.Enabled AS enabled,
        VolBytes      AS nb_bytes,
        VolumeName    AS volumename,
        VolStatus     AS volstatus,
@@ -2116,7 +2329,7 @@ SELECT InChanger     AS online,
  FROM Pool,
       Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
  WHERE Pool.PoolId = Media.PoolId
- AND VolumeName IN ($medias->{jmedias})
+ AND VolumeName IN ($media->{jmedias})
 ";
 
     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
@@ -2166,6 +2379,7 @@ SELECT LocationLog.Date    AS date,
 sub location_edit
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
 
     my $loc = $self->get_form('qlocation');
     unless ($loc->{qlocation}) {
@@ -2181,17 +2395,17 @@ WHERE Location.Location = $loc->{qlocation}
 ";
 
     my $row = $self->dbh_selectrow_hashref($query);
-
+    $row->{enabled} = human_enabled($row->{enabled});
     $self->display({ ID => $cur_id++,
                     %$row }, "location_edit.tpl") ;
-
 }
 
 sub location_save
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
 
-    my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
+    my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
     unless ($arg->{qlocation}) {
        return $self->error("Can't get location");
     }    
@@ -2202,8 +2416,7 @@ sub location_save
        return $self->error("Can't get new cost");
     }
 
-    my $enabled = CGI::param('enabled') || '';
-    $enabled = $enabled?1:0;
+    my $enabled = from_human_enabled($arg->{enabled});
 
     my $query = "
 UPDATE Location SET Cost     = $arg->{cost}, 
@@ -2214,12 +2427,14 @@ WHERE Location.Location = $arg->{qlocation}
 
     $self->dbh_do($query);
 
-    $self->display_location();
+    $self->location_display();
 }
 
 sub location_del
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
+
     my $arg = $self->get_form(qw/qlocation/) ;
 
     unless ($arg->{qlocation}) {
@@ -2239,18 +2454,19 @@ WHERE Location = $arg->{qlocation}
     }
 
     $query = "
-DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
+DELETE FROM Location WHERE Location = $arg->{qlocation}
 ";
 
     $self->dbh_do($query);
 
-    $self->display_location();
+    $self->location_display();
 }
 
-
 sub location_add
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
+
     my $arg = $self->get_form(qw/qlocation cost/) ;
 
     unless ($arg->{qlocation}) {
@@ -2271,10 +2487,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) = @_ ;
 
@@ -2300,8 +2516,8 @@ sub update_location
 {
     my ($self) = @_ ;
 
-    my $medias = $self->get_selected_media_location();
-    unless ($medias) {
+    my $media = $self->get_selected_media_location();
+    unless ($media) {
        return ;
     }
 
@@ -2309,11 +2525,520 @@ sub update_location
 
     $self->display({ email  => $self->{info}->{email_media},
                     %$arg,
-                     medias => [ values %$medias ],
+                     media => [ values %$media ],
                   },
                   "update_location.tpl");
 }
 
+###########################################################
+
+sub groups_edit
+{
+    my ($self) = @_;
+    $self->can_do('r_group_mgnt');
+
+    my $grp = $self->get_form(qw/qclient_group db_clients/);
+
+    unless ($grp->{qclient_group}) {
+       $self->display({ ID => $cur_id++,
+                        client_group => "''",
+                        %$grp,
+                    }, "groups_edit.tpl");
+       return;
+    }
+
+    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) = @_;
+    $self->can_do('r_group_mgnt');
+
+    my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
+
+    if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
+       my $query = "
+INSERT INTO client_group (client_group_name) 
+VALUES ($arg->{qnewgroup})
+";
+       $self->dbh_do($query);
+       $arg->{qclient_group} = $arg->{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) = @_;
+    $self->can_do('r_group_mgnt');
+
+    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 bweb_client_group_acl
+      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 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_roles
+{
+    my ($self) = @_;
+    if (not $self->{info}->{enable_security}) {
+        return 1;
+    }
+    # admin is a special user that can do everything
+    if ($self->{loginname} eq 'admin') {
+        return 1;
+    }
+    if (!$self->{loginname}) {
+       return 0;
+    }
+    # already fill
+    if (defined $self->{security}) {
+       return 1;
+    }
+    $self->{security} = {};
+    my $u = $self->dbh_quote($self->{loginname});
+           
+    my $query = "
+ SELECT use_acl, rolename
+  FROM bweb_user 
+       JOIN bweb_role_member USING (userid)
+       JOIN bweb_role USING (roleid)
+ WHERE username = $u
+";
+    my $rows = $self->dbh_selectall_arrayref($query);
+    # do cache with this role   
+    if (!$rows) {
+        return 0;
+    }
+    foreach my $r (@$rows) {
+       $self->{security}->{$r->[1]}=1;
+    }
+
+    $self->{security}->{use_acl} = $rows->[0]->[0];
+    return 1;
+}
+
+sub cant_do
+{
+    my ($self, $action) = @_;
+    # is security enabled in configuration ?
+    if (not $self->{info}->{enable_security}) {
+        return 0
+    }
+    # admin is a special user that can do everything
+    if ($self->{loginname} eq 'admin') {
+        return 0;
+    }
+    # must be logged
+    if (!$self->{loginname}) {
+       $self->{error} = "Can't do $action, your are not logged. " .
+           "Check security with your administrator";
+        return 1;
+    }
+    $self->get_roles();
+    if (!$self->{security}->{$action}) {
+        $self->{error} =
+           "$self->{loginname} sorry, but this action ($action) " .
+           "is not permited. " .
+           "Check security with your administrator";
+        return 1;
+    }
+    return 0;
+}
+
+# make like an assert (program die)
+sub can_do
+{
+    my ($self, $action) = @_;
+    if ($self->cant_do($action)) {
+        $self->error($self->{error});
+        $self->display_end();
+        exit 0;
+    }
+    return 1;
+}
+
+sub use_filter
+{
+    my ($self) = @_;
+
+    if (!$self->{info}->{enable_security} or 
+       !$self->{info}->{enable_security_acl})
+    {
+       return 0 ;
+    }
+    
+    if ($self->get_roles()) {
+       return $self->{security}->{use_acl};
+    } else {
+       return 0;
+    }
+}
+
+# JOIN Client USING (ClientId) " . $b->get_client_filter() . "
+sub get_client_filter
+{
+    my ($self, $login) = @_;
+    my $u;
+    if ($login) {
+       $u = $self->dbh_quote($login);
+    } elsif ($self->use_filter()) {
+       $u = $self->dbh_quote($self->{loginname});
+    } else {
+       return '';
+    }
+    return "
+ JOIN (SELECT ClientId FROM client_group_member
+   JOIN client_group USING (client_group_id) 
+   JOIN bweb_client_group_acl USING (client_group_id) 
+   JOIN bweb_user USING (userid)
+   WHERE bweb_user.username = $u 
+ ) AS filter USING (ClientId)";
+}
+
+#JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
+sub get_client_group_filter
+{
+    my ($self, $login) = @_;
+    my $u;
+    if ($login) {
+       $u = $self->dbh_quote($login);
+    } elsif ($self->use_filter()) {
+       $u = $self->dbh_quote($self->{loginname});
+    } else {
+       return '';
+    }
+    return "
+ JOIN (SELECT client_group_id 
+         FROM bweb_client_group_acl
+         JOIN bweb_user USING (userid)
+   WHERE bweb_user.username = $u 
+ ) AS filter USING (client_group_id)";
+}
+
+# role and username have to be quoted before
+# role and username can be a quoted list
+sub revoke
+{
+    my ($self, $role, $username) = @_;
+    $self->can_do("r_user_mgnt");
+    
+    my $nb = $self->dbh_do("
+ DELETE FROM bweb_role_member 
+       WHERE roleid = (SELECT roleid FROM bweb_role
+                        WHERE rolename IN ($role))
+         AND userid = (SELECT userid FROM bweb_user
+                        WHERE username IN ($username))");
+    return $nb;
+}
+
+# role and username have to be quoted before
+# role and username can be a quoted list
+sub grant
+{
+    my ($self, $role, $username) = @_;
+    $self->can_do("r_user_mgnt");
+
+    my $nb = $self->dbh_do("
+   INSERT INTO bweb_role_member (roleid, userid)
+     SELECT roleid, userid FROM bweb_role, bweb_user 
+      WHERE rolename IN ($role)
+        AND username IN ($username)
+     ");
+    return $nb;
+}
+
+# role and username have to be quoted before
+# role and username can be a quoted list
+sub grant_like
+{
+    my ($self, $copy, $user) = @_;
+    $self->can_do("r_user_mgnt");
+
+    my $nb = $self->dbh_do("
+  INSERT INTO bweb_role_member (roleid, userid) 
+   SELECT roleid, a.userid 
+     FROM bweb_user AS a, bweb_role_member 
+     JOIN bweb_user USING (userid)
+    WHERE bweb_user.username = $copy
+      AND a.username = $user");
+    return $nb;
+}
+
+# username can be a join quoted list of usernames
+sub revoke_all
+{
+    my ($self, $username) = @_;
+    $self->can_do("r_user_mgnt");
+
+    $self->dbh_do("
+   DELETE FROM bweb_role_member
+         WHERE userid IN (
+           SELECT userid 
+             FROM bweb_user 
+            WHERE username in ($username))");
+    $self->dbh_do("
+DELETE FROM bweb_client_group_acl 
+ WHERE userid IN (
+  SELECT userid 
+    FROM bweb_user 
+   WHERE username IN ($username))");
+    
+}
+
+sub users_del
+{
+    my ($self) = @_;
+    $self->can_do("r_user_mgnt");
+
+    my $arg = $self->get_form(qw/jusernames/);
+
+    unless ($arg->{jusernames}) {
+        return $self->error("Can't get user");
+    }
+
+    $self->{dbh}->begin_work();
+    {
+        $self->revoke_all($arg->{jusernames});
+        $self->dbh_do("
+DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
+    }
+    $self->{dbh}->commit();
+    
+    $self->display_users();
+}
+
+sub users_add
+{
+    my ($self) = @_;
+    $self->can_do("r_user_mgnt");
+
+    # we don't quote username directly to check that it is conform
+    my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate qcopy_username jclient_groups/) ;
+
+    if (not $arg->{qcreate}) {
+        $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
+        $self->display($arg, "display_user.tpl");
+        return 1;
+    }
+
+    my $u = $self->dbh_quote($arg->{username});
+    
+    $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
+
+    if (!$arg->{qpasswd}) {
+        $arg->{qpasswd} = "''";
+    }
+    if (!$arg->{qcomment}) {
+        $arg->{qcomment} = "''";
+    }
+
+    # will fail if user already exists
+    # UPDATE with mysql dbi does not return if update is ok
+    ($self->dbh_do("
+  UPDATE bweb_user 
+     SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment}, 
+         use_acl=$arg->{use_acl}
+   WHERE username = $u") 
+#     and (! $self->dbh_is_mysql() )
+     ) and
+    $self->dbh_do("
+  INSERT INTO bweb_user (username, passwd, use_acl, comment) 
+        VALUES ($u, $arg->{qpasswd}, $arg->{use_acl}, $arg->{qcomment})");
+
+    $self->{dbh}->begin_work();
+    {
+        $self->revoke_all($u);
+
+        if ($arg->{qcopy_username}) {
+            $self->grant_like($arg->{qcopy_username}, $u);
+        } else {
+            $self->grant($arg->{jrolenames}, $u);
+        }
+
+       if ($arg->{jclient_groups}) {
+           $self->dbh_do("
+INSERT INTO bweb_client_group_acl (client_group_id, userid)
+ SELECT client_group_id, userid 
+   FROM client_group, bweb_user
+  WHERE client_group_name IN ($arg->{jclient_groups})
+    AND username = $u
+");
+       }
+    }
+    $self->{dbh}->commit();
+
+    $self->display_users();
+}
+
+# TODO: we miss a matrix with all user/roles
+sub display_users
+{
+    my ($self) = @_;
+    $self->can_do("r_user_mgnt");
+
+    my $arg = $self->get_form(qw/db_usernames/) ;
+
+    if ($self->{dbh}->errstr) {
+        return $self->error("Can't use users with bweb, read INSTALL to enable them");
+    }
+
+    $self->display({ ID => $cur_id++,
+                     %$arg},
+                   "display_users.tpl");
+}
+
+sub display_user
+{
+    my ($self) = @_;
+    $self->can_do("r_user_mgnt");
+
+    my $arg = $self->get_form('username');
+    my $user = $self->dbh_quote($arg->{username});
+
+    my $userp = $self->dbh_selectrow_hashref("
+   SELECT username, passwd, comment, use_acl
+     FROM bweb_user
+    WHERE username = $user
+");
+    if (!$userp) {
+        return $self->error("Can't find $user in catalog");
+    }
+    my $filter = $self->get_client_group_filter($arg->{username});
+    my $scg = $self->dbh_selectall_hashref("
+ SELECT client_group_name AS name 
+   FROM client_group $filter
+", 'name');
+
+#  rolename  | userid
+#------------+--------
+# cancel_job |
+# restore    |
+# run_job    |      1
+
+    my $role = $self->dbh_selectall_hashref("
+SELECT rolename, temp.userid
+     FROM bweb_role
+     LEFT JOIN (SELECT roleid, userid
+                  FROM bweb_user JOIN bweb_role_member USING (userid)
+                 WHERE username = $user) AS temp USING (roleid)
+ORDER BY rolename
+", 'rolename');
+
+    $arg = $self->get_form(qw/db_usernames db_client_groups/);    
+
+    $self->display({
+        db_usernames => $arg->{db_usernames},
+        username => $userp->{username},
+        comment => $userp->{comment},
+        passwd => $userp->{passwd},
+       use_acl => $userp->{use_acl},
+       db_client_groups => $arg->{db_client_groups},
+       client_group => [ values %$scg ],
+        db_roles => [ values %$role], 
+    }, "display_user.tpl");
+}
+
+
+###########################################################
+
 sub get_media_max_size
 {
     my ($self, $type) = @_;
@@ -2356,7 +3081,8 @@ SELECT Media.Slot         AS slot,
        Media.VolUseDuration AS voluseduration,
        Media.VolRetention AS volretention,
        Media.Comment      AS comment,
-       PoolRecycle.Name   AS poolrecycle
+       PoolRecycle.Name   AS poolrecycle,
+       Media.Enabled      AS enabled
 
 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
            LEFT  JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
@@ -2368,6 +3094,7 @@ WHERE Media.VolumeName = $media->{qmedia}
     my $row = $self->dbh_selectrow_hashref($query);
     $row->{volretention} = human_sec($row->{volretention});
     $row->{voluseduration} = human_sec($row->{voluseduration});
+    $row->{enabled} = human_enabled($row->{enabled});
 
     my $elt = $self->get_form(qw/db_pools db_locations/);
 
@@ -2380,6 +3107,7 @@ WHERE Media.VolumeName = $media->{qmedia}
 sub save_location
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
     my $arg = $self->get_form('jmedias', 'qnewlocation') ;
 
@@ -2406,12 +3134,13 @@ sub save_location
     $self->display_media();
 }
 
-sub change_location
+sub location_change
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
-    my $medias = $self->get_selected_media_location();
-    unless ($medias) {
+    my $media = $self->get_selected_media_location();
+    unless ($media) {
        return $self->error("Can't get media selection");
     }
     my $newloc = CGI::param('newlocation');
@@ -2420,20 +3149,25 @@ sub change_location
     my $comm = CGI::param('comment') || '';
     $comm = $self->dbh_quote("$user: $comm");
 
-    my $query;
+    my $arg = $self->get_form('enabled');
+    my $en = human_enabled($arg->{enabled});
+    my $b = $self->get_bconsole();
 
-    foreach my $media (keys %$medias) {
+    my $query;
+    foreach my $vol (keys %$media) {
        $query = "
 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
  VALUES(
-       NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
-       (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
-       (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
+       NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$vol'),
+       (SELECT LocationId FROM Location WHERE Location = '$media->{$vol}->{location}'),
+       (SELECT VolStatus FROM Media WHERE VolumeName = '$vol')
       )
 ";
        $self->dbh_do($query);
        $self->debug($query);
+       $b->send_cmd("update volume=\"$vol\" enabled=$en");
     }
+    $b->close();
 
     my $q = new CGI;
     $q->param('action', 'update_location');
@@ -2442,8 +3176,8 @@ INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
     $self->display({ email  => $self->{info}->{email_media},
                     url => $url,
                     newlocation => $newloc,
-                    # [ { volumename => 'vol1' }, { volumename => 'vol2'\81 },..]
-                    medias => [ values %$medias ],
+                    # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
+                    media => [ values %$media ],
                   },
                   "change_location.tpl");
 
@@ -2452,10 +3186,13 @@ INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
 sub display_client_stats
 {
     my ($self, %arg) = @_ ;
+    $self->can_do('r_view_stat');
 
     my $client = $self->dbh_quote($arg{clientname});
-    my ($limit, $label) = $self->get_limit(%arg);
+    # get security filter
+    my $filter = $self->get_client_filter();
 
+    my ($limit, $label) = $self->get_limit(%arg);
     my $query = "
 SELECT 
     count(Job.JobId)     AS nb_jobs,
@@ -2463,7 +3200,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) $filter
 WHERE 
     Client.Name = $client
     $limit 
@@ -2474,6 +3211,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");
 }
@@ -2486,7 +3262,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}) ";
     }
@@ -2504,7 +3280,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,
@@ -2525,7 +3303,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,
@@ -2551,7 +3329,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
 ";
@@ -2571,14 +3350,17 @@ GROUP BY VolStatus
 sub display_running_job
 {
     my ($self) = @_;
+    return if $self->cant_do('r_view_running_job');
 
     my $arg = $self->get_form('client', 'jobid');
 
     if (!$arg->{client} and $arg->{jobid}) {
+       # get security filter
+       my $filter = $self->get_client_filter();
 
        my $query = "
 SELECT Client.Name AS name
-FROM Job INNER JOIN Client USING (ClientId)
+FROM Job INNER JOIN Client USING (ClientId) $filter
 WHERE Job.JobId = $arg->{jobid}
 ";
 
@@ -2604,7 +3386,11 @@ WHERE Job.JobId = $arg->{jobid}
 sub display_running_jobs
 {
     my ($self, $display_action) = @_;
-    
+    return if $self->cant_do('r_view_running_job');
+
+    # get security filter
+    my $filter = $self->get_client_filter();
+
     my $query = "
 SELECT Job.JobId AS jobid, 
        Job.Name  AS jobname,
@@ -2617,8 +3403,9 @@ $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(NOW())
                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
          AS duration,
        Client.Name AS clientname
-FROM Job INNER JOIN Client USING (ClientId) 
-WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
+FROM Job INNER JOIN Client USING (ClientId) $filter
+WHERE 
+  JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
 ";     
     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
     
@@ -2632,6 +3419,8 @@ WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
 sub eject_media
 {
     my ($self) = @_;
+    $self->can_do('r_media_mgnt');
+
     my %ret; 
     my $arg = $self->get_form('jmedias');
 
@@ -2661,7 +3450,7 @@ WHERE Media.VolumeName IN ($arg->{jmedias})
            $a->status();
            $a->{have_status} = 1;
        }
-
+       # TODO: set enabled
        print "eject $vol->{volumename} from $vol->{storage} : ";
        if ($a->send_to_io($vol->{slot})) {
            print "<img src='/bweb/T.png' alt='ok'><br/>";
@@ -2735,6 +3524,7 @@ sub ach_get
 sub ach_register
 {
     my ($self, $ach) = @_;
+    $self->can_do('r_configure');
 
     $self->{info}->{ach_list}->{$ach->{name}} = $ach;
 
@@ -2746,6 +3536,8 @@ sub ach_register
 sub ach_edit
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach');
     if (!$arg->{ach} 
        or !$self->{info}->{ach_list} 
@@ -2775,6 +3567,8 @@ sub ach_edit
 sub ach_del
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach');
 
     if (!$arg->{ach} 
@@ -2793,6 +3587,8 @@ sub ach_del
 sub ach_add
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
 
     my $b = $self->get_bconsole();
@@ -2836,6 +3632,8 @@ sub ach_add
 sub delete
 {
     my ($self) = @_;
+    $self->can_do('r_delete_job');
+
     my $arg = $self->get_form('jobid');
 
     if ($arg->{jobid}) {
@@ -2853,11 +3651,12 @@ sub delete
 sub do_update_media
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
     my $arg = $self->get_form(qw/media volstatus inchanger pool
                                 slot volretention voluseduration 
                                 maxvoljobs maxvolfiles maxvolbytes
-                                qcomment poolrecycle
+                                qcomment poolrecycle enabled
                              /);
 
     unless ($arg->{media}) {
@@ -2879,6 +3678,10 @@ sub do_update_media
        $update .= " slot=0 inchanger=no ";
     }
 
+    if ($arg->{enabled}) {
+        $update .= " enabled=$arg->{enabled} ";
+    }
+
     if ($arg->{pool}) {
        $update .= " pool=$arg->{pool} " ;
     }
@@ -2903,6 +3706,10 @@ sub do_update_media
        $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
     }    
 
+    if (defined $arg->{poolrecycle}) {
+       $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
+    }        
+    
     my $b = $self->get_bconsole();
 
     $self->display({
@@ -2920,9 +3727,6 @@ sub do_update_media
        $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} = "''";
     }
@@ -2942,6 +3746,7 @@ UPDATE Media
 sub update_slots
 {
     my ($self) = @_;
+    $self->can_do('r_autochanger_mgnt');
 
     my $ach = CGI::param('ach') ;
     $ach = $self->ach_get($ach);
@@ -2958,6 +3763,7 @@ sub update_slots
 sub get_job_log
 {
     my ($self) = @_;
+    $self->can_do('r_view_log');
 
     my $arg = $self->get_form('jobid', 'limit', 'offset');
     unless ($arg->{jobid}) {
@@ -2967,12 +3773,12 @@ sub get_job_log
     if ($arg->{limit} == 100) {
         $arg->{limit} = 1000;
     }
-
-    my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
+    # get security filter
+    my $filter = $self->get_client_filter();
 
     my $query = "
 SELECT Job.Name as name, Client.Name as clientname
- FROM  Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
+ FROM  Job INNER JOIN Client USING (ClientId) $filter
  WHERE JobId = $arg->{jobid}
 ";
 
@@ -2982,32 +3788,45 @@ SELECT Job.Name as name, Client.Name as clientname
        return $self->error("Can't find $arg->{jobid} in catalog");
     }
 
+    # display only Error and Warning messages
+    $filter = '';
+    if (CGI::param('error')) {
+       $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning' ";
+    }
+
+    my $logtext;
+    if (CGI::param('time') || $self->{info}->{display_log_time}) {
+       $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
+    } else {
+       $logtext = 'LogText';
+    }
+
     $query = "
-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})
-       )
+SELECT count(1) AS nbline, JobId AS jobid, 
+       GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
+  FROM  (
+    SELECT JobId, Time, LogText
+    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})
+       ) ) $filter
  ORDER BY LogId
  LIMIT $arg->{limit}
  OFFSET $arg->{offset}
+ ) AS temp
+ GROUP BY JobId
+
 ";
 
-    my $log = $self->dbh_selectall_arrayref($query);
+    my $log = $self->dbh_selectrow_hashref($query);
     unless ($log) {
        return $self->error("Can't get log for jobid $arg->{jobid}");
     }
 
-    my $logtxt;
-    if ($t) {
-       # log contains \n
-       $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ; 
-    } else {
-       $logtxt = join("", map { $_->[1] } @$log ) ; 
-    }
-    
-    $self->display({ lines=> $logtxt,
+    $self->display({ lines=> $log->{logtxt},
+                    nbline => $log->{nbline},
                     jobid => $arg->{jobid},
                     name  => $row->{name},
                     client => $row->{clientname},
@@ -3016,10 +3835,10 @@ SELECT Time AS time, LogText AS log
                 }, 'display_log.tpl');
 }
 
-
 sub label_barcodes
 {
     my ($self) = @_ ;
+    $self->can_do('r_autochanger_mgnt');
 
     my $arg = $self->get_form('ach', 'slots', 'drive');
 
@@ -3038,9 +3857,11 @@ sub label_barcodes
     }
 
     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} }) ;
     }
 
@@ -3058,14 +3879,10 @@ sub label_barcodes
   UPDATE Media 
        SET LocationId =   (SELECT LocationId 
                              FROM Location 
-                            WHERE Location = '$arg->{ach}'),
+                            WHERE Location = '$arg->{ach}')
 
-           RecyclePoolId = PoolId
-
-     WHERE Media.PoolId = (SELECT PoolId 
-                             FROM Pool
-                            WHERE Name = 'Scratch')
-       AND (LocationId = 0 OR LocationId IS NULL)
+     WHERE (LocationId = 0 OR LocationId IS NULL)
+       $slots_sql
 ");
 
 }
@@ -3073,6 +3890,7 @@ sub label_barcodes
 sub purge
 {
     my ($self) = @_;
+    $self->can_do('r_purge');
 
     my @volume = CGI::param('media');
 
@@ -3082,17 +3900,20 @@ sub purge
 
     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
 
-    $self->display({
-       content => $b->purge_volume(@volume),
-       title => "Purge media",
-       name => "purge volume=" . join(' volume=', @volume),
-    }, "command.tpl"); 
+    foreach my $v (@volume) {
+       $self->display({
+           content => $b->purge_volume($v),
+           title => "Purge media",
+           name => "purge volume=$v",
+       }, "command.tpl");
+    }  
     $b->close();
 }
 
 sub prune
 {
     my ($self) = @_;
+    $self->can_do('r_prune');
 
     my @volume = CGI::param('media');
     unless (@volume) {
@@ -3101,18 +3922,20 @@ sub prune
 
     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
 
-    $self->display({
-       content => $b->prune_volume(@volume),
-       title => "Prune media",
-       name => "prune volume=" . join(' volume=', @volume),
-    }, "command.tpl"); 
-
+    foreach my $v (@volume) {
+       $self->display({
+           content => $b->prune_volume($v),
+           title => "Prune volume",
+           name => "prune volume=$v",
+       }, "command.tpl");
+    }
     $b->close();
 }
 
 sub cancel_job
 {
     my ($self) = @_;
+    $self->can_do('r_cancel_job');
 
     my $arg = $self->get_form('jobid');
     unless ($arg->{jobid}) {
@@ -3163,6 +3986,7 @@ sub director_show_sched
 sub enable_disable_job
 {
     my ($self, $what) = @_ ;
+    $self->can_do('r_run_job');
 
     my $name = CGI::param('job') || '';
     unless ($name =~ /^[\w\d\.\-\s]+$/) {
@@ -3194,6 +4018,8 @@ sub get_bconsole
 sub run_job_select
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
 
     my $joblist = [ map { { name => $_ } } $b->list_job() ];
@@ -3229,12 +4055,18 @@ sub run_parse_job
 sub run_job_mod
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     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() ];
 
@@ -3249,13 +4081,15 @@ sub run_job_mod
        clients  => $clients,
        filesets => $filesets,
        storages => $storages,
-       %$attr,
+       %job_opt,
     }, "run_job_mod.tpl");
 }
 
 sub run_job
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
@@ -3268,11 +4102,13 @@ sub run_job
 sub run_job_now
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     # TODO: check input (don't use pool, level)
 
-    my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when');
+    my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
     my $job = CGI::param('job') || '';
     my $storage = CGI::param('storage') || '';
 
@@ -3282,6 +4118,7 @@ sub run_job_now
                        level => $arg->{level},
                        storage => $storage,
                        pool => $arg->{pool},
+                       fileset => $arg->{fileset},
                        when => $arg->{when},
                        );