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)?$!,
220 enable_security => qr/^(on)?$/,
225 load - load config_file
229 this function load the specified config_file.
237 unless (open(FP, $self->{config_file}))
239 return $self->error("can't load config_file $self->{config_file} : $!");
241 my $f=''; my $tmpbuffer;
242 while(read FP,$tmpbuffer,4096)
250 no strict; # I have no idea of the contents of the file
257 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...") ;
260 foreach my $k (keys %$VAR1) {
261 $self->{$k} = $VAR1->{$k};
269 load_old - load old configuration format
277 unless (open(FP, $self->{config_file}))
279 return $self->error("$self->{config_file} : $!");
282 while (my $line = <FP>)
285 my ($k, $v) = split(/\s*=\s*/, $line, 2);
297 save - save the current configuration to config_file
305 if ($self->{ach_list}) {
306 # shortcut for display_begin
307 $self->{achs} = [ map {{ name => $_ }}
308 keys %{$self->{ach_list}}
312 unless (open(FP, ">$self->{config_file}"))
314 return $self->error("$self->{config_file} : $!\n" .
315 "You must add this to your config file\n"
316 . Data::Dumper::Dumper($self));
319 print FP Data::Dumper::Dumper($self);
327 edit, view, modify - html form ouput
335 $self->display($self, "config_edit.tpl");
341 $self->display($self, "config_view.tpl");
349 # we need to reset checkbox first
351 $self->{enable_security} = 0;
352 $self->{display_log_time} = 0;
354 foreach my $k (CGI::param())
356 next unless (exists $k_re{$k}) ;
357 my $val = CGI::param($k);
358 if ($val =~ $k_re{$k}) {
361 $self->{error} .= "bad parameter : $k = [$val]";
367 if ($self->{error}) { # an error as occured
368 $self->display($self, 'error.tpl');
376 ################################################################
378 package Bweb::Client;
380 use base q/Bweb::Gui/;
384 Bweb::Client - Bacula FD
388 this package is use to do all Client operations like, parse status etc...
392 $client = new Bweb::Client(name => 'zog-fd');
393 $client->status(); # do a 'status client=zog-fd'
399 display_running_job - Html display of a running job
403 this function is used to display information about a current job
407 sub display_running_job
409 my ($self, $conf, $jobid) = @_ ;
411 my $status = $self->status($conf);
414 if ($status->{$jobid}) {
415 $self->display($status->{$jobid}, "client_job_status.tpl");
418 for my $id (keys %$status) {
419 $self->display($status->{$id}, "client_job_status.tpl");
426 $client = new Bweb::Client(name => 'plume-fd');
428 $client->status($bweb);
432 dirty hack to parse "status client=xxx-fd"
436 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
437 Backup Job started: 06-jun-06 17:22
438 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
439 Files Examined=10,697
440 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
446 JobName => Full_plume.2006-06-06_17.22.23,
449 Bytes => 194,484,132,
459 my ($self, $conf) = @_ ;
461 if (defined $self->{cur_jobs}) {
462 return $self->{cur_jobs} ;
466 my $b = new Bconsole(pref => $conf);
467 my $ret = $b->send_cmd("st client=$self->{name}");
471 for my $r (split(/\n/, $ret)) {
473 $r =~ s/(^\s+|\s+$)//g;
474 if ($r =~ /JobId (\d+) Job (\S+)/) {
476 $arg->{$jobid} = { @param, JobId => $jobid } ;
480 @param = ( JobName => $2 );
482 } elsif ($r =~ /=.+=/) {
483 push @param, split(/\s+|\s*=\s*/, $r) ;
485 } elsif ($r =~ /=/) { # one per line
486 push @param, split(/\s*=\s*/, $r) ;
488 } elsif ($r =~ /:/) { # one per line
489 push @param, split(/\s*:\s*/, $r, 2) ;
493 if ($jobid and @param) {
494 $arg->{$jobid} = { @param,
496 Client => $self->{name},
500 $self->{cur_jobs} = $arg ;
506 ################################################################
508 package Bweb::Autochanger;
510 use base q/Bweb::Gui/;
514 Bweb::Autochanger - Object to manage Autochanger
518 this package will parse the mtx output and manage drives.
522 $auto = new Bweb::Autochanger(precmd => 'sudo');
524 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
528 $auto->slot_is_full(10);
529 $auto->transfer(10, 11);
535 my ($class, %arg) = @_;
538 name => '', # autochanger name
539 label => {}, # where are volume { label1 => 40, label2 => drive0 }
540 drive => [], # drive use [ 'media1', 'empty', ..]
541 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
542 io => [], # io slot number list [ 41, 42, 43...]
543 info => {slot => 0, # informations (slot, drive, io)
547 mtxcmd => '/usr/sbin/mtx',
549 device => '/dev/changer',
550 precmd => '', # ssh command
551 bweb => undef, # link to bacula web object (use for display)
554 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
561 status - parse the output of mtx status
565 this function will launch mtx status and parse the output. it will
566 give a perlish view of the autochanger content.
568 it uses ssh if the autochanger is on a other host.
575 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
577 # TODO : reset all infos
578 $self->{info}->{drive} = 0;
579 $self->{info}->{slot} = 0;
580 $self->{info}->{io} = 0;
582 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
585 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
586 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
587 #Data Transfer Element 1:Empty
588 # Storage Element 1:Empty
589 # Storage Element 2:Full :VolumeTag=000002
590 # Storage Element 3:Empty
591 # Storage Element 4:Full :VolumeTag=000004
592 # Storage Element 5:Full :VolumeTag=000001
593 # Storage Element 6:Full :VolumeTag=000003
594 # Storage Element 7:Empty
595 # Storage Element 41 IMPORT/EXPORT:Empty
596 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
601 # Storage Element 7:Empty
602 # Storage Element 2:Full :VolumeTag=000002
603 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
606 $self->set_empty_slot($1);
608 $self->set_slot($1, $4);
611 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
614 $self->set_empty_drive($1);
616 $self->set_drive($1, $4, $6);
619 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
622 $self->set_empty_io($1);
624 $self->set_io($1, $4);
627 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
629 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
630 $self->{info}->{drive} = $1;
631 $self->{info}->{slot} = $2;
632 if ($l =~ /(\d+)\s+Import/) {
633 $self->{info}->{io} = $1 ;
635 $self->{info}->{io} = 0;
640 $self->debug($self) ;
645 my ($self, $slot) = @_;
648 if ($self->{slot}->[$slot] eq 'loaded') {
652 my $label = $self->{slot}->[$slot] ;
654 return $self->is_media_loaded($label);
659 my ($self, $drive, $slot) = @_;
661 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
662 return 0 if ($self->slot_is_full($slot)) ;
664 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
667 my $content = $self->get_slot($slot);
668 print "content = $content<br/> $drive => $slot<br/>";
669 $self->set_empty_drive($drive);
670 $self->set_slot($slot, $content);
673 $self->{error} = $out;
678 # TODO: load/unload have to use mtx script from bacula
681 my ($self, $drive, $slot) = @_;
683 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
684 return 0 unless ($self->slot_is_full($slot)) ;
686 print "Loading drive $drive with slot $slot<br/>\n";
687 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
690 my $content = $self->get_slot($slot);
691 print "content = $content<br/> $slot => $drive<br/>";
692 $self->set_drive($drive, $slot, $content);
695 $self->{error} = $out;
703 my ($self, $media) = @_;
705 unless ($self->{label}->{$media}) {
709 if ($self->{label}->{$media} =~ /drive\d+/) {
719 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
724 my ($self, $slot, $tag) = @_;
725 $self->{slot}->[$slot] = $tag || 'full';
726 push @{ $self->{io} }, $slot;
729 $self->{label}->{$tag} = $slot;
735 my ($self, $slot) = @_;
737 push @{ $self->{io} }, $slot;
739 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
740 $self->{slot}->[$slot] = 'empty';
746 my ($self, $slot) = @_;
747 return $self->{slot}->[$slot];
752 my ($self, $slot, $tag) = @_;
753 $self->{slot}->[$slot] = $tag || 'full';
756 $self->{label}->{$tag} = $slot;
762 my ($self, $slot) = @_;
764 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
765 $self->{slot}->[$slot] = 'empty';
771 my ($self, $drive) = @_;
772 $self->{drive}->[$drive] = 'empty';
777 my ($self, $drive, $slot, $tag) = @_;
778 $self->{drive}->[$drive] = $tag || $slot;
780 $self->{slot}->[$slot] = $tag || 'loaded';
783 $self->{label}->{$tag} = "drive$drive";
789 my ($self, $slot) = @_;
791 # slot don't exists => full
792 if (not defined $self->{slot}->[$slot]) {
796 if ($self->{slot}->[$slot] eq 'empty') {
799 return 1; # vol, full, loaded
802 sub slot_get_first_free
805 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
806 return $slot unless ($self->slot_is_full($slot));
810 sub io_get_first_free
814 foreach my $slot (@{ $self->{io} }) {
815 return $slot unless ($self->slot_is_full($slot));
822 my ($self, $media) = @_;
824 return $self->{label}->{$media} ;
829 my ($self, $media) = @_;
831 return defined $self->{label}->{$media} ;
836 my ($self, $slot) = @_;
838 unless ($self->slot_is_full($slot)) {
839 print "Autochanger $self->{name} slot $slot is empty\n";
844 if ($self->is_slot_loaded($slot)) {
847 print "Autochanger $self->{name} $slot is currently in use\n";
851 # autochanger must have I/O
852 unless ($self->have_io()) {
853 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
857 my $dst = $self->io_get_first_free();
860 print "Autochanger $self->{name} you must empty I/O first\n";
863 $self->transfer($slot, $dst);
868 my ($self, $src, $dst) = @_ ;
869 if ($self->{debug}) {
870 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
872 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
875 my $content = $self->get_slot($src);
876 $self->{slot}->[$src] = 'empty';
877 $self->set_slot($dst, $content);
880 $self->{error} = $out;
887 my ($self, $index) = @_;
888 return $self->{drive_name}->[$index];
891 # TODO : do a tapeinfo request to get informations
901 for my $slot (@{$self->{io}})
903 if ($self->is_slot_loaded($slot)) {
904 print "$slot is currently loaded\n";
908 if ($self->slot_is_full($slot))
910 my $free = $self->slot_get_first_free() ;
911 print "move $slot to $free :\n";
914 if ($self->transfer($slot, $free)) {
915 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
917 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
921 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
927 # TODO : this is with mtx status output,
928 # we can do an other function from bacula view (with StorageId)
932 my $bweb = $self->{bweb};
934 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
935 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
938 SELECT Media.VolumeName AS volumename,
939 Media.VolStatus AS volstatus,
940 Media.LastWritten AS lastwritten,
941 Media.VolBytes AS volbytes,
942 Media.MediaType AS mediatype,
944 Media.InChanger AS inchanger,
946 $bweb->{sql}->{FROM_UNIXTIME}(
947 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
948 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
951 INNER JOIN Pool USING (PoolId)
953 WHERE Media.VolumeName IN ($media_list)
956 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
958 # TODO : verify slot and bacula slot
962 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
964 if ($self->slot_is_full($slot)) {
966 my $vol = $self->{slot}->[$slot];
967 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
969 my $bslot = $all->{$vol}->{slot} ;
970 my $inchanger = $all->{$vol}->{inchanger};
972 # if bacula slot or inchanger flag is bad, we display a message
973 if ($bslot != $slot or !$inchanger) {
974 push @to_update, $slot;
977 $all->{$vol}->{realslot} = $slot;
979 push @{ $param }, $all->{$vol};
981 } else { # empty or no label
982 push @{ $param }, {realslot => $slot,
983 volstatus => 'Unknown',
984 volumename => $self->{slot}->[$slot]} ;
987 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
991 my $i=0; my $drives = [] ;
992 foreach my $d (@{ $self->{drive} }) {
993 $drives->[$i] = { index => $i,
994 load => $self->{drive}->[$i],
995 name => $self->{drive_name}->[$i],
1000 $bweb->display({ Name => $self->{name},
1001 nb_drive => $self->{info}->{drive},
1002 nb_io => $self->{info}->{io},
1005 Update => scalar(@to_update) },
1013 ################################################################
1017 use base q/Bweb::Gui/;
1021 Bweb - main Bweb package
1025 this package is use to compute and display informations
1030 use POSIX qw/strftime/;
1032 our $config_file='/etc/bacula/bweb.conf';
1038 %sql_func - hash to make query mysql/postgresql compliant
1044 UNIX_TIMESTAMP => '',
1045 FROM_UNIXTIME => '',
1046 TO_SEC => " interval '1 second' * ",
1047 SEC_TO_INT => "SEC_TO_INT",
1050 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1051 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1052 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1053 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1054 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1055 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1056 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1057 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1058 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1061 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1062 FROM_UNIXTIME => 'FROM_UNIXTIME',
1065 SEC_TO_TIME => 'SEC_TO_TIME',
1066 MATCH => " REGEXP ",
1067 STARTTIME_DAY => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1068 STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1069 STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1070 STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1071 STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1072 STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1073 STARTTIME_PWEEK => " DATE_FORMAT(StartTime, '%v') ",
1074 # with mysql < 5, you have to play with the ugly SHOW command
1075 DB_SIZE => " SELECT 0 ",
1076 # works only with mysql 5
1077 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1078 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1086 $self->{dbh}->disconnect();
1091 sub dbh_selectall_arrayref
1093 my ($self, $query) = @_;
1094 $self->connect_db();
1095 $self->debug($query);
1096 return $self->{dbh}->selectall_arrayref($query);
1101 my ($self, @what) = @_;
1102 return join(',', $self->dbh_quote(@what)) ;
1107 my ($self, @what) = @_;
1109 $self->connect_db();
1111 return map { $self->{dbh}->quote($_) } @what;
1113 return $self->{dbh}->quote($what[0]) ;
1119 my ($self, $query) = @_ ;
1120 $self->connect_db();
1121 $self->debug($query);
1122 return $self->{dbh}->do($query);
1125 sub dbh_selectall_hashref
1127 my ($self, $query, $join) = @_;
1129 $self->connect_db();
1130 $self->debug($query);
1131 return $self->{dbh}->selectall_hashref($query, $join) ;
1134 sub dbh_selectrow_hashref
1136 my ($self, $query) = @_;
1138 $self->connect_db();
1139 $self->debug($query);
1140 return $self->{dbh}->selectrow_hashref($query) ;
1145 my ($self, @what) = @_;
1146 if ($self->{conf}->{connection_string} =~ /dbi:mysql/i) {
1147 return 'CONCAT(' . join(',', @what) . ')' ;
1149 return join(' || ', @what);
1155 my ($self, $query) = @_;
1156 $self->debug($query, up => 1);
1157 return $self->{dbh}->prepare($query);
1163 my @unit = qw(B KB MB GB TB);
1164 my $val = shift || 0;
1166 my $format = '%i %s';
1167 while ($val / 1024 > 1) {
1171 $format = ($i>0)?'%0.1f %s':'%i %s';
1172 return sprintf($format, $val, $unit[$i]);
1175 # display Day, Hour, Year
1181 $val /= 60; # sec -> min
1183 if ($val / 60 <= 1) {
1187 $val /= 60; # min -> hour
1188 if ($val / 24 <= 1) {
1189 return "$val hours";
1192 $val /= 24; # hour -> day
1193 if ($val / 365 < 2) {
1197 $val /= 365 ; # day -> year
1199 return "$val years";
1205 my $val = shift || 0;
1207 if ($val == 1 or $val eq "yes") {
1209 } elsif ($val == 2 or $val eq "archived") {
1216 # get Day, Hour, Year
1222 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1226 my %times = ( m => 60,
1232 my $mult = $times{$2} || 0;
1242 unless ($self->{dbh}) {
1243 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1244 $self->{info}->{user},
1245 $self->{info}->{password});
1247 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1248 unless ($self->{dbh});
1250 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1252 if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
1253 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1260 my ($class, %arg) = @_;
1262 dbh => undef, # connect_db();
1264 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1270 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1272 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1273 $self->{sql} = $sql_func{$1};
1276 $self->{debug} = $self->{info}->{debug};
1277 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1285 $self->display($self->{info}, "begin.tpl");
1291 $self->display($self->{info}, "end.tpl");
1299 my $arg = $self->get_form("client", "qre_client", "jclient_groups", "qnotingroup");
1301 if ($arg->{qre_client}) {
1302 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1303 } elsif ($arg->{client}) {
1304 $where = "WHERE Name = '$arg->{client}' ";
1305 } elsif ($arg->{jclient_groups}) {
1306 $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
1307 JOIN client_group USING (client_group_id)
1308 WHERE client_group_name IN ($arg->{jclient_groups})";
1309 } elsif ($arg->{qnotingroup}) {
1312 (SELECT 1 FROM client_group_member
1313 WHERE Client.ClientId = client_group_member.ClientId
1320 SELECT Name AS name,
1322 AutoPrune AS autoprune,
1323 FileRetention AS fileretention,
1324 JobRetention AS jobretention
1329 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1331 my $dsp = { ID => $cur_id++,
1332 clients => [ values %$all] };
1334 $self->display($dsp, "client_list.tpl") ;
1339 my ($self, %arg) = @_;
1346 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1348 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1350 $self->{sql}->{TO_SEC}($arg{age})
1353 $label = "last " . human_sec($arg{age});
1356 if ($arg{groupby}) {
1357 $limit .= " GROUP BY $arg{groupby} ";
1361 $limit .= " ORDER BY $arg{order} ";
1365 $limit .= " LIMIT $arg{limit} ";
1366 $label .= " limited to $arg{limit}";
1370 $limit .= " OFFSET $arg{offset} ";
1371 $label .= " with $arg{offset} offset ";
1375 $label = 'no filter';
1378 return ($limit, $label);
1383 $bweb->get_form(...) - Get useful stuff
1387 This function get and check parameters against regexp.
1389 If word begin with 'q', the return will be quoted or join quoted
1390 if it's end with 's'.
1395 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1398 qclient => 'plume-fd',
1399 qpools => "'plume-fd', 'test-fd', '...'",
1406 my ($self, @what) = @_;
1407 my %what = map { $_ => 1 } @what;
1429 my %opt_ss =( # string with space
1433 my %opt_s = ( # default to ''
1454 my %opt_p = ( # option with path
1461 my %opt_r = (regexwhere => 1);
1463 my %opt_d = ( # option with date
1468 foreach my $i (@what) {
1469 if (exists $opt_i{$i}) {# integer param
1470 my $value = CGI::param($i) || $opt_i{$i} ;
1471 if ($value =~ /^(\d+)$/) {
1474 } elsif ($opt_s{$i}) { # simple string param
1475 my $value = CGI::param($i) || '';
1476 if ($value =~ /^([\w\d\.-]+)$/) {
1479 } elsif ($opt_ss{$i}) { # simple string param (with space)
1480 my $value = CGI::param($i) || '';
1481 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1484 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1485 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1487 $ret{$i} = $self->dbh_join(@value) ;
1490 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1491 my $value = CGI::param($1) ;
1493 $ret{$i} = $self->dbh_quote($value);
1496 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1497 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1498 grep { ! /^\s*$/ } CGI::param($1) ];
1499 } elsif (exists $opt_p{$i}) {
1500 my $value = CGI::param($i) || '';
1501 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1504 } elsif (exists $opt_r{$i}) {
1505 my $value = CGI::param($i) || '';
1506 if ($value =~ /^([^'"']+)$/) {
1509 } elsif (exists $opt_d{$i}) {
1510 my $value = CGI::param($i) || '';
1511 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1518 foreach my $s (CGI::param('slot')) {
1519 if ($s =~ /^(\d+)$/) {
1520 push @{$ret{slots}}, $s;
1526 my $when = CGI::param('when') || '';
1527 if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1532 if ($what{db_clients}) {
1534 SELECT Client.Name as clientname
1538 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1539 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1543 if ($what{db_client_groups}) {
1545 SELECT client_group_name AS name
1549 my $grps = $self->dbh_selectall_hashref($query, 'name');
1550 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1554 if ($what{db_mediatypes}) {
1556 SELECT MediaType as mediatype
1560 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1561 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1565 if ($what{db_locations}) {
1567 SELECT Location as location, Cost as cost
1570 my $loc = $self->dbh_selectall_hashref($query, 'location');
1571 $ret{db_locations} = [ sort { $a->{location}
1577 if ($what{db_pools}) {
1578 my $query = "SELECT Name as name FROM Pool";
1580 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1581 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1584 if ($what{db_filesets}) {
1586 SELECT FileSet.FileSet AS fileset
1590 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1592 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1593 values %$filesets] ;
1596 if ($what{db_jobnames}) {
1598 SELECT DISTINCT Job.Name AS jobname
1602 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1604 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1605 values %$jobnames] ;
1608 if ($what{db_devices}) {
1610 SELECT Device.Name AS name
1614 my $devices = $self->dbh_selectall_hashref($query, 'name');
1616 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1627 my $fields = $self->get_form(qw/age level status clients filesets
1629 db_clients limit db_filesets width height
1630 qclients qfilesets qjobnames db_jobnames/);
1633 my $url = CGI::url(-full => 0,
1636 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1638 # this organisation is to keep user choice between 2 click
1639 # TODO : fileset and client selection doesn't work
1648 sub display_client_job
1650 my ($self, %arg) = @_ ;
1652 $arg{order} = ' Job.JobId DESC ';
1653 my ($limit, $label) = $self->get_limit(%arg);
1655 my $clientname = $self->dbh_quote($arg{clientname});
1658 SELECT DISTINCT Job.JobId AS jobid,
1659 Job.Name AS jobname,
1660 FileSet.FileSet AS fileset,
1662 StartTime AS starttime,
1663 JobFiles AS jobfiles,
1664 JobBytes AS jobbytes,
1665 JobStatus AS jobstatus,
1666 JobErrors AS joberrors
1668 FROM Client,Job,FileSet
1669 WHERE Client.Name=$clientname
1670 AND Client.ClientId=Job.ClientId
1671 AND Job.FileSetId=FileSet.FileSetId
1675 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1677 $self->display({ clientname => $arg{clientname},
1680 Jobs => [ values %$all ],
1682 "display_client_job.tpl") ;
1685 sub get_selected_media_location
1689 my $media = $self->get_form('jmedias');
1691 unless ($media->{jmedias}) {
1696 SELECT Media.VolumeName AS volumename, Location.Location AS location
1697 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1698 WHERE Media.VolumeName IN ($media->{jmedias})
1701 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1703 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1712 my ($self, $in) = @_ ;
1714 my $media = $self->get_selected_media_location();
1720 my $elt = $self->get_form('db_locations');
1722 $self->display({ ID => $cur_id++,
1723 enabled => human_enabled($in),
1724 %$elt, # db_locations
1726 sort { $a->{volumename} cmp $b->{volumename} } values %$media
1736 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1738 $self->display($elt, "help_extern.tpl");
1741 sub help_extern_compute
1745 my $number = CGI::param('limit') || '' ;
1746 unless ($number =~ /^(\d+)$/) {
1747 return $self->error("Bad arg number : $number ");
1750 my ($sql, undef) = $self->get_param('pools',
1751 'locations', 'mediatypes');
1754 SELECT Media.VolumeName AS volumename,
1755 Media.VolStatus AS volstatus,
1756 Media.LastWritten AS lastwritten,
1757 Media.MediaType AS mediatype,
1758 Media.VolMounts AS volmounts,
1760 Media.Recycle AS recycle,
1761 $self->{sql}->{FROM_UNIXTIME}(
1762 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1763 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1766 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1767 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1769 WHERE Media.InChanger = 1
1770 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1772 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1776 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1778 $self->display({ Media => [ values %$all ] },
1779 "help_extern_compute.tpl");
1786 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1787 $self->display($param, "help_intern.tpl");
1790 sub help_intern_compute
1794 my $number = CGI::param('limit') || '' ;
1795 unless ($number =~ /^(\d+)$/) {
1796 return $self->error("Bad arg number : $number ");
1799 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1801 if (CGI::param('expired')) {
1803 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1804 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1810 SELECT Media.VolumeName AS volumename,
1811 Media.VolStatus AS volstatus,
1812 Media.LastWritten AS lastwritten,
1813 Media.MediaType AS mediatype,
1814 Media.VolMounts AS volmounts,
1816 $self->{sql}->{FROM_UNIXTIME}(
1817 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1818 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1821 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1822 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1824 WHERE Media.InChanger <> 1
1825 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1826 AND Media.Recycle = 1
1828 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1832 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1834 $self->display({ Media => [ values %$all ] },
1835 "help_intern_compute.tpl");
1841 my ($self, %arg) = @_ ;
1843 my ($limit, $label) = $self->get_limit(%arg);
1847 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1848 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1849 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1850 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1851 ($self->{sql}->{DB_SIZE}) AS db_size,
1852 (SELECT count(Job.JobId)
1854 WHERE Job.JobStatus IN ('E','e','f','A')
1857 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1860 my $row = $self->dbh_selectrow_hashref($query) ;
1862 $row->{nb_bytes} = human_size($row->{nb_bytes});
1864 $row->{db_size} = human_size($row->{db_size});
1865 $row->{label} = $label;
1867 $self->display($row, "general.tpl");
1872 my ($self, @what) = @_ ;
1873 my %elt = map { $_ => 1 } @what;
1878 if ($elt{clients}) {
1879 my @clients = grep { ! /^\s*$/ } CGI::param('client');
1881 $ret{clients} = \@clients;
1882 my $str = $self->dbh_join(@clients);
1883 $limit .= "AND Client.Name IN ($str) ";
1887 if ($elt{client_groups}) {
1888 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1890 $ret{client_groups} = \@clients;
1891 my $str = $self->dbh_join(@clients);
1892 $limit .= "AND client_group_name IN ($str) ";
1896 if ($elt{filesets}) {
1897 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1899 $ret{filesets} = \@filesets;
1900 my $str = $self->dbh_join(@filesets);
1901 $limit .= "AND FileSet.FileSet IN ($str) ";
1905 if ($elt{mediatypes}) {
1906 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
1908 $ret{mediatypes} = \@media;
1909 my $str = $self->dbh_join(@media);
1910 $limit .= "AND Media.MediaType IN ($str) ";
1915 my $client = CGI::param('client');
1916 $ret{client} = $client;
1917 $client = $self->dbh_join($client);
1918 $limit .= "AND Client.Name = $client ";
1922 my $level = CGI::param('level') || '';
1923 if ($level =~ /^(\w)$/) {
1925 $limit .= "AND Job.Level = '$1' ";
1930 my $jobid = CGI::param('jobid') || '';
1932 if ($jobid =~ /^(\d+)$/) {
1934 $limit .= "AND Job.JobId = '$1' ";
1939 my $status = CGI::param('status') || '';
1940 if ($status =~ /^(\w)$/) {
1943 $limit .= "AND Job.JobStatus IN ('f','E') ";
1944 } elsif ($1 eq 'W') {
1945 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
1947 $limit .= "AND Job.JobStatus = '$1' ";
1952 if ($elt{volstatus}) {
1953 my $status = CGI::param('volstatus') || '';
1954 if ($status =~ /^(\w+)$/) {
1956 $limit .= "AND Media.VolStatus = '$1' ";
1960 if ($elt{locations}) {
1961 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1963 $ret{locations} = \@location;
1964 my $str = $self->dbh_join(@location);
1965 $limit .= "AND Location.Location IN ($str) ";
1970 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1972 $ret{pools} = \@pool;
1973 my $str = $self->dbh_join(@pool);
1974 $limit .= "AND Pool.Name IN ($str) ";
1978 if ($elt{location}) {
1979 my $location = CGI::param('location') || '';
1981 $ret{location} = $location;
1982 $location = $self->dbh_quote($location);
1983 $limit .= "AND Location.Location = $location ";
1988 my $pool = CGI::param('pool') || '';
1991 $pool = $self->dbh_quote($pool);
1992 $limit .= "AND Pool.Name = $pool ";
1996 if ($elt{jobtype}) {
1997 my $jobtype = CGI::param('jobtype') || '';
1998 if ($jobtype =~ /^(\w)$/) {
2000 $limit .= "AND Job.Type = '$1' ";
2004 return ($limit, %ret);
2015 my ($self, %arg) = @_ ;
2017 $arg{order} = ' Job.JobId DESC ';
2019 my ($limit, $label) = $self->get_limit(%arg);
2020 my ($where, undef) = $self->get_param('clients',
2030 if (CGI::param('client_group')) {
2032 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2033 LEFT JOIN client_group USING (client_group_id)
2038 SELECT Job.JobId AS jobid,
2039 Client.Name AS client,
2040 FileSet.FileSet AS fileset,
2041 Job.Name AS jobname,
2043 StartTime AS starttime,
2045 Pool.Name AS poolname,
2046 JobFiles AS jobfiles,
2047 JobBytes AS jobbytes,
2048 JobStatus AS jobstatus,
2049 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2050 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2053 JobErrors AS joberrors
2056 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2057 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2059 WHERE Client.ClientId=Job.ClientId
2060 AND Job.JobStatus NOT IN ('R', 'C')
2065 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2067 $self->display({ Filter => $label,
2071 sort { $a->{jobid} <=> $b->{jobid} }
2078 # display job informations
2079 sub display_job_zoom
2081 my ($self, $jobid) = @_ ;
2083 $jobid = $self->dbh_quote($jobid);
2086 SELECT DISTINCT Job.JobId AS jobid,
2087 Client.Name AS client,
2088 Job.Name AS jobname,
2089 FileSet.FileSet AS fileset,
2091 Pool.Name AS poolname,
2092 StartTime AS starttime,
2093 JobFiles AS jobfiles,
2094 JobBytes AS jobbytes,
2095 JobStatus AS jobstatus,
2096 JobErrors AS joberrors,
2097 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2098 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2101 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2102 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2103 WHERE Client.ClientId=Job.ClientId
2104 AND Job.JobId = $jobid
2107 my $row = $self->dbh_selectrow_hashref($query) ;
2109 # display all volumes associate with this job
2111 SELECT Media.VolumeName as volumename
2112 FROM Job,Media,JobMedia
2113 WHERE Job.JobId = $jobid
2114 AND JobMedia.JobId=Job.JobId
2115 AND JobMedia.MediaId=Media.MediaId
2118 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2120 $row->{volumes} = [ values %$all ] ;
2122 $self->display($row, "display_job_zoom.tpl");
2125 sub display_job_group
2127 my ($self, %arg) = @_;
2129 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2131 my ($where, undef) = $self->get_param('client_groups',
2137 SELECT client_group_name AS client_group_name,
2138 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2139 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2140 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2141 COALESCE(jobok.nbjobs,0) AS nbjobok,
2142 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2143 COALESCE(jobok.duration, '0:0:0') AS duration
2145 FROM client_group LEFT JOIN (
2146 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2147 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2148 SUM(JobErrors) AS joberrors,
2149 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2150 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2153 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2154 JOIN client_group USING (client_group_id)
2156 WHERE JobStatus = 'T'
2159 ) AS jobok USING (client_group_name) LEFT JOIN
2162 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2163 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2164 SUM(JobErrors) AS joberrors
2165 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2166 JOIN client_group USING (client_group_id)
2168 WHERE JobStatus IN ('f','E', 'A')
2171 ) AS joberr USING (client_group_name)
2175 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2177 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2180 $self->display($rep, "display_job_group.tpl");
2185 my ($self, %arg) = @_ ;
2187 my ($limit, $label) = $self->get_limit(%arg);
2188 my ($where, %elt) = $self->get_param('pools',
2193 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2195 if ($arg->{jmedias}) {
2196 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2198 if ($arg->{qre_media}) {
2199 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2201 if ($arg->{expired}) {
2203 AND VolStatus = 'Full'
2204 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2205 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2206 ) < NOW() " . $where ;
2210 SELECT Media.VolumeName AS volumename,
2211 Media.VolBytes AS volbytes,
2212 Media.VolStatus AS volstatus,
2213 Media.MediaType AS mediatype,
2214 Media.InChanger AS online,
2215 Media.LastWritten AS lastwritten,
2216 Location.Location AS location,
2217 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2218 Pool.Name AS poolname,
2219 $self->{sql}->{FROM_UNIXTIME}(
2220 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2221 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2224 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2225 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2226 Media.MediaType AS MediaType
2228 WHERE Media.VolStatus = 'Full'
2229 GROUP BY Media.MediaType
2230 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2232 WHERE Media.PoolId=Pool.PoolId
2237 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2239 $self->display({ ID => $cur_id++,
2241 Location => $elt{location},
2242 Media => [ values %$all ],
2244 "display_media.tpl");
2247 sub display_allmedia
2251 my $pool = $self->get_form('db_pools');
2253 foreach my $name (@{ $pool->{db_pools} }) {
2254 CGI::param('pool', $name->{name});
2255 $self->display_media();
2259 sub display_media_zoom
2263 my $media = $self->get_form('jmedias');
2265 unless ($media->{jmedias}) {
2266 return $self->error("Can't get media selection");
2270 SELECT InChanger AS online,
2271 Media.Enabled AS enabled,
2272 VolBytes AS nb_bytes,
2273 VolumeName AS volumename,
2274 VolStatus AS volstatus,
2275 VolMounts AS nb_mounts,
2276 Media.VolUseDuration AS voluseduration,
2277 Media.MaxVolJobs AS maxvoljobs,
2278 Media.MaxVolFiles AS maxvolfiles,
2279 Media.MaxVolBytes AS maxvolbytes,
2280 VolErrors AS nb_errors,
2281 Pool.Name AS poolname,
2282 Location.Location AS location,
2283 Media.Recycle AS recycle,
2284 Media.VolRetention AS volretention,
2285 Media.LastWritten AS lastwritten,
2286 Media.VolReadTime/1000000 AS volreadtime,
2287 Media.VolWriteTime/1000000 AS volwritetime,
2288 Media.RecycleCount AS recyclecount,
2289 Media.Comment AS comment,
2290 $self->{sql}->{FROM_UNIXTIME}(
2291 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2292 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2295 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2296 WHERE Pool.PoolId = Media.PoolId
2297 AND VolumeName IN ($media->{jmedias})
2300 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2302 foreach my $media (values %$all) {
2303 my $mq = $self->dbh_quote($media->{volumename});
2306 SELECT DISTINCT Job.JobId AS jobid,
2308 Job.StartTime AS starttime,
2311 Job.JobFiles AS files,
2312 Job.JobBytes AS bytes,
2313 Job.jobstatus AS status
2314 FROM Media,JobMedia,Job
2315 WHERE Media.VolumeName=$mq
2316 AND Media.MediaId=JobMedia.MediaId
2317 AND JobMedia.JobId=Job.JobId
2320 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2323 SELECT LocationLog.Date AS date,
2324 Location.Location AS location,
2325 LocationLog.Comment AS comment
2326 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2327 WHERE Media.MediaId = LocationLog.MediaId
2328 AND Media.VolumeName = $mq
2332 my $log = $self->dbh_selectall_arrayref($query) ;
2334 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2337 $self->display({ jobs => [ values %$jobs ],
2338 LocationLog => $logtxt,
2340 "display_media_zoom.tpl");
2348 my $loc = $self->get_form('qlocation');
2349 unless ($loc->{qlocation}) {
2350 return $self->error("Can't get location");
2354 SELECT Location.Location AS location,
2355 Location.Cost AS cost,
2356 Location.Enabled AS enabled
2358 WHERE Location.Location = $loc->{qlocation}
2361 my $row = $self->dbh_selectrow_hashref($query);
2363 $self->display({ ID => $cur_id++,
2364 %$row }, "location_edit.tpl") ;
2372 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2373 unless ($arg->{qlocation}) {
2374 return $self->error("Can't get location");
2376 unless ($arg->{qnewlocation}) {
2377 return $self->error("Can't get new location name");
2379 unless ($arg->{cost}) {
2380 return $self->error("Can't get new cost");
2383 my $enabled = CGI::param('enabled') || '';
2384 $enabled = $enabled?1:0;
2387 UPDATE Location SET Cost = $arg->{cost},
2388 Location = $arg->{qnewlocation},
2390 WHERE Location.Location = $arg->{qlocation}
2393 $self->dbh_do($query);
2395 $self->location_display();
2401 my $arg = $self->get_form(qw/qlocation/) ;
2403 unless ($arg->{qlocation}) {
2404 return $self->error("Can't get location");
2408 SELECT count(Media.MediaId) AS nb
2409 FROM Media INNER JOIN Location USING (LocationID)
2410 WHERE Location = $arg->{qlocation}
2413 my $res = $self->dbh_selectrow_hashref($query);
2416 return $self->error("Sorry, the location must be empty");
2420 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2423 $self->dbh_do($query);
2425 $self->location_display();
2432 my $arg = $self->get_form(qw/qlocation cost/) ;
2434 unless ($arg->{qlocation}) {
2435 $self->display({}, "location_add.tpl");
2438 unless ($arg->{cost}) {
2439 return $self->error("Can't get new cost");
2442 my $enabled = CGI::param('enabled') || '';
2443 $enabled = $enabled?1:0;
2446 INSERT INTO Location (Location, Cost, Enabled)
2447 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2450 $self->dbh_do($query);
2452 $self->location_display();
2455 sub location_display
2460 SELECT Location.Location AS location,
2461 Location.Cost AS cost,
2462 Location.Enabled AS enabled,
2463 (SELECT count(Media.MediaId)
2465 WHERE Media.LocationId = Location.LocationId
2470 my $location = $self->dbh_selectall_hashref($query, 'location');
2472 $self->display({ ID => $cur_id++,
2473 Locations => [ values %$location ] },
2474 "display_location.tpl");
2481 my $media = $self->get_selected_media_location();
2486 my $arg = $self->get_form('db_locations', 'qnewlocation');
2488 $self->display({ email => $self->{info}->{email_media},
2490 media => [ values %$media ],
2492 "update_location.tpl");
2495 ###########################################################
2501 my $grp = $self->get_form(qw/qclient_group db_clients/);
2504 unless ($grp->{qclient_group}) {
2505 return $self->error("Can't get group");
2510 FROM Client JOIN client_group_member using (clientid)
2511 JOIN client_group using (client_group_id)
2512 WHERE client_group_name = $grp->{qclient_group}
2515 my $row = $self->dbh_selectall_hashref($query, "name");
2517 $self->display({ ID => $cur_id++,
2518 client_group => $grp->{qclient_group},
2520 client_group_member => [ values %$row]},
2528 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2529 unless ($arg->{qclient_group}) {
2530 return $self->error("Can't get groups");
2533 $self->{dbh}->begin_work();
2536 DELETE FROM client_group_member
2537 WHERE client_group_id IN
2538 (SELECT client_group_id
2540 WHERE client_group_name = $arg->{qclient_group})
2542 $self->dbh_do($query);
2545 INSERT INTO client_group_member (clientid, client_group_id)
2547 (SELECT client_group_id
2549 WHERE client_group_name = $arg->{qclient_group})
2550 FROM Client WHERE Name IN ($arg->{jclients})
2553 $self->dbh_do($query);
2555 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2558 SET client_group_name = $arg->{qnewgroup}
2559 WHERE client_group_name = $arg->{qclient_group}
2562 $self->dbh_do($query);
2565 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2567 $self->display_groups();
2573 my $arg = $self->get_form(qw/qclient_group/);
2575 unless ($arg->{qclient_group}) {
2576 return $self->error("Can't get groups");
2579 $self->{dbh}->begin_work();
2582 DELETE FROM client_group_member
2583 WHERE client_group_id IN
2584 (SELECT client_group_id
2586 WHERE client_group_name = $arg->{qclient_group});
2588 DELETE FROM client_group
2589 WHERE client_group_name = $arg->{qclient_group};
2591 $self->dbh_do($query);
2593 $self->{dbh}->commit();
2595 $self->display_groups();
2602 my $arg = $self->get_form(qw/qclient_group/) ;
2604 unless ($arg->{qclient_group}) {
2605 $self->display({}, "groups_add.tpl");
2610 INSERT INTO client_group (client_group_name)
2611 VALUES ($arg->{qclient_group})
2614 $self->dbh_do($query);
2616 $self->display_groups();
2623 my $arg = $self->get_form(qw/db_client_groups/) ;
2625 if ($self->{dbh}->errstr) {
2626 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2631 $self->display({ ID => $cur_id++,
2633 "display_groups.tpl");
2636 ###########################################################
2638 sub get_media_max_size
2640 my ($self, $type) = @_;
2642 "SELECT avg(VolBytes) AS size
2644 WHERE Media.VolStatus = 'Full'
2645 AND Media.MediaType = '$type'
2648 my $res = $self->selectrow_hashref($query);
2651 return $res->{size};
2661 my $media = $self->get_form('qmedia');
2663 unless ($media->{qmedia}) {
2664 return $self->error("Can't get media");
2668 SELECT Media.Slot AS slot,
2669 PoolMedia.Name AS poolname,
2670 Media.VolStatus AS volstatus,
2671 Media.InChanger AS inchanger,
2672 Location.Location AS location,
2673 Media.VolumeName AS volumename,
2674 Media.MaxVolBytes AS maxvolbytes,
2675 Media.MaxVolJobs AS maxvoljobs,
2676 Media.MaxVolFiles AS maxvolfiles,
2677 Media.VolUseDuration AS voluseduration,
2678 Media.VolRetention AS volretention,
2679 Media.Comment AS comment,
2680 PoolRecycle.Name AS poolrecycle,
2681 Media.Enabled AS enabled
2683 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2684 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2685 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2687 WHERE Media.VolumeName = $media->{qmedia}
2690 my $row = $self->dbh_selectrow_hashref($query);
2691 $row->{volretention} = human_sec($row->{volretention});
2692 $row->{voluseduration} = human_sec($row->{voluseduration});
2693 $row->{enabled} = human_enabled($row->{enabled});
2695 my $elt = $self->get_form(qw/db_pools db_locations/);
2700 }, "update_media.tpl");
2707 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2709 unless ($arg->{jmedias}) {
2710 return $self->error("Can't get selected media");
2713 unless ($arg->{qnewlocation}) {
2714 return $self->error("Can't get new location");
2719 SET LocationId = (SELECT LocationId
2721 WHERE Location = $arg->{qnewlocation})
2722 WHERE Media.VolumeName IN ($arg->{jmedias})
2725 my $nb = $self->dbh_do($query);
2727 print "$nb media updated, you may have to update your autochanger.";
2729 $self->display_media();
2736 my $media = $self->get_selected_media_location();
2738 return $self->error("Can't get media selection");
2740 my $newloc = CGI::param('newlocation');
2742 my $user = CGI::param('user') || 'unknown';
2743 my $comm = CGI::param('comment') || '';
2744 $comm = $self->dbh_quote("$user: $comm");
2746 my $arg = $self->get_form('enabled');
2747 my $en = human_enabled($arg->{enabled});
2748 my $b = $self->get_bconsole();
2751 foreach my $vol (keys %$media) {
2753 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2755 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$vol'),
2756 (SELECT LocationId FROM Location WHERE Location = '$media->{$vol}->{location}'),
2757 (SELECT VolStatus FROM Media WHERE VolumeName = '$vol')
2760 $self->dbh_do($query);
2761 $self->debug($query);
2762 $b->send_cmd("update volume=\"$vol\" enabled=$en");
2767 $q->param('action', 'update_location');
2768 my $url = $q->url(-full => 1, -query=>1);
2770 $self->display({ email => $self->{info}->{email_media},
2772 newlocation => $newloc,
2773 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
2774 media => [ values %$media ],
2776 "change_location.tpl");
2780 sub display_client_stats
2782 my ($self, %arg) = @_ ;
2784 my $client = $self->dbh_quote($arg{clientname});
2786 my ($limit, $label) = $self->get_limit(%arg);
2790 count(Job.JobId) AS nb_jobs,
2791 sum(Job.JobBytes) AS nb_bytes,
2792 sum(Job.JobErrors) AS nb_err,
2793 sum(Job.JobFiles) AS nb_files,
2794 Client.Name AS clientname
2795 FROM Job JOIN Client USING (ClientId)
2797 Client.Name = $client
2799 GROUP BY Client.Name
2802 my $row = $self->dbh_selectrow_hashref($query);
2804 $row->{ID} = $cur_id++;
2805 $row->{label} = $label;
2806 $row->{grapharg} = "client";
2808 $self->display($row, "display_client_stats.tpl");
2812 sub display_group_stats
2814 my ($self, %arg) = @_ ;
2816 my $carg = $self->get_form(qw/qclient_group/);
2818 unless ($carg->{qclient_group}) {
2819 return $self->error("Can't get group");
2822 my ($limit, $label) = $self->get_limit(%arg);
2826 count(Job.JobId) AS nb_jobs,
2827 sum(Job.JobBytes) AS nb_bytes,
2828 sum(Job.JobErrors) AS nb_err,
2829 sum(Job.JobFiles) AS nb_files,
2830 client_group.client_group_name AS clientname
2831 FROM Job JOIN Client USING (ClientId)
2832 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
2833 JOIN client_group USING (client_group_id)
2835 client_group.client_group_name = $carg->{qclient_group}
2837 GROUP BY client_group.client_group_name
2840 my $row = $self->dbh_selectrow_hashref($query);
2842 $row->{ID} = $cur_id++;
2843 $row->{label} = $label;
2844 $row->{grapharg} = "client_group";
2846 $self->display($row, "display_client_stats.tpl");
2849 # poolname can be undef
2852 my ($self, $poolname) = @_ ;
2856 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2857 if ($arg->{jmediatypes}) {
2858 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2859 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
2862 # TODO : afficher les tailles et les dates
2865 SELECT subq.volmax AS volmax,
2866 subq.volnum AS volnum,
2867 subq.voltotal AS voltotal,
2869 Pool.Recycle AS recycle,
2870 Pool.VolRetention AS volretention,
2871 Pool.VolUseDuration AS voluseduration,
2872 Pool.MaxVolJobs AS maxvoljobs,
2873 Pool.MaxVolFiles AS maxvolfiles,
2874 Pool.MaxVolBytes AS maxvolbytes,
2875 subq.PoolId AS PoolId,
2876 subq.MediaType AS mediatype,
2877 $self->{sql}->{CAT_POOL_TYPE} AS uniq
2880 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2881 count(Media.MediaId) AS volnum,
2882 sum(Media.VolBytes) AS voltotal,
2883 Media.PoolId AS PoolId,
2884 Media.MediaType AS MediaType
2886 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2887 Media.MediaType AS MediaType
2889 WHERE Media.VolStatus = 'Full'
2890 GROUP BY Media.MediaType
2891 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2892 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2894 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2898 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
2901 SELECT Pool.Name AS name,
2902 sum(VolBytes) AS size
2903 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2904 WHERE Media.VolStatus IN ('Recycled', 'Purged')
2908 my $empty = $self->dbh_selectall_hashref($query, 'name');
2910 foreach my $p (values %$all) {
2911 if ($p->{volmax} > 0) { # mysql returns 0.0000
2912 # we remove Recycled/Purged media from pool usage
2913 if (defined $empty->{$p->{name}}) {
2914 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2916 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2918 $p->{poolusage} = 0;
2922 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2924 WHERE PoolId=$p->{poolid}
2925 AND Media.MediaType = '$p->{mediatype}'
2929 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2930 foreach my $t (values %$content) {
2931 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2936 $self->display({ ID => $cur_id++,
2937 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2938 Pools => [ values %$all ]},
2939 "display_pool.tpl");
2942 sub display_running_job
2946 my $arg = $self->get_form('client', 'jobid');
2948 if (!$arg->{client} and $arg->{jobid}) {
2951 SELECT Client.Name AS name
2952 FROM Job INNER JOIN Client USING (ClientId)
2953 WHERE Job.JobId = $arg->{jobid}
2956 my $row = $self->dbh_selectrow_hashref($query);
2959 $arg->{client} = $row->{name};
2960 CGI::param('client', $arg->{client});
2964 if ($arg->{client}) {
2965 my $cli = new Bweb::Client(name => $arg->{client});
2966 $cli->display_running_job($self->{info}, $arg->{jobid});
2967 if ($arg->{jobid}) {
2968 $self->get_job_log();
2971 $self->error("Can't get client or jobid");
2975 sub display_running_jobs
2977 my ($self, $display_action) = @_;
2980 SELECT Job.JobId AS jobid,
2981 Job.Name AS jobname,
2983 Job.StartTime AS starttime,
2984 Job.JobFiles AS jobfiles,
2985 Job.JobBytes AS jobbytes,
2986 Job.JobStatus AS jobstatus,
2987 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2988 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2990 Client.Name AS clientname
2991 FROM Job INNER JOIN Client USING (ClientId)
2992 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2994 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2996 $self->display({ ID => $cur_id++,
2997 display_action => $display_action,
2998 Jobs => [ values %$all ]},
2999 "running_job.tpl") ;
3002 # return the autochanger list to update
3007 my $arg = $self->get_form('jmedias');
3009 unless ($arg->{jmedias}) {
3010 return $self->error("Can't get media selection");
3014 SELECT Media.VolumeName AS volumename,
3015 Storage.Name AS storage,
3016 Location.Location AS location,
3018 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
3019 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3020 WHERE Media.VolumeName IN ($arg->{jmedias})
3021 AND Media.InChanger = 1
3024 my $all = $self->dbh_selectall_hashref($query, 'volumename');
3026 foreach my $vol (values %$all) {
3027 my $a = $self->ach_get($vol->{location});
3029 $ret{$vol->{location}} = 1;
3031 unless ($a->{have_status}) {
3033 $a->{have_status} = 1;
3036 print "eject $vol->{volumename} from $vol->{storage} : ";
3037 if ($a->send_to_io($vol->{slot})) {
3038 print "<img src='/bweb/T.png' alt='ok'><br/>";
3040 print "<img src='/bweb/E.png' alt='err'><br/>";
3050 my ($to, $subject, $content) = (CGI::param('email'),
3051 CGI::param('subject'),
3052 CGI::param('content'));
3053 $to =~ s/[^\w\d\.\@<>,]//;
3054 $subject =~ s/[^\w\d\.\[\]]/ /;
3056 open(MAIL, "|mail -s '$subject' '$to'") ;
3057 print MAIL $content;
3067 my $arg = $self->get_form('jobid', 'client');
3069 print CGI::header('text/brestore');
3070 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3071 print "client=$arg->{client}\n" if ($arg->{client});
3072 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3076 # TODO : move this to Bweb::Autochanger ?
3077 # TODO : make this internal to not eject tape ?
3083 my ($self, $name) = @_;
3086 return $self->error("Can't get your autochanger name ach");
3089 unless ($self->{info}->{ach_list}) {
3090 return $self->error("Could not find any autochanger");
3093 my $a = $self->{info}->{ach_list}->{$name};
3096 $self->error("Can't get your autochanger $name from your ach_list");
3101 $a->{debug} = $self->{debug};
3108 my ($self, $ach) = @_;
3110 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3112 $self->{info}->save();
3120 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 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3132 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3134 my $b = $self->get_bconsole();
3136 my @storages = $b->list_storage() ;
3138 $ach->{devices} = [ map { { name => $_ } } @storages ];
3140 $self->display($ach, "ach_add.tpl");
3141 delete $ach->{drives};
3142 delete $ach->{devices};
3149 my $arg = $self->get_form('ach');
3152 or !$self->{info}->{ach_list}
3153 or !$self->{info}->{ach_list}->{$arg->{ach}})
3155 return $self->error("Can't get autochanger name");
3158 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3160 $self->{info}->save();
3161 $self->{info}->view();
3167 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3169 my $b = $self->get_bconsole();
3170 my @storages = $b->list_storage() ;
3172 unless ($arg->{ach}) {
3173 $arg->{devices} = [ map { { name => $_ } } @storages ];
3174 return $self->display($arg, "ach_add.tpl");
3178 foreach my $drive (CGI::param('drives'))
3180 unless (grep(/^$drive$/,@storages)) {
3181 return $self->error("Can't find $drive in storage list");
3184 my $index = CGI::param("index_$drive");
3185 unless (defined $index and $index =~ /^(\d+)$/) {
3186 return $self->error("Can't get $drive index");
3189 $drives[$index] = $drive;
3193 return $self->error("Can't get drives from Autochanger");
3196 my $a = new Bweb::Autochanger(name => $arg->{ach},
3197 precmd => $arg->{precmd},
3198 drive_name => \@drives,
3199 device => $arg->{device},
3200 mtxcmd => $arg->{mtxcmd});
3202 $self->ach_register($a) ;
3204 $self->{info}->view();
3210 my $arg = $self->get_form('jobid');
3212 if ($arg->{jobid}) {
3213 my $b = $self->get_bconsole();
3214 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3218 title => "Delete a job ",
3219 name => "delete jobid=$arg->{jobid}",
3228 my $arg = $self->get_form(qw/media volstatus inchanger pool
3229 slot volretention voluseduration
3230 maxvoljobs maxvolfiles maxvolbytes
3231 qcomment poolrecycle enabled
3234 unless ($arg->{media}) {
3235 return $self->error("Can't find media selection");
3238 my $update = "update volume=$arg->{media} ";
3240 if ($arg->{volstatus}) {
3241 $update .= " volstatus=$arg->{volstatus} ";
3244 if ($arg->{inchanger}) {
3245 $update .= " inchanger=yes " ;
3247 $update .= " slot=$arg->{slot} ";
3250 $update .= " slot=0 inchanger=no ";
3253 if ($arg->{enabled}) {
3254 $update .= " enabled=$arg->{enabled} ";
3258 $update .= " pool=$arg->{pool} " ;
3261 if (defined $arg->{volretention}) {
3262 $update .= " volretention=\"$arg->{volretention}\" " ;
3265 if (defined $arg->{voluseduration}) {
3266 $update .= " voluse=\"$arg->{voluseduration}\" " ;
3269 if (defined $arg->{maxvoljobs}) {
3270 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3273 if (defined $arg->{maxvolfiles}) {
3274 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3277 if (defined $arg->{maxvolbytes}) {
3278 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3281 if (defined $arg->{poolrecycle}) {
3282 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
3285 my $b = $self->get_bconsole();
3288 content => $b->send_cmd($update),
3289 title => "Update a volume ",
3295 my $media = $self->dbh_quote($arg->{media});
3297 my $loc = CGI::param('location') || '';
3299 $loc = $self->dbh_quote($loc); # is checked by db
3300 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3302 if (!$arg->{qcomment}) {
3303 $arg->{qcomment} = "''";
3305 push @q, "Comment=$arg->{qcomment}";
3310 SET " . join (',', @q) . "
3311 WHERE Media.VolumeName = $media
3313 $self->dbh_do($query);
3315 $self->update_media();
3322 my $ach = CGI::param('ach') ;
3323 $ach = $self->ach_get($ach);
3325 return $self->error("Bad autochanger name");
3329 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3330 $b->update_slots($ach->{name});
3338 my $arg = $self->get_form('jobid', 'limit', 'offset');
3339 unless ($arg->{jobid}) {
3340 return $self->error("Can't get jobid");
3343 if ($arg->{limit} == 100) {
3344 $arg->{limit} = 1000;
3347 my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3349 # display only Error and Warning messages
3351 if (CGI::param('error')) {
3352 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning' ";
3356 SELECT Job.Name as name, Client.Name as clientname
3357 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3358 WHERE JobId = $arg->{jobid}
3361 my $row = $self->dbh_selectrow_hashref($query);
3364 return $self->error("Can't find $arg->{jobid} in catalog");
3368 SELECT Time AS time, LogText AS log
3370 WHERE ( Log.JobId = $arg->{jobid}
3371 OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
3372 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3377 OFFSET $arg->{offset}
3380 my $log = $self->dbh_selectall_arrayref($query);
3382 return $self->error("Can't get log for jobid $arg->{jobid}");
3388 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
3390 $logtxt = join("", map { $_->[1] } @$log ) ;
3393 $self->display({ lines=> $logtxt,
3394 jobid => $arg->{jobid},
3395 name => $row->{name},
3396 client => $row->{clientname},
3397 offset => $arg->{offset},
3398 limit => $arg->{limit},
3399 }, 'display_log.tpl');
3407 my $arg = $self->get_form('ach', 'slots', 'drive');
3409 unless ($arg->{ach}) {
3410 return $self->error("Can't find autochanger name");
3413 my $a = $self->ach_get($arg->{ach});
3415 return $self->error("Can't find autochanger name in configuration");
3418 my $storage = $a->get_drive_name($arg->{drive});
3420 return $self->error("Can't get your drive name");
3426 if ($arg->{slots}) {
3427 $slots = join(",", @{ $arg->{slots} });
3428 $slots_sql = " AND Slot IN ($slots) ";
3429 $t += 60*scalar( @{ $arg->{slots} }) ;
3432 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3433 print "<h1>This command can take long time, be patient...</h1>";
3435 $b->label_barcodes(storage => $storage,
3436 drive => $arg->{drive},
3444 SET LocationId = (SELECT LocationId
3446 WHERE Location = '$arg->{ach}')
3448 WHERE (LocationId = 0 OR LocationId IS NULL)
3458 my @volume = CGI::param('media');
3461 return $self->error("Can't get media selection");
3464 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3466 foreach my $v (@volume) {
3468 content => $b->purge_volume($v),
3469 title => "Purge media",
3470 name => "purge volume=$v",
3480 my @volume = CGI::param('media');
3482 return $self->error("Can't get media selection");
3485 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3487 foreach my $v (@volume) {
3489 content => $b->prune_volume($v),
3490 title => "Prune volume",
3491 name => "prune volume=$v",
3501 my $arg = $self->get_form('jobid');
3502 unless ($arg->{jobid}) {
3503 return $self->error("Can't get jobid");
3506 my $b = $self->get_bconsole();
3508 content => $b->cancel($arg->{jobid}),
3509 title => "Cancel job",
3510 name => "cancel jobid=$arg->{jobid}",
3516 # Warning, we display current fileset
3519 my $arg = $self->get_form('fileset');
3521 if ($arg->{fileset}) {
3522 my $b = $self->get_bconsole();
3523 my $ret = $b->get_fileset($arg->{fileset});
3524 $self->display({ fileset => $arg->{fileset},
3526 }, "fileset_view.tpl");
3528 $self->error("Can't get fileset name");
3532 sub director_show_sched
3536 my $arg = $self->get_form('days');
3538 my $b = $self->get_bconsole();
3539 my $ret = $b->director_get_sched( $arg->{days} );
3544 }, "scheduled_job.tpl");
3547 sub enable_disable_job
3549 my ($self, $what) = @_ ;
3551 my $name = CGI::param('job') || '';
3552 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3553 return $self->error("Can't find job name");
3556 my $b = $self->get_bconsole();
3566 content => $b->send_cmd("$cmd job=\"$name\""),
3567 title => "$cmd $name",
3568 name => "$cmd job=\"$name\"",
3575 return new Bconsole(pref => $self->{info});
3581 my $b = $self->get_bconsole();
3583 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3585 $self->display({ Jobs => $joblist }, "run_job.tpl");
3590 my ($self, $ouput) = @_;
3593 foreach my $l (split(/\r\n/, $ouput)) {
3594 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3600 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3606 foreach my $k (keys %arg) {
3607 $lowcase{lc($k)} = $arg{$k} ;
3616 my $b = $self->get_bconsole();
3618 my $job = CGI::param('job') || '';
3620 # we take informations from director, and we overwrite with user wish
3621 my $info = $b->send_cmd("show job=\"$job\"");
3622 my $attr = $self->run_parse_job($info);
3624 my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3625 my %job_opt = (%$attr, %$arg);
3627 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3629 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3630 my $clients = [ map { { name => $_ } }$b->list_client()];
3631 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3632 my $storages= [ map { { name => $_ } }$b->list_storage()];
3637 clients => $clients,
3638 filesets => $filesets,
3639 storages => $storages,
3641 }, "run_job_mod.tpl");
3647 my $b = $self->get_bconsole();
3649 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3659 my $b = $self->get_bconsole();
3661 # TODO: check input (don't use pool, level)
3663 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3664 my $job = CGI::param('job') || '';
3665 my $storage = CGI::param('storage') || '';
3667 my $jobid = $b->run(job => $job,
3668 client => $arg->{client},
3669 priority => $arg->{priority},
3670 level => $arg->{level},
3671 storage => $storage,
3672 pool => $arg->{pool},
3673 fileset => $arg->{fileset},
3674 when => $arg->{when},
3677 print $jobid, $b->{error};
3679 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";