=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
our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
user => qr/^([\w\d\.-]+)$/i,
password => qr/^(.*)$/i,
+ fv_write_path => qr!^([/\w\d\.-]+)$!,
template_dir => qr!^([/\w\d\.-]+)$!,
debug => qr/^(on)?$/,
email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
{
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...") ;
+ }
- while (my $line = <FP>)
+ 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>)
{
chomp($line);
my ($k, $v) = split(/\s*=\s*/, $line, 2);
- $self->{$k} = $v;
+ if ($k_re{$k}) {
+ $self->{$k} = $v;
+ }
}
close(FP);
{
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;
}
sub view
{
my ($self) = @_ ;
-
- $self->display($self, "config_view.tpl");
+ $self->display($self, "config_view.tpl");
}
sub modify
}
}
- $self->display($self, "config_view.tpl");
+ $self->view();
if ($self->{error}) { # an error as occured
$self->display($self, 'error.tpl');
=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) = @_;
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;
}
}
+sub get_drive_name
+{
+ my ($self, $index) = @_;
+ return $self->{drive_name}->[$index];
+}
+
# TODO : do a tapeinfo request to get informations
sub tapeinfo
{
}
$all->{$vol}->{realslot} = $slot;
- $all->{$vol}->{volbytes} = Bweb::human_size($all->{$vol}->{volbytes}) ;
-
+
push @{ $param }, $all->{$vol};
} else { # empty or no label
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
=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
{
unless ($self->{dbh});
$self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
+
+ if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
+ $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
+ }
}
}
{
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
$label = "last " . human_sec($arg{age});
}
+ if ($arg{groupby}) {
+ $limit .= " GROUP BY $arg{groupby} ";
+ }
+
if ($arg{order}) {
$limit .= " ORDER BY $arg{order} ";
}
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} ;
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) ;
}
} 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;
+ }
}
}
}
}
+ 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
$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;
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,
-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
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++,
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);
}
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);
}
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);
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);
}
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);
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
'level',
'filesets',
'jobtype',
+ 'pools',
'jobid',
'status');
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 =>
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
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
{
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,
";
my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
- foreach (values %$all) {
- $_->{volbytes} = human_size($_->{volbytes}) ;
- }
$self->display({ ID => $cur_id++,
Pool => $elt{pool},
Media.Recycle AS recycle,
Media.VolRetention AS volretention,
Media.LastWritten AS lastwritten,
- Media.VolReadTime/100000 AS volreadtime,
- Media.VolWriteTime/100000 AS volwritetime,
+ 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)
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});
- $media->{volreadtime} = human_sec($media->{volreadtime});
- $media->{volwritetime} = human_sec($media->{volwritetime});
my $mq = $self->dbh_quote($media->{volumename});
$query = "
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");
}
$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) = @_ ;
}
}
-sub do_update_media
-{
- 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 $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 $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();
- }
-}
-
sub update_media
{
my ($self) = @_ ;
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,
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}
$self->display({
%$elt,
%$row,
- },
- "update_media.tpl");
+ }, "update_media.tpl");
}
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
(SELECT VolStatus FROM Media WHERE VolumeName = '$media')
)
";
-
+ $self->dbh_do($query);
$self->debug($query);
}
$row->{ID} = $cur_id++;
$row->{label} = $label;
- $row->{nb_bytes} = human_size($row->{nb_bytes}) ;
$self->display($row, "display_client_stats.tpl");
}
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 sum(subq.volmax) AS volmax,
- sum(subq.volnum) AS volnum,
- sum(subq.voltotal) AS voltotal,
+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,
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
- ) AS subq
-INNER JOIN Pool ON (Pool.PoolId = subq.PoolId)
-GROUP BY subq.PoolId
+ 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') ;
- foreach my $p (values %$all) {
- $p->{maxvolbytes} = human_size($p->{maxvolbytes}) ;
- $p->{volretention} = human_sec($p->{volretention}) ;
- $p->{voluseduration} = human_sec($p->{voluseduration}) ;
+ $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');
- if ($p->{volmax}) {
+ foreach my $p (values %$all) {
+ if ($p->{volmax} > 0) { # mysql returns 0.0000
+ # we remove Recycled/Purged media from pool usage
+ if (defined $empty->{$p->{name}}) {
+ $p->{voltotal} -= $empty->{$p->{name}}->{size};
+ }
$p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
} else {
$p->{poolusage} = 0;
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');
$self->debug($all);
$self->display({ ID => $cur_id++,
+ MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
Pools => [ values %$all ]},
"display_pool.tpl");
}
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,
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>";
}
}
+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) = @_;
# 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
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}");
}
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>" ;
drive => $arg->{drive},
pool => 'Scratch',
slots => $slots) ;
+ $b->close();
print "</pre>";
}
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({
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
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",
}, "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({
return $self->error("Can't find job name");
}
- my $b = new Bconsole(pref => $self->{info}) ;
+ my $b = $self->get_bconsole();
my $cmd;
if ($what) {
}, "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");
}
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,
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,
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') || '';
level => $arg->{level},
storage => $storage,
pool => $arg->{pool},
+ when => $arg->{when},
);
print $jobid, $b->{error};