1 ################################################################
6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2009 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 Kern Sibbald.
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 my ($self, $what) = @_;
100 my $old = $self->{debug};
103 $self->{debug} = $old;
108 error - display an error to the user
112 this function set $self->{error} with arg, display a message with
113 error.tpl and return 0
118 return $self->error("Can't use this file");
125 my ($self, $what) = @_;
126 $self->{error} = $what;
127 $self->display($self, 'error.tpl');
133 display - display an html page with HTML::Template
137 this function is use to render all html codes. it takes an
138 ref hash as arg in which all param are usable in template.
140 it will use user template_dir then global template_dir
141 to search the template file.
143 hash keys are not sensitive. See HTML::Template for more
144 explanations about the hash ref. (it's can be quiet hard to understand)
148 $ref = { name => 'me', age => 26 };
149 $self->display($ref, "people.tpl");
155 my ($self, $hash, $tpl) = @_ ;
156 my $dir = $self->{template_dir} || $template_dir;
157 my $lang = $self->{lang} || 'en';
158 my $template = HTML::Template->new(filename => $tpl,
159 path =>["$dir/$lang",
162 die_on_bad_params => 0,
163 case_sensitive => 0);
165 foreach my $var (qw/limit offset/) {
167 unless ($hash->{$var}) {
168 my $value = CGI::param($var) || '';
170 if ($value =~ /^(\d+)$/) {
171 $template->param($var, $1) ;
176 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
177 $template->param('loginname', CGI::remote_user());
179 $template->param($hash);
180 print $template->output();
184 ################################################################
186 package Bweb::Config;
188 use base q/Bweb::Gui/;
192 Bweb::Config - read, write, display, modify configuration
196 this package is used for manage configuration
200 $conf = new Bweb::Config(config_file => '/path/to/conf');
211 =head1 PACKAGE VARIABLE
213 %k_re - hash of all acceptable option.
217 this variable permit to check all option with a regexp.
221 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
222 user => qr/^([\w\d\.-]+)$/i,
223 password => qr/^(.*)$/,
224 fv_write_path => qr!^([/\w\d\.-]*)$!,
225 template_dir => qr!^([/\w\d\.-]+)$!,
226 debug => qr/^(on)?$/,
227 lang => qr/^(\w\w)?$/,
228 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
229 graph_font => qr!^([/\w\d\.-]+.ttf)?$!,
230 bconsole => qr!^(.+)?$!,
231 syslog_file => qr!^(.+)?$!,
232 log_dir => qr!^(.+)?$!,
233 wiki_url => qr!(.*)$!,
234 stat_job_table => qr!^(\w*)$!,
235 display_log_time => qr!^(on)?$!,
236 enable_security => qr/^(on)?$/,
237 enable_security_acl => qr/^(on)?$/,
238 default_age => qr/^((?:\d+(?:[ywdhms]\s*?)?)+)\s*$/,
243 load - load config_file
247 this function load the specified config_file.
255 unless (open(FP, $self->{config_file}))
257 return $self->error("can't load config_file $self->{config_file} : $!");
259 my $f=''; my $tmpbuffer;
260 while(read FP,$tmpbuffer,4096)
268 no strict; # I have no idea of the contents of the file
275 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...") ;
279 $self->{default_age} = '7d';
281 foreach my $k (keys %$VAR1) {
282 $self->{$k} = $VAR1->{$k};
290 load_old - load old configuration format
298 unless (open(FP, $self->{config_file}))
300 return $self->error("$self->{config_file} : $!");
303 while (my $line = <FP>)
306 my ($k, $v) = split(/\s*=\s*/, $line, 2);
318 save - save the current configuration to config_file
326 if ($self->{ach_list}) {
327 # shortcut for display_begin
328 $self->{achs} = [ map {{ name => $_ }}
329 keys %{$self->{ach_list}}
333 unless (open(FP, ">$self->{config_file}"))
335 return $self->error("$self->{config_file} : $!\n" .
336 "You must add this to your config file\n"
337 . Data::Dumper::Dumper($self));
340 print FP Data::Dumper::Dumper($self);
348 edit, view, modify - html form ouput
356 $self->display($self, "config_edit.tpl");
362 $self->display($self, "config_view.tpl");
370 # we need to reset checkbox first
372 $self->{display_log_time} = 0;
373 $self->{enable_security} = 0;
374 $self->{enable_security_acl} = 0;
376 foreach my $k (CGI::param())
378 next unless (exists $k_re{$k}) ;
379 my $val = CGI::param($k);
380 if ($val =~ $k_re{$k}) {
383 $self->{error} .= "bad parameter : $k = [$val]";
389 if ($self->{error}) { # an error as occured
390 $self->display($self, 'error.tpl');
398 ################################################################
400 package Bweb::Client;
402 use base q/Bweb::Gui/;
406 Bweb::Client - Bacula FD
410 this package is use to do all Client operations like, parse status etc...
414 $client = new Bweb::Client(name => 'zog-fd');
415 $client->status(); # do a 'status client=zog-fd'
421 display_running_job - Html display of a running job
425 this function is used to display information about a current job
429 sub display_running_job
431 my ($self, $bweb, $jobid, $infos) = @_ ;
432 my $status = $self->status($bweb->{info});
435 if ($status->{$jobid}) {
436 $status = $status->{$jobid};
437 $status->{last_jobbytes} = $infos->{jobbytes};
438 $status->{last_jobfiles} = $infos->{jobfiles};
439 $status->{corr_jobbytes} = $infos->{corr_jobbytes};
440 $status->{corr_jobfiles} = $infos->{corr_jobfiles};
441 $status->{jobbytes}=$status->{Bytes};
442 $status->{jobbytes} =~ s![^\d]!!g;
443 $status->{jobfiles}=$status->{'Files Examined'};
444 $status->{jobfiles} =~ s/,//g;
445 $bweb->display($status, "client_job_status.tpl");
448 for my $id (keys %$status) {
449 $bweb->display($status->{$id}, "client_job_status.tpl");
456 $client = new Bweb::Client(name => 'plume-fd');
458 $client->status($bweb);
462 dirty hack to parse "status client=xxx-fd"
466 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
467 Backup Job started: 06-jun-06 17:22
468 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
469 Files Examined=10,697
470 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
476 JobName => Full_plume.2006-06-06_17.22.23,
479 Bytes => 194,484,132,
489 my ($self, $conf) = @_ ;
491 if (defined $self->{cur_jobs}) {
492 return $self->{cur_jobs} ;
496 my $b = new Bconsole(pref => $conf);
497 my $ret = $b->send_cmd("st client=$self->{name}");
501 for my $r (split(/\n/, $ret)) {
503 $r =~ s/(^\s+|\s+$)//g;
504 if ($r =~ /JobId (\d+) Job (\S+)/) {
506 $arg->{$jobid} = { @param, JobId => $jobid } ;
510 @param = ( JobName => $2 );
512 } elsif ($r =~ /=.+=/) {
513 push @param, split(/\s+|\s*=\s*/, $r) ;
515 } elsif ($r =~ /=/) { # one per line
516 push @param, split(/\s*=\s*/, $r) ;
518 } elsif ($r =~ /:/) { # one per line
519 push @param, split(/\s*:\s*/, $r, 2) ;
523 if ($jobid and @param) {
524 $arg->{$jobid} = { @param,
526 Client => $self->{name},
530 $self->{cur_jobs} = $arg ;
536 ################################################################
538 package Bweb::Autochanger;
540 use base q/Bweb::Gui/;
544 Bweb::Autochanger - Object to manage Autochanger
548 this package will parse the mtx output and manage drives.
552 $auto = new Bweb::Autochanger(precmd => 'sudo');
554 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
558 $auto->slot_is_full(10);
559 $auto->transfer(10, 11);
565 my ($class, %arg) = @_;
568 name => '', # autochanger name
569 label => {}, # where are volume { label1 => 40, label2 => drive0 }
570 drive => [], # drive use [ 'media1', 'empty', ..]
571 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
572 io => [], # io slot number list [ 41, 42, 43...]
573 info => {slot => 0, # informations (slot, drive, io)
577 mtxcmd => '/usr/sbin/mtx',
579 device => '/dev/changer',
580 precmd => '', # ssh command
581 bweb => undef, # link to bacula web object (use for display)
584 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
591 status - parse the output of mtx status
595 this function will launch mtx status and parse the output. it will
596 give a perlish view of the autochanger content.
598 it uses ssh if the autochanger is on a other host.
605 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
607 # TODO : reset all infos
608 $self->{info}->{drive} = 0;
609 $self->{info}->{slot} = 0;
610 $self->{info}->{io} = 0;
612 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
615 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
616 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
617 #Data Transfer Element 1:Empty
618 # Storage Element 1:Empty
619 # Storage Element 2:Full :VolumeTag=000002
620 # Storage Element 3:Empty
621 # Storage Element 4:Full :VolumeTag=000004
622 # Storage Element 5:Full :VolumeTag=000001
623 # Storage Element 6:Full :VolumeTag=000003
624 # Storage Element 7:Empty
625 # Storage Element 41 IMPORT/EXPORT:Empty
626 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
631 # Storage Element 7:Empty
632 # Storage Element 2:Full :VolumeTag=000002
633 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d.-]+))?/){
636 $self->set_empty_slot($1);
638 $self->set_slot($1, $4);
641 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d.-]+))?)?/) {
644 $self->set_empty_drive($1);
646 $self->set_drive($1, $4, $6);
649 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w.-]+))?/)
652 $self->set_empty_io($1);
654 $self->set_io($1, $4);
657 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
659 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
660 $self->{info}->{drive} = $1;
661 $self->{info}->{slot} = $2;
662 if ($l =~ /(\d+)\s+Import/) {
663 $self->{info}->{io} = $1 ;
665 $self->{info}->{io} = 0;
670 $self->debug($self) ;
675 my ($self, $slot) = @_;
678 if ($self->{slot}->[$slot] eq 'loaded') {
682 my $label = $self->{slot}->[$slot] ;
684 return $self->is_media_loaded($label);
689 my ($self, $drive, $slot) = @_;
691 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
692 return 0 if ($self->slot_is_full($slot)) ;
694 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
697 my $content = $self->get_slot($slot);
698 print "content = $content<br/> $drive => $slot<br/>";
699 $self->set_empty_drive($drive);
700 $self->set_slot($slot, $content);
703 $self->{error} = $out;
708 # TODO: load/unload have to use mtx script from bacula
711 my ($self, $drive, $slot) = @_;
713 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
714 return 0 unless ($self->slot_is_full($slot)) ;
716 print "Loading drive $drive with slot $slot<br/>\n";
717 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
720 my $content = $self->get_slot($slot);
721 print "content = $content<br/> $slot => $drive<br/>";
722 $self->set_drive($drive, $slot, $content);
725 $self->{error} = $out;
733 my ($self, $media) = @_;
735 unless ($self->{label}->{$media}) {
739 if ($self->{label}->{$media} =~ /drive\d+/) {
749 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
754 my ($self, $slot, $tag) = @_;
755 $self->{slot}->[$slot] = $tag || 'full';
756 push @{ $self->{io} }, $slot;
759 $self->{label}->{$tag} = $slot;
765 my ($self, $slot) = @_;
767 push @{ $self->{io} }, $slot;
769 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
770 $self->{slot}->[$slot] = 'empty';
776 my ($self, $slot) = @_;
777 return $self->{slot}->[$slot];
782 my ($self, $slot, $tag) = @_;
783 $self->{slot}->[$slot] = $tag || 'full';
786 $self->{label}->{$tag} = $slot;
792 my ($self, $slot) = @_;
794 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
795 $self->{slot}->[$slot] = 'empty';
801 my ($self, $drive) = @_;
802 $self->{drive}->[$drive] = 'empty';
807 my ($self, $drive, $slot, $tag) = @_;
808 $self->{drive}->[$drive] = $tag || $slot;
809 $self->{drive_slot}->[$drive] = $slot;
811 $self->{slot}->[$slot] = $tag || 'loaded';
814 $self->{label}->{$tag} = "drive$drive";
820 my ($self, $slot) = @_;
822 # slot don't exists => full
823 if (not defined $self->{slot}->[$slot]) {
827 if ($self->{slot}->[$slot] eq 'empty') {
830 return 1; # vol, full, loaded
833 sub slot_get_first_free
836 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
837 return $slot unless ($self->slot_is_full($slot));
841 sub io_get_first_free
845 foreach my $slot (@{ $self->{io} }) {
846 return $slot unless ($self->slot_is_full($slot));
853 my ($self, $media) = @_;
855 return $self->{label}->{$media} ;
860 my ($self, $media) = @_;
862 return defined $self->{label}->{$media} ;
867 my ($self, $slot) = @_;
869 unless ($self->slot_is_full($slot)) {
870 print "Autochanger $self->{name} slot $slot is empty<br>\n";
875 if ($self->is_slot_loaded($slot)) {
878 print "Autochanger $self->{name} $slot is currently in use<br>\n";
882 # autochanger must have I/O
883 unless ($self->have_io()) {
884 print "Autochanger $self->{name} don't have I/O, you can take media yourself<br>\n";
888 my $dst = $self->io_get_first_free();
891 print "Autochanger $self->{name} mailbox is full, you must empty I/O first<br>\n";
895 $self->transfer($slot, $dst);
900 my ($self, $src, $dst) = @_ ;
901 if ($self->{debug}) {
902 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
904 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
907 my $content = $self->get_slot($src);
908 $self->{slot}->[$src] = 'empty';
909 $self->set_slot($dst, $content);
912 $self->{error} = $out;
919 my ($self, $index) = @_;
920 return $self->{drive_name}->[$index];
923 # TODO : do a tapeinfo request to get informations
933 print "<table><tr>\n";
934 for my $slot (@{$self->{io}})
936 if ($self->is_slot_loaded($slot)) {
937 print "<td></td><td>Slot $slot is currently loaded</td></tr>\n";
941 if ($self->slot_is_full($slot))
943 my $free = $self->slot_get_first_free() ;
944 print "</tr><tr><td>move slot $slot to $free :</td>";
947 if ($self->transfer($slot, $free)) {
948 print "<td><img src='/bweb/T.png' alt='ok'></td>\n";
950 print "<td><img src='/bweb/E.png' alt='ok' title='$self->{error}'></td>\n";
954 $self->{error} = "<td><img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'></td>\n";
958 print "</tr></table>\n";
961 # TODO : this is with mtx status output,
962 # we can do an other function from bacula view (with StorageId)
966 my $bweb = $self->{bweb};
968 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
969 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
972 SELECT Media.VolumeName AS volumename,
973 Media.VolStatus AS volstatus,
974 Media.LastWritten AS lastwritten,
975 Media.VolBytes AS volbytes,
976 Media.MediaType AS mediatype,
978 Media.InChanger AS inchanger,
980 $bweb->{sql}->{FROM_UNIXTIME}(
981 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
982 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
985 INNER JOIN Pool USING (PoolId)
987 WHERE Media.VolumeName IN ($media_list)
990 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
992 # TODO : verify slot and bacula slot
996 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
998 if ($self->slot_is_full($slot)) {
1000 my $vol = $self->{slot}->[$slot];
1001 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
1003 my $bslot = $all->{$vol}->{slot} ;
1004 my $inchanger = $all->{$vol}->{inchanger};
1006 # if bacula slot or inchanger flag is bad, we display a message
1007 if ($bslot != $slot or !$inchanger) {
1008 push @to_update, $slot;
1011 $all->{$vol}->{realslot} = $slot;
1013 push @{ $param }, $all->{$vol};
1015 } else { # empty or no label
1016 push @{ $param }, {realslot => $slot,
1017 volstatus => 'Unknown',
1018 volumename => $self->{slot}->[$slot]} ;
1021 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
1025 my $i=0; my $drives = [] ;
1026 foreach my $d (@{ $self->{drive} }) {
1027 $drives->[$i] = { index => $i,
1028 load => $self->{drive}->[$i],
1029 name => $self->{drive_name}->[$i],
1034 $bweb->display({ Name => $self->{name},
1035 nb_drive => $self->{info}->{drive},
1036 nb_io => $self->{info}->{io},
1039 Update => scalar(@to_update) },
1046 ################################################################
1048 package Bweb::Sched;
1049 use base q/Bweb::Gui/;
1053 Bweb::Sched() - Bweb package that parse show schedule ouput
1055 new Bweb::Sched(format => '%Y-%m-%d', year => 2008);
1059 my $b = $bweb->get_bconsole();
1060 my $s = $b->send_cmd("show schedule");
1061 my $sched = new Bweb::Sched(begin => '2007-01-01', end => '2007-01-02 12:00');
1062 $sched->parse_scheds(split(/\r?\n/, $s));
1073 'level' => 'Differential',
1080 my ($class, @arg) = @_;
1081 my $self = $class->SUPER::new(@arg);
1083 # we compare the current schedule date with begin and end
1084 # in a float form ex: 20071212.1243 > 20070101
1085 if ($self->{begin} and $self->{end}) {
1086 ($self->{fbegin}, $self->{fend}) = ($self->{begin}, $self->{end});
1087 $self->{fbegin} =~ s/(-|:)//g; $self->{fbegin} =~ s/ /./;
1088 $self->{fend} =~ s/(-|:)//g; $self->{fend} =~ s/ /./;
1091 bless($self,$class);
1093 if ($self->{bconsole}) {
1094 my $sel = $self->{name}?"=\"$self->{name}\"":'';
1095 my $b = $self->{bconsole};
1096 my $out = $b->send_cmd("show schedule$sel");
1097 $self->parse_scheds(split(/\r?\n/, $out));
1098 undef $self->{bconsole}; # useless now
1104 # cleanup and add a schedule
1107 my ($self, $name, $info) = @_;
1108 # bacula uses dates that start from 0, we start from 1
1109 foreach (@{$info->{mday}},@{$info->{month}}) { $_++ };
1112 $info->{event} = [ $self->get_events($info, $self->{format}) ];
1114 foreach my $i (qw/hour mday month wday wom woy mins/) {
1118 push @{$self->{schedules}->{$name}}, $info;
1121 # return the name of all schedules
1124 my ($self, $name) = @_;
1126 return keys %{ $self->{schedules} };
1129 # return an array of all schedule
1132 my ($self, $sched) = @_;
1133 return $self->{schedules}->{$sched};
1136 # return an ref array of all events
1137 # [ '2007-12-19 04:05', '2007-12-19 04:05' ]
1140 my ($self, $sched) = @_;
1141 return $sched->{event};
1144 # return the pool name
1147 my ($self, $sched) = @_;
1148 return $sched->{pool} || '';
1151 # return the level name (Incremental, Differential, Full)
1154 my ($self, $sched) = @_;
1155 return $sched->{level};
1158 # parse bacula sched bitmap
1161 my ($self, @output) = @_;
1168 foreach my $ligne (@output) {
1169 if ($ligne =~ /Schedule: name=(.+)/) {
1170 if ($name and $elt) {
1171 $elt->{level} = $run;
1172 $self->add_sched($name, $elt);
1177 elsif ($ligne =~ /Run Level=(.+)/) {
1178 if ($name and $elt) {
1179 $elt->{level} = $run;
1180 $self->add_sched($name, $elt);
1185 elsif ($ligne =~ /(hour|mday|month|mins)=(.+)/) {
1186 # All theses lines have the same format
1188 my ($k,$v) = ($1,$2);
1189 # we get all values (0 1 4 9)
1190 $elt->{$k}=[split (/\s/,$v)];
1192 # we make a bitmap for this kind of data (0 0 1 0 0 0 1) for a quick access
1193 elsif ($ligne =~ /(wday|wom|woy)=(.+)/) {
1194 my ($k,$v) = ($1,$2);
1195 foreach my $e (split (/\s/,$v)) {
1199 elsif ($ligne =~ /Pool: name=(.+?) PoolType/) {
1204 if ($name and $elt) {
1205 $elt->{level} = $run;
1206 $self->add_sched($name, $elt);
1210 use Date::Calc qw(:all);
1212 # read bacula schedule bitmap and get $format date string
1216 my ($self, $s,$format) = @_;
1217 my $year = $self->{year} || ((localtime)[5] + 1900);
1218 $format = $format || '%u-%02u-%02u %02u:%02u';
1220 foreach my $m (@{$s->{month}}) # mois de l'annee
1222 foreach my $md (@{$s->{mday}}) # jour du mois
1224 # print " m=$m md=$md\n";
1225 # we check if this day exists (31 fev)
1226 next if (!check_date($year,$m,$md));
1227 # print " check_date ok\n";
1229 my $w = ($md-1)/7; # we use the same thing than bacula
1230 next if (! $s->{wom}->[$w]);
1231 # print " wom ok\n";
1233 # on recupere le jour de la semaine
1234 my $wd = Day_of_Week($year,$m,$md);
1236 my ($w1,undef) = Week_of_Year($year,$m,$wd);
1237 next if (! $s->{woy}->[$w1-1]); # bacula 0-51
1238 # print " woy ok\n";
1240 $wd = 0 if ($wd == 7) ; # sunday is 0 on bacula
1241 next if (! $s->{wday}->[$wd]);
1242 # print " wday ok\n";
1244 foreach my $h (@{$s->{hour}}) # hour of the day
1246 foreach my $min (@{$s->{mins}}) # minute
1248 if ($self->{fbegin}) {
1250 my $d = sprintf('%d%0.2d%0.2d.%0.2d%0.2d',
1251 $year,$m,$md,$h,$min);
1252 next if ($d < $self->{fbegin} or $d > $self->{fend});
1254 push @ret, sprintf($format, $year,$m,$md,$h,$min);
1263 ################################################################
1267 use base q/Bweb::Gui/;
1271 Bweb - main Bweb package
1275 this package is use to compute and display informations
1280 use POSIX qw/strftime/;
1282 our $config_file='/etc/bacula/bweb.conf';
1287 %sql_func - hash to make query mysql/postgresql compliant
1293 UNIX_TIMESTAMP => '',
1294 FROM_UNIXTIME => '',
1295 TO_SEC => " interval '1 second' * ",
1296 SEC_TO_INT => "SEC_TO_INT",
1299 STARTTIME_SEC => " date_trunc('sec', Job.StartTime) ",
1300 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1301 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1302 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1303 STARTTIME_WEEK => " date_trunc('week', Job.StartTime) ",
1304 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1305 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1306 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1307 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1308 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1309 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1313 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1314 FROM_UNIXTIME => 'FROM_UNIXTIME',
1317 SEC_TO_TIME => 'SEC_TO_TIME',
1318 MATCH => " REGEXP ",
1319 STARTTIME_SEC => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %T') ",
1320 STARTTIME_DAY => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d') ",
1321 STARTTIME_HOUR => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %H') ",
1322 STARTTIME_MONTH => " DATE_FORMAT(Job.StartTime, '%Y-%m') ",
1323 STARTTIME_WEEK => " DATE_FORMAT(Job.StartTime, '%Y-%v') ",
1324 STARTTIME_PHOUR=> " DATE_FORMAT(Job.StartTime, '%H') ",
1325 STARTTIME_PDAY => " DATE_FORMAT(Job.StartTime, '%d') ",
1326 STARTTIME_PMONTH => " DATE_FORMAT(Job.StartTime, '%m') ",
1327 STARTTIME_PWEEK => " DATE_FORMAT(Job.StartTime, '%v') ",
1328 # with mysql < 5, you have to play with the ugly SHOW command
1329 DB_SIZE => " SELECT 0 ",
1330 # works only with mysql 5
1331 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1332 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1333 CONCAT_SEP => " SEPARATOR '' ",
1340 return $self->{info}->{dbi} =~ /dbi:mysql/i;
1347 $self->{dbh}->disconnect();
1352 sub dbh_selectall_arrayref
1354 my ($self, $query) = @_;
1355 $self->connect_db();
1356 $self->debug($query);
1357 return $self->{dbh}->selectall_arrayref($query);
1362 my ($self, @what) = @_;
1363 return join(',', $self->dbh_quote(@what)) ;
1368 my ($self, @what) = @_;
1370 $self->connect_db();
1372 return map { $self->{dbh}->quote($_) } @what;
1374 return $self->{dbh}->quote($what[0]) ;
1380 my ($self, $query) = @_ ;
1381 $self->connect_db();
1382 $self->debug($query);
1383 return $self->{dbh}->do($query);
1386 sub dbh_selectall_hashref
1388 my ($self, $query, $join) = @_;
1390 $self->connect_db();
1391 $self->debug($query);
1392 return $self->{dbh}->selectall_hashref($query, $join) ;
1395 sub dbh_selectrow_hashref
1397 my ($self, $query) = @_;
1399 $self->connect_db();
1400 $self->debug($query);
1401 return $self->{dbh}->selectrow_hashref($query) ;
1406 my ($self, @what) = @_;
1407 if ($self->dbh_is_mysql()) {
1408 return 'CONCAT(' . join(',', @what) . ')' ;
1410 return join(' || ', @what);
1416 my ($self, $query) = @_;
1417 $self->debug($query, up => 1);
1418 return $self->{dbh}->prepare($query);
1424 my @unit = qw(B KB MB GB TB);
1425 my $val = shift || 0;
1427 my $format = '%i %s';
1428 while ($val / 1024 > 1) {
1432 $format = ($i>0)?'%0.1f %s':'%i %s';
1433 return sprintf($format, $val, $unit[$i]);
1440 if ($val =~ /^(?:\d+(?:[ywdhms]\s*)?)+\s*$/) {
1455 # display Day, Hour, Year
1461 $val /= 60; # sec -> min
1463 if ($val / 60 <= 1) {
1467 $val /= 60; # min -> hour
1468 if ($val / 24 <= 1) {
1469 return "$val hours";
1472 $val /= 24; # hour -> day
1473 if ($val / 365 < 2) {
1477 $val /= 365 ; # day -> year
1479 return "$val years";
1485 my $val = shift || 0;
1487 if ($val eq '1' or $val eq "yes") {
1489 } elsif ($val eq '2' or $val eq "archived") {
1497 sub from_human_enabled
1499 my $val = shift || 0;
1501 if ($val eq '1' or $val eq "yes") {
1503 } elsif ($val eq '2' or $val eq "archived") {
1510 # get Day, Hour, Year
1516 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1520 my %times = ( m => 60,
1526 my $mult = $times{$2} || 0;
1531 # get long term statistic table
1535 my $ret = $self->{info}->{stat_job_table} || 'JobHisto';
1536 if ($ret !~ m/^job$/i) {
1537 $ret = "(SELECT * FROM Job UNION SELECT * FROM $ret)";
1546 unless ($self->{dbh}) {
1548 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1549 $self->{info}->{user},
1550 $self->{info}->{password});
1552 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1553 unless ($self->{dbh});
1555 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1557 if ($self->dbh_is_mysql()) {
1558 $self->{dbh}->do("SET group_concat_max_len=1000000");
1560 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1567 my ($class, %arg) = @_;
1569 dbh => undef, # connect_db();
1571 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1577 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1579 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1580 $self->{sql} = $sql_func{$1};
1583 $self->{loginname} = CGI::remote_user();
1584 $self->{debug} = $self->{info}->{debug};
1585 $self->{lang} = $self->{info}->{lang};
1586 $self->{template_dir} = $self->{info}->{template_dir};
1594 if ($self->{info}->{enable_security}) {
1595 $self->get_roles(); # get lang
1597 $self->display($self->{info}, "begin.tpl");
1603 $self->display($self->{info}, "end.tpl");
1609 my $arg = $self->get_form("qclient");
1610 my $f1 = $self->get_client_group_filter();
1611 my $f2 = $self->get_client_filter();
1613 # client_group_name | here
1614 #-------------------+-----
1619 SELECT client_group_name, max(here) AS here FROM (
1620 SELECT client_group_name, 1 AS here
1622 JOIN client_group_member USING (client_group_id)
1623 JOIN Client USING (ClientId) $f2
1624 WHERE Name = $arg->{qclient}
1626 SELECT client_group_name, 0
1627 FROM client_group $f1
1629 GROUP by client_group_name";
1631 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
1633 $self->display({ client_group => [ values %$all ], %$arg }, "client_edit.tpl");
1639 my $where=''; # by default
1641 my $arg = $self->get_form("client", "qre_client",
1642 "jclient_groups", "qnotingroup");
1644 if ($arg->{qre_client}) {
1645 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1646 } elsif ($arg->{client}) {
1647 $where = "WHERE Name = '$arg->{client}' ";
1648 } elsif ($arg->{jclient_groups}) {
1649 # $filter could already contains client_group_member
1651 JOIN client_group_member USING (ClientId)
1652 JOIN client_group USING (client_group_id)
1653 WHERE client_group_name IN ($arg->{jclient_groups}) ";
1654 } elsif ($arg->{qnotingroup}) {
1657 (SELECT 1 FROM client_group_member
1658 WHERE Client.ClientId = client_group_member.ClientId
1664 SELECT Name AS name,
1666 AutoPrune AS autoprune,
1667 FileRetention AS fileretention,
1668 JobRetention AS jobretention
1669 FROM Client " . $self->get_client_filter() .
1672 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1674 my $dsp = { ID => $cur_id++,
1675 clients => [ values %$all] };
1677 $self->display($dsp, "client_list.tpl") ;
1682 my ($self, %arg) = @_;
1687 if ($arg{since} and $arg{age}) {
1688 my $now = "$self->{sql}->{UNIX_TIMESTAMP}(TIMESTAMP '$arg{since}')";
1690 AND $self->{sql}->{UNIX_TIMESTAMP}(StartTime) > $now
1691 AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) < ($now + $self->{sql}->{TO_SEC}($arg{age}))";
1692 $label .= "since $arg{since} and during " . human_sec($arg{age});
1694 } elsif ($arg{age}) {
1696 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1698 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1700 $self->{sql}->{TO_SEC}($arg{age})
1703 $label = "last " . human_sec($arg{age});
1706 if ($arg{groupby}) {
1707 $limit .= " GROUP BY $arg{groupby} ";
1711 $limit .= " ORDER BY $arg{order} ";
1715 $limit .= " LIMIT $arg{limit} ";
1716 $label .= " limited to $arg{limit}";
1720 $limit .= " OFFSET $arg{offset} ";
1721 $label .= " with $arg{offset} offset ";
1725 $label = 'no filter';
1728 return ($limit, $label);
1733 $bweb->get_form(...) - Get useful stuff
1737 This function get and check parameters against regexp.
1739 If word begin with 'q', the return will be quoted or join quoted
1740 if it's end with 's'.
1745 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1748 qclient => 'plume-fd',
1749 qpools => "'plume-fd', 'test-fd', '...'",
1756 my ($self, @what) = @_;
1757 my %what = map { $_ => 1 } @what;
1771 age => $self->{info}->{default_age},
1781 my %opt_ss =( # string with space
1785 my %opt_s = ( # default to ''
1807 my %opt_p = ( # option with path
1814 my %opt_r = (regexwhere => 1);
1815 my %opt_d = ( # option with date
1819 my %opt_t = (when => 2, # option with time
1820 begin => 1, # 1 hh:min are optionnal
1821 end => 1, # 2 hh:min are required
1824 foreach my $i (@what) {
1825 if (exists $opt_i{$i}) {# integer param
1826 my $value = CGI::param($i) || $opt_i{$i} ;
1827 if ($value =~ /^(\d+)$/) {
1829 } elsif ($i eq 'age' && # can have unit
1830 $value =~ /^(?:\d+(?:[ywdhms]\s*)?)+\s*$/) # 2y1h2m34s
1832 $ret{$i} = human_sec_unit($value);
1834 } elsif ($opt_s{$i}) { # simple string param
1835 my $value = CGI::param($i) || '';
1836 if ($value =~ /^([\w\d\.-]+)$/) {
1839 } elsif ($opt_ss{$i}) { # simple string param (with space)
1840 my $value = CGI::param($i) || '';
1841 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1844 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1845 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1847 $ret{$i} = $self->dbh_join(@value) ;
1850 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1851 my $value = CGI::param($1) ;
1853 $ret{$i} = $self->dbh_quote($value);
1856 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1857 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1858 grep { ! /^\s*$/ } CGI::param($1) ];
1859 } elsif (exists $opt_p{$i}) {
1860 my $value = CGI::param($i) || '';
1861 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1864 } elsif (exists $opt_r{$i}) {
1865 my $value = CGI::param($i) || '';
1866 if ($value =~ /^([^'"']+)$/) {
1869 } elsif (exists $opt_d{$i}) {
1870 my $value = CGI::param($i) || '';
1871 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1874 } elsif (exists $opt_t{$i}) { # 1: hh:min optionnal, 2: hh:min required
1875 my $when = CGI::param($i) || '';
1876 if ($when =~ /(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?)/) {
1877 if ($opt_t{$i} == 1 or defined $2) {
1884 if ($what{storage_cmd}) {
1885 if (!grep {/^\Q$ret{storage_cmd}\E$/} ('mount', 'umount', 'release','status')) {
1886 delete $ret{storage_cmd};
1891 foreach my $s (CGI::param('slot')) {
1892 if ($s =~ /^(\d+)$/) {
1893 push @{$ret{slots}}, $s;
1899 my $age = $ret{age} || human_sec_unit($opt_i{age});
1900 my $since = CGI::param('since') || strftime('%F %T', localtime(time - $age));
1901 if ($since =~ /^(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)$/) {
1907 my $lang = CGI::param('lang') || 'en';
1908 if ($lang =~ /^(\w\w)$/) {
1913 if ($what{db_clients}) {
1915 if ($what{filter}) {
1916 # get security filter only if asked
1917 $filter = $self->get_client_filter();
1921 SELECT Client.Name as clientname
1925 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1926 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1930 if ($what{db_client_groups}) {
1932 if ($what{filter}) {
1933 # get security filter only if asked
1934 $filter = $self->get_client_group_filter();
1938 SELECT client_group_name AS name, comment AS comment
1939 FROM client_group $filter
1941 my $grps = $self->dbh_selectall_hashref($query, 'name');
1942 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1946 if ($what{db_usernames}) {
1948 SELECT username, comment
1951 my $users = $self->dbh_selectall_hashref($query, 'username');
1952 $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} }
1956 if ($what{db_roles}) {
1958 SELECT rolename, comment
1961 my $r = $self->dbh_selectall_hashref($query, 'rolename');
1962 $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} }
1966 if ($what{db_mediatypes}) {
1968 SELECT MediaType as mediatype
1971 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1972 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1976 if ($what{db_locations}) {
1978 SELECT Location as location, Cost as cost
1981 my $loc = $self->dbh_selectall_hashref($query, 'location');
1982 $ret{db_locations} = [ sort { $a->{location}
1988 if ($what{db_pools}) {
1989 my $query = "SELECT Name as name FROM Pool";
1991 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1992 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1995 if ($what{db_filesets}) {
1997 SELECT FileSet.FileSet AS fileset
2000 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
2002 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
2003 values %$filesets] ;
2006 if ($what{db_jobnames}) {
2008 if ($what{filter}) {
2009 $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
2012 SELECT DISTINCT Job.Name AS jobname
2015 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
2017 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
2018 values %$jobnames] ;
2021 if ($what{db_devices}) {
2023 SELECT Device.Name AS name
2026 my $devices = $self->dbh_selectall_hashref($query, 'name');
2028 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
2038 $self->can_do('r_view_stat');
2039 my $fields = $self->get_form(qw/age level status clients filesets
2040 graph gtype type filter db_clients
2041 limit db_filesets width height
2042 qclients qfilesets qjobnames db_jobnames/);
2044 my $url = CGI::url(-full => 0,
2047 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
2049 # this organisation is to keep user choice between 2 click
2050 # TODO : fileset and client selection doesn't work
2057 if ($fields->{gtype} and $fields->{gtype} eq 'balloon') {
2058 system("./bgraph.pl");
2062 sub get_selected_media_location
2066 my $media = $self->get_form('jmedias');
2068 unless ($media->{jmedias}) {
2073 SELECT Media.VolumeName AS volumename, Location.Location AS location
2074 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2075 WHERE Media.VolumeName IN ($media->{jmedias})
2078 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2080 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
2089 my ($self, $in) = @_ ;
2090 $self->can_do('r_media_mgnt');
2091 my $media = $self->get_selected_media_location();
2097 my $elt = $self->get_form('db_locations');
2099 $self->display({ ID => $cur_id++,
2100 enabled => human_enabled($in),
2101 %$elt, # db_locations
2103 sort { $a->{volumename} cmp $b->{volumename} } values %$media
2112 $self->can_do('r_media_mgnt');
2114 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
2116 $self->display($elt, "help_extern.tpl");
2119 sub help_extern_compute
2122 $self->can_do('r_media_mgnt');
2124 my $number = CGI::param('limit') || '' ;
2125 unless ($number =~ /^(\d+)$/) {
2126 return $self->error("Bad arg number : $number ");
2129 my ($sql, undef) = $self->get_param('pools',
2130 'locations', 'mediatypes');
2133 SELECT Media.VolumeName AS volumename,
2134 Media.VolStatus AS volstatus,
2135 Media.LastWritten AS lastwritten,
2136 Media.MediaType AS mediatype,
2137 Media.VolMounts AS volmounts,
2139 Media.Recycle AS recycle,
2140 $self->{sql}->{FROM_UNIXTIME}(
2141 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2142 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2145 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2146 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2148 WHERE Media.InChanger = 1
2149 AND Media.VolStatus IN ('Disabled', 'Error', 'Full', 'Used')
2151 ORDER BY expire DESC, recycle, Media.VolMounts DESC
2155 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2157 $self->display({ Media => [ values %$all ] },
2158 "help_extern_compute.tpl");
2164 $self->can_do('r_media_mgnt');
2166 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
2167 $self->display($param, "help_intern.tpl");
2170 sub help_intern_compute
2173 $self->can_do('r_media_mgnt');
2175 my $number = CGI::param('limit') || '' ;
2176 unless ($number =~ /^(\d+)$/) {
2177 return $self->error("Bad arg number : $number ");
2180 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
2182 if (CGI::param('expired')) {
2183 # we take only expired volumes or purged/recycle ones
2186 ( ($self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2187 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2190 Media.VolStatus IN ('Purged', 'Recycle')
2197 SELECT Media.VolumeName AS volumename,
2198 Media.VolStatus AS volstatus,
2199 Media.LastWritten AS lastwritten,
2200 Media.MediaType AS mediatype,
2201 Media.VolMounts AS volmounts,
2203 $self->{sql}->{FROM_UNIXTIME}(
2204 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2205 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2208 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2209 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
2211 WHERE Media.InChanger <> 1
2212 AND Media.VolStatus IN ('Purged', 'Full', 'Append', 'Recycle')
2213 AND Media.Recycle = 1
2215 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
2219 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2221 $self->display({ Media => [ values %$all ] },
2222 "help_intern_compute.tpl");
2228 my ($self, %arg) = @_ ;
2230 my ($limit, $label) = $self->get_limit(%arg);
2234 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
2235 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
2236 (SELECT count(Job.JobId) FROM Job) AS nb_job,
2237 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
2238 ($self->{sql}->{DB_SIZE}) AS db_size,
2239 (SELECT count(Job.JobId)
2241 WHERE Job.JobStatus IN ('E','e','f','A')
2244 (SELECT count(Client.ClientId) FROM Client) AS nb_client
2247 my $row = $self->dbh_selectrow_hashref($query) ;
2249 $row->{nb_bytes} = human_size($row->{nb_bytes});
2251 $row->{db_size} = human_size($row->{db_size});
2252 $row->{label} = $label;
2253 $row->{age} = $arg{age};
2255 $self->display($row, "general.tpl");
2260 my ($self, @what) = @_ ;
2261 my %elt = map { $_ => 1 } @what;
2266 if ($elt{clients}) {
2267 my @clients = grep { ! /^\s*$/ } CGI::param('client');
2269 $ret{clients} = \@clients;
2270 my $str = $self->dbh_join(@clients);
2271 $limit .= "AND Client.Name IN ($str) ";
2275 if ($elt{client_groups}) {
2276 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
2278 $ret{client_groups} = \@clients;
2279 my $str = $self->dbh_join(@clients);
2280 $limit .= "AND client_group_name IN ($str) ";
2284 if ($elt{filesets}) {
2285 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
2287 $ret{filesets} = \@filesets;
2288 my $str = $self->dbh_join(@filesets);
2289 $limit .= "AND FileSet.FileSet IN ($str) ";
2293 if ($elt{mediatypes}) {
2294 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
2296 $ret{mediatypes} = \@media;
2297 my $str = $self->dbh_join(@media);
2298 $limit .= "AND Media.MediaType IN ($str) ";
2303 my $client = CGI::param('client');
2305 $ret{client} = $client;
2306 $client = $self->dbh_quote($client);
2307 $limit .= "AND Client.Name = $client ";
2312 my $level = CGI::param('level') || '';
2313 if ($level =~ /^(\w)$/) {
2315 $limit .= "AND Job.Level = '$1' ";
2320 my $jobid = CGI::param('jobid') || '';
2322 if ($jobid =~ /^(\d+)$/) {
2324 $limit .= "AND Job.JobId = '$1' ";
2329 my $status = CGI::param('status') || '';
2330 if ($status =~ /^(\w)$/) {
2333 $limit .= "AND Job.JobStatus IN ('E','e','f','A') ";
2334 } elsif ($1 eq 'W') {
2335 $limit .= "AND Job.JobStatus IN ('T', 'W') OR Job.JobErrors > 0 ";
2337 $limit .= "AND Job.JobStatus = '$1' ";
2342 if ($elt{volstatus}) {
2343 my $status = CGI::param('volstatus') || '';
2344 if ($status =~ /^(\w+)$/) {
2346 $limit .= "AND Media.VolStatus = '$1' ";
2350 if ($elt{locations}) {
2351 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
2353 $ret{locations} = \@location;
2354 my $str = $self->dbh_join(@location);
2355 $limit .= "AND Location.Location IN ($str) ";
2360 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
2362 $ret{pools} = \@pool;
2363 my $str = $self->dbh_join(@pool);
2364 $limit .= "AND Pool.Name IN ($str) ";
2368 if ($elt{location}) {
2369 my $location = CGI::param('location') || '';
2371 $ret{location} = $location;
2372 $location = $self->dbh_quote($location);
2373 $limit .= "AND Location.Location = $location ";
2378 my $pool = CGI::param('pool') || '';
2381 $pool = $self->dbh_quote($pool);
2382 $limit .= "AND Pool.Name = $pool ";
2386 if ($elt{jobtype}) {
2387 my $jobtype = CGI::param('jobtype') || '';
2388 if ($jobtype =~ /^(\w)$/) {
2390 $limit .= "AND Job.Type = '$1' ";
2394 return ($limit, %ret);
2405 my ($self, %arg) = @_ ;
2406 return if $self->cant_do('r_view_job');
2408 $arg{order} = ' Job.JobId DESC ';
2410 my ($limit, $label) = $self->get_limit(%arg);
2411 my ($where, undef) = $self->get_param('clients',
2420 if (CGI::param('client_group')) {
2422 JOIN client_group_member USING (ClientId)
2423 JOIN client_group USING (client_group_id)
2426 my $filter = $self->get_client_filter();
2429 SELECT Job.JobId AS jobid,
2430 Client.Name AS client,
2431 FileSet.FileSet AS fileset,
2432 Job.Name AS jobname,
2434 StartTime AS starttime,
2436 Pool.Name AS poolname,
2437 JobFiles AS jobfiles,
2438 JobBytes AS jobbytes,
2439 JobStatus AS jobstatus,
2441 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2442 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2445 JobErrors AS joberrors
2447 FROM Client $filter $cgq,
2448 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2449 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2450 WHERE Client.ClientId=Job.ClientId
2451 AND Job.JobStatus NOT IN ('R', 'C')
2456 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2458 $self->display({ Filter => $label,
2462 sort { $a->{jobid} <=> $b->{jobid} }
2469 # display job informations
2470 sub display_job_zoom
2472 my ($self, $jobid) = @_ ;
2473 $self->can_do('r_view_job');
2475 $jobid = $self->dbh_quote($jobid);
2477 # get security filter
2478 my $filter = $self->get_client_filter();
2481 SELECT DISTINCT Job.JobId AS jobid,
2482 Client.Name AS client,
2483 Job.Name AS jobname,
2484 FileSet.FileSet AS fileset,
2486 Pool.Name AS poolname,
2487 StartTime AS starttime,
2488 JobFiles AS jobfiles,
2489 JobBytes AS jobbytes,
2490 JobStatus AS jobstatus,
2491 JobErrors AS joberrors,
2493 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2494 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2496 FROM Client $filter,
2497 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2498 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2499 WHERE Client.ClientId=Job.ClientId
2500 AND Job.JobId = $jobid
2503 my $row = $self->dbh_selectrow_hashref($query) ;
2505 # display all volumes associate with this job
2507 SELECT Media.VolumeName as volumename
2508 FROM Job,Media,JobMedia
2509 WHERE Job.JobId = $jobid
2510 AND JobMedia.JobId=Job.JobId
2511 AND JobMedia.MediaId=Media.MediaId
2514 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2516 $row->{volumes} = [ values %$all ] ;
2517 $row->{wiki_url} = $self->{info}->{wiki_url};
2519 $self->display($row, "display_job_zoom.tpl");
2522 sub display_job_group
2524 my ($self, %arg) = @_;
2525 $self->can_do('r_view_job');
2527 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2529 my ($where, undef) = $self->get_param('client_groups',
2532 my $filter = $self->get_client_group_filter();
2535 SELECT client_group_name AS client_group_name,
2536 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2537 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2538 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2539 COALESCE(jobok.nbjobs,0) AS nbjobok,
2540 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2541 COALESCE(jobok.duration, '0:0:0') AS duration
2543 FROM client_group $filter LEFT JOIN (
2544 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2545 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2546 SUM(JobErrors) AS joberrors,
2547 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2548 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2551 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2552 JOIN client_group USING (client_group_id)
2554 WHERE Type IN ('B', 'R') AND JobStatus IN ('T', 'W')
2557 ) AS jobok USING (client_group_name) LEFT JOIN
2560 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2561 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2562 SUM(JobErrors) AS joberrors
2563 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2564 JOIN client_group USING (client_group_id)
2566 WHERE Type IN ('B', 'R') AND JobStatus IN ('f','E', 'A')
2569 ) AS joberr USING (client_group_name)
2573 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2575 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2578 $self->display($rep, "display_job_group.tpl");
2583 my ($self, %arg) = @_ ;
2584 $self->can_do('r_view_media');
2586 my ($limit, $label) = $self->get_limit(%arg);
2587 my ($where, %elt) = $self->get_param('pools',
2592 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2594 if ($arg->{jmedias}) {
2595 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2597 if ($arg->{qre_media}) {
2598 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2600 if ($arg->{expired}) {
2602 AND VolStatus = ('Full', 'Used')
2603 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2604 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2605 ) < NOW() " . $where ;
2609 SELECT Media.VolumeName AS volumename,
2610 Media.VolBytes AS volbytes,
2611 Media.VolStatus AS volstatus,
2612 Media.MediaType AS mediatype,
2613 Media.InChanger AS online,
2614 Media.LastWritten AS lastwritten,
2615 Location.Location AS location,
2616 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2617 Pool.Name AS poolname,
2618 $self->{sql}->{FROM_UNIXTIME}(
2619 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2620 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2623 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2624 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2625 Media.MediaType AS MediaType
2627 WHERE Media.VolStatus = 'Full'
2628 GROUP BY Media.MediaType
2629 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2631 WHERE Media.PoolId=Pool.PoolId
2636 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2638 $self->display({ ID => $cur_id++,
2640 Location => $elt{location},
2641 Media => [ values %$all ],
2643 "display_media.tpl");
2646 sub display_allmedia
2650 my $pool = $self->get_form('db_pools');
2652 foreach my $name (@{ $pool->{db_pools} }) {
2653 CGI::param('pool', $name->{name});
2654 $self->display_media();
2658 sub display_media_zoom
2662 my $media = $self->get_form('jmedias');
2664 unless ($media->{jmedias}) {
2665 return $self->error("Can't get media selection");
2669 SELECT InChanger AS online,
2670 Media.Enabled AS enabled,
2671 VolBytes AS nb_bytes,
2672 VolumeName AS volumename,
2673 VolStatus AS volstatus,
2674 VolMounts AS nb_mounts,
2675 Media.VolUseDuration AS voluseduration,
2676 Media.MaxVolJobs AS maxvoljobs,
2677 Media.MaxVolFiles AS maxvolfiles,
2678 Media.MaxVolBytes AS maxvolbytes,
2679 VolErrors AS nb_errors,
2680 Pool.Name AS poolname,
2681 Location.Location AS location,
2682 Media.Recycle AS recycle,
2683 Media.VolRetention AS volretention,
2684 Media.LastWritten AS lastwritten,
2685 Media.VolReadTime/1000000 AS volreadtime,
2686 Media.VolWriteTime/1000000 AS volwritetime,
2687 Media.RecycleCount AS recyclecount,
2688 Media.Comment AS comment,
2689 $self->{sql}->{FROM_UNIXTIME}(
2690 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2691 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2694 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2695 WHERE Pool.PoolId = Media.PoolId
2696 AND VolumeName IN ($media->{jmedias})
2699 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2701 foreach my $media (values %$all) {
2702 my $mq = $self->dbh_quote($media->{volumename});
2705 SELECT DISTINCT Job.JobId AS jobid,
2707 Job.StartTime AS starttime,
2710 Job.JobFiles AS files,
2711 Job.JobBytes AS bytes,
2712 Job.jobstatus AS status
2713 FROM Media,JobMedia,Job
2714 WHERE Media.VolumeName=$mq
2715 AND Media.MediaId=JobMedia.MediaId
2716 AND JobMedia.JobId=Job.JobId
2719 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2722 SELECT LocationLog.Date AS date,
2723 Location.Location AS location,
2724 LocationLog.Comment AS comment
2725 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2726 WHERE Media.MediaId = LocationLog.MediaId
2727 AND Media.VolumeName = $mq
2731 my $log = $self->dbh_selectall_arrayref($query) ;
2733 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2736 $self->display({ jobs => [ values %$jobs ],
2737 LocationLog => $logtxt,
2739 "display_media_zoom.tpl");
2746 $self->can_do('r_location_mgnt');
2748 my $loc = $self->get_form('qlocation');
2749 unless ($loc->{qlocation}) {
2750 return $self->error("Can't get location");
2754 SELECT Location.Location AS location,
2755 Location.Cost AS cost,
2756 Location.Enabled AS enabled
2758 WHERE Location.Location = $loc->{qlocation}
2761 my $row = $self->dbh_selectrow_hashref($query);
2762 $row->{enabled} = human_enabled($row->{enabled});
2763 $self->display({ ID => $cur_id++,
2764 %$row }, "location_edit.tpl") ;
2770 $self->can_do('r_location_mgnt');
2772 my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
2773 unless ($arg->{qlocation}) {
2774 return $self->error("Can't get location");
2776 unless ($arg->{qnewlocation}) {
2777 return $self->error("Can't get new location name");
2779 unless ($arg->{cost}) {
2780 return $self->error("Can't get new cost");
2783 my $enabled = from_human_enabled($arg->{enabled});
2786 UPDATE Location SET Cost = $arg->{cost},
2787 Location = $arg->{qnewlocation},
2789 WHERE Location.Location = $arg->{qlocation}
2792 $self->dbh_do($query);
2794 $self->location_display();
2800 $self->can_do('r_location_mgnt');
2802 my $arg = $self->get_form(qw/qlocation/) ;
2804 unless ($arg->{qlocation}) {
2805 return $self->error("Can't get location");
2809 SELECT count(Media.MediaId) AS nb
2810 FROM Media INNER JOIN Location USING (LocationID)
2811 WHERE Location = $arg->{qlocation}
2814 my $res = $self->dbh_selectrow_hashref($query);
2817 return $self->error("Sorry, the location must be empty");
2821 DELETE FROM Location WHERE Location = $arg->{qlocation}
2824 $self->dbh_do($query);
2826 $self->location_display();
2832 $self->can_do('r_location_mgnt');
2834 my $arg = $self->get_form(qw/qlocation cost/) ;
2836 unless ($arg->{qlocation}) {
2837 $self->display({}, "location_add.tpl");
2840 unless ($arg->{cost}) {
2841 return $self->error("Can't get new cost");
2844 my $enabled = CGI::param('enabled') || '';
2845 $enabled = from_human_enabled($enabled);
2848 INSERT INTO Location (Location, Cost, Enabled)
2849 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2852 $self->dbh_do($query);
2854 $self->location_display();
2857 sub location_display
2862 SELECT Location.Location AS location,
2863 Location.Cost AS cost,
2864 Location.Enabled AS enabled,
2865 (SELECT count(Media.MediaId)
2867 WHERE Media.LocationId = Location.LocationId
2872 my $location = $self->dbh_selectall_hashref($query, 'location');
2874 $self->display({ ID => $cur_id++,
2875 Locations => [ values %$location ] },
2876 "display_location.tpl");
2883 my $media = $self->get_selected_media_location();
2888 my $arg = $self->get_form('db_locations', 'qnewlocation');
2890 $self->display({ email => $self->{info}->{email_media},
2892 media => [ values %$media ],
2894 "update_location.tpl");
2897 ###########################################################
2902 my $arg = $self->get_form(qw/jclient_groups qclient/);
2904 unless ($arg->{qclient}) {
2905 return $self->error("Can't get client name");
2908 $self->can_do('r_group_mgnt');
2910 my $f1 = $self->get_client_filter();
2911 my $f2 = $self->get_client_group_filter();
2913 $self->{dbh}->begin_work();
2916 DELETE FROM client_group_member
2920 WHERE Client.Name = $arg->{qclient})
2922 $self->dbh_do($query);
2924 if ($arg->{jclient_groups}) {
2926 INSERT INTO client_group_member (client_group_id, ClientId)
2927 (SELECT client_group_id, (SELECT ClientId
2929 WHERE Name = $arg->{qclient})
2930 FROM client_group $f2 WHERE client_group_name IN ($arg->{jclient_groups})
2933 $self->dbh_do($query);
2936 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2938 $self->display_clients();
2944 my $grp = $self->get_form(qw/qclient_group db_clients/);
2946 unless ($grp->{qclient_group}) {
2947 $self->can_do('r_group_mgnt');
2948 $self->display({ ID => $cur_id++,
2949 client_group => "''",
2951 }, "groups_edit.tpl");
2955 unless ($self->cant_do('r_group_mgnt')) {
2956 $self->can_do('r_view_group');
2961 FROM Client JOIN client_group_member using (ClientId)
2962 JOIN client_group using (client_group_id)
2963 WHERE client_group_name = $grp->{qclient_group}
2966 my $row = $self->dbh_selectall_hashref($query, "name");
2968 $self->display({ ID => $cur_id++,
2969 client_group => $grp->{qclient_group},
2971 client_group_member => [ values %$row]},
2978 $self->can_do('r_group_mgnt');
2980 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup qcomment/);
2981 if (!$arg->{qcomment}) {
2982 $arg->{qcomment} = "''";
2985 if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
2987 INSERT INTO client_group (client_group_name, comment)
2988 VALUES ($arg->{qnewgroup}, $arg->{qcomment})
2990 $self->dbh_do($query);
2991 $arg->{qclient_group} = $arg->{qnewgroup};
2994 unless ($arg->{qclient_group}) {
2995 return $self->error("Can't get groups");
2998 $self->{dbh}->begin_work();
3001 DELETE FROM client_group_member
3002 WHERE client_group_id IN
3003 (SELECT client_group_id
3005 WHERE client_group_name = $arg->{qclient_group})
3007 $self->dbh_do($query);
3009 if ($arg->{jclients}) {
3011 INSERT INTO client_group_member (ClientId, client_group_id)
3013 (SELECT client_group_id
3015 WHERE client_group_name = $arg->{qclient_group})
3016 FROM Client WHERE Name IN ($arg->{jclients})
3019 $self->dbh_do($query);
3021 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
3024 SET client_group_name = $arg->{qnewgroup}, comment = $arg->{qcomment}
3025 WHERE client_group_name = $arg->{qclient_group}
3028 $self->dbh_do($query);
3031 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
3033 $self->display_groups();
3039 $self->can_do('r_group_mgnt');
3041 my $arg = $self->get_form(qw/qclient_group/);
3043 unless ($arg->{qclient_group}) {
3044 return $self->error("Can't get groups");
3047 $self->{dbh}->begin_work();
3050 DELETE FROM client_group_member
3051 WHERE client_group_id IN
3052 (SELECT client_group_id
3054 WHERE client_group_name = $arg->{qclient_group})");
3057 DELETE FROM bweb_client_group_acl
3058 WHERE client_group_id IN
3059 (SELECT client_group_id
3061 WHERE client_group_name = $arg->{qclient_group})");
3064 DELETE FROM client_group
3065 WHERE client_group_name = $arg->{qclient_group}");
3067 $self->{dbh}->commit();
3068 $self->display_groups();
3076 if ($self->cant_do('r_group_mgnt')) {
3077 $arg = $self->get_form(qw/db_client_groups filter/) ;
3079 $arg = $self->get_form(qw/db_client_groups/) ;
3082 if ($self->{dbh}->errstr) {
3083 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
3088 $self->display({ ID => $cur_id++,
3090 "display_groups.tpl");
3093 ###########################################################
3098 if (not $self->{info}->{enable_security}) {
3101 if (!$self->{loginname}) {
3102 $self->error("Can't get your login name");
3103 $self->display_end();
3106 # admin is a special user that can do everything
3107 if ($self->{loginname} eq 'admin') {
3111 if (defined $self->{security}) {
3114 $self->{security} = {};
3115 my $u = $self->dbh_quote($self->{loginname});
3118 SELECT use_acl, rolename, tpl
3120 JOIN bweb_role_member USING (userid)
3121 JOIN bweb_role USING (roleid)
3124 my $rows = $self->dbh_selectall_arrayref($query);
3125 # do cache with this role
3126 if (!$rows or !scalar(@$rows)) {
3127 $self->error("Can't get $self->{loginname}'s roles");
3128 $self->display_end();
3131 foreach my $r (@$rows) {
3132 $self->{security}->{$r->[1]}=1;
3134 $self->{security}->{use_acl} = $rows->[0]->[0];
3135 if ($rows->[0]->[2] =~ /^(\w\w)$/) {
3143 my ($self, $client) = @_;
3145 my $filter = $self->get_client_filter();
3149 my $cont = $self->dbh_selectrow_hashref("
3152 WHERE Name = '$client'
3154 return defined $cont;
3159 my ($self, $action) = @_;
3160 # is security enabled in configuration ?
3161 if (not $self->{info}->{enable_security}) {
3164 # admin is a special user that can do everything
3165 if ($self->{loginname} eq 'admin') {
3169 if (!$self->{loginname}) {
3170 $self->{error} = "Can't do $action, your are not logged. " .
3171 "Check security with your administrator";
3174 if (!$self->get_roles()) {
3177 if (!$self->{security}->{$action}) {
3179 "$self->{loginname} sorry, but this action ($action) " .
3180 "is not permited. " .
3181 "Check security with your administrator";
3187 # make like an assert (program die)
3190 my ($self, $action) = @_;
3191 if ($self->cant_do($action)) {
3192 $self->error($self->{error});
3193 $self->display_end();
3203 if (!$self->{info}->{enable_security} or
3204 !$self->{info}->{enable_security_acl})
3209 if ($self->get_roles()) {
3210 return $self->{security}->{use_acl};
3216 # JOIN Client USING (ClientId) " . $b->get_client_filter() . "
3217 sub get_client_filter
3219 my ($self, $login) = @_;
3222 $u = $self->dbh_quote($login);
3223 } elsif ($self->use_filter()) {
3224 $u = $self->dbh_quote($self->{loginname});
3229 JOIN (SELECT ClientId FROM client_group_member
3230 JOIN client_group USING (client_group_id)
3231 JOIN bweb_client_group_acl USING (client_group_id)
3232 JOIN bweb_user USING (userid)
3233 WHERE bweb_user.username = $u
3234 ) AS filter USING (ClientId)";
3237 #JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
3238 sub get_client_group_filter
3240 my ($self, $login) = @_;
3243 $u = $self->dbh_quote($login);
3244 } elsif ($self->use_filter()) {
3245 $u = $self->dbh_quote($self->{loginname});
3250 JOIN (SELECT client_group_id
3251 FROM bweb_client_group_acl
3252 JOIN bweb_user USING (userid)
3253 WHERE bweb_user.username = $u
3254 ) AS filter USING (client_group_id)";
3257 # role and username have to be quoted before
3258 # role and username can be a quoted list
3261 my ($self, $role, $username) = @_;
3262 $self->can_do("r_user_mgnt");
3264 my $nb = $self->dbh_do("
3265 DELETE FROM bweb_role_member
3266 WHERE roleid = (SELECT roleid FROM bweb_role
3267 WHERE rolename IN ($role))
3268 AND userid = (SELECT userid FROM bweb_user
3269 WHERE username IN ($username))");
3273 # role and username have to be quoted before
3274 # role and username can be a quoted list
3277 my ($self, $role, $username) = @_;
3278 $self->can_do("r_user_mgnt");
3280 my $nb = $self->dbh_do("
3281 INSERT INTO bweb_role_member (roleid, userid)
3282 SELECT roleid, userid FROM bweb_role, bweb_user
3283 WHERE rolename IN ($role)
3284 AND username IN ($username)
3289 # role and username have to be quoted before
3290 # role and username can be a quoted list
3293 my ($self, $copy, $user) = @_;
3294 $self->can_do("r_user_mgnt");
3296 my $nb = $self->dbh_do("
3297 INSERT INTO bweb_role_member (roleid, userid)
3298 SELECT roleid, a.userid
3299 FROM bweb_user AS a, bweb_role_member
3300 JOIN bweb_user USING (userid)
3301 WHERE bweb_user.username = $copy
3302 AND a.username = $user");
3306 # username can be a join quoted list of usernames
3309 my ($self, $username) = @_;
3310 $self->can_do("r_user_mgnt");
3313 DELETE FROM bweb_role_member
3317 WHERE username in ($username))");
3319 DELETE FROM bweb_client_group_acl
3323 WHERE username IN ($username))");
3330 $self->can_do("r_user_mgnt");
3332 my $arg = $self->get_form(qw/jusernames/);
3334 unless ($arg->{jusernames}) {
3335 return $self->error("Can't get user");
3338 $self->{dbh}->begin_work();
3340 $self->revoke_all($arg->{jusernames});
3342 DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
3344 $self->{dbh}->commit();
3346 $self->display_users();
3352 $self->can_do("r_user_mgnt");
3354 # we don't quote username directly to check that it is conform
3355 my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate
3356 lang qcopy_username jclient_groups/) ;
3358 if (not $arg->{qcreate}) {
3359 $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
3360 $self->display($arg, "display_user.tpl");
3364 my $u = $self->dbh_quote($arg->{username});
3366 $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
3368 if (!$arg->{qpasswd}) {
3369 $arg->{qpasswd} = "''";
3371 if (!$arg->{qcomment}) {
3372 $arg->{qcomment} = "''";
3375 # will fail if user already exists
3376 # UPDATE with mysql dbi does not return if update is ok
3379 SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment},
3380 use_acl=$arg->{use_acl}, tpl='$arg->{lang}'
3381 WHERE username = $u")
3382 # and (! $self->dbh_is_mysql() )
3385 INSERT INTO bweb_user (username, passwd, use_acl, comment, tpl)
3386 VALUES ($u, $arg->{qpasswd}, $arg->{use_acl},
3387 $arg->{qcomment}, '$arg->{lang}')");
3389 $self->{dbh}->begin_work();
3391 $self->revoke_all($u);
3393 if ($arg->{qcopy_username}) {
3394 $self->grant_like($arg->{qcopy_username}, $u);
3396 $self->grant($arg->{jrolenames}, $u);
3399 if ($arg->{jclient_groups}) {
3401 INSERT INTO bweb_client_group_acl (client_group_id, userid)
3402 SELECT client_group_id, userid
3403 FROM client_group, bweb_user
3404 WHERE client_group_name IN ($arg->{jclient_groups})
3409 $self->{dbh}->commit();
3411 $self->display_users();
3414 # TODO: we miss a matrix with all user/roles
3418 $self->can_do("r_user_mgnt");
3420 my $arg = $self->get_form(qw/db_usernames/) ;
3422 if ($self->{dbh}->errstr) {
3423 return $self->error("Can't use users with bweb, read INSTALL to enable them");
3426 $self->display({ ID => $cur_id++,
3428 "display_users.tpl");
3434 $self->can_do("r_user_mgnt");
3436 my $arg = $self->get_form('username');
3437 my $user = $self->dbh_quote($arg->{username});
3439 my $userp = $self->dbh_selectrow_hashref("
3440 SELECT username, passwd, comment, use_acl, tpl
3442 WHERE username = $user
3445 return $self->error("Can't find $user in catalog");
3447 my $filter = $self->get_client_group_filter($arg->{username});
3448 my $scg = $self->dbh_selectall_hashref("
3449 SELECT client_group_name AS name
3450 FROM client_group $filter
3454 #------------+--------
3459 my $role = $self->dbh_selectall_hashref("
3460 SELECT rolename, max(here) AS userid FROM (
3461 SELECT rolename, 1 AS here
3463 JOIN bweb_role_member USING (userid)
3464 JOIN bweb_role USING (roleid)
3465 WHERE username = $user
3470 GROUP by rolename", 'rolename');
3472 $arg = $self->get_form(qw/db_usernames db_client_groups/);
3475 db_usernames => $arg->{db_usernames},
3476 username => $userp->{username},
3477 comment => $userp->{comment},
3478 passwd => $userp->{passwd},
3479 lang => $userp->{tpl},
3480 use_acl => $userp->{use_acl},
3481 db_client_groups => $arg->{db_client_groups},
3482 client_group => [ values %$scg ],
3483 db_roles => [ values %$role],
3484 }, "display_user.tpl");
3488 ###########################################################
3490 sub get_media_max_size
3492 my ($self, $type) = @_;
3494 "SELECT avg(VolBytes) AS size
3496 WHERE Media.VolStatus = 'Full'
3497 AND Media.MediaType = '$type'
3500 my $res = $self->selectrow_hashref($query);
3503 return $res->{size};
3513 my $media = $self->get_form('qmedia');
3515 unless ($media->{qmedia}) {
3516 return $self->error("Can't get media");
3520 SELECT Media.Slot AS slot,
3521 PoolMedia.Name AS poolname,
3522 Media.VolStatus AS volstatus,
3523 Media.InChanger AS inchanger,
3524 Location.Location AS location,
3525 Media.VolumeName AS volumename,
3526 Media.MaxVolBytes AS maxvolbytes,
3527 Media.MaxVolJobs AS maxvoljobs,
3528 Media.MaxVolFiles AS maxvolfiles,
3529 Media.VolUseDuration AS voluseduration,
3530 Media.VolRetention AS volretention,
3531 Media.Comment AS comment,
3532 PoolRecycle.Name AS poolrecycle,
3533 Media.Enabled AS enabled
3535 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
3536 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
3537 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3539 WHERE Media.VolumeName = $media->{qmedia}
3542 my $row = $self->dbh_selectrow_hashref($query);
3543 $row->{volretention} = human_sec($row->{volretention});
3544 $row->{voluseduration} = human_sec($row->{voluseduration});
3545 $row->{enabled} = human_enabled($row->{enabled});
3547 my $elt = $self->get_form(qw/db_pools db_locations/);
3552 }, "update_media.tpl");
3558 $self->can_do('r_media_mgnt');
3560 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
3562 unless ($arg->{jmedias}) {
3563 return $self->error("Can't get selected media");
3566 unless ($arg->{qnewlocation}) {
3567 return $self->error("Can't get new location");
3572 SET LocationId = (SELECT LocationId
3574 WHERE Location = $arg->{qnewlocation})
3575 WHERE Media.VolumeName IN ($arg->{jmedias})
3578 my $nb = $self->dbh_do($query);
3580 print "$nb media updated, you may have to update your autochanger.";
3582 $self->display_media();
3588 $self->can_do('r_media_mgnt');
3590 my $media = $self->get_selected_media_location();
3592 return $self->error("Can't get media selection");
3594 my $newloc = CGI::param('newlocation');
3596 my $user = CGI::param('user') || 'unknown';
3597 my $comm = CGI::param('comment') || '';
3598 $comm = $self->dbh_quote("$user: $comm");
3600 my $arg = $self->get_form('enabled');
3601 my $en = from_human_enabled($arg->{enabled});
3602 my $b = $self->get_bconsole();
3605 foreach my $vol (keys %$media) {
3607 INSERT INTO LocationLog (Date,Comment,MediaId,LocationId,NewEnabled,NewVolStatus)
3608 SELECT NOW(), $comm, Media.MediaId, Location.LocationId, $en, VolStatus
3609 FROM Media, Location
3610 WHERE Media.VolumeName = '$vol'
3611 AND Location.Location = '$media->{$vol}->{location}'
3613 $self->dbh_do($query);
3614 $self->debug($query);
3615 $b->send_cmd("update volume=\"$vol\" enabled=$en");
3620 $q->param('action', 'update_location');
3621 my $url = $q->url(-full => 1, -query=>1);
3623 $self->display({ email => $self->{info}->{email_media},
3625 newlocation => $newloc,
3626 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
3627 media => [ values %$media ],
3629 "change_location.tpl");
3633 sub display_client_stats
3635 my ($self, %arg) = @_ ;
3636 $self->can_do('r_view_stat');
3638 my $client = $self->dbh_quote($arg{clientname});
3639 # get security filter
3640 my $filter = $self->get_client_filter();
3642 my ($limit, $label) = $self->get_limit(%arg);
3645 count(Job.JobId) AS nb_jobs,
3646 sum(Job.JobBytes) AS nb_bytes,
3647 sum(Job.JobErrors) AS nb_err,
3648 sum(Job.JobFiles) AS nb_files,
3649 Client.Name AS clientname
3650 FROM Job JOIN Client USING (ClientId) $filter
3652 Client.Name = $client
3654 GROUP BY Client.Name
3657 my $row = $self->dbh_selectrow_hashref($query);
3659 $row->{ID} = $cur_id++;
3660 $row->{label} = $label;
3661 $row->{grapharg} = "client";
3662 $row->{age} = $arg{age};
3664 $self->display($row, "display_client_stats.tpl");
3668 sub _display_group_stats
3670 my ($self, %arg) = @_ ;
3672 my $carg = $self->get_form(qw/qclient_group/);
3674 unless ($carg->{qclient_group}) {
3675 return $self->error("Can't get group");
3677 my $jobt = $self->get_stat_table();
3678 my ($limit, $label) = $self->get_limit(%arg);
3682 count(Job.JobId) AS nb_jobs,
3683 sum(Job.JobBytes) AS nb_bytes,
3684 sum(Job.JobErrors) AS nb_err,
3685 sum(Job.JobFiles) AS nb_files,
3686 client_group.client_group_name AS clientname
3688 JOIN Client USING (ClientId)
3689 JOIN client_group_member ON (Client.ClientId = client_group_member.ClientId)
3690 JOIN client_group USING (client_group_id)
3692 client_group.client_group_name = $carg->{qclient_group}
3694 GROUP BY client_group.client_group_name
3697 my $row = $self->dbh_selectrow_hashref($query);
3699 $row->{ID} = $cur_id++;
3700 $row->{label} = $label;
3701 $row->{grapharg} = "client_group";
3703 $self->display($row, "display_client_stats.tpl");
3706 # [ name, num, value, joberrors, nb_job ] =>
3708 # [ { name => 'ALL',
3709 # events => [ { num => 1, label => '2007-01',
3710 # value => 'T', title => 10 },
3711 # { num => 2, label => '2007-02',
3712 # value => 'R', title => 11 },
3715 # { name => 'Other',
3719 sub make_overview_tab
3721 my ($self, $q) = @_;
3722 my $ret = $self->dbh_selectall_arrayref($q);
3726 for my $elt (@$ret) {
3727 if ($cur_name and $cur_name ne $elt->[0]) { # order by name, num
3728 push @items, { name => $cur_name, events => $events};
3731 $cur_name = $elt->[0];
3733 { num => $elt->[1], status => $elt->[2],
3734 joberrors => $elt->[3], title => "$elt->[4] jobs"};
3736 push @items, { name => $cur_name, events => $events};
3740 sub get_time_overview
3742 my ($self, $arg) = @_; # want since et age from get_form();
3743 my $type = $arg->{type} || 'day';
3744 if ($type =~ /^(day|week|hour|month)$/) {
3750 my $jobt = $self->get_stat_table();
3751 my $stime1 = $self->{sql}->{"STARTTIME_P" . $type}; # get 1,2,3
3752 $stime1 =~ s/Job.StartTime/date/;
3753 my $stime2 = $self->{sql}->{"STARTTIME_" . $type}; # get 2007-01-03, 2007-01-23
3755 my ($limit, $label) = $self->get_limit('since' => $arg->{since},
3756 'age' => $arg->{age});
3757 return ($stime1, $stime2, $limit, $label, $jobt);
3760 # lu ma me je ve sa di
3761 # groupe1 v v x w v v v overview
3762 # |-- s1 v v v v v v v overview_zoom
3763 # |-- s2 v v x v v v v
3764 # `-- s3 v v v w v v v
3765 sub display_overview_zoom
3768 $self->can_do('r_view_stat');
3770 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3772 if (!$arg->{jclient_groups}) {
3773 return $self->error("Can't get client_group selection");
3775 my ($filter2, undef) = $self->get_param(qw/level jobtype/);
3776 my ($stime1, $stime2, $limit, $label, $jobt) = $self->get_time_overview($arg);
3778 my $filter = $self->get_client_filter();
3780 SELECT name, $stime1 AS num,
3781 JobStatus AS value, joberrors, nb_job
3783 SELECT $stime2 AS date,
3784 Client.Name AS name,
3785 MAX(severity) AS severity,
3787 SUM(JobErrors) AS joberrors
3789 JOIN client_group_member USING (ClientId)
3790 JOIN client_group USING (client_group_id)
3791 JOIN Client USING (ClientId) $filter
3792 JOIN Status USING (JobStatus)
3793 WHERE client_group_name IN ($arg->{jclient_groups})
3794 AND JobStatus IN ('T', 'W', 'f', 'A', 'e', 'E')
3796 GROUP BY Client.Name, date
3797 ) AS sub JOIN Status USING (severity)
3800 my $items = $self->make_overview_tab($q);
3801 $self->display({label => $label,
3802 action => "job;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client=",
3803 items => $items}, "overview.tpl");
3806 sub display_overview
3809 $self->can_do('r_view_stat');
3811 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3812 my ($filter2, undef) = $self->get_param(qw/client_groups level jobtype/);
3813 my $filter3 = $self->get_client_group_filter();
3814 my ($stime1, $stime2, $filter1, $label, $jobt) = $self->get_time_overview($arg);
3817 SELECT name, $stime1 AS num,
3818 JobStatus AS value, joberrors, nb_job
3820 SELECT $stime2 AS date,
3821 client_group_name AS name,
3822 MAX(severity) AS severity,
3824 SUM(JobErrors) AS joberrors
3826 JOIN client_group_member USING (ClientId)
3827 JOIN client_group USING (client_group_id) $filter3
3828 JOIN Status USING (JobStatus)
3829 WHERE JobStatus IN ('T', 'W', 'f', 'A', 'e', 'E')
3831 GROUP BY client_group_name, date
3832 ) AS sub JOIN Status USING (severity)
3835 my $items = $self->make_overview_tab($q);
3836 $self->display({label=>$label,
3837 action => "overview_zoom;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client_group=",
3838 items => $items}, "overview.tpl");
3842 # poolname can be undef
3845 my ($self, $poolname) = @_ ;
3846 $self->can_do('r_view_media');
3851 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
3852 if ($arg->{jmediatypes}) {
3853 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
3854 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
3857 # TODO : afficher les tailles et les dates
3860 SELECT subq.volmax AS volmax,
3861 subq.volnum AS volnum,
3862 subq.voltotal AS voltotal,
3864 Pool.Recycle AS recycle,
3865 Pool.VolRetention AS volretention,
3866 Pool.VolUseDuration AS voluseduration,
3867 Pool.MaxVolJobs AS maxvoljobs,
3868 Pool.MaxVolFiles AS maxvolfiles,
3869 Pool.MaxVolBytes AS maxvolbytes,
3870 subq.PoolId AS PoolId,
3871 subq.MediaType AS mediatype,
3872 $self->{sql}->{CAT_POOL_TYPE} AS uniq
3875 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
3876 count(Media.MediaId) AS volnum,
3877 sum(Media.VolBytes) AS voltotal,
3878 Media.PoolId AS PoolId,
3879 Media.MediaType AS MediaType
3881 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
3882 Media.MediaType AS MediaType
3884 WHERE Media.VolStatus = 'Full'
3885 GROUP BY Media.MediaType
3886 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
3887 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
3889 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
3893 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
3896 SELECT Pool.Name AS name,
3897 sum(VolBytes) AS size
3898 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
3899 WHERE Media.VolStatus IN ('Recycled', 'Purged')
3903 my $empty = $self->dbh_selectall_hashref($query, 'name');
3905 foreach my $p (values %$all) {
3906 if ($p->{volmax} > 0) { # mysql returns 0.0000
3907 # we remove Recycled/Purged media from pool usage
3908 if (defined $empty->{$p->{name}}) {
3909 $p->{voltotal} -= $empty->{$p->{name}}->{size};
3911 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
3913 $p->{poolusage} = 0;
3917 SELECT VolStatus AS volstatus, count(MediaId) AS nb
3919 WHERE PoolId=$p->{poolid}
3920 AND Media.MediaType = '$p->{mediatype}'
3924 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
3925 foreach my $t (values %$content) {
3926 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
3931 $self->display({ ID => $cur_id++,
3932 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
3933 Pools => [ values %$all ]},
3934 "display_pool.tpl");
3937 # With this function, we get an estimation of next jobfiles/jobbytes count
3938 sub get_estimate_query
3940 my ($self, $mode, $job, $level) = @_;
3941 # get security filter
3942 my $filter = $self->get_client_filter();
3946 if ($self->dbh_is_mysql()) { # mysql doesn't have statistics functions
3948 SELECT jobname AS jobname,
3949 0.1 AS corr_jobbytes, AVG(jobbytes) AS jobbytes,
3950 COUNT(1) AS nb_jobbytes ";
3952 # postgresql have functions that permit to handle lineal regression
3954 # REGR_SLOPE(Y,X) = get x
3955 # REGR_INTERCEPT(Y,X) = get b
3956 # and we need y when x=now()
3957 # CORR gives the correlation
3958 # (TODO: display progress bar only if CORR > 0.8)
3959 my $now = scalar(time);
3961 SELECT temp.jobname AS jobname,
3962 CORR(jobbytes,jobtdate) AS corr_jobbytes,
3963 ($now*REGR_SLOPE(jobbytes,jobtdate)
3964 + REGR_INTERCEPT(jobbytes,jobtdate)) AS jobbytes,
3965 COUNT(1) AS nb_jobbytes ";
3967 # if it's a differential, we need to compare since the last full
3969 # F D D D F D D D F I I I I D I I I
3971 # | # # # # # # | # #
3972 # | # # # # # # # # | # # # # # # # # #
3973 # +----------------- +-------------------
3975 if ($level eq 'D') {
3977 AND Job.StartTime > (
3980 WHERE Job.Name = '$job'
3982 AND Job.JobStatus IN ('T', 'W')
3983 ORDER BY Job.StartTime DESC LIMIT 1
3990 SELECT Job.Name AS jobname,
3991 JobBytes AS jobbytes,
3992 JobTDate AS jobtdate
3993 FROM Job INNER JOIN Client USING (ClientId) $filter
3994 WHERE Job.Name = '$job'
3995 AND Job.Level = '$level'
3996 AND Job.JobStatus IN ('T', 'W')
3998 ORDER BY StartTime DESC
4000 ) AS temp GROUP BY temp.jobname
4003 if ($mode eq 'jobfiles') {
4004 $query =~ s/jobbytes/jobfiles/g;
4005 $query =~ s/JobBytes/JobFiles/g;
4010 sub display_running_job
4013 return if $self->cant_do('r_view_running_job');
4015 my $arg = $self->get_form('jobid');
4017 return $self->error("Can't get jobid") unless ($arg->{jobid});
4019 # get security filter
4020 my $filter = $self->get_client_filter();
4023 SELECT Client.Name AS name, Job.Name AS jobname,
4024 Job.Level AS level, Type AS type, JobStatus AS jobstatus
4025 FROM Job INNER JOIN Client USING (ClientId) $filter
4026 WHERE Job.JobId = $arg->{jobid}
4029 my $row = $self->dbh_selectrow_hashref($query);
4032 $arg->{client} = $row->{name};
4034 return $self->error("Can't get client");
4037 my $status = $row->{jobstatus};
4039 if ($status =~ /[TfAaEWD]/) {
4040 $self->display_job_zoom($arg->{jobid});
4041 $self->get_job_log();
4045 if ($row->{type} eq 'B') {
4046 # for jobfiles, we use only last Full backup. status client= returns
4047 # all files that have been checked
4048 my $query1 = $self->get_estimate_query('jobfiles', $row->{jobname}, 'F');
4049 my $query2 = $self->get_estimate_query('jobbytes',
4050 $row->{jobname}, $row->{level});
4052 # LEFT JOIN because we always have a previous Full
4054 SELECT corr_jobbytes, jobbytes, corr_jobfiles, jobfiles
4055 FROM ($query1) AS A LEFT JOIN ($query2) AS B USING (jobname)
4057 $row = $self->dbh_selectrow_hashref($query);
4060 $row->{jobbytes} = $row->{jobfiles} = 0;
4063 if ($status =~ /[RBSmMsjlL]/) {
4064 my $cli = new Bweb::Client(name => $arg->{client});
4065 $cli->display_running_job($self, $arg->{jobid}, $row);
4067 if ($arg->{jobid}) {
4068 $self->get_job_log();
4072 sub display_running_jobs
4074 my ($self, $display_action) = @_;
4075 return if $self->cant_do('r_view_running_job');
4077 # get security filter
4078 my $filter = $self->get_client_filter();
4081 SELECT Job.JobId AS jobid,
4082 Job.Name AS jobname,
4084 Job.StartTime AS starttime,
4085 Job.JobFiles AS jobfiles,
4086 Job.JobBytes AS jobbytes,
4087 Job.JobStatus AS jobstatus,
4088 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
4089 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
4091 Client.Name AS clientname
4092 FROM Job INNER JOIN Client USING (ClientId) $filter
4094 JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
4096 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
4098 $self->display({ ID => $cur_id++,
4099 display_action => $display_action,
4100 Jobs => [ values %$all ]},
4101 "running_job.tpl") ;
4104 sub display_group_stats
4107 my $arg = $self->get_form('age', 'since');
4108 return if $self->cant_do('r_view_stat');
4110 my $filter = $self->get_client_group_filter();
4112 my $jobt = $self->get_stat_table();
4114 my ($limit, $label) = $self->get_limit(%$arg);
4115 my ($where, undef) = $self->get_param('client_groups', 'level');
4118 SELECT client_group_name AS name, nb_byte, nb_file, nb_job, nb_err, nb_resto
4121 SELECT sum(JobBytes) AS nb_byte,
4122 sum(JobFiles) AS nb_file,
4123 count(1) AS nb_job, client_group_name
4124 FROM $jobt AS Job JOIN client_group_member USING (ClientId)
4125 JOIN client_group USING (client_group_id) $filter
4126 WHERE JobStatus IN ('T', 'W') AND Type IN ('M', 'B', 'g')
4128 GROUP BY client_group_name ORDER BY client_group_name
4132 SELECT count(1) AS nb_err, client_group_name
4133 FROM $jobt AS Job JOIN client_group_member USING (ClientId)
4134 JOIN client_group USING (client_group_id)
4135 WHERE JobStatus IN ('E','e','f','A') AND Type = 'B'
4137 GROUP BY client_group_name ORDER BY client_group_name
4139 ) AS T3 USING (client_group_name) LEFT JOIN (
4141 SELECT count(1) AS nb_resto, client_group_name
4142 FROM $jobt AS Job JOIN client_group_member USING (ClientId)
4143 JOIN client_group USING (client_group_id)
4144 WHERE JobStatus IN ('T','W') AND Type = 'R'
4146 GROUP BY client_group_name ORDER BY client_group_name
4148 ) AS T2 USING (client_group_name)
4150 $self->debug($query);
4151 my $all = $self->dbh_selectall_hashref($query, 'name') ;
4154 $self->display({ ID => $cur_id++,
4156 Stats => [ values %$all ]},
4157 "display_stats.tpl") ;
4160 # return the autochanger list to update
4164 $self->can_do('r_media_mgnt');
4167 my $arg = $self->get_form('jmedias');
4169 unless ($arg->{jmedias}) {
4170 return $self->error("Can't get media selection");
4174 SELECT Media.VolumeName AS volumename,
4175 Storage.Name AS storage,
4176 Location.Location AS location,
4178 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
4179 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
4180 WHERE Media.VolumeName IN ($arg->{jmedias})
4181 AND Media.InChanger = 1
4184 my $all = $self->dbh_selectall_hashref($query, 'volumename');
4186 foreach my $vol (values %$all) {
4187 my $a = $self->ach_get($vol->{location});
4189 $ret{$vol->{location}} = 1;
4191 unless ($a->{have_status}) {
4193 $a->{have_status} = 1;
4196 print "eject $vol->{volumename} from $vol->{storage} : ";
4197 if ($a->send_to_io($vol->{slot})) {
4198 print "<img src='/bweb/T.png' alt='ok'><br/>";
4200 print "<img src='/bweb/E.png' alt='err'><br/>";
4210 my ($to, $subject, $content) = (CGI::param('email'),
4211 CGI::param('subject'),
4212 CGI::param('content'));
4213 $to =~ s/[^\w\d\.\@<>,]//;
4214 $subject =~ s/[^\w\d\.\[\]]/ /;
4216 open(MAIL, "|mail -s '$subject' '$to'") ;
4217 print MAIL $content;
4227 my $arg = $self->get_form('jobid', 'client');
4229 print CGI::header('text/brestore');
4230 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
4231 print "client=$arg->{client}\n" if ($arg->{client});
4232 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
4236 # TODO : move this to Bweb::Autochanger ?
4237 # TODO : make this internal to not eject tape ?
4243 my ($self, $name) = @_;
4246 return $self->error("Can't get your autochanger name ach");
4249 unless ($self->{info}->{ach_list}) {
4250 return $self->error("Could not find any autochanger");
4253 my $a = $self->{info}->{ach_list}->{$name};
4256 $self->error("Can't get your autochanger $name from your ach_list");
4261 $a->{debug} = $self->{debug};
4268 my ($self, $ach) = @_;
4269 $self->can_do('r_configure');
4271 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
4273 $self->{info}->save();
4281 $self->can_do('r_configure');
4283 my $arg = $self->get_form('ach');
4285 or !$self->{info}->{ach_list}
4286 or !$self->{info}->{ach_list}->{$arg->{ach}})
4288 return $self->error("Can't get autochanger name");
4291 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
4295 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
4297 my $b = $self->get_bconsole();
4299 my @storages = $b->list_storage() ;
4301 $ach->{devices} = [ map { { name => $_ } } @storages ];
4303 $self->display($ach, "ach_add.tpl");
4304 delete $ach->{drives};
4305 delete $ach->{devices};
4312 $self->can_do('r_configure');
4314 my $arg = $self->get_form('ach');
4317 or !$self->{info}->{ach_list}
4318 or !$self->{info}->{ach_list}->{$arg->{ach}})
4320 return $self->error("Can't get autochanger name");
4323 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
4325 $self->{info}->save();
4326 $self->{info}->view();
4332 $self->can_do('r_configure');
4334 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
4336 my $b = $self->get_bconsole();
4337 my @storages = $b->list_storage() ;
4339 unless ($arg->{ach}) {
4340 $arg->{devices} = [ map { { name => $_ } } @storages ];
4341 return $self->display($arg, "ach_add.tpl");
4345 foreach my $drive (CGI::param('drives'))
4347 unless (grep(/^$drive$/,@storages)) {
4348 return $self->error("Can't find $drive in storage list");
4351 my $index = CGI::param("index_$drive");
4352 unless (defined $index and $index =~ /^(\d+)$/) {
4353 return $self->error("Can't get $drive index");
4356 $drives[$index] = $drive;
4360 return $self->error("Can't get drives from Autochanger");
4363 my $a = new Bweb::Autochanger(name => $arg->{ach},
4364 precmd => $arg->{precmd},
4365 drive_name => \@drives,
4366 device => $arg->{device},
4367 mtxcmd => $arg->{mtxcmd});
4369 $self->ach_register($a) ;
4371 $self->{info}->view();
4377 $self->can_do('r_delete_job');
4379 my $arg = $self->get_form('jobid');
4381 if ($arg->{jobid}) {
4382 my $b = $self->get_bconsole();
4383 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
4387 title => "Delete a job ",
4388 name => "delete jobid=$arg->{jobid}",
4397 $self->can_do('r_media_mgnt');
4399 my $arg = $self->get_form(qw/media volstatus inchanger pool
4400 slot volretention voluseduration
4401 maxvoljobs maxvolfiles maxvolbytes
4402 qcomment poolrecycle enabled
4405 unless ($arg->{media}) {
4406 return $self->error("Can't find media selection");
4409 my $update = "update volume=$arg->{media} ";
4411 if ($arg->{volstatus}) {
4412 $update .= " volstatus=$arg->{volstatus} ";
4415 if ($arg->{inchanger}) {
4416 $update .= " inchanger=yes " ;
4418 $update .= " slot=$arg->{slot} ";
4421 $update .= " slot=0 inchanger=no ";
4424 if ($arg->{enabled}) {
4425 $update .= " enabled=$arg->{enabled} ";
4429 $update .= " pool=$arg->{pool} " ;
4432 if (defined $arg->{volretention}) {
4433 $update .= " volretention=\"$arg->{volretention}\" " ;
4436 if (defined $arg->{voluseduration}) {
4437 $update .= " voluse=\"$arg->{voluseduration}\" " ;
4440 if (defined $arg->{maxvoljobs}) {
4441 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
4444 if (defined $arg->{maxvolfiles}) {
4445 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
4448 if (defined $arg->{maxvolbytes}) {
4449 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
4452 if (defined $arg->{poolrecycle}) {
4453 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
4456 my $b = $self->get_bconsole();
4459 content => $b->send_cmd($update),
4460 title => "Update a volume ",
4468 my $media = $self->dbh_quote($arg->{media});
4470 my $loc = CGI::param('location') || '';
4472 $loc = $self->dbh_quote($loc); # is checked by db
4473 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
4475 if (!$arg->{qcomment}) {
4476 $arg->{qcomment} = "''";
4478 push @q, "Comment=$arg->{qcomment}";
4483 SET " . join (',', @q) . "
4484 WHERE Media.VolumeName = $media
4486 $self->dbh_do($query);
4488 $self->update_media();
4494 $self->can_do('r_autochanger_mgnt');
4496 my $ach = CGI::param('ach') ;
4497 $ach = $self->ach_get($ach);
4499 return $self->error("Bad autochanger name");
4503 title => "Scanning autochanger content ",
4504 name => "update slots",
4508 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
4509 $b->update_slots($ach->{name});
4519 $self->can_do('r_view_log');
4521 my $arg = $self->get_form('jobid', 'limit', 'offset');
4522 unless ($arg->{jobid}) {
4523 return $self->error("Can't get jobid");
4526 if ($arg->{limit} == 100) {
4527 $arg->{limit} = 1000;
4529 # get security filter
4530 my $filter = $self->get_client_filter();
4533 SELECT Job.Name as name, Client.Name as clientname
4534 FROM Job INNER JOIN Client USING (ClientId) $filter
4535 WHERE JobId = $arg->{jobid}
4538 my $row = $self->dbh_selectrow_hashref($query);
4541 return $self->error("Can't find $arg->{jobid} in catalog");
4544 # display only Error and Warning messages
4546 if (CGI::param('error')) {
4547 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning|ERR=' ";
4551 if (CGI::param('time') || $self->{info}->{display_log_time}) {
4552 $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
4554 $logtext = 'LogText';
4558 SELECT count(1) AS nbline,
4559 GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt, id
4561 SELECT 1 AS id, Time, LogText
4563 WHERE ( Log.JobId = $arg->{jobid}
4565 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
4566 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
4570 OFFSET $arg->{offset}
4576 my $log = $self->dbh_selectrow_hashref($query);
4578 return $self->error("Can't get log for jobid $arg->{jobid}");
4580 $log->{logtxt} =~ s/\0//g;
4581 $self->display({ lines=> $log->{logtxt},
4582 nbline => $log->{nbline},
4583 jobid => $arg->{jobid},
4584 name => $row->{name},
4585 client => $row->{clientname},
4586 offset => $arg->{offset},
4587 limit => $arg->{limit},
4588 }, 'display_log.tpl');
4591 sub cancel_future_job
4594 $self->can_do('r_cancel_job');
4596 my $arg = $self->get_form(qw/job pool level client when/);
4598 if ( !$arg->{job} or !$arg->{pool} or !$arg->{level}
4599 or !$arg->{client} or !$arg->{when})
4601 return $self->error("Can't get enough information to mark this job as canceled");
4604 $arg->{level} =~ s/^(.).+/$1/; # we keep the first letter
4605 my $jobtable = $self->{info}->{stat_job_table} || 'JobHisto';
4607 if ($jobtable =~ /^Job$/i) {
4608 return $self->error("Can add records only in history table");
4610 my $jname = "$arg->{job}.$arg->{when}";
4613 my $found = $self->dbh_selectrow_hashref("
4618 AND Name = '$arg->{job}'
4621 return $self->error("$jname is already in history table");
4625 INSERT INTO $jobtable
4626 (JobId, Name, Job, Type, Level, JobStatus, SchedTime, StartTime, EndTime,
4627 RealEndTime, ClientId, PoolId)
4629 (0, '$arg->{job}', '$jname', 'B', '$arg->{level}', 'A',
4630 '$arg->{when}', '$arg->{when}', '$arg->{when}', '$arg->{when}',
4631 (SELECT ClientId FROM Client WHERE Name = '$arg->{client}'),
4632 (SELECT PoolId FROM Pool WHERE Name = '$arg->{pool}')
4635 $self->display({ Filter => "Dummy record for $jname",
4639 client => $arg->{client},
4640 jobname => $arg->{job},
4641 pool => $arg->{pool},
4642 level => $arg->{level},
4643 starttime => $arg->{when},
4644 duration => '00:00:00',
4657 $self->can_do('r_media_mgnt');
4658 my $arg = $self->get_form('storage', 'pool', 'nb', 'media', 'offset');
4659 my $b = $self->get_bconsole();
4661 if (!$arg->{storage} || !$arg->{pool} || not defined $arg->{nb} || !$arg->{media} || !$arg->{offset}) {
4662 CGI::param(offset => 0);
4663 $arg = $self->get_form('db_pools');
4664 $arg->{storage} = [ map { { name => $_ } }$b->list_storage()];
4665 $self->display($arg, 'add_media.tpl');
4670 if ($arg->{nb} > 0) {
4671 $arg->{offset} = $arg->{offset}?$arg->{offset}:1;
4672 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n$arg->{nb}\n$arg->{media}\n$arg->{offset}\n";
4674 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n0\n$arg->{media}\n";
4680 CGI::param('media', '');
4681 CGI::param('re_media', $arg->{media});
4682 $self->display_media();
4688 $self->can_do('r_autochanger_mgnt');
4690 my $arg = $self->get_form('ach', 'slots', 'drive', 'pool');
4692 unless ($arg->{ach}) {
4693 return $self->error("Can't find autochanger name");
4696 my $a = $self->ach_get($arg->{ach});
4698 return $self->error("Can't find autochanger name in configuration");
4701 my $storage = $a->get_drive_name($arg->{drive});
4703 return $self->error("Can't get your drive name");
4709 if ($arg->{slots}) {
4710 $slots = join(",", @{ $arg->{slots} });
4711 $slots_sql = " AND Slot IN ($slots) ";
4712 $t += 60*scalar( @{ $arg->{slots} }) ;
4714 my $pool = $arg->{pool} || 'Scratch';
4715 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
4716 print "<h1>This command can take long time, be patient...</h1>";
4718 $b->label_barcodes(storage => $storage,
4719 drive => $arg->{drive},
4727 SET LocationId = (SELECT LocationId
4729 WHERE Location = '$arg->{ach}')
4731 WHERE (LocationId = 0 OR LocationId IS NULL)
4740 $self->can_do('r_purge');
4742 my @volume = CGI::param('media');
4745 return $self->error("Can't get media selection");
4748 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4750 foreach my $v (@volume) {
4752 content => $b->purge_volume($v),
4753 title => "Purge media",
4754 name => "purge volume=$v",
4764 $self->can_do('r_prune');
4766 my @volume = CGI::param('media');
4768 return $self->error("Can't get media selection");
4771 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4773 foreach my $v (@volume) {
4775 content => $b->prune_volume($v),
4776 title => "Prune volume",
4777 name => "prune volume=$v",
4787 $self->can_do('r_cancel_job');
4789 my $arg = $self->get_form('jobid');
4790 unless ($arg->{jobid}) {
4791 return $self->error("Can't get jobid");
4794 my $b = $self->get_bconsole();
4796 content => $b->cancel($arg->{jobid}),
4797 title => "Cancel job",
4798 name => "cancel jobid=$arg->{jobid}",
4805 # Warning, we display current fileset
4808 my $arg = $self->get_form('fileset');
4810 if ($arg->{fileset}) {
4811 my $b = $self->get_bconsole();
4812 my $ret = $b->get_fileset($arg->{fileset});
4813 $self->display({ fileset => $arg->{fileset},
4815 }, "fileset_view.tpl");
4817 $self->error("Can't get fileset name");
4821 sub director_show_sched
4824 $self->can_do('r_view_job');
4825 my $arg = $self->get_form('days');
4827 my $b = $self->get_bconsole();
4828 my $ret = $b->director_get_sched( $arg->{days} );
4833 }, "scheduled_job.tpl");
4836 sub enable_disable_job
4838 my ($self, $what) = @_ ;
4839 $self->can_do('r_run_job');
4841 my $arg = $self->get_form('job');
4843 return $self->error("Can't find job name");
4846 my $b = $self->get_bconsole();
4856 content => $b->send_cmd("$cmd job=\"$arg->{job}\""),
4857 title => "$cmd $arg->{job}",
4858 name => "$cmd job=\"$arg->{job}\"",
4866 return new Bconsole(pref => $self->{info});
4872 $self->can_do('r_storage_mgnt');
4873 my $arg = $self->get_form(qw/storage storage_cmd drive slot/);
4874 my $b = $self->get_bconsole();
4876 if ($arg->{storage} and $arg->{storage_cmd}) {
4877 my $cmd = "$arg->{storage_cmd} storage=\"$arg->{storage}\" drive=$arg->{drive} slot=$arg->{slot}";
4878 my $ret = $b->send_cmd($cmd);
4882 title => "Storage ",
4887 my $storages= [ map { { name => $_ } } $b->list_storage()];
4888 $self->display({ storage => $storages}, "cmd_storage.tpl");
4895 $self->can_do('r_run_job');
4897 my $b = $self->get_bconsole();
4899 my $joblist = [ map { { name => $_ } } $b->list_job() ];
4901 $self->display({ Jobs => $joblist }, "run_job.tpl");
4906 my ($self, $ouput) = @_;
4909 $self->debug($ouput);
4910 foreach my $l (split(/\r?\n/, $ouput)) {
4912 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
4918 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
4924 foreach my $k (keys %arg) {
4925 $lowcase{lc($k)} = $arg{$k} ;
4927 $self->debug(\%lowcase);
4934 $self->can_do('r_run_job');
4936 my $b = $self->get_bconsole();
4937 my $arg = $self->get_form(qw/pool level client fileset storage media job/);
4940 return $self->error("Can't get job name");
4943 # we take informations from director, and we overwrite with user wish
4944 my $info = $b->send_cmd("show job=\"$arg->{job}\"");
4945 my $attr = $self->run_parse_job($info);
4947 if (!$arg->{pool} and $arg->{media}) {
4948 my $r = $self->dbh_selectrow_hashref("
4949 SELECT Pool.Name AS name
4950 FROM Media JOIN Pool USING (PoolId)
4951 WHERE Media.VolumeName = '$arg->{media}'
4952 AND Pool.Name != 'Scratch'
4955 $arg->{pool} = $r->{name};
4959 my %job_opt = (%$attr, %$arg);
4961 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4963 my $pools = [ map { { name => $_ } } $b->list_pool() ];
4964 my $clients = [ map { { name => $_ } }$b->list_client()];
4965 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
4966 my $storages= [ map { { name => $_ } }$b->list_storage()];
4971 clients => $clients,
4972 filesets => $filesets,
4973 storages => $storages,
4975 }, "run_job_mod.tpl");
4981 $self->can_do('r_run_job');
4983 my $b = $self->get_bconsole();
4985 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4995 $self->can_do('r_run_job');
4997 my $b = $self->get_bconsole();
4999 # TODO: check input (don't use pool, level)
5001 my $arg = $self->get_form(qw/pool level client priority when
5002 fileset job storage/);
5004 return $self->error("Can't get your job name");
5007 my $jobid = $b->run(job => $arg->{job},
5008 client => $arg->{client},
5009 priority => $arg->{priority},
5010 level => $arg->{level},
5011 storage => $arg->{storage},
5012 pool => $arg->{pool},
5013 fileset => $arg->{fileset},
5014 when => $arg->{when},
5019 print "<br>You can follow job (jobid=$jobid) execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a><script type='text/javascript' language='JavaScript'>setTimeout(function() { window.location='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'},2000);</script>";
5022 sub display_next_job
5026 my $arg = $self->get_form(qw/job begin end/);
5028 return $self->error("Can't get job name");
5031 my $b = $self->get_bconsole();
5033 my $job = $b->send_cmd("show job=\"$arg->{job}\"");
5034 my $attr = $self->run_parse_job($job);
5036 if (!$attr->{schedule}) {
5037 return $self->error("Can't get $arg->{job} schedule");
5039 my $jpool=$attr->{pool} || '';
5041 my $sched = new Bweb::Sched(bconsole => $b, name => $attr->{schedule},
5042 begin => $arg->{begin}, end => $arg->{end});
5044 my $ss = $sched->get_scheds($attr->{schedule});
5047 foreach my $s (@$ss) {
5048 my $level = $sched->get_level($s);
5049 my $pool = $sched->get_pool($s) || $jpool;
5050 my $evt = $sched->get_event($s);
5051 push @ret, map { "$_ : $pool ($level)\n" } @$evt;
5054 print "<b>$arg->{job}:</b><pre>", sort @ret, "</pre><br>";
5057 # permit to verify for higher level backup
5058 # we attempt a Increment, we made a Full, that ok
5059 # TODO: Pool may have change
5060 sub get_higher_level
5062 my ($self, $level) = @_;
5063 if ($level eq 'F') {
5065 } elsif ($level eq 'D') {
5067 } elsif ($level eq 'I') {
5068 return "'F', 'D', 'I'";
5073 # check jobs against their schedule
5076 my ($self, $sched, $schedname, $job, $job_pool, $client, $type) = @_;
5077 return undef if (!$self->can_view_client($client));
5079 my $sch = $sched->get_scheds($schedname);
5080 return undef if (!$sch);
5083 foreach my $s (@$sch) {
5085 if ($type eq 'B') { # we take the pool only for backup job
5086 $pool = $sched->get_pool($s) || $job_pool;
5088 my $level = $sched->get_level($s);
5089 my ($l) = ($level =~ m/^(.)/); # we keep the first letter
5090 $l = $self->get_higher_level($l);
5091 my $evts = $sched->get_event($s);
5092 my $end = $sched->{end}; # this backup must have start before the next one
5093 foreach my $evt (reverse @$evts) {
5094 my $all = $self->dbh_selectrow_hashref("
5097 JOIN Client USING (ClientId) LEFT JOIN Pool USING (PoolId)
5098 WHERE Job.StartTime >= '$evt'
5099 AND Job.StartTime < '$end'
5100 AND Job.Name = '$job'
5101 AND Job.Type = '$type'
5102 AND Job.JobStatus IN ('T', 'W')
5103 AND Job.Level IN ($l)
5104 " . ($pool?" AND Pool.Name = '$pool' ":'') . "
5105 AND Client.Name = '$client'
5111 push @{$self->{tmp}}, {date => $evt, level => $level,
5112 type => 'Backup', name => $job,
5113 pool => $pool, volume => $pool,
5121 sub display_missing_job
5124 my $arg = $self->get_form(qw/begin end age/);
5126 if (!$arg->{begin}) { # TODO: change this
5127 $arg->{begin} = strftime('%F %T', localtime(time - $arg->{age}));
5130 $arg->{end} = strftime('%F %T', localtime(time));
5132 $self->{tmp} = []; # check_job use this for result
5134 my $bconsole = $self->get_bconsole();
5136 my $sched = new Bweb::Sched(bconsole => $bconsole,
5137 begin => $arg->{begin},
5138 end => $arg->{end});
5140 my $job = $bconsole->send_cmd("show job");
5141 my ($jname, $jsched, $jclient, $jpool, $jtype);
5142 foreach my $j (split(/\r?\n/, $job)) {
5143 if ($j =~ /Job: name=([\w\d\-]+?) JobType=(\d+)/i) {
5144 if ($jname and $jsched) {
5145 $self->check_job($sched, $jsched, $jname,
5146 $jpool, $jclient, $jtype);
5150 $jclient = $jpool = $jsched = undef;
5151 } elsif ($j =~ /Client: name=(.+?) address=/i) {
5153 } elsif ($j =~ /Pool: name=([\w\d\-]+) PoolType=/i) {
5155 } elsif ($j =~ /Schedule: name=([\w\d\-]+)/i) {
5161 title => "Missing Job (since $arg->{begin} to $arg->{end})",
5162 list => $self->{tmp},
5163 wiki_url => $self->{info}->{wiki_url},
5165 }, "scheduled_job.tpl");
5167 delete $self->{tmp};