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 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1053 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1056 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1057 FROM_UNIXTIME => 'FROM_UNIXTIME',
1060 SEC_TO_TIME => 'SEC_TO_TIME',
1061 MATCH => " REGEXP ",
1062 STARTTIME_DAY => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1063 STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1064 STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1065 STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1066 STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1067 STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1068 # with mysql < 5, you have to play with the ugly SHOW command
1069 DB_SIZE => " SELECT 0 ",
1070 # works only with mysql 5
1071 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1072 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1080 $self->{dbh}->disconnect();
1085 sub dbh_selectall_arrayref
1087 my ($self, $query) = @_;
1088 $self->connect_db();
1089 $self->debug($query);
1090 return $self->{dbh}->selectall_arrayref($query);
1095 my ($self, @what) = @_;
1096 return join(',', $self->dbh_quote(@what)) ;
1101 my ($self, @what) = @_;
1103 $self->connect_db();
1105 return map { $self->{dbh}->quote($_) } @what;
1107 return $self->{dbh}->quote($what[0]) ;
1113 my ($self, $query) = @_ ;
1114 $self->connect_db();
1115 $self->debug($query);
1116 return $self->{dbh}->do($query);
1119 sub dbh_selectall_hashref
1121 my ($self, $query, $join) = @_;
1123 $self->connect_db();
1124 $self->debug($query);
1125 return $self->{dbh}->selectall_hashref($query, $join) ;
1128 sub dbh_selectrow_hashref
1130 my ($self, $query) = @_;
1132 $self->connect_db();
1133 $self->debug($query);
1134 return $self->{dbh}->selectrow_hashref($query) ;
1140 my @unit = qw(B KB MB GB TB);
1141 my $val = shift || 0;
1143 my $format = '%i %s';
1144 while ($val / 1024 > 1) {
1148 $format = ($i>0)?'%0.1f %s':'%i %s';
1149 return sprintf($format, $val, $unit[$i]);
1152 # display Day, Hour, Year
1158 $val /= 60; # sec -> min
1160 if ($val / 60 <= 1) {
1164 $val /= 60; # min -> hour
1165 if ($val / 24 <= 1) {
1166 return "$val hours";
1169 $val /= 24; # hour -> day
1170 if ($val / 365 < 2) {
1174 $val /= 365 ; # day -> year
1176 return "$val years";
1179 # get Day, Hour, Year
1185 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1189 my %times = ( m => 60,
1195 my $mult = $times{$2} || 0;
1205 unless ($self->{dbh}) {
1206 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1207 $self->{info}->{user},
1208 $self->{info}->{password});
1210 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1211 unless ($self->{dbh});
1213 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1215 if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
1216 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1223 my ($class, %arg) = @_;
1225 dbh => undef, # connect_db();
1227 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1233 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1235 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1236 $self->{sql} = $sql_func{$1};
1239 $self->{debug} = $self->{info}->{debug};
1240 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1248 $self->display($self->{info}, "begin.tpl");
1254 $self->display($self->{info}, "end.tpl");
1262 my $arg = $self->get_form("client", "qre_client", "jclient_groups", "qnotingroup");
1264 if ($arg->{qre_client}) {
1265 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1266 } elsif ($arg->{client}) {
1267 $where = "WHERE Name = '$arg->{client}' ";
1268 } elsif ($arg->{jclient_groups}) {
1269 $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
1270 JOIN client_group USING (client_group_id)
1271 WHERE client_group_name IN ($arg->{jclient_groups})";
1272 } elsif ($arg->{qnotingroup}) {
1275 (SELECT 1 FROM client_group_member
1276 WHERE Client.ClientId = client_group_member.ClientId
1283 SELECT Name AS name,
1285 AutoPrune AS autoprune,
1286 FileRetention AS fileretention,
1287 JobRetention AS jobretention
1292 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1294 my $dsp = { ID => $cur_id++,
1295 clients => [ values %$all] };
1297 $self->display($dsp, "client_list.tpl") ;
1302 my ($self, %arg) = @_;
1309 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1311 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1313 $self->{sql}->{TO_SEC}($arg{age})
1316 $label = "last " . human_sec($arg{age});
1319 if ($arg{groupby}) {
1320 $limit .= " GROUP BY $arg{groupby} ";
1324 $limit .= " ORDER BY $arg{order} ";
1328 $limit .= " LIMIT $arg{limit} ";
1329 $label .= " limited to $arg{limit}";
1333 $limit .= " OFFSET $arg{offset} ";
1334 $label .= " with $arg{offset} offset ";
1338 $label = 'no filter';
1341 return ($limit, $label);
1346 $bweb->get_form(...) - Get useful stuff
1350 This function get and check parameters against regexp.
1352 If word begin with 'q', the return will be quoted or join quoted
1353 if it's end with 's'.
1358 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1361 qclient => 'plume-fd',
1362 qpools => "'plume-fd', 'test-fd', '...'",
1369 my ($self, @what) = @_;
1370 my %what = map { $_ => 1 } @what;
1392 my %opt_ss =( # string with space
1396 my %opt_s = ( # default to ''
1413 my %opt_p = ( # option with path
1420 my %opt_r = (regexwhere => 1);
1422 my %opt_d = ( # option with date
1427 foreach my $i (@what) {
1428 if (exists $opt_i{$i}) {# integer param
1429 my $value = CGI::param($i) || $opt_i{$i} ;
1430 if ($value =~ /^(\d+)$/) {
1433 } elsif ($opt_s{$i}) { # simple string param
1434 my $value = CGI::param($i) || '';
1435 if ($value =~ /^([\w\d\.-]+)$/) {
1438 } elsif ($opt_ss{$i}) { # simple string param (with space)
1439 my $value = CGI::param($i) || '';
1440 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1443 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1444 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1446 $ret{$i} = $self->dbh_join(@value) ;
1449 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1450 my $value = CGI::param($1) ;
1452 $ret{$i} = $self->dbh_quote($value);
1455 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1456 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1457 grep { ! /^\s*$/ } CGI::param($1) ];
1458 } elsif (exists $opt_p{$i}) {
1459 my $value = CGI::param($i) || '';
1460 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1463 } elsif (exists $opt_r{$i}) {
1464 my $value = CGI::param($i) || '';
1465 if ($value =~ /^([^'"']+)$/) {
1468 } elsif (exists $opt_d{$i}) {
1469 my $value = CGI::param($i) || '';
1470 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1477 foreach my $s (CGI::param('slot')) {
1478 if ($s =~ /^(\d+)$/) {
1479 push @{$ret{slots}}, $s;
1485 my $when = CGI::param('when') || '';
1486 if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1491 if ($what{db_clients}) {
1493 SELECT Client.Name as clientname
1497 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1498 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1502 if ($what{db_client_groups}) {
1504 SELECT client_group_name AS name
1508 my $grps = $self->dbh_selectall_hashref($query, 'name');
1509 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1513 if ($what{db_mediatypes}) {
1515 SELECT MediaType as mediatype
1519 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1520 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1524 if ($what{db_locations}) {
1526 SELECT Location as location, Cost as cost
1529 my $loc = $self->dbh_selectall_hashref($query, 'location');
1530 $ret{db_locations} = [ sort { $a->{location}
1536 if ($what{db_pools}) {
1537 my $query = "SELECT Name as name FROM Pool";
1539 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1540 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1543 if ($what{db_filesets}) {
1545 SELECT FileSet.FileSet AS fileset
1549 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1551 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1552 values %$filesets] ;
1555 if ($what{db_jobnames}) {
1557 SELECT DISTINCT Job.Name AS jobname
1561 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1563 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1564 values %$jobnames] ;
1567 if ($what{db_devices}) {
1569 SELECT Device.Name AS name
1573 my $devices = $self->dbh_selectall_hashref($query, 'name');
1575 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1586 my $fields = $self->get_form(qw/age level status clients filesets
1588 db_clients limit db_filesets width height
1589 qclients qfilesets qjobnames db_jobnames/);
1592 my $url = CGI::url(-full => 0,
1595 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1597 # this organisation is to keep user choice between 2 click
1598 # TODO : fileset and client selection doesn't work
1607 sub display_client_job
1609 my ($self, %arg) = @_ ;
1611 $arg{order} = ' Job.JobId DESC ';
1612 my ($limit, $label) = $self->get_limit(%arg);
1614 my $clientname = $self->dbh_quote($arg{clientname});
1617 SELECT DISTINCT Job.JobId AS jobid,
1618 Job.Name AS jobname,
1619 FileSet.FileSet AS fileset,
1621 StartTime AS starttime,
1622 JobFiles AS jobfiles,
1623 JobBytes AS jobbytes,
1624 JobStatus AS jobstatus,
1625 JobErrors AS joberrors
1627 FROM Client,Job,FileSet
1628 WHERE Client.Name=$clientname
1629 AND Client.ClientId=Job.ClientId
1630 AND Job.FileSetId=FileSet.FileSetId
1634 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1636 $self->display({ clientname => $arg{clientname},
1639 Jobs => [ values %$all ],
1641 "display_client_job.tpl") ;
1644 sub get_selected_media_location
1648 my $medias = $self->get_form('jmedias');
1650 unless ($medias->{jmedias}) {
1655 SELECT Media.VolumeName AS volumename, Location.Location AS location
1656 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1657 WHERE Media.VolumeName IN ($medias->{jmedias})
1660 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1662 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1673 my $medias = $self->get_selected_media_location();
1679 my $elt = $self->get_form('db_locations');
1681 $self->display({ ID => $cur_id++,
1682 %$elt, # db_locations
1684 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1694 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1696 $self->display($elt, "help_extern.tpl");
1699 sub help_extern_compute
1703 my $number = CGI::param('limit') || '' ;
1704 unless ($number =~ /^(\d+)$/) {
1705 return $self->error("Bad arg number : $number ");
1708 my ($sql, undef) = $self->get_param('pools',
1709 'locations', 'mediatypes');
1712 SELECT Media.VolumeName AS volumename,
1713 Media.VolStatus AS volstatus,
1714 Media.LastWritten AS lastwritten,
1715 Media.MediaType AS mediatype,
1716 Media.VolMounts AS volmounts,
1718 Media.Recycle AS recycle,
1719 $self->{sql}->{FROM_UNIXTIME}(
1720 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1721 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1724 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1725 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1727 WHERE Media.InChanger = 1
1728 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1730 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1734 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1736 $self->display({ Medias => [ values %$all ] },
1737 "help_extern_compute.tpl");
1744 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1745 $self->display($param, "help_intern.tpl");
1748 sub help_intern_compute
1752 my $number = CGI::param('limit') || '' ;
1753 unless ($number =~ /^(\d+)$/) {
1754 return $self->error("Bad arg number : $number ");
1757 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1759 if (CGI::param('expired')) {
1761 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1762 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1768 SELECT Media.VolumeName AS volumename,
1769 Media.VolStatus AS volstatus,
1770 Media.LastWritten AS lastwritten,
1771 Media.MediaType AS mediatype,
1772 Media.VolMounts AS volmounts,
1774 $self->{sql}->{FROM_UNIXTIME}(
1775 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1776 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1779 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1780 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1782 WHERE Media.InChanger <> 1
1783 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1784 AND Media.Recycle = 1
1786 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1790 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1792 $self->display({ Medias => [ values %$all ] },
1793 "help_intern_compute.tpl");
1799 my ($self, %arg) = @_ ;
1801 my ($limit, $label) = $self->get_limit(%arg);
1805 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1806 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1807 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1808 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1809 ($self->{sql}->{DB_SIZE}) AS db_size,
1810 (SELECT count(Job.JobId)
1812 WHERE Job.JobStatus IN ('E','e','f','A')
1815 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1818 my $row = $self->dbh_selectrow_hashref($query) ;
1820 $row->{nb_bytes} = human_size($row->{nb_bytes});
1822 $row->{db_size} = human_size($row->{db_size});
1823 $row->{label} = $label;
1825 $self->display($row, "general.tpl");
1830 my ($self, @what) = @_ ;
1831 my %elt = map { $_ => 1 } @what;
1836 if ($elt{clients}) {
1837 my @clients = grep { ! /^\s*$/ } CGI::param('client');
1839 $ret{clients} = \@clients;
1840 my $str = $self->dbh_join(@clients);
1841 $limit .= "AND Client.Name IN ($str) ";
1845 if ($elt{client_groups}) {
1846 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1848 $ret{client_groups} = \@clients;
1849 my $str = $self->dbh_join(@clients);
1850 $limit .= "AND client_group_name IN ($str) ";
1854 if ($elt{filesets}) {
1855 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1857 $ret{filesets} = \@filesets;
1858 my $str = $self->dbh_join(@filesets);
1859 $limit .= "AND FileSet.FileSet IN ($str) ";
1863 if ($elt{mediatypes}) {
1864 my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
1866 $ret{mediatypes} = \@medias;
1867 my $str = $self->dbh_join(@medias);
1868 $limit .= "AND Media.MediaType IN ($str) ";
1873 my $client = CGI::param('client');
1874 $ret{client} = $client;
1875 $client = $self->dbh_join($client);
1876 $limit .= "AND Client.Name = $client ";
1880 my $level = CGI::param('level') || '';
1881 if ($level =~ /^(\w)$/) {
1883 $limit .= "AND Job.Level = '$1' ";
1888 my $jobid = CGI::param('jobid') || '';
1890 if ($jobid =~ /^(\d+)$/) {
1892 $limit .= "AND Job.JobId = '$1' ";
1897 my $status = CGI::param('status') || '';
1898 if ($status =~ /^(\w)$/) {
1901 $limit .= "AND Job.JobStatus IN ('f','E') ";
1902 } elsif ($1 eq 'W') {
1903 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
1905 $limit .= "AND Job.JobStatus = '$1' ";
1910 if ($elt{volstatus}) {
1911 my $status = CGI::param('volstatus') || '';
1912 if ($status =~ /^(\w+)$/) {
1914 $limit .= "AND Media.VolStatus = '$1' ";
1918 if ($elt{locations}) {
1919 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1921 $ret{locations} = \@location;
1922 my $str = $self->dbh_join(@location);
1923 $limit .= "AND Location.Location IN ($str) ";
1928 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1930 $ret{pools} = \@pool;
1931 my $str = $self->dbh_join(@pool);
1932 $limit .= "AND Pool.Name IN ($str) ";
1936 if ($elt{location}) {
1937 my $location = CGI::param('location') || '';
1939 $ret{location} = $location;
1940 $location = $self->dbh_quote($location);
1941 $limit .= "AND Location.Location = $location ";
1946 my $pool = CGI::param('pool') || '';
1949 $pool = $self->dbh_quote($pool);
1950 $limit .= "AND Pool.Name = $pool ";
1954 if ($elt{jobtype}) {
1955 my $jobtype = CGI::param('jobtype') || '';
1956 if ($jobtype =~ /^(\w)$/) {
1958 $limit .= "AND Job.Type = '$1' ";
1962 return ($limit, %ret);
1973 my ($self, %arg) = @_ ;
1975 $arg{order} = ' Job.JobId DESC ';
1977 my ($limit, $label) = $self->get_limit(%arg);
1978 my ($where, undef) = $self->get_param('clients',
1988 if (CGI::param('client_group')) {
1990 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
1991 LEFT JOIN client_group USING (client_group_id)
1996 SELECT Job.JobId AS jobid,
1997 Client.Name AS client,
1998 FileSet.FileSet AS fileset,
1999 Job.Name AS jobname,
2001 StartTime AS starttime,
2003 Pool.Name AS poolname,
2004 JobFiles AS jobfiles,
2005 JobBytes AS jobbytes,
2006 JobStatus AS jobstatus,
2007 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2008 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2011 JobErrors AS joberrors
2014 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2015 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2017 WHERE Client.ClientId=Job.ClientId
2018 AND Job.JobStatus NOT IN ('R', 'C')
2023 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2025 $self->display({ Filter => $label,
2029 sort { $a->{jobid} <=> $b->{jobid} }
2036 # display job informations
2037 sub display_job_zoom
2039 my ($self, $jobid) = @_ ;
2041 $jobid = $self->dbh_quote($jobid);
2044 SELECT DISTINCT Job.JobId AS jobid,
2045 Client.Name AS client,
2046 Job.Name AS jobname,
2047 FileSet.FileSet AS fileset,
2049 Pool.Name AS poolname,
2050 StartTime AS starttime,
2051 JobFiles AS jobfiles,
2052 JobBytes AS jobbytes,
2053 JobStatus AS jobstatus,
2054 JobErrors AS joberrors,
2055 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2056 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2059 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2060 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2061 WHERE Client.ClientId=Job.ClientId
2062 AND Job.JobId = $jobid
2065 my $row = $self->dbh_selectrow_hashref($query) ;
2067 # display all volumes associate with this job
2069 SELECT Media.VolumeName as volumename
2070 FROM Job,Media,JobMedia
2071 WHERE Job.JobId = $jobid
2072 AND JobMedia.JobId=Job.JobId
2073 AND JobMedia.MediaId=Media.MediaId
2076 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2078 $row->{volumes} = [ values %$all ] ;
2080 $self->display($row, "display_job_zoom.tpl");
2083 sub display_job_group
2085 my ($self, %arg) = @_;
2087 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2089 my ($where, undef) = $self->get_param('client_groups',
2095 SELECT client_group_name AS client_group_name,
2096 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2097 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2098 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2099 COALESCE(jobok.nbjobs,0) AS nbjobok,
2100 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2101 COALESCE(jobok.duration, '0:0:0') AS duration
2103 FROM client_group LEFT JOIN (
2104 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2105 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2106 SUM(JobErrors) AS joberrors,
2107 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2108 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2111 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2112 JOIN client_group USING (client_group_id)
2114 WHERE JobStatus = 'T'
2117 ) AS jobok USING (client_group_name) LEFT JOIN
2120 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2121 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2122 SUM(JobErrors) AS joberrors
2123 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2124 JOIN client_group USING (client_group_id)
2126 WHERE JobStatus IN ('f','E', 'A')
2129 ) AS joberr USING (client_group_name)
2133 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2135 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2138 $self->display($rep, "display_job_group.tpl");
2143 my ($self, %arg) = @_ ;
2145 my ($limit, $label) = $self->get_limit(%arg);
2146 my ($where, %elt) = $self->get_param('pools',
2151 my $arg = $self->get_form('jmedias', 'qre_media');
2153 if ($arg->{jmedias}) {
2154 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2156 if ($arg->{qre_media}) {
2157 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2161 SELECT Media.VolumeName AS volumename,
2162 Media.VolBytes AS volbytes,
2163 Media.VolStatus AS volstatus,
2164 Media.MediaType AS mediatype,
2165 Media.InChanger AS online,
2166 Media.LastWritten AS lastwritten,
2167 Location.Location AS location,
2168 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2169 Pool.Name AS poolname,
2170 $self->{sql}->{FROM_UNIXTIME}(
2171 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2172 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2175 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2176 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2177 Media.MediaType AS MediaType
2179 WHERE Media.VolStatus = 'Full'
2180 GROUP BY Media.MediaType
2181 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2183 WHERE Media.PoolId=Pool.PoolId
2188 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2190 $self->display({ ID => $cur_id++,
2192 Location => $elt{location},
2193 Medias => [ values %$all ]
2195 "display_media.tpl");
2202 my $pool = $self->get_form('db_pools');
2204 foreach my $name (@{ $pool->{db_pools} }) {
2205 CGI::param('pool', $name->{name});
2206 $self->display_media();
2210 sub display_media_zoom
2214 my $medias = $self->get_form('jmedias');
2216 unless ($medias->{jmedias}) {
2217 return $self->error("Can't get media selection");
2221 SELECT InChanger AS online,
2222 VolBytes AS nb_bytes,
2223 VolumeName AS volumename,
2224 VolStatus AS volstatus,
2225 VolMounts AS nb_mounts,
2226 Media.VolUseDuration AS voluseduration,
2227 Media.MaxVolJobs AS maxvoljobs,
2228 Media.MaxVolFiles AS maxvolfiles,
2229 Media.MaxVolBytes AS maxvolbytes,
2230 VolErrors AS nb_errors,
2231 Pool.Name AS poolname,
2232 Location.Location AS location,
2233 Media.Recycle AS recycle,
2234 Media.VolRetention AS volretention,
2235 Media.LastWritten AS lastwritten,
2236 Media.VolReadTime/1000000 AS volreadtime,
2237 Media.VolWriteTime/1000000 AS volwritetime,
2238 Media.RecycleCount AS recyclecount,
2239 Media.Comment AS comment,
2240 $self->{sql}->{FROM_UNIXTIME}(
2241 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2242 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2245 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2246 WHERE Pool.PoolId = Media.PoolId
2247 AND VolumeName IN ($medias->{jmedias})
2250 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2252 foreach my $media (values %$all) {
2253 my $mq = $self->dbh_quote($media->{volumename});
2256 SELECT DISTINCT Job.JobId AS jobid,
2258 Job.StartTime AS starttime,
2261 Job.JobFiles AS files,
2262 Job.JobBytes AS bytes,
2263 Job.jobstatus AS status
2264 FROM Media,JobMedia,Job
2265 WHERE Media.VolumeName=$mq
2266 AND Media.MediaId=JobMedia.MediaId
2267 AND JobMedia.JobId=Job.JobId
2270 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2273 SELECT LocationLog.Date AS date,
2274 Location.Location AS location,
2275 LocationLog.Comment AS comment
2276 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2277 WHERE Media.MediaId = LocationLog.MediaId
2278 AND Media.VolumeName = $mq
2282 my $log = $self->dbh_selectall_arrayref($query) ;
2284 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2287 $self->display({ jobs => [ values %$jobs ],
2288 LocationLog => $logtxt,
2290 "display_media_zoom.tpl");
2298 my $loc = $self->get_form('qlocation');
2299 unless ($loc->{qlocation}) {
2300 return $self->error("Can't get location");
2304 SELECT Location.Location AS location,
2305 Location.Cost AS cost,
2306 Location.Enabled AS enabled
2308 WHERE Location.Location = $loc->{qlocation}
2311 my $row = $self->dbh_selectrow_hashref($query);
2313 $self->display({ ID => $cur_id++,
2314 %$row }, "location_edit.tpl") ;
2322 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2323 unless ($arg->{qlocation}) {
2324 return $self->error("Can't get location");
2326 unless ($arg->{qnewlocation}) {
2327 return $self->error("Can't get new location name");
2329 unless ($arg->{cost}) {
2330 return $self->error("Can't get new cost");
2333 my $enabled = CGI::param('enabled') || '';
2334 $enabled = $enabled?1:0;
2337 UPDATE Location SET Cost = $arg->{cost},
2338 Location = $arg->{qnewlocation},
2340 WHERE Location.Location = $arg->{qlocation}
2343 $self->dbh_do($query);
2345 $self->location_display();
2351 my $arg = $self->get_form(qw/qlocation/) ;
2353 unless ($arg->{qlocation}) {
2354 return $self->error("Can't get location");
2358 SELECT count(Media.MediaId) AS nb
2359 FROM Media INNER JOIN Location USING (LocationID)
2360 WHERE Location = $arg->{qlocation}
2363 my $res = $self->dbh_selectrow_hashref($query);
2366 return $self->error("Sorry, the location must be empty");
2370 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2373 $self->dbh_do($query);
2375 $self->location_display();
2382 my $arg = $self->get_form(qw/qlocation cost/) ;
2384 unless ($arg->{qlocation}) {
2385 $self->display({}, "location_add.tpl");
2388 unless ($arg->{cost}) {
2389 return $self->error("Can't get new cost");
2392 my $enabled = CGI::param('enabled') || '';
2393 $enabled = $enabled?1:0;
2396 INSERT INTO Location (Location, Cost, Enabled)
2397 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2400 $self->dbh_do($query);
2402 $self->location_display();
2405 sub location_display
2410 SELECT Location.Location AS location,
2411 Location.Cost AS cost,
2412 Location.Enabled AS enabled,
2413 (SELECT count(Media.MediaId)
2415 WHERE Media.LocationId = Location.LocationId
2420 my $location = $self->dbh_selectall_hashref($query, 'location');
2422 $self->display({ ID => $cur_id++,
2423 Locations => [ values %$location ] },
2424 "display_location.tpl");
2431 my $medias = $self->get_selected_media_location();
2436 my $arg = $self->get_form('db_locations', 'qnewlocation');
2438 $self->display({ email => $self->{info}->{email_media},
2440 medias => [ values %$medias ],
2442 "update_location.tpl");
2445 ###########################################################
2451 my $grp = $self->get_form(qw/qclient_group db_clients/);
2454 unless ($grp->{qclient_group}) {
2455 return $self->error("Can't get group");
2460 FROM Client JOIN client_group_member using (clientid)
2461 JOIN client_group using (client_group_id)
2462 WHERE client_group_name = $grp->{qclient_group}
2465 my $row = $self->dbh_selectall_hashref($query, "name");
2467 $self->display({ ID => $cur_id++,
2468 client_group => $grp->{qclient_group},
2470 client_group_member => [ values %$row]},
2478 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2479 unless ($arg->{qclient_group}) {
2480 return $self->error("Can't get groups");
2483 $self->{dbh}->begin_work();
2486 DELETE FROM client_group_member
2487 WHERE client_group_id IN
2488 (SELECT client_group_id
2490 WHERE client_group_name = $arg->{qclient_group})
2492 $self->dbh_do($query);
2495 INSERT INTO client_group_member (clientid, client_group_id)
2497 (SELECT client_group_id
2499 WHERE client_group_name = $arg->{qclient_group})
2500 FROM Client WHERE Name IN ($arg->{jclients})
2503 $self->dbh_do($query);
2505 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2508 SET client_group_name = $arg->{qnewgroup}
2509 WHERE client_group_name = $arg->{qclient_group}
2512 $self->dbh_do($query);
2515 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2517 $self->display_groups();
2523 my $arg = $self->get_form(qw/qclient_group/);
2525 unless ($arg->{qclient_group}) {
2526 return $self->error("Can't get groups");
2529 $self->{dbh}->begin_work();
2532 DELETE FROM client_group_member
2533 WHERE client_group_id IN
2534 (SELECT client_group_id
2536 WHERE client_group_name = $arg->{qclient_group});
2538 DELETE FROM client_group
2539 WHERE client_group_name = $arg->{qclient_group};
2541 $self->dbh_do($query);
2543 $self->{dbh}->commit();
2545 $self->display_groups();
2552 my $arg = $self->get_form(qw/qclient_group/) ;
2554 unless ($arg->{qclient_group}) {
2555 $self->display({}, "groups_add.tpl");
2560 INSERT INTO client_group (client_group_name)
2561 VALUES ($arg->{qclient_group})
2564 $self->dbh_do($query);
2566 $self->display_groups();
2573 my $arg = $self->get_form(qw/db_client_groups/) ;
2575 if ($self->{dbh}->errstr) {
2576 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2581 $self->display({ ID => $cur_id++,
2583 "display_groups.tpl");
2586 ###########################################################
2588 sub get_media_max_size
2590 my ($self, $type) = @_;
2592 "SELECT avg(VolBytes) AS size
2594 WHERE Media.VolStatus = 'Full'
2595 AND Media.MediaType = '$type'
2598 my $res = $self->selectrow_hashref($query);
2601 return $res->{size};
2611 my $media = $self->get_form('qmedia');
2613 unless ($media->{qmedia}) {
2614 return $self->error("Can't get media");
2618 SELECT Media.Slot AS slot,
2619 PoolMedia.Name AS poolname,
2620 Media.VolStatus AS volstatus,
2621 Media.InChanger AS inchanger,
2622 Location.Location AS location,
2623 Media.VolumeName AS volumename,
2624 Media.MaxVolBytes AS maxvolbytes,
2625 Media.MaxVolJobs AS maxvoljobs,
2626 Media.MaxVolFiles AS maxvolfiles,
2627 Media.VolUseDuration AS voluseduration,
2628 Media.VolRetention AS volretention,
2629 Media.Comment AS comment,
2630 PoolRecycle.Name AS poolrecycle
2632 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2633 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2634 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2636 WHERE Media.VolumeName = $media->{qmedia}
2639 my $row = $self->dbh_selectrow_hashref($query);
2640 $row->{volretention} = human_sec($row->{volretention});
2641 $row->{voluseduration} = human_sec($row->{voluseduration});
2643 my $elt = $self->get_form(qw/db_pools db_locations/);
2648 }, "update_media.tpl");
2655 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2657 unless ($arg->{jmedias}) {
2658 return $self->error("Can't get selected media");
2661 unless ($arg->{qnewlocation}) {
2662 return $self->error("Can't get new location");
2667 SET LocationId = (SELECT LocationId
2669 WHERE Location = $arg->{qnewlocation})
2670 WHERE Media.VolumeName IN ($arg->{jmedias})
2673 my $nb = $self->dbh_do($query);
2675 print "$nb media updated, you may have to update your autochanger.";
2677 $self->display_media();
2684 my $medias = $self->get_selected_media_location();
2686 return $self->error("Can't get media selection");
2688 my $newloc = CGI::param('newlocation');
2690 my $user = CGI::param('user') || 'unknown';
2691 my $comm = CGI::param('comment') || '';
2692 $comm = $self->dbh_quote("$user: $comm");
2696 foreach my $media (keys %$medias) {
2698 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2700 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2701 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2702 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2705 $self->dbh_do($query);
2706 $self->debug($query);
2710 $q->param('action', 'update_location');
2711 my $url = $q->url(-full => 1, -query=>1);
2713 $self->display({ email => $self->{info}->{email_media},
2715 newlocation => $newloc,
2716 # [ { volumename => 'vol1' }, { volumename => 'vol2'
\81\81 },..]
2717 medias => [ values %$medias ],
2719 "change_location.tpl");
2723 sub display_client_stats
2725 my ($self, %arg) = @_ ;
2727 my $client = $self->dbh_quote($arg{clientname});
2729 my ($limit, $label) = $self->get_limit(%arg);
2733 count(Job.JobId) AS nb_jobs,
2734 sum(Job.JobBytes) AS nb_bytes,
2735 sum(Job.JobErrors) AS nb_err,
2736 sum(Job.JobFiles) AS nb_files,
2737 Client.Name AS clientname
2738 FROM Job JOIN Client USING (ClientId)
2740 Client.Name = $client
2742 GROUP BY Client.Name
2745 my $row = $self->dbh_selectrow_hashref($query);
2747 $row->{ID} = $cur_id++;
2748 $row->{label} = $label;
2749 $row->{grapharg} = "client";
2751 $self->display($row, "display_client_stats.tpl");
2755 sub display_group_stats
2757 my ($self, %arg) = @_ ;
2759 my $carg = $self->get_form(qw/qclient_group/);
2761 unless ($carg->{qclient_group}) {
2762 return $self->error("Can't get group");
2765 my ($limit, $label) = $self->get_limit(%arg);
2769 count(Job.JobId) AS nb_jobs,
2770 sum(Job.JobBytes) AS nb_bytes,
2771 sum(Job.JobErrors) AS nb_err,
2772 sum(Job.JobFiles) AS nb_files,
2773 client_group.client_group_name AS clientname
2774 FROM Job JOIN Client USING (ClientId)
2775 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
2776 JOIN client_group USING (client_group_id)
2778 client_group.client_group_name = $carg->{qclient_group}
2780 GROUP BY client_group.client_group_name
2783 my $row = $self->dbh_selectrow_hashref($query);
2785 $row->{ID} = $cur_id++;
2786 $row->{label} = $label;
2787 $row->{grapharg} = "client_group";
2789 $self->display($row, "display_client_stats.tpl");
2792 # poolname can be undef
2795 my ($self, $poolname) = @_ ;
2799 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2800 if ($arg->{jmediatypes}) {
2801 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2802 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
2805 # TODO : afficher les tailles et les dates
2808 SELECT subq.volmax AS volmax,
2809 subq.volnum AS volnum,
2810 subq.voltotal AS voltotal,
2812 Pool.Recycle AS recycle,
2813 Pool.VolRetention AS volretention,
2814 Pool.VolUseDuration AS voluseduration,
2815 Pool.MaxVolJobs AS maxvoljobs,
2816 Pool.MaxVolFiles AS maxvolfiles,
2817 Pool.MaxVolBytes AS maxvolbytes,
2818 subq.PoolId AS PoolId,
2819 subq.MediaType AS mediatype,
2820 $self->{sql}->{CAT_POOL_TYPE} AS uniq
2823 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2824 count(Media.MediaId) AS volnum,
2825 sum(Media.VolBytes) AS voltotal,
2826 Media.PoolId AS PoolId,
2827 Media.MediaType AS MediaType
2829 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2830 Media.MediaType AS MediaType
2832 WHERE Media.VolStatus = 'Full'
2833 GROUP BY Media.MediaType
2834 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2835 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2837 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2841 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
2844 SELECT Pool.Name AS name,
2845 sum(VolBytes) AS size
2846 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2847 WHERE Media.VolStatus IN ('Recycled', 'Purged')
2851 my $empty = $self->dbh_selectall_hashref($query, 'name');
2853 foreach my $p (values %$all) {
2854 if ($p->{volmax} > 0) { # mysql returns 0.0000
2855 # we remove Recycled/Purged media from pool usage
2856 if (defined $empty->{$p->{name}}) {
2857 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2859 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2861 $p->{poolusage} = 0;
2865 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2867 WHERE PoolId=$p->{poolid}
2868 AND Media.MediaType = '$p->{mediatype}'
2872 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2873 foreach my $t (values %$content) {
2874 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2879 $self->display({ ID => $cur_id++,
2880 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2881 Pools => [ values %$all ]},
2882 "display_pool.tpl");
2885 sub display_running_job
2889 my $arg = $self->get_form('client', 'jobid');
2891 if (!$arg->{client} and $arg->{jobid}) {
2894 SELECT Client.Name AS name
2895 FROM Job INNER JOIN Client USING (ClientId)
2896 WHERE Job.JobId = $arg->{jobid}
2899 my $row = $self->dbh_selectrow_hashref($query);
2902 $arg->{client} = $row->{name};
2903 CGI::param('client', $arg->{client});
2907 if ($arg->{client}) {
2908 my $cli = new Bweb::Client(name => $arg->{client});
2909 $cli->display_running_job($self->{info}, $arg->{jobid});
2910 if ($arg->{jobid}) {
2911 $self->get_job_log();
2914 $self->error("Can't get client or jobid");
2918 sub display_running_jobs
2920 my ($self, $display_action) = @_;
2923 SELECT Job.JobId AS jobid,
2924 Job.Name AS jobname,
2926 Job.StartTime AS starttime,
2927 Job.JobFiles AS jobfiles,
2928 Job.JobBytes AS jobbytes,
2929 Job.JobStatus AS jobstatus,
2930 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2931 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2933 Client.Name AS clientname
2934 FROM Job INNER JOIN Client USING (ClientId)
2935 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2937 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2939 $self->display({ ID => $cur_id++,
2940 display_action => $display_action,
2941 Jobs => [ values %$all ]},
2942 "running_job.tpl") ;
2945 # return the autochanger list to update
2950 my $arg = $self->get_form('jmedias');
2952 unless ($arg->{jmedias}) {
2953 return $self->error("Can't get media selection");
2957 SELECT Media.VolumeName AS volumename,
2958 Storage.Name AS storage,
2959 Location.Location AS location,
2961 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2962 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2963 WHERE Media.VolumeName IN ($arg->{jmedias})
2964 AND Media.InChanger = 1
2967 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2969 foreach my $vol (values %$all) {
2970 my $a = $self->ach_get($vol->{location});
2972 $ret{$vol->{location}} = 1;
2974 unless ($a->{have_status}) {
2976 $a->{have_status} = 1;
2979 print "eject $vol->{volumename} from $vol->{storage} : ";
2980 if ($a->send_to_io($vol->{slot})) {
2981 print "<img src='/bweb/T.png' alt='ok'><br/>";
2983 print "<img src='/bweb/E.png' alt='err'><br/>";
2993 my ($to, $subject, $content) = (CGI::param('email'),
2994 CGI::param('subject'),
2995 CGI::param('content'));
2996 $to =~ s/[^\w\d\.\@<>,]//;
2997 $subject =~ s/[^\w\d\.\[\]]/ /;
2999 open(MAIL, "|mail -s '$subject' '$to'") ;
3000 print MAIL $content;
3010 my $arg = $self->get_form('jobid', 'client');
3012 print CGI::header('text/brestore');
3013 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3014 print "client=$arg->{client}\n" if ($arg->{client});
3015 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3019 # TODO : move this to Bweb::Autochanger ?
3020 # TODO : make this internal to not eject tape ?
3026 my ($self, $name) = @_;
3029 return $self->error("Can't get your autochanger name ach");
3032 unless ($self->{info}->{ach_list}) {
3033 return $self->error("Could not find any autochanger");
3036 my $a = $self->{info}->{ach_list}->{$name};
3039 $self->error("Can't get your autochanger $name from your ach_list");
3044 $a->{debug} = $self->{debug};
3051 my ($self, $ach) = @_;
3053 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3055 $self->{info}->save();
3063 my $arg = $self->get_form('ach');
3065 or !$self->{info}->{ach_list}
3066 or !$self->{info}->{ach_list}->{$arg->{ach}})
3068 return $self->error("Can't get autochanger name");
3071 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3075 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3077 my $b = $self->get_bconsole();
3079 my @storages = $b->list_storage() ;
3081 $ach->{devices} = [ map { { name => $_ } } @storages ];
3083 $self->display($ach, "ach_add.tpl");
3084 delete $ach->{drives};
3085 delete $ach->{devices};
3092 my $arg = $self->get_form('ach');
3095 or !$self->{info}->{ach_list}
3096 or !$self->{info}->{ach_list}->{$arg->{ach}})
3098 return $self->error("Can't get autochanger name");
3101 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3103 $self->{info}->save();
3104 $self->{info}->view();
3110 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3112 my $b = $self->get_bconsole();
3113 my @storages = $b->list_storage() ;
3115 unless ($arg->{ach}) {
3116 $arg->{devices} = [ map { { name => $_ } } @storages ];
3117 return $self->display($arg, "ach_add.tpl");
3121 foreach my $drive (CGI::param('drives'))
3123 unless (grep(/^$drive$/,@storages)) {
3124 return $self->error("Can't find $drive in storage list");
3127 my $index = CGI::param("index_$drive");
3128 unless (defined $index and $index =~ /^(\d+)$/) {
3129 return $self->error("Can't get $drive index");
3132 $drives[$index] = $drive;
3136 return $self->error("Can't get drives from Autochanger");
3139 my $a = new Bweb::Autochanger(name => $arg->{ach},
3140 precmd => $arg->{precmd},
3141 drive_name => \@drives,
3142 device => $arg->{device},
3143 mtxcmd => $arg->{mtxcmd});
3145 $self->ach_register($a) ;
3147 $self->{info}->view();
3153 my $arg = $self->get_form('jobid');
3155 if ($arg->{jobid}) {
3156 my $b = $self->get_bconsole();
3157 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3161 title => "Delete a job ",
3162 name => "delete jobid=$arg->{jobid}",
3171 my $arg = $self->get_form(qw/media volstatus inchanger pool
3172 slot volretention voluseduration
3173 maxvoljobs maxvolfiles maxvolbytes
3174 qcomment poolrecycle
3177 unless ($arg->{media}) {
3178 return $self->error("Can't find media selection");
3181 my $update = "update volume=$arg->{media} ";
3183 if ($arg->{volstatus}) {
3184 $update .= " volstatus=$arg->{volstatus} ";
3187 if ($arg->{inchanger}) {
3188 $update .= " inchanger=yes " ;
3190 $update .= " slot=$arg->{slot} ";
3193 $update .= " slot=0 inchanger=no ";
3197 $update .= " pool=$arg->{pool} " ;
3200 if (defined $arg->{volretention}) {
3201 $update .= " volretention=\"$arg->{volretention}\" " ;
3204 if (defined $arg->{voluseduration}) {
3205 $update .= " voluse=\"$arg->{voluseduration}\" " ;
3208 if (defined $arg->{maxvoljobs}) {
3209 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3212 if (defined $arg->{maxvolfiles}) {
3213 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3216 if (defined $arg->{maxvolbytes}) {
3217 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3220 if (defined $arg->{poolrecycle}) {
3221 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
3224 my $b = $self->get_bconsole();
3227 content => $b->send_cmd($update),
3228 title => "Update a volume ",
3234 my $media = $self->dbh_quote($arg->{media});
3236 my $loc = CGI::param('location') || '';
3238 $loc = $self->dbh_quote($loc); # is checked by db
3239 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3241 if (!$arg->{qcomment}) {
3242 $arg->{qcomment} = "''";
3244 push @q, "Comment=$arg->{qcomment}";
3249 SET " . join (',', @q) . "
3250 WHERE Media.VolumeName = $media
3252 $self->dbh_do($query);
3254 $self->update_media();
3261 my $ach = CGI::param('ach') ;
3262 $ach = $self->ach_get($ach);
3264 return $self->error("Bad autochanger name");
3268 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3269 $b->update_slots($ach->{name});
3277 my $arg = $self->get_form('jobid', 'limit', 'offset');
3278 unless ($arg->{jobid}) {
3279 return $self->error("Can't get jobid");
3282 if ($arg->{limit} == 100) {
3283 $arg->{limit} = 1000;
3286 my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3289 SELECT Job.Name as name, Client.Name as clientname
3290 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3291 WHERE JobId = $arg->{jobid}
3294 my $row = $self->dbh_selectrow_hashref($query);
3297 return $self->error("Can't find $arg->{jobid} in catalog");
3301 SELECT Time AS time, LogText AS log
3303 WHERE Log.JobId = $arg->{jobid}
3304 OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
3305 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3309 OFFSET $arg->{offset}
3312 my $log = $self->dbh_selectall_arrayref($query);
3314 return $self->error("Can't get log for jobid $arg->{jobid}");
3320 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
3322 $logtxt = join("", map { $_->[1] } @$log ) ;
3325 $self->display({ lines=> $logtxt,
3326 jobid => $arg->{jobid},
3327 name => $row->{name},
3328 client => $row->{clientname},
3329 offset => $arg->{offset},
3330 limit => $arg->{limit},
3331 }, 'display_log.tpl');
3339 my $arg = $self->get_form('ach', 'slots', 'drive');
3341 unless ($arg->{ach}) {
3342 return $self->error("Can't find autochanger name");
3345 my $a = $self->ach_get($arg->{ach});
3347 return $self->error("Can't find autochanger name in configuration");
3350 my $storage = $a->get_drive_name($arg->{drive});
3352 return $self->error("Can't get your drive name");
3358 if ($arg->{slots}) {
3359 $slots = join(",", @{ $arg->{slots} });
3360 $slots_sql = " AND Slot IN ($slots) ";
3361 $t += 60*scalar( @{ $arg->{slots} }) ;
3364 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3365 print "<h1>This command can take long time, be patient...</h1>";
3367 $b->label_barcodes(storage => $storage,
3368 drive => $arg->{drive},
3376 SET LocationId = (SELECT LocationId
3378 WHERE Location = '$arg->{ach}')
3380 WHERE (LocationId = 0 OR LocationId IS NULL)
3390 my @volume = CGI::param('media');
3393 return $self->error("Can't get media selection");
3396 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3399 content => $b->purge_volume(@volume),
3400 title => "Purge media",
3401 name => "purge volume=" . join(' volume=', @volume),
3410 my @volume = CGI::param('media');
3412 return $self->error("Can't get media selection");
3415 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3418 content => $b->prune_volume(@volume),
3419 title => "Prune media",
3420 name => "prune volume=" . join(' volume=', @volume),
3430 my $arg = $self->get_form('jobid');
3431 unless ($arg->{jobid}) {
3432 return $self->error("Can't get jobid");
3435 my $b = $self->get_bconsole();
3437 content => $b->cancel($arg->{jobid}),
3438 title => "Cancel job",
3439 name => "cancel jobid=$arg->{jobid}",
3445 # Warning, we display current fileset
3448 my $arg = $self->get_form('fileset');
3450 if ($arg->{fileset}) {
3451 my $b = $self->get_bconsole();
3452 my $ret = $b->get_fileset($arg->{fileset});
3453 $self->display({ fileset => $arg->{fileset},
3455 }, "fileset_view.tpl");
3457 $self->error("Can't get fileset name");
3461 sub director_show_sched
3465 my $arg = $self->get_form('days');
3467 my $b = $self->get_bconsole();
3468 my $ret = $b->director_get_sched( $arg->{days} );
3473 }, "scheduled_job.tpl");
3476 sub enable_disable_job
3478 my ($self, $what) = @_ ;
3480 my $name = CGI::param('job') || '';
3481 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3482 return $self->error("Can't find job name");
3485 my $b = $self->get_bconsole();
3495 content => $b->send_cmd("$cmd job=\"$name\""),
3496 title => "$cmd $name",
3497 name => "$cmd job=\"$name\"",
3504 return new Bconsole(pref => $self->{info});
3510 my $b = $self->get_bconsole();
3512 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3514 $self->display({ Jobs => $joblist }, "run_job.tpl");
3519 my ($self, $ouput) = @_;
3522 foreach my $l (split(/\r\n/, $ouput)) {
3523 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3529 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3535 foreach my $k (keys %arg) {
3536 $lowcase{lc($k)} = $arg{$k} ;
3545 my $b = $self->get_bconsole();
3547 my $job = CGI::param('job') || '';
3549 # we take informations from director, and we overwrite with user wish
3550 my $info = $b->send_cmd("show job=\"$job\"");
3551 my $attr = $self->run_parse_job($info);
3553 my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3554 my %job_opt = (%$attr, %$arg);
3556 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3558 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3559 my $clients = [ map { { name => $_ } }$b->list_client()];
3560 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3561 my $storages= [ map { { name => $_ } }$b->list_storage()];
3566 clients => $clients,
3567 filesets => $filesets,
3568 storages => $storages,
3570 }, "run_job_mod.tpl");
3576 my $b = $self->get_bconsole();
3578 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3588 my $b = $self->get_bconsole();
3590 # TODO: check input (don't use pool, level)
3592 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3593 my $job = CGI::param('job') || '';
3594 my $storage = CGI::param('storage') || '';
3596 my $jobid = $b->run(job => $job,
3597 client => $arg->{client},
3598 priority => $arg->{priority},
3599 level => $arg->{level},
3600 storage => $storage,
3601 pool => $arg->{pool},
3602 fileset => $arg->{fileset},
3603 when => $arg->{when},
3606 print $jobid, $b->{error};
3608 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";