1 ################################################################
6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
11 The main author of Bweb is Eric Bollengier.
12 The main author of Bacula is Kern Sibbald, with contributions from
13 many others, a complete list can be found in the file AUTHORS.
15 This program is Free Software; you can redistribute it and/or
16 modify it under the terms of version two of the GNU General Public
17 License as published by the Free Software Foundation plus additions
18 that are listed in the file LICENSE.
20 This program is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 Bacula® is a registered trademark of John Walker.
31 The licensor of Bacula is the Free Software Foundation Europe
32 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
33 Switzerland, email:ftf@fsfeurope.org.
45 Bweb::Gui - Base package for all Bweb object
49 This package define base fonction like new, display, etc..
54 our $template_dir='/usr/share/bweb/tpl';
58 new - creation a of new Bweb object
62 This function take an hash of argument and place them
65 IE : $obj = new Obj(name => 'test', age => '10');
67 $obj->{name} eq 'test' and $obj->{age} eq 10
73 my ($class, %arg) = @_;
78 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
85 my ($self, $what) = @_;
89 print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
91 print "<pre>$what</pre>";
98 error - display an error to the user
102 this function set $self->{error} with arg, display a message with
103 error.tpl and return 0
108 return $self->error("Can't use this file");
115 my ($self, $what) = @_;
116 $self->{error} = $what;
117 $self->display($self, 'error.tpl');
123 display - display an html page with HTML::Template
127 this function is use to render all html codes. it takes an
128 ref hash as arg in which all param are usable in template.
130 it will use global template_dir to search the template file.
132 hash keys are not sensitive. See HTML::Template for more
133 explanations about the hash ref. (it's can be quiet hard to understand)
137 $ref = { name => 'me', age => 26 };
138 $self->display($ref, "people.tpl");
144 my ($self, $hash, $tpl) = @_ ;
146 my $template = HTML::Template->new(filename => $tpl,
147 path =>[$template_dir],
148 die_on_bad_params => 0,
149 case_sensitive => 0);
151 foreach my $var (qw/limit offset/) {
153 unless ($hash->{$var}) {
154 my $value = CGI::param($var) || '';
156 if ($value =~ /^(\d+)$/) {
157 $template->param($var, $1) ;
162 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
163 $template->param('loginname', CGI::remote_user());
165 $template->param($hash);
166 print $template->output();
170 ################################################################
172 package Bweb::Config;
174 use base q/Bweb::Gui/;
178 Bweb::Config - read, write, display, modify configuration
182 this package is used for manage configuration
186 $conf = new Bweb::Config(config_file => '/path/to/conf');
197 =head1 PACKAGE VARIABLE
199 %k_re - hash of all acceptable option.
203 this variable permit to check all option with a regexp.
207 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
208 user => qr/^([\w\d\.-]+)$/i,
209 password => qr/^(.*)$/i,
210 fv_write_path => qr!^([/\w\d\.-]*)$!,
211 template_dir => qr!^([/\w\d\.-]+)$!,
212 debug => qr/^(on)?$/,
213 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
214 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
215 bconsole => qr!^(.+)?$!,
216 syslog_file => qr!^(.+)?$!,
217 log_dir => qr!^(.+)?$!,
218 stat_job_table => qr!^(\w*)$!,
219 display_log_time => qr!^(on)?$!,
224 load - load config_file
228 this function load the specified config_file.
236 unless (open(FP, $self->{config_file}))
238 return $self->error("can't load config_file $self->{config_file} : $!");
240 my $f=''; my $tmpbuffer;
241 while(read FP,$tmpbuffer,4096)
249 no strict; # I have no idea of the contents of the file
256 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...") ;
259 foreach my $k (keys %$VAR1) {
260 $self->{$k} = $VAR1->{$k};
268 load_old - load old configuration format
276 unless (open(FP, $self->{config_file}))
278 return $self->error("$self->{config_file} : $!");
281 while (my $line = <FP>)
284 my ($k, $v) = split(/\s*=\s*/, $line, 2);
296 save - save the current configuration to config_file
304 if ($self->{ach_list}) {
305 # shortcut for display_begin
306 $self->{achs} = [ map {{ name => $_ }}
307 keys %{$self->{ach_list}}
311 unless (open(FP, ">$self->{config_file}"))
313 return $self->error("$self->{config_file} : $!\n" .
314 "You must add this to your config file\n"
315 . Data::Dumper::Dumper($self));
318 print FP Data::Dumper::Dumper($self);
326 edit, view, modify - html form ouput
334 $self->display($self, "config_edit.tpl");
340 $self->display($self, "config_view.tpl");
350 foreach my $k (CGI::param())
352 next unless (exists $k_re{$k}) ;
353 my $val = CGI::param($k);
354 if ($val =~ $k_re{$k}) {
357 $self->{error} .= "bad parameter : $k = [$val]";
363 if ($self->{error}) { # an error as occured
364 $self->display($self, 'error.tpl');
372 ################################################################
374 package Bweb::Client;
376 use base q/Bweb::Gui/;
380 Bweb::Client - Bacula FD
384 this package is use to do all Client operations like, parse status etc...
388 $client = new Bweb::Client(name => 'zog-fd');
389 $client->status(); # do a 'status client=zog-fd'
395 display_running_job - Html display of a running job
399 this function is used to display information about a current job
403 sub display_running_job
405 my ($self, $conf, $jobid) = @_ ;
407 my $status = $self->status($conf);
410 if ($status->{$jobid}) {
411 $self->display($status->{$jobid}, "client_job_status.tpl");
414 for my $id (keys %$status) {
415 $self->display($status->{$id}, "client_job_status.tpl");
422 $client = new Bweb::Client(name => 'plume-fd');
424 $client->status($bweb);
428 dirty hack to parse "status client=xxx-fd"
432 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
433 Backup Job started: 06-jun-06 17:22
434 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
435 Files Examined=10,697
436 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
442 JobName => Full_plume.2006-06-06_17.22.23,
445 Bytes => 194,484,132,
455 my ($self, $conf) = @_ ;
457 if (defined $self->{cur_jobs}) {
458 return $self->{cur_jobs} ;
462 my $b = new Bconsole(pref => $conf);
463 my $ret = $b->send_cmd("st client=$self->{name}");
467 for my $r (split(/\n/, $ret)) {
469 $r =~ s/(^\s+|\s+$)//g;
470 if ($r =~ /JobId (\d+) Job (\S+)/) {
472 $arg->{$jobid} = { @param, JobId => $jobid } ;
476 @param = ( JobName => $2 );
478 } elsif ($r =~ /=.+=/) {
479 push @param, split(/\s+|\s*=\s*/, $r) ;
481 } elsif ($r =~ /=/) { # one per line
482 push @param, split(/\s*=\s*/, $r) ;
484 } elsif ($r =~ /:/) { # one per line
485 push @param, split(/\s*:\s*/, $r, 2) ;
489 if ($jobid and @param) {
490 $arg->{$jobid} = { @param,
492 Client => $self->{name},
496 $self->{cur_jobs} = $arg ;
502 ################################################################
504 package Bweb::Autochanger;
506 use base q/Bweb::Gui/;
510 Bweb::Autochanger - Object to manage Autochanger
514 this package will parse the mtx output and manage drives.
518 $auto = new Bweb::Autochanger(precmd => 'sudo');
520 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
524 $auto->slot_is_full(10);
525 $auto->transfer(10, 11);
531 my ($class, %arg) = @_;
534 name => '', # autochanger name
535 label => {}, # where are volume { label1 => 40, label2 => drive0 }
536 drive => [], # drive use [ 'media1', 'empty', ..]
537 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
538 io => [], # io slot number list [ 41, 42, 43...]
539 info => {slot => 0, # informations (slot, drive, io)
543 mtxcmd => '/usr/sbin/mtx',
545 device => '/dev/changer',
546 precmd => '', # ssh command
547 bweb => undef, # link to bacula web object (use for display)
550 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
557 status - parse the output of mtx status
561 this function will launch mtx status and parse the output. it will
562 give a perlish view of the autochanger content.
564 it uses ssh if the autochanger is on a other host.
571 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
573 # TODO : reset all infos
574 $self->{info}->{drive} = 0;
575 $self->{info}->{slot} = 0;
576 $self->{info}->{io} = 0;
578 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
581 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
582 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
583 #Data Transfer Element 1:Empty
584 # Storage Element 1:Empty
585 # Storage Element 2:Full :VolumeTag=000002
586 # Storage Element 3:Empty
587 # Storage Element 4:Full :VolumeTag=000004
588 # Storage Element 5:Full :VolumeTag=000001
589 # Storage Element 6:Full :VolumeTag=000003
590 # Storage Element 7:Empty
591 # Storage Element 41 IMPORT/EXPORT:Empty
592 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
597 # Storage Element 7:Empty
598 # Storage Element 2:Full :VolumeTag=000002
599 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
602 $self->set_empty_slot($1);
604 $self->set_slot($1, $4);
607 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
610 $self->set_empty_drive($1);
612 $self->set_drive($1, $4, $6);
615 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
618 $self->set_empty_io($1);
620 $self->set_io($1, $4);
623 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
625 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
626 $self->{info}->{drive} = $1;
627 $self->{info}->{slot} = $2;
628 if ($l =~ /(\d+)\s+Import/) {
629 $self->{info}->{io} = $1 ;
631 $self->{info}->{io} = 0;
636 $self->debug($self) ;
641 my ($self, $slot) = @_;
644 if ($self->{slot}->[$slot] eq 'loaded') {
648 my $label = $self->{slot}->[$slot] ;
650 return $self->is_media_loaded($label);
655 my ($self, $drive, $slot) = @_;
657 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
658 return 0 if ($self->slot_is_full($slot)) ;
660 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
663 my $content = $self->get_slot($slot);
664 print "content = $content<br/> $drive => $slot<br/>";
665 $self->set_empty_drive($drive);
666 $self->set_slot($slot, $content);
669 $self->{error} = $out;
674 # TODO: load/unload have to use mtx script from bacula
677 my ($self, $drive, $slot) = @_;
679 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
680 return 0 unless ($self->slot_is_full($slot)) ;
682 print "Loading drive $drive with slot $slot<br/>\n";
683 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
686 my $content = $self->get_slot($slot);
687 print "content = $content<br/> $slot => $drive<br/>";
688 $self->set_drive($drive, $slot, $content);
691 $self->{error} = $out;
699 my ($self, $media) = @_;
701 unless ($self->{label}->{$media}) {
705 if ($self->{label}->{$media} =~ /drive\d+/) {
715 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
720 my ($self, $slot, $tag) = @_;
721 $self->{slot}->[$slot] = $tag || 'full';
722 push @{ $self->{io} }, $slot;
725 $self->{label}->{$tag} = $slot;
731 my ($self, $slot) = @_;
733 push @{ $self->{io} }, $slot;
735 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
736 $self->{slot}->[$slot] = 'empty';
742 my ($self, $slot) = @_;
743 return $self->{slot}->[$slot];
748 my ($self, $slot, $tag) = @_;
749 $self->{slot}->[$slot] = $tag || 'full';
752 $self->{label}->{$tag} = $slot;
758 my ($self, $slot) = @_;
760 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
761 $self->{slot}->[$slot] = 'empty';
767 my ($self, $drive) = @_;
768 $self->{drive}->[$drive] = 'empty';
773 my ($self, $drive, $slot, $tag) = @_;
774 $self->{drive}->[$drive] = $tag || $slot;
776 $self->{slot}->[$slot] = $tag || 'loaded';
779 $self->{label}->{$tag} = "drive$drive";
785 my ($self, $slot) = @_;
787 # slot don't exists => full
788 if (not defined $self->{slot}->[$slot]) {
792 if ($self->{slot}->[$slot] eq 'empty') {
795 return 1; # vol, full, loaded
798 sub slot_get_first_free
801 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
802 return $slot unless ($self->slot_is_full($slot));
806 sub io_get_first_free
810 foreach my $slot (@{ $self->{io} }) {
811 return $slot unless ($self->slot_is_full($slot));
818 my ($self, $media) = @_;
820 return $self->{label}->{$media} ;
825 my ($self, $media) = @_;
827 return defined $self->{label}->{$media} ;
832 my ($self, $slot) = @_;
834 unless ($self->slot_is_full($slot)) {
835 print "Autochanger $self->{name} slot $slot is empty\n";
840 if ($self->is_slot_loaded($slot)) {
843 print "Autochanger $self->{name} $slot is currently in use\n";
847 # autochanger must have I/O
848 unless ($self->have_io()) {
849 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
853 my $dst = $self->io_get_first_free();
856 print "Autochanger $self->{name} you must empty I/O first\n";
859 $self->transfer($slot, $dst);
864 my ($self, $src, $dst) = @_ ;
865 if ($self->{debug}) {
866 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
868 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
871 my $content = $self->get_slot($src);
872 $self->{slot}->[$src] = 'empty';
873 $self->set_slot($dst, $content);
876 $self->{error} = $out;
883 my ($self, $index) = @_;
884 return $self->{drive_name}->[$index];
887 # TODO : do a tapeinfo request to get informations
897 for my $slot (@{$self->{io}})
899 if ($self->is_slot_loaded($slot)) {
900 print "$slot is currently loaded\n";
904 if ($self->slot_is_full($slot))
906 my $free = $self->slot_get_first_free() ;
907 print "move $slot to $free :\n";
910 if ($self->transfer($slot, $free)) {
911 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
913 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
917 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
923 # TODO : this is with mtx status output,
924 # we can do an other function from bacula view (with StorageId)
928 my $bweb = $self->{bweb};
930 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
931 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
934 SELECT Media.VolumeName AS volumename,
935 Media.VolStatus AS volstatus,
936 Media.LastWritten AS lastwritten,
937 Media.VolBytes AS volbytes,
938 Media.MediaType AS mediatype,
940 Media.InChanger AS inchanger,
942 $bweb->{sql}->{FROM_UNIXTIME}(
943 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
944 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
947 INNER JOIN Pool USING (PoolId)
949 WHERE Media.VolumeName IN ($media_list)
952 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
954 # TODO : verify slot and bacula slot
958 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
960 if ($self->slot_is_full($slot)) {
962 my $vol = $self->{slot}->[$slot];
963 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
965 my $bslot = $all->{$vol}->{slot} ;
966 my $inchanger = $all->{$vol}->{inchanger};
968 # if bacula slot or inchanger flag is bad, we display a message
969 if ($bslot != $slot or !$inchanger) {
970 push @to_update, $slot;
973 $all->{$vol}->{realslot} = $slot;
975 push @{ $param }, $all->{$vol};
977 } else { # empty or no label
978 push @{ $param }, {realslot => $slot,
979 volstatus => 'Unknown',
980 volumename => $self->{slot}->[$slot]} ;
983 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
987 my $i=0; my $drives = [] ;
988 foreach my $d (@{ $self->{drive} }) {
989 $drives->[$i] = { index => $i,
990 load => $self->{drive}->[$i],
991 name => $self->{drive_name}->[$i],
996 $bweb->display({ Name => $self->{name},
997 nb_drive => $self->{info}->{drive},
998 nb_io => $self->{info}->{io},
1001 Update => scalar(@to_update) },
1009 ################################################################
1013 use base q/Bweb::Gui/;
1017 Bweb - main Bweb package
1021 this package is use to compute and display informations
1026 use POSIX qw/strftime/;
1028 our $config_file='/etc/bacula/bweb.conf';
1034 %sql_func - hash to make query mysql/postgresql compliant
1040 UNIX_TIMESTAMP => '',
1041 FROM_UNIXTIME => '',
1042 TO_SEC => " interval '1 second' * ",
1043 SEC_TO_INT => "SEC_TO_INT",
1046 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1047 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1048 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1049 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1050 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1051 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1052 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1053 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1054 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1057 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1058 FROM_UNIXTIME => 'FROM_UNIXTIME',
1061 SEC_TO_TIME => 'SEC_TO_TIME',
1062 MATCH => " REGEXP ",
1063 STARTTIME_DAY => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1064 STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1065 STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1066 STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1067 STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1068 STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1069 STARTTIME_PWEEK => " DATE_FORMAT(StartTime, '%v') ",
1070 # with mysql < 5, you have to play with the ugly SHOW command
1071 DB_SIZE => " SELECT 0 ",
1072 # works only with mysql 5
1073 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1074 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1082 $self->{dbh}->disconnect();
1087 sub dbh_selectall_arrayref
1089 my ($self, $query) = @_;
1090 $self->connect_db();
1091 $self->debug($query);
1092 return $self->{dbh}->selectall_arrayref($query);
1097 my ($self, @what) = @_;
1098 return join(',', $self->dbh_quote(@what)) ;
1103 my ($self, @what) = @_;
1105 $self->connect_db();
1107 return map { $self->{dbh}->quote($_) } @what;
1109 return $self->{dbh}->quote($what[0]) ;
1115 my ($self, $query) = @_ ;
1116 $self->connect_db();
1117 $self->debug($query);
1118 return $self->{dbh}->do($query);
1121 sub dbh_selectall_hashref
1123 my ($self, $query, $join) = @_;
1125 $self->connect_db();
1126 $self->debug($query);
1127 return $self->{dbh}->selectall_hashref($query, $join) ;
1130 sub dbh_selectrow_hashref
1132 my ($self, $query) = @_;
1134 $self->connect_db();
1135 $self->debug($query);
1136 return $self->{dbh}->selectrow_hashref($query) ;
1141 my ($self, @what) = @_;
1142 if ($self->{conf}->{connection_string} =~ /dbi:mysql/i) {
1143 return 'CONCAT(' . join(',', @what) . ')' ;
1145 return join(' || ', @what);
1151 my ($self, $query) = @_;
1152 $self->debug($query, up => 1);
1153 return $self->{dbh}->prepare($query);
1159 my @unit = qw(B KB MB GB TB);
1160 my $val = shift || 0;
1162 my $format = '%i %s';
1163 while ($val / 1024 > 1) {
1167 $format = ($i>0)?'%0.1f %s':'%i %s';
1168 return sprintf($format, $val, $unit[$i]);
1171 # display Day, Hour, Year
1177 $val /= 60; # sec -> min
1179 if ($val / 60 <= 1) {
1183 $val /= 60; # min -> hour
1184 if ($val / 24 <= 1) {
1185 return "$val hours";
1188 $val /= 24; # hour -> day
1189 if ($val / 365 < 2) {
1193 $val /= 365 ; # day -> year
1195 return "$val years";
1198 # get Day, Hour, Year
1204 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1208 my %times = ( m => 60,
1214 my $mult = $times{$2} || 0;
1224 unless ($self->{dbh}) {
1225 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1226 $self->{info}->{user},
1227 $self->{info}->{password});
1229 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1230 unless ($self->{dbh});
1232 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1234 if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
1235 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1242 my ($class, %arg) = @_;
1244 dbh => undef, # connect_db();
1246 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1252 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1254 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1255 $self->{sql} = $sql_func{$1};
1258 $self->{debug} = $self->{info}->{debug};
1259 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1267 $self->display($self->{info}, "begin.tpl");
1273 $self->display($self->{info}, "end.tpl");
1281 my $arg = $self->get_form("client", "qre_client", "jclient_groups", "qnotingroup");
1283 if ($arg->{qre_client}) {
1284 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1285 } elsif ($arg->{client}) {
1286 $where = "WHERE Name = '$arg->{client}' ";
1287 } elsif ($arg->{jclient_groups}) {
1288 $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
1289 JOIN client_group USING (client_group_id)
1290 WHERE client_group_name IN ($arg->{jclient_groups})";
1291 } elsif ($arg->{qnotingroup}) {
1294 (SELECT 1 FROM client_group_member
1295 WHERE Client.ClientId = client_group_member.ClientId
1302 SELECT Name AS name,
1304 AutoPrune AS autoprune,
1305 FileRetention AS fileretention,
1306 JobRetention AS jobretention
1311 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1313 my $dsp = { ID => $cur_id++,
1314 clients => [ values %$all] };
1316 $self->display($dsp, "client_list.tpl") ;
1321 my ($self, %arg) = @_;
1328 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1330 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1332 $self->{sql}->{TO_SEC}($arg{age})
1335 $label = "last " . human_sec($arg{age});
1338 if ($arg{groupby}) {
1339 $limit .= " GROUP BY $arg{groupby} ";
1343 $limit .= " ORDER BY $arg{order} ";
1347 $limit .= " LIMIT $arg{limit} ";
1348 $label .= " limited to $arg{limit}";
1352 $limit .= " OFFSET $arg{offset} ";
1353 $label .= " with $arg{offset} offset ";
1357 $label = 'no filter';
1360 return ($limit, $label);
1365 $bweb->get_form(...) - Get useful stuff
1369 This function get and check parameters against regexp.
1371 If word begin with 'q', the return will be quoted or join quoted
1372 if it's end with 's'.
1377 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1380 qclient => 'plume-fd',
1381 qpools => "'plume-fd', 'test-fd', '...'",
1388 my ($self, @what) = @_;
1389 my %what = map { $_ => 1 } @what;
1411 my %opt_ss =( # string with space
1415 my %opt_s = ( # default to ''
1433 my %opt_p = ( # option with path
1440 my %opt_r = (regexwhere => 1);
1442 my %opt_d = ( # option with date
1447 foreach my $i (@what) {
1448 if (exists $opt_i{$i}) {# integer param
1449 my $value = CGI::param($i) || $opt_i{$i} ;
1450 if ($value =~ /^(\d+)$/) {
1453 } elsif ($opt_s{$i}) { # simple string param
1454 my $value = CGI::param($i) || '';
1455 if ($value =~ /^([\w\d\.-]+)$/) {
1458 } elsif ($opt_ss{$i}) { # simple string param (with space)
1459 my $value = CGI::param($i) || '';
1460 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1463 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1464 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1466 $ret{$i} = $self->dbh_join(@value) ;
1469 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1470 my $value = CGI::param($1) ;
1472 $ret{$i} = $self->dbh_quote($value);
1475 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1476 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1477 grep { ! /^\s*$/ } CGI::param($1) ];
1478 } elsif (exists $opt_p{$i}) {
1479 my $value = CGI::param($i) || '';
1480 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1483 } elsif (exists $opt_r{$i}) {
1484 my $value = CGI::param($i) || '';
1485 if ($value =~ /^([^'"']+)$/) {
1488 } elsif (exists $opt_d{$i}) {
1489 my $value = CGI::param($i) || '';
1490 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1497 foreach my $s (CGI::param('slot')) {
1498 if ($s =~ /^(\d+)$/) {
1499 push @{$ret{slots}}, $s;
1505 my $when = CGI::param('when') || '';
1506 if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1511 if ($what{db_clients}) {
1513 SELECT Client.Name as clientname
1517 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1518 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1522 if ($what{db_client_groups}) {
1524 SELECT client_group_name AS name
1528 my $grps = $self->dbh_selectall_hashref($query, 'name');
1529 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1533 if ($what{db_mediatypes}) {
1535 SELECT MediaType as mediatype
1539 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1540 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1544 if ($what{db_locations}) {
1546 SELECT Location as location, Cost as cost
1549 my $loc = $self->dbh_selectall_hashref($query, 'location');
1550 $ret{db_locations} = [ sort { $a->{location}
1556 if ($what{db_pools}) {
1557 my $query = "SELECT Name as name FROM Pool";
1559 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1560 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1563 if ($what{db_filesets}) {
1565 SELECT FileSet.FileSet AS fileset
1569 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1571 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1572 values %$filesets] ;
1575 if ($what{db_jobnames}) {
1577 SELECT DISTINCT Job.Name AS jobname
1581 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1583 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1584 values %$jobnames] ;
1587 if ($what{db_devices}) {
1589 SELECT Device.Name AS name
1593 my $devices = $self->dbh_selectall_hashref($query, 'name');
1595 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1606 my $fields = $self->get_form(qw/age level status clients filesets
1608 db_clients limit db_filesets width height
1609 qclients qfilesets qjobnames db_jobnames/);
1612 my $url = CGI::url(-full => 0,
1615 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1617 # this organisation is to keep user choice between 2 click
1618 # TODO : fileset and client selection doesn't work
1627 sub display_client_job
1629 my ($self, %arg) = @_ ;
1631 $arg{order} = ' Job.JobId DESC ';
1632 my ($limit, $label) = $self->get_limit(%arg);
1634 my $clientname = $self->dbh_quote($arg{clientname});
1637 SELECT DISTINCT Job.JobId AS jobid,
1638 Job.Name AS jobname,
1639 FileSet.FileSet AS fileset,
1641 StartTime AS starttime,
1642 JobFiles AS jobfiles,
1643 JobBytes AS jobbytes,
1644 JobStatus AS jobstatus,
1645 JobErrors AS joberrors
1647 FROM Client,Job,FileSet
1648 WHERE Client.Name=$clientname
1649 AND Client.ClientId=Job.ClientId
1650 AND Job.FileSetId=FileSet.FileSetId
1654 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1656 $self->display({ clientname => $arg{clientname},
1659 Jobs => [ values %$all ],
1661 "display_client_job.tpl") ;
1664 sub get_selected_media_location
1668 my $medias = $self->get_form('jmedias');
1670 unless ($medias->{jmedias}) {
1675 SELECT Media.VolumeName AS volumename, Location.Location AS location
1676 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1677 WHERE Media.VolumeName IN ($medias->{jmedias})
1680 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1682 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1693 my $medias = $self->get_selected_media_location();
1699 my $elt = $self->get_form('db_locations');
1701 $self->display({ ID => $cur_id++,
1702 %$elt, # db_locations
1704 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1714 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1716 $self->display($elt, "help_extern.tpl");
1719 sub help_extern_compute
1723 my $number = CGI::param('limit') || '' ;
1724 unless ($number =~ /^(\d+)$/) {
1725 return $self->error("Bad arg number : $number ");
1728 my ($sql, undef) = $self->get_param('pools',
1729 'locations', 'mediatypes');
1732 SELECT Media.VolumeName AS volumename,
1733 Media.VolStatus AS volstatus,
1734 Media.LastWritten AS lastwritten,
1735 Media.MediaType AS mediatype,
1736 Media.VolMounts AS volmounts,
1738 Media.Recycle AS recycle,
1739 $self->{sql}->{FROM_UNIXTIME}(
1740 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1741 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1744 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1745 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1747 WHERE Media.InChanger = 1
1748 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1750 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1754 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1756 $self->display({ Medias => [ values %$all ] },
1757 "help_extern_compute.tpl");
1764 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1765 $self->display($param, "help_intern.tpl");
1768 sub help_intern_compute
1772 my $number = CGI::param('limit') || '' ;
1773 unless ($number =~ /^(\d+)$/) {
1774 return $self->error("Bad arg number : $number ");
1777 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1779 if (CGI::param('expired')) {
1781 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1782 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1788 SELECT Media.VolumeName AS volumename,
1789 Media.VolStatus AS volstatus,
1790 Media.LastWritten AS lastwritten,
1791 Media.MediaType AS mediatype,
1792 Media.VolMounts AS volmounts,
1794 $self->{sql}->{FROM_UNIXTIME}(
1795 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1796 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1799 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1800 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1802 WHERE Media.InChanger <> 1
1803 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1804 AND Media.Recycle = 1
1806 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1810 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1812 $self->display({ Medias => [ values %$all ] },
1813 "help_intern_compute.tpl");
1819 my ($self, %arg) = @_ ;
1821 my ($limit, $label) = $self->get_limit(%arg);
1825 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1826 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1827 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1828 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1829 ($self->{sql}->{DB_SIZE}) AS db_size,
1830 (SELECT count(Job.JobId)
1832 WHERE Job.JobStatus IN ('E','e','f','A')
1835 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1838 my $row = $self->dbh_selectrow_hashref($query) ;
1840 $row->{nb_bytes} = human_size($row->{nb_bytes});
1842 $row->{db_size} = human_size($row->{db_size});
1843 $row->{label} = $label;
1845 $self->display($row, "general.tpl");
1850 my ($self, @what) = @_ ;
1851 my %elt = map { $_ => 1 } @what;
1856 if ($elt{clients}) {
1857 my @clients = grep { ! /^\s*$/ } CGI::param('client');
1859 $ret{clients} = \@clients;
1860 my $str = $self->dbh_join(@clients);
1861 $limit .= "AND Client.Name IN ($str) ";
1865 if ($elt{client_groups}) {
1866 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1868 $ret{client_groups} = \@clients;
1869 my $str = $self->dbh_join(@clients);
1870 $limit .= "AND client_group_name IN ($str) ";
1874 if ($elt{filesets}) {
1875 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1877 $ret{filesets} = \@filesets;
1878 my $str = $self->dbh_join(@filesets);
1879 $limit .= "AND FileSet.FileSet IN ($str) ";
1883 if ($elt{mediatypes}) {
1884 my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
1886 $ret{mediatypes} = \@medias;
1887 my $str = $self->dbh_join(@medias);
1888 $limit .= "AND Media.MediaType IN ($str) ";
1893 my $client = CGI::param('client');
1894 $ret{client} = $client;
1895 $client = $self->dbh_join($client);
1896 $limit .= "AND Client.Name = $client ";
1900 my $level = CGI::param('level') || '';
1901 if ($level =~ /^(\w)$/) {
1903 $limit .= "AND Job.Level = '$1' ";
1908 my $jobid = CGI::param('jobid') || '';
1910 if ($jobid =~ /^(\d+)$/) {
1912 $limit .= "AND Job.JobId = '$1' ";
1917 my $status = CGI::param('status') || '';
1918 if ($status =~ /^(\w)$/) {
1921 $limit .= "AND Job.JobStatus IN ('f','E') ";
1922 } elsif ($1 eq 'W') {
1923 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
1925 $limit .= "AND Job.JobStatus = '$1' ";
1930 if ($elt{volstatus}) {
1931 my $status = CGI::param('volstatus') || '';
1932 if ($status =~ /^(\w+)$/) {
1934 $limit .= "AND Media.VolStatus = '$1' ";
1938 if ($elt{locations}) {
1939 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1941 $ret{locations} = \@location;
1942 my $str = $self->dbh_join(@location);
1943 $limit .= "AND Location.Location IN ($str) ";
1948 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1950 $ret{pools} = \@pool;
1951 my $str = $self->dbh_join(@pool);
1952 $limit .= "AND Pool.Name IN ($str) ";
1956 if ($elt{location}) {
1957 my $location = CGI::param('location') || '';
1959 $ret{location} = $location;
1960 $location = $self->dbh_quote($location);
1961 $limit .= "AND Location.Location = $location ";
1966 my $pool = CGI::param('pool') || '';
1969 $pool = $self->dbh_quote($pool);
1970 $limit .= "AND Pool.Name = $pool ";
1974 if ($elt{jobtype}) {
1975 my $jobtype = CGI::param('jobtype') || '';
1976 if ($jobtype =~ /^(\w)$/) {
1978 $limit .= "AND Job.Type = '$1' ";
1982 return ($limit, %ret);
1993 my ($self, %arg) = @_ ;
1995 $arg{order} = ' Job.JobId DESC ';
1997 my ($limit, $label) = $self->get_limit(%arg);
1998 my ($where, undef) = $self->get_param('clients',
2008 if (CGI::param('client_group')) {
2010 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2011 LEFT JOIN client_group USING (client_group_id)
2016 SELECT Job.JobId AS jobid,
2017 Client.Name AS client,
2018 FileSet.FileSet AS fileset,
2019 Job.Name AS jobname,
2021 StartTime AS starttime,
2023 Pool.Name AS poolname,
2024 JobFiles AS jobfiles,
2025 JobBytes AS jobbytes,
2026 JobStatus AS jobstatus,
2027 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2028 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2031 JobErrors AS joberrors
2034 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2035 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2037 WHERE Client.ClientId=Job.ClientId
2038 AND Job.JobStatus NOT IN ('R', 'C')
2043 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2045 $self->display({ Filter => $label,
2049 sort { $a->{jobid} <=> $b->{jobid} }
2056 # display job informations
2057 sub display_job_zoom
2059 my ($self, $jobid) = @_ ;
2061 $jobid = $self->dbh_quote($jobid);
2064 SELECT DISTINCT Job.JobId AS jobid,
2065 Client.Name AS client,
2066 Job.Name AS jobname,
2067 FileSet.FileSet AS fileset,
2069 Pool.Name AS poolname,
2070 StartTime AS starttime,
2071 JobFiles AS jobfiles,
2072 JobBytes AS jobbytes,
2073 JobStatus AS jobstatus,
2074 JobErrors AS joberrors,
2075 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2076 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2079 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2080 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2081 WHERE Client.ClientId=Job.ClientId
2082 AND Job.JobId = $jobid
2085 my $row = $self->dbh_selectrow_hashref($query) ;
2087 # display all volumes associate with this job
2089 SELECT Media.VolumeName as volumename
2090 FROM Job,Media,JobMedia
2091 WHERE Job.JobId = $jobid
2092 AND JobMedia.JobId=Job.JobId
2093 AND JobMedia.MediaId=Media.MediaId
2096 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2098 $row->{volumes} = [ values %$all ] ;
2100 $self->display($row, "display_job_zoom.tpl");
2103 sub display_job_group
2105 my ($self, %arg) = @_;
2107 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2109 my ($where, undef) = $self->get_param('client_groups',
2115 SELECT client_group_name AS client_group_name,
2116 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2117 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2118 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2119 COALESCE(jobok.nbjobs,0) AS nbjobok,
2120 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2121 COALESCE(jobok.duration, '0:0:0') AS duration
2123 FROM client_group LEFT JOIN (
2124 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2125 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2126 SUM(JobErrors) AS joberrors,
2127 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2128 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2131 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2132 JOIN client_group USING (client_group_id)
2134 WHERE JobStatus = 'T'
2137 ) AS jobok USING (client_group_name) LEFT JOIN
2140 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2141 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2142 SUM(JobErrors) AS joberrors
2143 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2144 JOIN client_group USING (client_group_id)
2146 WHERE JobStatus IN ('f','E', 'A')
2149 ) AS joberr USING (client_group_name)
2153 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2155 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2158 $self->display($rep, "display_job_group.tpl");
2163 my ($self, %arg) = @_ ;
2165 my ($limit, $label) = $self->get_limit(%arg);
2166 my ($where, %elt) = $self->get_param('pools',
2171 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2173 if ($arg->{jmedias}) {
2174 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2176 if ($arg->{qre_media}) {
2177 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2179 if ($arg->{expired}) {
2181 AND VolStatus = 'Full'
2182 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2183 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2184 ) < NOW() " . $where ;
2188 SELECT Media.VolumeName AS volumename,
2189 Media.VolBytes AS volbytes,
2190 Media.VolStatus AS volstatus,
2191 Media.MediaType AS mediatype,
2192 Media.InChanger AS online,
2193 Media.LastWritten AS lastwritten,
2194 Location.Location AS location,
2195 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2196 Pool.Name AS poolname,
2197 $self->{sql}->{FROM_UNIXTIME}(
2198 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2199 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2202 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2203 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2204 Media.MediaType AS MediaType
2206 WHERE Media.VolStatus = 'Full'
2207 GROUP BY Media.MediaType
2208 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2210 WHERE Media.PoolId=Pool.PoolId
2215 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2217 $self->display({ ID => $cur_id++,
2219 Location => $elt{location},
2220 Medias => [ values %$all ],
2222 "display_media.tpl");
2229 my $pool = $self->get_form('db_pools');
2231 foreach my $name (@{ $pool->{db_pools} }) {
2232 CGI::param('pool', $name->{name});
2233 $self->display_media();
2237 sub display_media_zoom
2241 my $medias = $self->get_form('jmedias');
2243 unless ($medias->{jmedias}) {
2244 return $self->error("Can't get media selection");
2248 SELECT InChanger AS online,
2249 VolBytes AS nb_bytes,
2250 VolumeName AS volumename,
2251 VolStatus AS volstatus,
2252 VolMounts AS nb_mounts,
2253 Media.VolUseDuration AS voluseduration,
2254 Media.MaxVolJobs AS maxvoljobs,
2255 Media.MaxVolFiles AS maxvolfiles,
2256 Media.MaxVolBytes AS maxvolbytes,
2257 VolErrors AS nb_errors,
2258 Pool.Name AS poolname,
2259 Location.Location AS location,
2260 Media.Recycle AS recycle,
2261 Media.VolRetention AS volretention,
2262 Media.LastWritten AS lastwritten,
2263 Media.VolReadTime/1000000 AS volreadtime,
2264 Media.VolWriteTime/1000000 AS volwritetime,
2265 Media.RecycleCount AS recyclecount,
2266 Media.Comment AS comment,
2267 $self->{sql}->{FROM_UNIXTIME}(
2268 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2269 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2272 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2273 WHERE Pool.PoolId = Media.PoolId
2274 AND VolumeName IN ($medias->{jmedias})
2277 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2279 foreach my $media (values %$all) {
2280 my $mq = $self->dbh_quote($media->{volumename});
2283 SELECT DISTINCT Job.JobId AS jobid,
2285 Job.StartTime AS starttime,
2288 Job.JobFiles AS files,
2289 Job.JobBytes AS bytes,
2290 Job.jobstatus AS status
2291 FROM Media,JobMedia,Job
2292 WHERE Media.VolumeName=$mq
2293 AND Media.MediaId=JobMedia.MediaId
2294 AND JobMedia.JobId=Job.JobId
2297 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2300 SELECT LocationLog.Date AS date,
2301 Location.Location AS location,
2302 LocationLog.Comment AS comment
2303 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2304 WHERE Media.MediaId = LocationLog.MediaId
2305 AND Media.VolumeName = $mq
2309 my $log = $self->dbh_selectall_arrayref($query) ;
2311 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2314 $self->display({ jobs => [ values %$jobs ],
2315 LocationLog => $logtxt,
2317 "display_media_zoom.tpl");
2325 my $loc = $self->get_form('qlocation');
2326 unless ($loc->{qlocation}) {
2327 return $self->error("Can't get location");
2331 SELECT Location.Location AS location,
2332 Location.Cost AS cost,
2333 Location.Enabled AS enabled
2335 WHERE Location.Location = $loc->{qlocation}
2338 my $row = $self->dbh_selectrow_hashref($query);
2340 $self->display({ ID => $cur_id++,
2341 %$row }, "location_edit.tpl") ;
2349 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2350 unless ($arg->{qlocation}) {
2351 return $self->error("Can't get location");
2353 unless ($arg->{qnewlocation}) {
2354 return $self->error("Can't get new location name");
2356 unless ($arg->{cost}) {
2357 return $self->error("Can't get new cost");
2360 my $enabled = CGI::param('enabled') || '';
2361 $enabled = $enabled?1:0;
2364 UPDATE Location SET Cost = $arg->{cost},
2365 Location = $arg->{qnewlocation},
2367 WHERE Location.Location = $arg->{qlocation}
2370 $self->dbh_do($query);
2372 $self->location_display();
2378 my $arg = $self->get_form(qw/qlocation/) ;
2380 unless ($arg->{qlocation}) {
2381 return $self->error("Can't get location");
2385 SELECT count(Media.MediaId) AS nb
2386 FROM Media INNER JOIN Location USING (LocationID)
2387 WHERE Location = $arg->{qlocation}
2390 my $res = $self->dbh_selectrow_hashref($query);
2393 return $self->error("Sorry, the location must be empty");
2397 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2400 $self->dbh_do($query);
2402 $self->location_display();
2409 my $arg = $self->get_form(qw/qlocation cost/) ;
2411 unless ($arg->{qlocation}) {
2412 $self->display({}, "location_add.tpl");
2415 unless ($arg->{cost}) {
2416 return $self->error("Can't get new cost");
2419 my $enabled = CGI::param('enabled') || '';
2420 $enabled = $enabled?1:0;
2423 INSERT INTO Location (Location, Cost, Enabled)
2424 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2427 $self->dbh_do($query);
2429 $self->location_display();
2432 sub location_display
2437 SELECT Location.Location AS location,
2438 Location.Cost AS cost,
2439 Location.Enabled AS enabled,
2440 (SELECT count(Media.MediaId)
2442 WHERE Media.LocationId = Location.LocationId
2447 my $location = $self->dbh_selectall_hashref($query, 'location');
2449 $self->display({ ID => $cur_id++,
2450 Locations => [ values %$location ] },
2451 "display_location.tpl");
2458 my $medias = $self->get_selected_media_location();
2463 my $arg = $self->get_form('db_locations', 'qnewlocation');
2465 $self->display({ email => $self->{info}->{email_media},
2467 medias => [ values %$medias ],
2469 "update_location.tpl");
2472 ###########################################################
2478 my $grp = $self->get_form(qw/qclient_group db_clients/);
2481 unless ($grp->{qclient_group}) {
2482 return $self->error("Can't get group");
2487 FROM Client JOIN client_group_member using (clientid)
2488 JOIN client_group using (client_group_id)
2489 WHERE client_group_name = $grp->{qclient_group}
2492 my $row = $self->dbh_selectall_hashref($query, "name");
2494 $self->display({ ID => $cur_id++,
2495 client_group => $grp->{qclient_group},
2497 client_group_member => [ values %$row]},
2505 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2506 unless ($arg->{qclient_group}) {
2507 return $self->error("Can't get groups");
2510 $self->{dbh}->begin_work();
2513 DELETE FROM client_group_member
2514 WHERE client_group_id IN
2515 (SELECT client_group_id
2517 WHERE client_group_name = $arg->{qclient_group})
2519 $self->dbh_do($query);
2522 INSERT INTO client_group_member (clientid, client_group_id)
2524 (SELECT client_group_id
2526 WHERE client_group_name = $arg->{qclient_group})
2527 FROM Client WHERE Name IN ($arg->{jclients})
2530 $self->dbh_do($query);
2532 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2535 SET client_group_name = $arg->{qnewgroup}
2536 WHERE client_group_name = $arg->{qclient_group}
2539 $self->dbh_do($query);
2542 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2544 $self->display_groups();
2550 my $arg = $self->get_form(qw/qclient_group/);
2552 unless ($arg->{qclient_group}) {
2553 return $self->error("Can't get groups");
2556 $self->{dbh}->begin_work();
2559 DELETE FROM client_group_member
2560 WHERE client_group_id IN
2561 (SELECT client_group_id
2563 WHERE client_group_name = $arg->{qclient_group});
2565 DELETE FROM client_group
2566 WHERE client_group_name = $arg->{qclient_group};
2568 $self->dbh_do($query);
2570 $self->{dbh}->commit();
2572 $self->display_groups();
2579 my $arg = $self->get_form(qw/qclient_group/) ;
2581 unless ($arg->{qclient_group}) {
2582 $self->display({}, "groups_add.tpl");
2587 INSERT INTO client_group (client_group_name)
2588 VALUES ($arg->{qclient_group})
2591 $self->dbh_do($query);
2593 $self->display_groups();
2600 my $arg = $self->get_form(qw/db_client_groups/) ;
2602 if ($self->{dbh}->errstr) {
2603 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2608 $self->display({ ID => $cur_id++,
2610 "display_groups.tpl");
2613 ###########################################################
2615 sub get_media_max_size
2617 my ($self, $type) = @_;
2619 "SELECT avg(VolBytes) AS size
2621 WHERE Media.VolStatus = 'Full'
2622 AND Media.MediaType = '$type'
2625 my $res = $self->selectrow_hashref($query);
2628 return $res->{size};
2638 my $media = $self->get_form('qmedia');
2640 unless ($media->{qmedia}) {
2641 return $self->error("Can't get media");
2645 SELECT Media.Slot AS slot,
2646 PoolMedia.Name AS poolname,
2647 Media.VolStatus AS volstatus,
2648 Media.InChanger AS inchanger,
2649 Location.Location AS location,
2650 Media.VolumeName AS volumename,
2651 Media.MaxVolBytes AS maxvolbytes,
2652 Media.MaxVolJobs AS maxvoljobs,
2653 Media.MaxVolFiles AS maxvolfiles,
2654 Media.VolUseDuration AS voluseduration,
2655 Media.VolRetention AS volretention,
2656 Media.Comment AS comment,
2657 PoolRecycle.Name AS poolrecycle
2659 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2660 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2661 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2663 WHERE Media.VolumeName = $media->{qmedia}
2666 my $row = $self->dbh_selectrow_hashref($query);
2667 $row->{volretention} = human_sec($row->{volretention});
2668 $row->{voluseduration} = human_sec($row->{voluseduration});
2670 my $elt = $self->get_form(qw/db_pools db_locations/);
2675 }, "update_media.tpl");
2682 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2684 unless ($arg->{jmedias}) {
2685 return $self->error("Can't get selected media");
2688 unless ($arg->{qnewlocation}) {
2689 return $self->error("Can't get new location");
2694 SET LocationId = (SELECT LocationId
2696 WHERE Location = $arg->{qnewlocation})
2697 WHERE Media.VolumeName IN ($arg->{jmedias})
2700 my $nb = $self->dbh_do($query);
2702 print "$nb media updated, you may have to update your autochanger.";
2704 $self->display_media();
2711 my $medias = $self->get_selected_media_location();
2713 return $self->error("Can't get media selection");
2715 my $newloc = CGI::param('newlocation');
2717 my $user = CGI::param('user') || 'unknown';
2718 my $comm = CGI::param('comment') || '';
2719 $comm = $self->dbh_quote("$user: $comm");
2723 foreach my $media (keys %$medias) {
2725 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2727 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2728 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2729 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2732 $self->dbh_do($query);
2733 $self->debug($query);
2737 $q->param('action', 'update_location');
2738 my $url = $q->url(-full => 1, -query=>1);
2740 $self->display({ email => $self->{info}->{email_media},
2742 newlocation => $newloc,
2743 # [ { volumename => 'vol1' }, { volumename => 'vol2'
\81\81 },..]
2744 medias => [ values %$medias ],
2746 "change_location.tpl");
2750 sub display_client_stats
2752 my ($self, %arg) = @_ ;
2754 my $client = $self->dbh_quote($arg{clientname});
2756 my ($limit, $label) = $self->get_limit(%arg);
2760 count(Job.JobId) AS nb_jobs,
2761 sum(Job.JobBytes) AS nb_bytes,
2762 sum(Job.JobErrors) AS nb_err,
2763 sum(Job.JobFiles) AS nb_files,
2764 Client.Name AS clientname
2765 FROM Job JOIN Client USING (ClientId)
2767 Client.Name = $client
2769 GROUP BY Client.Name
2772 my $row = $self->dbh_selectrow_hashref($query);
2774 $row->{ID} = $cur_id++;
2775 $row->{label} = $label;
2776 $row->{grapharg} = "client";
2778 $self->display($row, "display_client_stats.tpl");
2782 sub display_group_stats
2784 my ($self, %arg) = @_ ;
2786 my $carg = $self->get_form(qw/qclient_group/);
2788 unless ($carg->{qclient_group}) {
2789 return $self->error("Can't get group");
2792 my ($limit, $label) = $self->get_limit(%arg);
2796 count(Job.JobId) AS nb_jobs,
2797 sum(Job.JobBytes) AS nb_bytes,
2798 sum(Job.JobErrors) AS nb_err,
2799 sum(Job.JobFiles) AS nb_files,
2800 client_group.client_group_name AS clientname
2801 FROM Job JOIN Client USING (ClientId)
2802 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
2803 JOIN client_group USING (client_group_id)
2805 client_group.client_group_name = $carg->{qclient_group}
2807 GROUP BY client_group.client_group_name
2810 my $row = $self->dbh_selectrow_hashref($query);
2812 $row->{ID} = $cur_id++;
2813 $row->{label} = $label;
2814 $row->{grapharg} = "client_group";
2816 $self->display($row, "display_client_stats.tpl");
2819 # poolname can be undef
2822 my ($self, $poolname) = @_ ;
2826 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2827 if ($arg->{jmediatypes}) {
2828 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2829 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
2832 # TODO : afficher les tailles et les dates
2835 SELECT subq.volmax AS volmax,
2836 subq.volnum AS volnum,
2837 subq.voltotal AS voltotal,
2839 Pool.Recycle AS recycle,
2840 Pool.VolRetention AS volretention,
2841 Pool.VolUseDuration AS voluseduration,
2842 Pool.MaxVolJobs AS maxvoljobs,
2843 Pool.MaxVolFiles AS maxvolfiles,
2844 Pool.MaxVolBytes AS maxvolbytes,
2845 subq.PoolId AS PoolId,
2846 subq.MediaType AS mediatype,
2847 $self->{sql}->{CAT_POOL_TYPE} AS uniq
2850 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2851 count(Media.MediaId) AS volnum,
2852 sum(Media.VolBytes) AS voltotal,
2853 Media.PoolId AS PoolId,
2854 Media.MediaType AS MediaType
2856 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2857 Media.MediaType AS MediaType
2859 WHERE Media.VolStatus = 'Full'
2860 GROUP BY Media.MediaType
2861 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2862 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2864 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2868 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
2871 SELECT Pool.Name AS name,
2872 sum(VolBytes) AS size
2873 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2874 WHERE Media.VolStatus IN ('Recycled', 'Purged')
2878 my $empty = $self->dbh_selectall_hashref($query, 'name');
2880 foreach my $p (values %$all) {
2881 if ($p->{volmax} > 0) { # mysql returns 0.0000
2882 # we remove Recycled/Purged media from pool usage
2883 if (defined $empty->{$p->{name}}) {
2884 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2886 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2888 $p->{poolusage} = 0;
2892 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2894 WHERE PoolId=$p->{poolid}
2895 AND Media.MediaType = '$p->{mediatype}'
2899 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2900 foreach my $t (values %$content) {
2901 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2906 $self->display({ ID => $cur_id++,
2907 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2908 Pools => [ values %$all ]},
2909 "display_pool.tpl");
2912 sub display_running_job
2916 my $arg = $self->get_form('client', 'jobid');
2918 if (!$arg->{client} and $arg->{jobid}) {
2921 SELECT Client.Name AS name
2922 FROM Job INNER JOIN Client USING (ClientId)
2923 WHERE Job.JobId = $arg->{jobid}
2926 my $row = $self->dbh_selectrow_hashref($query);
2929 $arg->{client} = $row->{name};
2930 CGI::param('client', $arg->{client});
2934 if ($arg->{client}) {
2935 my $cli = new Bweb::Client(name => $arg->{client});
2936 $cli->display_running_job($self->{info}, $arg->{jobid});
2937 if ($arg->{jobid}) {
2938 $self->get_job_log();
2941 $self->error("Can't get client or jobid");
2945 sub display_running_jobs
2947 my ($self, $display_action) = @_;
2950 SELECT Job.JobId AS jobid,
2951 Job.Name AS jobname,
2953 Job.StartTime AS starttime,
2954 Job.JobFiles AS jobfiles,
2955 Job.JobBytes AS jobbytes,
2956 Job.JobStatus AS jobstatus,
2957 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2958 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2960 Client.Name AS clientname
2961 FROM Job INNER JOIN Client USING (ClientId)
2962 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2964 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2966 $self->display({ ID => $cur_id++,
2967 display_action => $display_action,
2968 Jobs => [ values %$all ]},
2969 "running_job.tpl") ;
2972 # return the autochanger list to update
2977 my $arg = $self->get_form('jmedias');
2979 unless ($arg->{jmedias}) {
2980 return $self->error("Can't get media selection");
2984 SELECT Media.VolumeName AS volumename,
2985 Storage.Name AS storage,
2986 Location.Location AS location,
2988 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2989 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2990 WHERE Media.VolumeName IN ($arg->{jmedias})
2991 AND Media.InChanger = 1
2994 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2996 foreach my $vol (values %$all) {
2997 my $a = $self->ach_get($vol->{location});
2999 $ret{$vol->{location}} = 1;
3001 unless ($a->{have_status}) {
3003 $a->{have_status} = 1;
3006 print "eject $vol->{volumename} from $vol->{storage} : ";
3007 if ($a->send_to_io($vol->{slot})) {
3008 print "<img src='/bweb/T.png' alt='ok'><br/>";
3010 print "<img src='/bweb/E.png' alt='err'><br/>";
3020 my ($to, $subject, $content) = (CGI::param('email'),
3021 CGI::param('subject'),
3022 CGI::param('content'));
3023 $to =~ s/[^\w\d\.\@<>,]//;
3024 $subject =~ s/[^\w\d\.\[\]]/ /;
3026 open(MAIL, "|mail -s '$subject' '$to'") ;
3027 print MAIL $content;
3037 my $arg = $self->get_form('jobid', 'client');
3039 print CGI::header('text/brestore');
3040 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3041 print "client=$arg->{client}\n" if ($arg->{client});
3042 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3046 # TODO : move this to Bweb::Autochanger ?
3047 # TODO : make this internal to not eject tape ?
3053 my ($self, $name) = @_;
3056 return $self->error("Can't get your autochanger name ach");
3059 unless ($self->{info}->{ach_list}) {
3060 return $self->error("Could not find any autochanger");
3063 my $a = $self->{info}->{ach_list}->{$name};
3066 $self->error("Can't get your autochanger $name from your ach_list");
3071 $a->{debug} = $self->{debug};
3078 my ($self, $ach) = @_;
3080 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3082 $self->{info}->save();
3090 my $arg = $self->get_form('ach');
3092 or !$self->{info}->{ach_list}
3093 or !$self->{info}->{ach_list}->{$arg->{ach}})
3095 return $self->error("Can't get autochanger name");
3098 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3102 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3104 my $b = $self->get_bconsole();
3106 my @storages = $b->list_storage() ;
3108 $ach->{devices} = [ map { { name => $_ } } @storages ];
3110 $self->display($ach, "ach_add.tpl");
3111 delete $ach->{drives};
3112 delete $ach->{devices};
3119 my $arg = $self->get_form('ach');
3122 or !$self->{info}->{ach_list}
3123 or !$self->{info}->{ach_list}->{$arg->{ach}})
3125 return $self->error("Can't get autochanger name");
3128 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3130 $self->{info}->save();
3131 $self->{info}->view();
3137 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3139 my $b = $self->get_bconsole();
3140 my @storages = $b->list_storage() ;
3142 unless ($arg->{ach}) {
3143 $arg->{devices} = [ map { { name => $_ } } @storages ];
3144 return $self->display($arg, "ach_add.tpl");
3148 foreach my $drive (CGI::param('drives'))
3150 unless (grep(/^$drive$/,@storages)) {
3151 return $self->error("Can't find $drive in storage list");
3154 my $index = CGI::param("index_$drive");
3155 unless (defined $index and $index =~ /^(\d+)$/) {
3156 return $self->error("Can't get $drive index");
3159 $drives[$index] = $drive;
3163 return $self->error("Can't get drives from Autochanger");
3166 my $a = new Bweb::Autochanger(name => $arg->{ach},
3167 precmd => $arg->{precmd},
3168 drive_name => \@drives,
3169 device => $arg->{device},
3170 mtxcmd => $arg->{mtxcmd});
3172 $self->ach_register($a) ;
3174 $self->{info}->view();
3180 my $arg = $self->get_form('jobid');
3182 if ($arg->{jobid}) {
3183 my $b = $self->get_bconsole();
3184 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3188 title => "Delete a job ",
3189 name => "delete jobid=$arg->{jobid}",
3198 my $arg = $self->get_form(qw/media volstatus inchanger pool
3199 slot volretention voluseduration
3200 maxvoljobs maxvolfiles maxvolbytes
3201 qcomment poolrecycle
3204 unless ($arg->{media}) {
3205 return $self->error("Can't find media selection");
3208 my $update = "update volume=$arg->{media} ";
3210 if ($arg->{volstatus}) {
3211 $update .= " volstatus=$arg->{volstatus} ";
3214 if ($arg->{inchanger}) {
3215 $update .= " inchanger=yes " ;
3217 $update .= " slot=$arg->{slot} ";
3220 $update .= " slot=0 inchanger=no ";
3224 $update .= " pool=$arg->{pool} " ;
3227 if (defined $arg->{volretention}) {
3228 $update .= " volretention=\"$arg->{volretention}\" " ;
3231 if (defined $arg->{voluseduration}) {
3232 $update .= " voluse=\"$arg->{voluseduration}\" " ;
3235 if (defined $arg->{maxvoljobs}) {
3236 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3239 if (defined $arg->{maxvolfiles}) {
3240 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3243 if (defined $arg->{maxvolbytes}) {
3244 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3247 if (defined $arg->{poolrecycle}) {
3248 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
3251 my $b = $self->get_bconsole();
3254 content => $b->send_cmd($update),
3255 title => "Update a volume ",
3261 my $media = $self->dbh_quote($arg->{media});
3263 my $loc = CGI::param('location') || '';
3265 $loc = $self->dbh_quote($loc); # is checked by db
3266 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3268 if (!$arg->{qcomment}) {
3269 $arg->{qcomment} = "''";
3271 push @q, "Comment=$arg->{qcomment}";
3276 SET " . join (',', @q) . "
3277 WHERE Media.VolumeName = $media
3279 $self->dbh_do($query);
3281 $self->update_media();
3288 my $ach = CGI::param('ach') ;
3289 $ach = $self->ach_get($ach);
3291 return $self->error("Bad autochanger name");
3295 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3296 $b->update_slots($ach->{name});
3304 my $arg = $self->get_form('jobid', 'limit', 'offset');
3305 unless ($arg->{jobid}) {
3306 return $self->error("Can't get jobid");
3309 if ($arg->{limit} == 100) {
3310 $arg->{limit} = 1000;
3313 my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3316 SELECT Job.Name as name, Client.Name as clientname
3317 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3318 WHERE JobId = $arg->{jobid}
3321 my $row = $self->dbh_selectrow_hashref($query);
3324 return $self->error("Can't find $arg->{jobid} in catalog");
3328 SELECT Time AS time, LogText AS log
3330 WHERE Log.JobId = $arg->{jobid}
3331 OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
3332 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3336 OFFSET $arg->{offset}
3339 my $log = $self->dbh_selectall_arrayref($query);
3341 return $self->error("Can't get log for jobid $arg->{jobid}");
3347 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
3349 $logtxt = join("", map { $_->[1] } @$log ) ;
3352 $self->display({ lines=> $logtxt,
3353 jobid => $arg->{jobid},
3354 name => $row->{name},
3355 client => $row->{clientname},
3356 offset => $arg->{offset},
3357 limit => $arg->{limit},
3358 }, 'display_log.tpl');
3366 my $arg = $self->get_form('ach', 'slots', 'drive');
3368 unless ($arg->{ach}) {
3369 return $self->error("Can't find autochanger name");
3372 my $a = $self->ach_get($arg->{ach});
3374 return $self->error("Can't find autochanger name in configuration");
3377 my $storage = $a->get_drive_name($arg->{drive});
3379 return $self->error("Can't get your drive name");
3385 if ($arg->{slots}) {
3386 $slots = join(",", @{ $arg->{slots} });
3387 $slots_sql = " AND Slot IN ($slots) ";
3388 $t += 60*scalar( @{ $arg->{slots} }) ;
3391 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3392 print "<h1>This command can take long time, be patient...</h1>";
3394 $b->label_barcodes(storage => $storage,
3395 drive => $arg->{drive},
3403 SET LocationId = (SELECT LocationId
3405 WHERE Location = '$arg->{ach}')
3407 WHERE (LocationId = 0 OR LocationId IS NULL)
3417 my @volume = CGI::param('media');
3420 return $self->error("Can't get media selection");
3423 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3425 foreach my $v (@volume) {
3427 content => $b->purge_volume($v),
3428 title => "Purge media",
3429 name => "purge volume=$v",
3439 my @volume = CGI::param('media');
3441 return $self->error("Can't get media selection");
3444 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3446 foreach my $v (@volume) {
3448 content => $b->prune_volume($v),
3449 title => "Prune volume",
3450 name => "prune volume=$v",
3460 my $arg = $self->get_form('jobid');
3461 unless ($arg->{jobid}) {
3462 return $self->error("Can't get jobid");
3465 my $b = $self->get_bconsole();
3467 content => $b->cancel($arg->{jobid}),
3468 title => "Cancel job",
3469 name => "cancel jobid=$arg->{jobid}",
3475 # Warning, we display current fileset
3478 my $arg = $self->get_form('fileset');
3480 if ($arg->{fileset}) {
3481 my $b = $self->get_bconsole();
3482 my $ret = $b->get_fileset($arg->{fileset});
3483 $self->display({ fileset => $arg->{fileset},
3485 }, "fileset_view.tpl");
3487 $self->error("Can't get fileset name");
3491 sub director_show_sched
3495 my $arg = $self->get_form('days');
3497 my $b = $self->get_bconsole();
3498 my $ret = $b->director_get_sched( $arg->{days} );
3503 }, "scheduled_job.tpl");
3506 sub enable_disable_job
3508 my ($self, $what) = @_ ;
3510 my $name = CGI::param('job') || '';
3511 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3512 return $self->error("Can't find job name");
3515 my $b = $self->get_bconsole();
3525 content => $b->send_cmd("$cmd job=\"$name\""),
3526 title => "$cmd $name",
3527 name => "$cmd job=\"$name\"",
3534 return new Bconsole(pref => $self->{info});
3540 my $b = $self->get_bconsole();
3542 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3544 $self->display({ Jobs => $joblist }, "run_job.tpl");
3549 my ($self, $ouput) = @_;
3552 foreach my $l (split(/\r\n/, $ouput)) {
3553 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3559 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3565 foreach my $k (keys %arg) {
3566 $lowcase{lc($k)} = $arg{$k} ;
3575 my $b = $self->get_bconsole();
3577 my $job = CGI::param('job') || '';
3579 # we take informations from director, and we overwrite with user wish
3580 my $info = $b->send_cmd("show job=\"$job\"");
3581 my $attr = $self->run_parse_job($info);
3583 my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3584 my %job_opt = (%$attr, %$arg);
3586 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3588 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3589 my $clients = [ map { { name => $_ } }$b->list_client()];
3590 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3591 my $storages= [ map { { name => $_ } }$b->list_storage()];
3596 clients => $clients,
3597 filesets => $filesets,
3598 storages => $storages,
3600 }, "run_job_mod.tpl");
3606 my $b = $self->get_bconsole();
3608 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3618 my $b = $self->get_bconsole();
3620 # TODO: check input (don't use pool, level)
3622 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3623 my $job = CGI::param('job') || '';
3624 my $storage = CGI::param('storage') || '';
3626 my $jobid = $b->run(job => $job,
3627 client => $arg->{client},
3628 priority => $arg->{priority},
3629 level => $arg->{level},
3630 storage => $storage,
3631 pool => $arg->{pool},
3632 fileset => $arg->{fileset},
3633 when => $arg->{when},
3636 print $jobid, $b->{error};
3638 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";