1 ################################################################
6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
11 The main author of Bweb is Eric Bollengier.
12 The main author of Bacula is Kern Sibbald, with contributions from
13 many others, a complete list can be found in the file AUTHORS.
15 This program is Free Software; you can redistribute it and/or
16 modify it under the terms of version two of the GNU General Public
17 License as published by the Free Software Foundation plus additions
18 that are listed in the file LICENSE.
20 This program is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 Bacula® is a registered trademark of John Walker.
31 The licensor of Bacula is the Free Software Foundation Europe
32 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
33 Switzerland, email:ftf@fsfeurope.org.
45 Bweb::Gui - Base package for all Bweb object
49 This package define base fonction like new, display, etc..
54 our $template_dir='/usr/share/bweb/tpl';
58 new - creation a of new Bweb object
62 This function take an hash of argument and place them
65 IE : $obj = new Obj(name => 'test', age => '10');
67 $obj->{name} eq 'test' and $obj->{age} eq 10
73 my ($class, %arg) = @_;
78 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
85 my ($self, $what) = @_;
89 print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
91 print "<pre>$what</pre>";
98 error - display an error to the user
102 this function set $self->{error} with arg, display a message with
103 error.tpl and return 0
108 return $self->error("Can't use this file");
115 my ($self, $what) = @_;
116 $self->{error} = $what;
117 $self->display($self, 'error.tpl');
123 display - display an html page with HTML::Template
127 this function is use to render all html codes. it takes an
128 ref hash as arg in which all param are usable in template.
130 it will use user template_dir then global template_dir
131 to search the template file.
133 hash keys are not sensitive. See HTML::Template for more
134 explanations about the hash ref. (it's can be quiet hard to understand)
138 $ref = { name => 'me', age => 26 };
139 $self->display($ref, "people.tpl");
145 my ($self, $hash, $tpl) = @_ ;
146 my $dir = $self->{template_dir} || $template_dir;
147 my $lang = $self->{lang} || 'en';
148 my $template = HTML::Template->new(filename => $tpl,
149 path =>["$dir/$lang",
151 die_on_bad_params => 0,
152 case_sensitive => 0);
154 foreach my $var (qw/limit offset/) {
156 unless ($hash->{$var}) {
157 my $value = CGI::param($var) || '';
159 if ($value =~ /^(\d+)$/) {
160 $template->param($var, $1) ;
165 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
166 $template->param('loginname', CGI::remote_user());
168 $template->param($hash);
169 print $template->output();
173 ################################################################
175 package Bweb::Config;
177 use base q/Bweb::Gui/;
181 Bweb::Config - read, write, display, modify configuration
185 this package is used for manage configuration
189 $conf = new Bweb::Config(config_file => '/path/to/conf');
200 =head1 PACKAGE VARIABLE
202 %k_re - hash of all acceptable option.
206 this variable permit to check all option with a regexp.
210 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
211 user => qr/^([\w\d\.-]+)$/i,
212 password => qr/^(.*)$/,
213 fv_write_path => qr!^([/\w\d\.-]*)$!,
214 template_dir => qr!^([/\w\d\.-]+)$!,
215 debug => qr/^(on)?$/,
216 lang => qr/^(\w\w)?$/,
217 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
218 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
219 bconsole => qr!^(.+)?$!,
220 syslog_file => qr!^(.+)?$!,
221 log_dir => qr!^(.+)?$!,
222 wiki_url => qr!(.*)$!,
223 stat_job_table => qr!^(\w*)$!,
224 display_log_time => qr!^(on)?$!,
225 enable_security => qr/^(on)?$/,
226 enable_security_acl => qr/^(on)?$/,
231 load - load config_file
235 this function load the specified config_file.
243 unless (open(FP, $self->{config_file}))
245 return $self->error("can't load config_file $self->{config_file} : $!");
247 my $f=''; my $tmpbuffer;
248 while(read FP,$tmpbuffer,4096)
256 no strict; # I have no idea of the contents of the file
263 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...") ;
266 foreach my $k (keys %$VAR1) {
267 $self->{$k} = $VAR1->{$k};
275 load_old - load old configuration format
283 unless (open(FP, $self->{config_file}))
285 return $self->error("$self->{config_file} : $!");
288 while (my $line = <FP>)
291 my ($k, $v) = split(/\s*=\s*/, $line, 2);
303 save - save the current configuration to config_file
311 if ($self->{ach_list}) {
312 # shortcut for display_begin
313 $self->{achs} = [ map {{ name => $_ }}
314 keys %{$self->{ach_list}}
318 unless (open(FP, ">$self->{config_file}"))
320 return $self->error("$self->{config_file} : $!\n" .
321 "You must add this to your config file\n"
322 . Data::Dumper::Dumper($self));
325 print FP Data::Dumper::Dumper($self);
333 edit, view, modify - html form ouput
341 $self->display($self, "config_edit.tpl");
347 $self->display($self, "config_view.tpl");
355 # we need to reset checkbox first
357 $self->{display_log_time} = 0;
358 $self->{enable_security} = 0;
359 $self->{enable_security_acl} = 0;
361 foreach my $k (CGI::param())
363 next unless (exists $k_re{$k}) ;
364 my $val = CGI::param($k);
365 if ($val =~ $k_re{$k}) {
368 $self->{error} .= "bad parameter : $k = [$val]";
374 if ($self->{error}) { # an error as occured
375 $self->display($self, 'error.tpl');
383 ################################################################
385 package Bweb::Client;
387 use base q/Bweb::Gui/;
391 Bweb::Client - Bacula FD
395 this package is use to do all Client operations like, parse status etc...
399 $client = new Bweb::Client(name => 'zog-fd');
400 $client->status(); # do a 'status client=zog-fd'
406 display_running_job - Html display of a running job
410 this function is used to display information about a current job
414 sub display_running_job
416 my ($self, $conf, $jobid) = @_ ;
418 my $status = $self->status($conf);
421 if ($status->{$jobid}) {
422 $self->display($status->{$jobid}, "client_job_status.tpl");
425 for my $id (keys %$status) {
426 $self->display($status->{$id}, "client_job_status.tpl");
433 $client = new Bweb::Client(name => 'plume-fd');
435 $client->status($bweb);
439 dirty hack to parse "status client=xxx-fd"
443 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
444 Backup Job started: 06-jun-06 17:22
445 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
446 Files Examined=10,697
447 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
453 JobName => Full_plume.2006-06-06_17.22.23,
456 Bytes => 194,484,132,
466 my ($self, $conf) = @_ ;
468 if (defined $self->{cur_jobs}) {
469 return $self->{cur_jobs} ;
473 my $b = new Bconsole(pref => $conf);
474 my $ret = $b->send_cmd("st client=$self->{name}");
478 for my $r (split(/\n/, $ret)) {
480 $r =~ s/(^\s+|\s+$)//g;
481 if ($r =~ /JobId (\d+) Job (\S+)/) {
483 $arg->{$jobid} = { @param, JobId => $jobid } ;
487 @param = ( JobName => $2 );
489 } elsif ($r =~ /=.+=/) {
490 push @param, split(/\s+|\s*=\s*/, $r) ;
492 } elsif ($r =~ /=/) { # one per line
493 push @param, split(/\s*=\s*/, $r) ;
495 } elsif ($r =~ /:/) { # one per line
496 push @param, split(/\s*:\s*/, $r, 2) ;
500 if ($jobid and @param) {
501 $arg->{$jobid} = { @param,
503 Client => $self->{name},
507 $self->{cur_jobs} = $arg ;
513 ################################################################
515 package Bweb::Autochanger;
517 use base q/Bweb::Gui/;
521 Bweb::Autochanger - Object to manage Autochanger
525 this package will parse the mtx output and manage drives.
529 $auto = new Bweb::Autochanger(precmd => 'sudo');
531 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
535 $auto->slot_is_full(10);
536 $auto->transfer(10, 11);
542 my ($class, %arg) = @_;
545 name => '', # autochanger name
546 label => {}, # where are volume { label1 => 40, label2 => drive0 }
547 drive => [], # drive use [ 'media1', 'empty', ..]
548 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
549 io => [], # io slot number list [ 41, 42, 43...]
550 info => {slot => 0, # informations (slot, drive, io)
554 mtxcmd => '/usr/sbin/mtx',
556 device => '/dev/changer',
557 precmd => '', # ssh command
558 bweb => undef, # link to bacula web object (use for display)
561 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
568 status - parse the output of mtx status
572 this function will launch mtx status and parse the output. it will
573 give a perlish view of the autochanger content.
575 it uses ssh if the autochanger is on a other host.
582 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
584 # TODO : reset all infos
585 $self->{info}->{drive} = 0;
586 $self->{info}->{slot} = 0;
587 $self->{info}->{io} = 0;
589 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
592 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
593 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
594 #Data Transfer Element 1:Empty
595 # Storage Element 1:Empty
596 # Storage Element 2:Full :VolumeTag=000002
597 # Storage Element 3:Empty
598 # Storage Element 4:Full :VolumeTag=000004
599 # Storage Element 5:Full :VolumeTag=000001
600 # Storage Element 6:Full :VolumeTag=000003
601 # Storage Element 7:Empty
602 # Storage Element 41 IMPORT/EXPORT:Empty
603 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
608 # Storage Element 7:Empty
609 # Storage Element 2:Full :VolumeTag=000002
610 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
613 $self->set_empty_slot($1);
615 $self->set_slot($1, $4);
618 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
621 $self->set_empty_drive($1);
623 $self->set_drive($1, $4, $6);
626 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
629 $self->set_empty_io($1);
631 $self->set_io($1, $4);
634 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
636 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
637 $self->{info}->{drive} = $1;
638 $self->{info}->{slot} = $2;
639 if ($l =~ /(\d+)\s+Import/) {
640 $self->{info}->{io} = $1 ;
642 $self->{info}->{io} = 0;
647 $self->debug($self) ;
652 my ($self, $slot) = @_;
655 if ($self->{slot}->[$slot] eq 'loaded') {
659 my $label = $self->{slot}->[$slot] ;
661 return $self->is_media_loaded($label);
666 my ($self, $drive, $slot) = @_;
668 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
669 return 0 if ($self->slot_is_full($slot)) ;
671 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
674 my $content = $self->get_slot($slot);
675 print "content = $content<br/> $drive => $slot<br/>";
676 $self->set_empty_drive($drive);
677 $self->set_slot($slot, $content);
680 $self->{error} = $out;
685 # TODO: load/unload have to use mtx script from bacula
688 my ($self, $drive, $slot) = @_;
690 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
691 return 0 unless ($self->slot_is_full($slot)) ;
693 print "Loading drive $drive with slot $slot<br/>\n";
694 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
697 my $content = $self->get_slot($slot);
698 print "content = $content<br/> $slot => $drive<br/>";
699 $self->set_drive($drive, $slot, $content);
702 $self->{error} = $out;
710 my ($self, $media) = @_;
712 unless ($self->{label}->{$media}) {
716 if ($self->{label}->{$media} =~ /drive\d+/) {
726 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
731 my ($self, $slot, $tag) = @_;
732 $self->{slot}->[$slot] = $tag || 'full';
733 push @{ $self->{io} }, $slot;
736 $self->{label}->{$tag} = $slot;
742 my ($self, $slot) = @_;
744 push @{ $self->{io} }, $slot;
746 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
747 $self->{slot}->[$slot] = 'empty';
753 my ($self, $slot) = @_;
754 return $self->{slot}->[$slot];
759 my ($self, $slot, $tag) = @_;
760 $self->{slot}->[$slot] = $tag || 'full';
763 $self->{label}->{$tag} = $slot;
769 my ($self, $slot) = @_;
771 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
772 $self->{slot}->[$slot] = 'empty';
778 my ($self, $drive) = @_;
779 $self->{drive}->[$drive] = 'empty';
784 my ($self, $drive, $slot, $tag) = @_;
785 $self->{drive}->[$drive] = $tag || $slot;
787 $self->{slot}->[$slot] = $tag || 'loaded';
790 $self->{label}->{$tag} = "drive$drive";
796 my ($self, $slot) = @_;
798 # slot don't exists => full
799 if (not defined $self->{slot}->[$slot]) {
803 if ($self->{slot}->[$slot] eq 'empty') {
806 return 1; # vol, full, loaded
809 sub slot_get_first_free
812 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
813 return $slot unless ($self->slot_is_full($slot));
817 sub io_get_first_free
821 foreach my $slot (@{ $self->{io} }) {
822 return $slot unless ($self->slot_is_full($slot));
829 my ($self, $media) = @_;
831 return $self->{label}->{$media} ;
836 my ($self, $media) = @_;
838 return defined $self->{label}->{$media} ;
843 my ($self, $slot) = @_;
845 unless ($self->slot_is_full($slot)) {
846 print "Autochanger $self->{name} slot $slot is empty\n";
851 if ($self->is_slot_loaded($slot)) {
854 print "Autochanger $self->{name} $slot is currently in use\n";
858 # autochanger must have I/O
859 unless ($self->have_io()) {
860 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
864 my $dst = $self->io_get_first_free();
867 print "Autochanger $self->{name} you must empty I/O first\n";
870 $self->transfer($slot, $dst);
875 my ($self, $src, $dst) = @_ ;
876 if ($self->{debug}) {
877 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
879 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
882 my $content = $self->get_slot($src);
883 $self->{slot}->[$src] = 'empty';
884 $self->set_slot($dst, $content);
887 $self->{error} = $out;
894 my ($self, $index) = @_;
895 return $self->{drive_name}->[$index];
898 # TODO : do a tapeinfo request to get informations
908 for my $slot (@{$self->{io}})
910 if ($self->is_slot_loaded($slot)) {
911 print "$slot is currently loaded\n";
915 if ($self->slot_is_full($slot))
917 my $free = $self->slot_get_first_free() ;
918 print "move $slot to $free :\n";
921 if ($self->transfer($slot, $free)) {
922 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
924 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
928 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
934 # TODO : this is with mtx status output,
935 # we can do an other function from bacula view (with StorageId)
939 my $bweb = $self->{bweb};
941 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
942 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
945 SELECT Media.VolumeName AS volumename,
946 Media.VolStatus AS volstatus,
947 Media.LastWritten AS lastwritten,
948 Media.VolBytes AS volbytes,
949 Media.MediaType AS mediatype,
951 Media.InChanger AS inchanger,
953 $bweb->{sql}->{FROM_UNIXTIME}(
954 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
955 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
958 INNER JOIN Pool USING (PoolId)
960 WHERE Media.VolumeName IN ($media_list)
963 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
965 # TODO : verify slot and bacula slot
969 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
971 if ($self->slot_is_full($slot)) {
973 my $vol = $self->{slot}->[$slot];
974 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
976 my $bslot = $all->{$vol}->{slot} ;
977 my $inchanger = $all->{$vol}->{inchanger};
979 # if bacula slot or inchanger flag is bad, we display a message
980 if ($bslot != $slot or !$inchanger) {
981 push @to_update, $slot;
984 $all->{$vol}->{realslot} = $slot;
986 push @{ $param }, $all->{$vol};
988 } else { # empty or no label
989 push @{ $param }, {realslot => $slot,
990 volstatus => 'Unknown',
991 volumename => $self->{slot}->[$slot]} ;
994 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
998 my $i=0; my $drives = [] ;
999 foreach my $d (@{ $self->{drive} }) {
1000 $drives->[$i] = { index => $i,
1001 load => $self->{drive}->[$i],
1002 name => $self->{drive_name}->[$i],
1007 $bweb->display({ Name => $self->{name},
1008 nb_drive => $self->{info}->{drive},
1009 nb_io => $self->{info}->{io},
1012 Update => scalar(@to_update) },
1019 ################################################################
1021 package Bweb::Sched;
1022 use base q/Bweb::Gui/;
1026 Bweb::Sched() - Bweb package that parse show schedule ouput
1028 new Bweb::Sched(format => '%Y-%m-%d', year => 2008);
1032 my $b = $bweb->get_bconsole();
1033 my $s = $b->send_cmd("show schedule");
1034 my $sched = new Bweb::Sched(begin => '2007-01-01', end => '2007-01-02 12:00');
1035 $sched->parse_scheds(split(/\r?\n/, $s));
1046 'level' => 'Differential',
1053 my ($class, @arg) = @_;
1054 my $self = $class->SUPER::new(@arg);
1056 # we compare the current schedule date with begin and end
1057 # in a float form ex: 20071212.1243 > 20070101
1058 if ($self->{begin} and $self->{end}) {
1059 ($self->{fbegin}, $self->{fend}) = ($self->{begin}, $self->{end});
1060 $self->{fbegin} =~ s/(-|:)//g; $self->{fbegin} =~ s/ /./;
1061 $self->{fend} =~ s/(-|:)//g; $self->{fend} =~ s/ /./;
1064 bless($self,$class);
1066 if ($self->{bconsole}) {
1067 my $sel = $self->{name}?"=\"$self->{name}\"":'';
1068 my $b = $self->{bconsole};
1069 my $out = $b->send_cmd("show schedule$sel");
1070 $self->parse_scheds(split(/\r?\n/, $out));
1071 undef $self->{bconsole}; # useless now
1077 # cleanup and add a schedule
1080 my ($self, $name, $info) = @_;
1081 # bacula uses dates that start from 0, we start from 1
1082 foreach (@{$info->{mday}},@{$info->{month}}) { $_++ };
1085 $info->{event} = [ $self->get_events($info, $self->{format}) ];
1087 foreach my $i (qw/hour mday month wday wom woy mins/) {
1091 push @{$self->{schedules}->{$name}}, $info;
1094 # return the name of all schedules
1097 my ($self, $name) = @_;
1099 return keys %{ $self->{schedules} };
1102 # return an array of all schedule
1105 my ($self, $sched) = @_;
1106 return $self->{schedules}->{$sched};
1109 # return an ref array of all events
1110 # [ '2007-12-19 04:05', '2007-12-19 04:05' ]
1113 my ($self, $sched) = @_;
1114 return $sched->{event};
1117 # return the pool name
1120 my ($self, $sched) = @_;
1121 return $sched->{pool} || '';
1124 # return the level name (Incremental, Differential, Full)
1127 my ($self, $sched) = @_;
1128 return $sched->{level};
1131 # parse bacula sched bitmap
1134 my ($self, @output) = @_;
1141 foreach my $ligne (@output) {
1142 if ($ligne =~ /Schedule: name=(.+)/) {
1143 if ($name and $elt) {
1144 $elt->{level} = $run;
1145 $self->add_sched($name, $elt);
1150 elsif ($ligne =~ /Run Level=(.+)/) {
1151 if ($name and $elt) {
1152 $elt->{level} = $run;
1153 $self->add_sched($name, $elt);
1158 elsif ($ligne =~ /(hour|mday|month|mins)=(.+)/) {
1159 # All theses lines have the same format
1161 my ($k,$v) = ($1,$2);
1162 # we get all values (0 1 4 9)
1163 $elt->{$k}=[split (/\s/,$v)];
1165 # we make a bitmap for this kind of data (0 0 1 0 0 0 1) for a quick access
1166 elsif ($ligne =~ /(wday|wom|woy)=(.+)/) {
1167 my ($k,$v) = ($1,$2);
1168 foreach my $e (split (/\s/,$v)) {
1172 elsif ($ligne =~ /Pool: name=(.+?) PoolType/) {
1177 if ($name and $elt) {
1178 $elt->{level} = $run;
1179 $self->add_sched($name, $elt);
1183 use Date::Calc qw(:all);
1185 # read bacula schedule bitmap and get $format date string
1189 my ($self, $s,$format) = @_;
1190 my $year = $self->{year} || ((localtime)[5] + 1900);
1191 $format = $format || '%u-%02u-%02u %02u:%02u';
1193 foreach my $m (@{$s->{month}}) # mois de l'annee
1195 foreach my $md (@{$s->{mday}}) # jour du mois
1197 # print " m=$m md=$md\n";
1198 # we check if this day exists (31 fev)
1199 next if (!check_date($year,$m,$md));
1200 # print " check_date ok\n";
1202 my $w = ($md-1)/7; # we use the same thing than bacula
1203 next if (! $s->{wom}->[$w]);
1204 # print " wom ok\n";
1206 # on recupere le jour de la semaine
1207 my $wd = Day_of_Week($year,$m,$md);
1209 my ($w1,undef) = Week_of_Year($year,$m,$wd);
1210 next if (! $s->{woy}->[$w1-1]); # bacula 0-51
1211 # print " woy ok\n";
1213 $wd = 0 if ($wd == 7) ; # sunday is 0 on bacula
1214 next if (! $s->{wday}->[$wd]);
1215 # print " wday ok\n";
1217 foreach my $h (@{$s->{hour}}) # hour of the day
1219 foreach my $min (@{$s->{mins}}) # minute
1221 if ($self->{fbegin}) {
1223 my $d = sprintf('%d%0.2d%0.2d.%0.2d%0.2d',
1224 $year,$m,$md,$h,$min);
1225 next if ($d < $self->{fbegin} or $d > $self->{fend});
1227 push @ret, sprintf($format, $year,$m,$md,$h,$min);
1236 ################################################################
1240 use base q/Bweb::Gui/;
1244 Bweb - main Bweb package
1248 this package is use to compute and display informations
1253 use POSIX qw/strftime/;
1255 our $config_file='/etc/bacula/bweb.conf';
1261 %sql_func - hash to make query mysql/postgresql compliant
1267 UNIX_TIMESTAMP => '',
1268 FROM_UNIXTIME => '',
1269 TO_SEC => " interval '1 second' * ",
1270 SEC_TO_INT => "SEC_TO_INT",
1273 STARTTIME_SEC => " date_trunc('sec', Job.StartTime) ",
1274 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1275 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1276 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1277 STARTTIME_WEEK => " date_trunc('week', Job.StartTime) ",
1278 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1279 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1280 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1281 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1282 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1283 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1287 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1288 FROM_UNIXTIME => 'FROM_UNIXTIME',
1291 SEC_TO_TIME => 'SEC_TO_TIME',
1292 MATCH => " REGEXP ",
1293 STARTTIME_SEC => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %T') ",
1294 STARTTIME_DAY => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d') ",
1295 STARTTIME_HOUR => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %H') ",
1296 STARTTIME_MONTH => " DATE_FORMAT(Job.StartTime, '%Y-%m') ",
1297 STARTTIME_WEEK => " DATE_FORMAT(Job.StartTime, '%Y-%v') ",
1298 STARTTIME_PHOUR=> " DATE_FORMAT(Job.StartTime, '%H') ",
1299 STARTTIME_PDAY => " DATE_FORMAT(Job.StartTime, '%d') ",
1300 STARTTIME_PMONTH => " DATE_FORMAT(Job.StartTime, '%m') ",
1301 STARTTIME_PWEEK => " DATE_FORMAT(Job.StartTime, '%v') ",
1302 # with mysql < 5, you have to play with the ugly SHOW command
1303 DB_SIZE => " SELECT 0 ",
1304 # works only with mysql 5
1305 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1306 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1307 CONCAT_SEP => " SEPARATOR '' ",
1314 return $self->{info}->{dbi} =~ /dbi:mysql/i;
1321 $self->{dbh}->disconnect();
1326 sub dbh_selectall_arrayref
1328 my ($self, $query) = @_;
1329 $self->connect_db();
1330 $self->debug($query);
1331 return $self->{dbh}->selectall_arrayref($query);
1336 my ($self, @what) = @_;
1337 return join(',', $self->dbh_quote(@what)) ;
1342 my ($self, @what) = @_;
1344 $self->connect_db();
1346 return map { $self->{dbh}->quote($_) } @what;
1348 return $self->{dbh}->quote($what[0]) ;
1354 my ($self, $query) = @_ ;
1355 $self->connect_db();
1356 $self->debug($query);
1357 return $self->{dbh}->do($query);
1360 sub dbh_selectall_hashref
1362 my ($self, $query, $join) = @_;
1364 $self->connect_db();
1365 $self->debug($query);
1366 return $self->{dbh}->selectall_hashref($query, $join) ;
1369 sub dbh_selectrow_hashref
1371 my ($self, $query) = @_;
1373 $self->connect_db();
1374 $self->debug($query);
1375 return $self->{dbh}->selectrow_hashref($query) ;
1380 my ($self, @what) = @_;
1381 if ($self->dbh_is_mysql()) {
1382 return 'CONCAT(' . join(',', @what) . ')' ;
1384 return join(' || ', @what);
1390 my ($self, $query) = @_;
1391 $self->debug($query, up => 1);
1392 return $self->{dbh}->prepare($query);
1398 my @unit = qw(B KB MB GB TB);
1399 my $val = shift || 0;
1401 my $format = '%i %s';
1402 while ($val / 1024 > 1) {
1406 $format = ($i>0)?'%0.1f %s':'%i %s';
1407 return sprintf($format, $val, $unit[$i]);
1410 # display Day, Hour, Year
1416 $val /= 60; # sec -> min
1418 if ($val / 60 <= 1) {
1422 $val /= 60; # min -> hour
1423 if ($val / 24 <= 1) {
1424 return "$val hours";
1427 $val /= 24; # hour -> day
1428 if ($val / 365 < 2) {
1432 $val /= 365 ; # day -> year
1434 return "$val years";
1440 my $val = shift || 0;
1442 if ($val eq '1' or $val eq "yes") {
1444 } elsif ($val eq '2' or $val eq "archived") {
1452 sub from_human_enabled
1454 my $val = shift || 0;
1456 if ($val eq '1' or $val eq "yes") {
1458 } elsif ($val eq '2' or $val eq "archived") {
1465 # get Day, Hour, Year
1471 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1475 my %times = ( m => 60,
1481 my $mult = $times{$2} || 0;
1491 unless ($self->{dbh}) {
1493 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1494 $self->{info}->{user},
1495 $self->{info}->{password});
1497 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1498 unless ($self->{dbh});
1500 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1502 if ($self->dbh_is_mysql()) {
1503 $self->{dbh}->do("SET group_concat_max_len=1000000");
1505 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1512 my ($class, %arg) = @_;
1514 dbh => undef, # connect_db();
1516 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1522 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1524 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1525 $self->{sql} = $sql_func{$1};
1528 $self->{loginname} = CGI::remote_user();
1529 $self->{debug} = $self->{info}->{debug};
1530 $self->{lang} = $self->{info}->{lang};
1531 $self->{template_dir} = $self->{info}->{template_dir};
1539 if ($self->{info}->{enable_security}) {
1540 $self->get_roles(); # get lang
1542 $self->display($self->{info}, "begin.tpl");
1548 $self->display($self->{info}, "end.tpl");
1554 my $where=''; # by default
1556 my $arg = $self->get_form("client", "qre_client",
1557 "jclient_groups", "qnotingroup");
1559 if ($arg->{qre_client}) {
1560 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1561 } elsif ($arg->{client}) {
1562 $where = "WHERE Name = '$arg->{client}' ";
1563 } elsif ($arg->{jclient_groups}) {
1564 # $filter could already contains client_group_member
1566 JOIN client_group_member USING (ClientId)
1567 JOIN client_group USING (client_group_id)
1568 WHERE client_group_name IN ($arg->{jclient_groups}) ";
1569 } elsif ($arg->{qnotingroup}) {
1572 (SELECT 1 FROM client_group_member
1573 WHERE Client.ClientId = client_group_member.ClientId
1579 SELECT Name AS name,
1581 AutoPrune AS autoprune,
1582 FileRetention AS fileretention,
1583 JobRetention AS jobretention
1584 FROM Client " . $self->get_client_filter() .
1587 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1589 my $dsp = { ID => $cur_id++,
1590 clients => [ values %$all] };
1592 $self->display($dsp, "client_list.tpl") ;
1597 my ($self, %arg) = @_;
1602 if ($arg{since} and $arg{age}) {
1603 my $now = "$self->{sql}->{UNIX_TIMESTAMP}(TIMESTAMP '$arg{since}')";
1605 AND $self->{sql}->{UNIX_TIMESTAMP}(StartTime) > $now
1606 AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) < ($now + $self->{sql}->{TO_SEC}($arg{age}))";
1607 $label .= "since $arg{since} and during " . human_sec($arg{age});
1609 } elsif ($arg{age}) {
1611 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1613 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1615 $self->{sql}->{TO_SEC}($arg{age})
1618 $label = "last " . human_sec($arg{age});
1621 if ($arg{groupby}) {
1622 $limit .= " GROUP BY $arg{groupby} ";
1626 $limit .= " ORDER BY $arg{order} ";
1630 $limit .= " LIMIT $arg{limit} ";
1631 $label .= " limited to $arg{limit}";
1635 $limit .= " OFFSET $arg{offset} ";
1636 $label .= " with $arg{offset} offset ";
1640 $label = 'no filter';
1643 return ($limit, $label);
1648 $bweb->get_form(...) - Get useful stuff
1652 This function get and check parameters against regexp.
1654 If word begin with 'q', the return will be quoted or join quoted
1655 if it's end with 's'.
1660 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1663 qclient => 'plume-fd',
1664 qpools => "'plume-fd', 'test-fd', '...'",
1671 my ($self, @what) = @_;
1672 my %what = map { $_ => 1 } @what;
1695 my %opt_ss =( # string with space
1699 my %opt_s = ( # default to ''
1721 my %opt_p = ( # option with path
1728 my %opt_r = (regexwhere => 1);
1729 my %opt_d = ( # option with date
1733 my %opt_t = (when => 2, # option with time
1734 begin => 1, # 1 hh:min are optionnal
1735 end => 1, # 2 hh:min are required
1738 foreach my $i (@what) {
1739 if (exists $opt_i{$i}) {# integer param
1740 my $value = CGI::param($i) || $opt_i{$i} ;
1741 if ($value =~ /^(\d+)$/) {
1744 } elsif ($opt_s{$i}) { # simple string param
1745 my $value = CGI::param($i) || '';
1746 if ($value =~ /^([\w\d\.-]+)$/) {
1749 } elsif ($opt_ss{$i}) { # simple string param (with space)
1750 my $value = CGI::param($i) || '';
1751 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1754 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1755 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1757 $ret{$i} = $self->dbh_join(@value) ;
1760 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1761 my $value = CGI::param($1) ;
1763 $ret{$i} = $self->dbh_quote($value);
1766 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1767 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1768 grep { ! /^\s*$/ } CGI::param($1) ];
1769 } elsif (exists $opt_p{$i}) {
1770 my $value = CGI::param($i) || '';
1771 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1774 } elsif (exists $opt_r{$i}) {
1775 my $value = CGI::param($i) || '';
1776 if ($value =~ /^([^'"']+)$/) {
1779 } elsif (exists $opt_d{$i}) {
1780 my $value = CGI::param($i) || '';
1781 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1784 } elsif (exists $opt_t{$i}) { # 1: hh:min optionnal, 2: hh:min required
1785 my $when = CGI::param($i) || '';
1786 if ($when =~ /(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)/) {
1787 if ($opt_t{$i} == 1 or defined $2) {
1794 if ($what{storage_cmd}) {
1795 if (!grep {/^$ret{storage_cmd}$/} ('mount', 'umount', 'release','status')) {
1796 delete $ret{storage_cmd};
1801 foreach my $s (CGI::param('slot')) {
1802 if ($s =~ /^(\d+)$/) {
1803 push @{$ret{slots}}, $s;
1809 my $age = $ret{age} || $opt_i{age};
1810 my $since = CGI::param('since') || strftime('%F %T', localtime(time - $age));
1811 if ($since =~ /^(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)$/) {
1817 my $lang = CGI::param('lang') || 'en';
1818 if ($lang =~ /^(\w\w)$/) {
1823 if ($what{db_clients}) {
1825 if ($what{filter}) {
1826 # get security filter only if asked
1827 $filter = $self->get_client_filter();
1831 SELECT Client.Name as clientname
1835 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1836 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1840 if ($what{db_client_groups}) {
1842 if ($what{filter}) {
1843 # get security filter only if asked
1844 $filter = $self->get_client_group_filter();
1848 SELECT client_group_name AS name
1849 FROM client_group $filter
1851 my $grps = $self->dbh_selectall_hashref($query, 'name');
1852 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1856 if ($what{db_usernames}) {
1861 my $users = $self->dbh_selectall_hashref($query, 'username');
1862 $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} }
1866 if ($what{db_roles}) {
1871 my $r = $self->dbh_selectall_hashref($query, 'rolename');
1872 $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} }
1876 if ($what{db_mediatypes}) {
1878 SELECT MediaType as mediatype
1881 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1882 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1886 if ($what{db_locations}) {
1888 SELECT Location as location, Cost as cost
1891 my $loc = $self->dbh_selectall_hashref($query, 'location');
1892 $ret{db_locations} = [ sort { $a->{location}
1898 if ($what{db_pools}) {
1899 my $query = "SELECT Name as name FROM Pool";
1901 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1902 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1905 if ($what{db_filesets}) {
1907 SELECT FileSet.FileSet AS fileset
1910 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1912 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1913 values %$filesets] ;
1916 if ($what{db_jobnames}) {
1918 if ($what{filter}) {
1919 $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
1922 SELECT DISTINCT Job.Name AS jobname
1925 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1927 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1928 values %$jobnames] ;
1931 if ($what{db_devices}) {
1933 SELECT Device.Name AS name
1936 my $devices = $self->dbh_selectall_hashref($query, 'name');
1938 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1948 $self->can_do('r_view_stat');
1949 my $fields = $self->get_form(qw/age level status clients filesets
1950 graph gtype type filter db_clients
1951 limit db_filesets width height
1952 qclients qfilesets qjobnames db_jobnames/);
1954 my $url = CGI::url(-full => 0,
1957 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1959 # this organisation is to keep user choice between 2 click
1960 # TODO : fileset and client selection doesn't work
1967 if ($fields->{gtype} eq 'balloon') {
1968 system("./bgraph.pl");
1972 sub get_selected_media_location
1976 my $media = $self->get_form('jmedias');
1978 unless ($media->{jmedias}) {
1983 SELECT Media.VolumeName AS volumename, Location.Location AS location
1984 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1985 WHERE Media.VolumeName IN ($media->{jmedias})
1988 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1990 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1999 my ($self, $in) = @_ ;
2000 $self->can_do('r_media_mgnt');
2001 my $media = $self->get_selected_media_location();
2007 my $elt = $self->get_form('db_locations');
2009 $self->display({ ID => $cur_id++,
2010 enabled => human_enabled($in),
2011 %$elt, # db_locations
2013 sort { $a->{volumename} cmp $b->{volumename} } values %$media
2022 $self->can_do('r_media_mgnt');
2024 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
2026 $self->display($elt, "help_extern.tpl");
2029 sub help_extern_compute
2032 $self->can_do('r_media_mgnt');
2034 my $number = CGI::param('limit') || '' ;
2035 unless ($number =~ /^(\d+)$/) {
2036 return $self->error("Bad arg number : $number ");
2039 my ($sql, undef) = $self->get_param('pools',
2040 'locations', 'mediatypes');
2043 SELECT Media.VolumeName AS volumename,
2044 Media.VolStatus AS volstatus,
2045 Media.LastWritten AS lastwritten,
2046 Media.MediaType AS mediatype,
2047 Media.VolMounts AS volmounts,
2049 Media.Recycle AS recycle,
2050 $self->{sql}->{FROM_UNIXTIME}(
2051 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2052 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2055 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2056 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2058 WHERE Media.InChanger = 1
2059 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
2061 ORDER BY expire DESC, recycle, Media.VolMounts DESC
2065 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2067 $self->display({ Media => [ values %$all ] },
2068 "help_extern_compute.tpl");
2074 $self->can_do('r_media_mgnt');
2076 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
2077 $self->display($param, "help_intern.tpl");
2080 sub help_intern_compute
2083 $self->can_do('r_media_mgnt');
2085 my $number = CGI::param('limit') || '' ;
2086 unless ($number =~ /^(\d+)$/) {
2087 return $self->error("Bad arg number : $number ");
2090 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
2092 if (CGI::param('expired')) {
2094 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2095 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2101 SELECT Media.VolumeName AS volumename,
2102 Media.VolStatus AS volstatus,
2103 Media.LastWritten AS lastwritten,
2104 Media.MediaType AS mediatype,
2105 Media.VolMounts AS volmounts,
2107 $self->{sql}->{FROM_UNIXTIME}(
2108 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2109 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2112 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2113 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
2115 WHERE Media.InChanger <> 1
2116 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
2117 AND Media.Recycle = 1
2119 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
2123 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2125 $self->display({ Media => [ values %$all ] },
2126 "help_intern_compute.tpl");
2132 my ($self, %arg) = @_ ;
2134 my ($limit, $label) = $self->get_limit(%arg);
2138 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
2139 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
2140 (SELECT count(Job.JobId) FROM Job) AS nb_job,
2141 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
2142 ($self->{sql}->{DB_SIZE}) AS db_size,
2143 (SELECT count(Job.JobId)
2145 WHERE Job.JobStatus IN ('E','e','f','A')
2148 (SELECT count(Client.ClientId) FROM Client) AS nb_client
2151 my $row = $self->dbh_selectrow_hashref($query) ;
2153 $row->{nb_bytes} = human_size($row->{nb_bytes});
2155 $row->{db_size} = human_size($row->{db_size});
2156 $row->{label} = $label;
2158 $self->display($row, "general.tpl");
2163 my ($self, @what) = @_ ;
2164 my %elt = map { $_ => 1 } @what;
2169 if ($elt{clients}) {
2170 my @clients = grep { ! /^\s*$/ } CGI::param('client');
2172 $ret{clients} = \@clients;
2173 my $str = $self->dbh_join(@clients);
2174 $limit .= "AND Client.Name IN ($str) ";
2178 if ($elt{client_groups}) {
2179 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
2181 $ret{client_groups} = \@clients;
2182 my $str = $self->dbh_join(@clients);
2183 $limit .= "AND client_group_name IN ($str) ";
2187 if ($elt{filesets}) {
2188 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
2190 $ret{filesets} = \@filesets;
2191 my $str = $self->dbh_join(@filesets);
2192 $limit .= "AND FileSet.FileSet IN ($str) ";
2196 if ($elt{mediatypes}) {
2197 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
2199 $ret{mediatypes} = \@media;
2200 my $str = $self->dbh_join(@media);
2201 $limit .= "AND Media.MediaType IN ($str) ";
2206 my $client = CGI::param('client');
2207 $ret{client} = $client;
2208 $client = $self->dbh_join($client);
2209 $limit .= "AND Client.Name = $client ";
2213 my $level = CGI::param('level') || '';
2214 if ($level =~ /^(\w)$/) {
2216 $limit .= "AND Job.Level = '$1' ";
2221 my $jobid = CGI::param('jobid') || '';
2223 if ($jobid =~ /^(\d+)$/) {
2225 $limit .= "AND Job.JobId = '$1' ";
2230 my $status = CGI::param('status') || '';
2231 if ($status =~ /^(\w)$/) {
2234 $limit .= "AND Job.JobStatus IN ('f','E') ";
2235 } elsif ($1 eq 'W') {
2236 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
2238 $limit .= "AND Job.JobStatus = '$1' ";
2243 if ($elt{volstatus}) {
2244 my $status = CGI::param('volstatus') || '';
2245 if ($status =~ /^(\w+)$/) {
2247 $limit .= "AND Media.VolStatus = '$1' ";
2251 if ($elt{locations}) {
2252 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
2254 $ret{locations} = \@location;
2255 my $str = $self->dbh_join(@location);
2256 $limit .= "AND Location.Location IN ($str) ";
2261 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
2263 $ret{pools} = \@pool;
2264 my $str = $self->dbh_join(@pool);
2265 $limit .= "AND Pool.Name IN ($str) ";
2269 if ($elt{location}) {
2270 my $location = CGI::param('location') || '';
2272 $ret{location} = $location;
2273 $location = $self->dbh_quote($location);
2274 $limit .= "AND Location.Location = $location ";
2279 my $pool = CGI::param('pool') || '';
2282 $pool = $self->dbh_quote($pool);
2283 $limit .= "AND Pool.Name = $pool ";
2287 if ($elt{jobtype}) {
2288 my $jobtype = CGI::param('jobtype') || '';
2289 if ($jobtype =~ /^(\w)$/) {
2291 $limit .= "AND Job.Type = '$1' ";
2295 return ($limit, %ret);
2306 my ($self, %arg) = @_ ;
2307 return if $self->cant_do('r_view_job');
2309 $arg{order} = ' Job.JobId DESC ';
2311 my ($limit, $label) = $self->get_limit(%arg);
2312 my ($where, undef) = $self->get_param('clients',
2321 if (CGI::param('client_group')) {
2323 JOIN client_group_member USING (ClientId)
2324 JOIN client_group USING (client_group_id)
2327 my $filter = $self->get_client_filter();
2330 SELECT Job.JobId AS jobid,
2331 Client.Name AS client,
2332 FileSet.FileSet AS fileset,
2333 Job.Name AS jobname,
2335 StartTime AS starttime,
2337 Pool.Name AS poolname,
2338 JobFiles AS jobfiles,
2339 JobBytes AS jobbytes,
2340 JobStatus AS jobstatus,
2341 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2342 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2345 JobErrors AS joberrors
2347 FROM Client $filter $cgq,
2348 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2349 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2350 WHERE Client.ClientId=Job.ClientId
2351 AND Job.JobStatus NOT IN ('R', 'C')
2356 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2358 $self->display({ Filter => $label,
2362 sort { $a->{jobid} <=> $b->{jobid} }
2369 # display job informations
2370 sub display_job_zoom
2372 my ($self, $jobid) = @_ ;
2373 $self->can_do('r_view_job');
2375 $jobid = $self->dbh_quote($jobid);
2377 # get security filter
2378 my $filter = $self->get_client_filter();
2381 SELECT DISTINCT Job.JobId AS jobid,
2382 Client.Name AS client,
2383 Job.Name AS jobname,
2384 FileSet.FileSet AS fileset,
2386 Pool.Name AS poolname,
2387 StartTime AS starttime,
2388 JobFiles AS jobfiles,
2389 JobBytes AS jobbytes,
2390 JobStatus AS jobstatus,
2391 JobErrors AS joberrors,
2392 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2393 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2395 FROM Client $filter,
2396 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2397 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2398 WHERE Client.ClientId=Job.ClientId
2399 AND Job.JobId = $jobid
2402 my $row = $self->dbh_selectrow_hashref($query) ;
2404 # display all volumes associate with this job
2406 SELECT Media.VolumeName as volumename
2407 FROM Job,Media,JobMedia
2408 WHERE Job.JobId = $jobid
2409 AND JobMedia.JobId=Job.JobId
2410 AND JobMedia.MediaId=Media.MediaId
2413 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2415 $row->{volumes} = [ values %$all ] ;
2416 $row->{wiki_url} = $self->{info}->{wiki_url};
2418 $self->display($row, "display_job_zoom.tpl");
2421 sub display_job_group
2423 my ($self, %arg) = @_;
2424 $self->can_do('r_view_job');
2426 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2428 my ($where, undef) = $self->get_param('client_groups',
2431 my $filter = $self->get_client_group_filter();
2434 SELECT client_group_name AS client_group_name,
2435 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2436 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2437 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2438 COALESCE(jobok.nbjobs,0) AS nbjobok,
2439 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2440 COALESCE(jobok.duration, '0:0:0') AS duration
2442 FROM client_group $filter LEFT JOIN (
2443 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2444 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2445 SUM(JobErrors) AS joberrors,
2446 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2447 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2450 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2451 JOIN client_group USING (client_group_id)
2453 WHERE JobStatus = 'T'
2456 ) AS jobok USING (client_group_name) LEFT JOIN
2459 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2460 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2461 SUM(JobErrors) AS joberrors
2462 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2463 JOIN client_group USING (client_group_id)
2465 WHERE JobStatus IN ('f','E', 'A')
2468 ) AS joberr USING (client_group_name)
2472 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2474 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2477 $self->display($rep, "display_job_group.tpl");
2482 my ($self, %arg) = @_ ;
2483 $self->can_do('r_view_media');
2485 my ($limit, $label) = $self->get_limit(%arg);
2486 my ($where, %elt) = $self->get_param('pools',
2491 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2493 if ($arg->{jmedias}) {
2494 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2496 if ($arg->{qre_media}) {
2497 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2499 if ($arg->{expired}) {
2501 AND VolStatus = 'Full'
2502 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2503 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2504 ) < NOW() " . $where ;
2508 SELECT Media.VolumeName AS volumename,
2509 Media.VolBytes AS volbytes,
2510 Media.VolStatus AS volstatus,
2511 Media.MediaType AS mediatype,
2512 Media.InChanger AS online,
2513 Media.LastWritten AS lastwritten,
2514 Location.Location AS location,
2515 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2516 Pool.Name AS poolname,
2517 $self->{sql}->{FROM_UNIXTIME}(
2518 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2519 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2522 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2523 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2524 Media.MediaType AS MediaType
2526 WHERE Media.VolStatus = 'Full'
2527 GROUP BY Media.MediaType
2528 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2530 WHERE Media.PoolId=Pool.PoolId
2535 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2537 $self->display({ ID => $cur_id++,
2539 Location => $elt{location},
2540 Media => [ values %$all ],
2542 "display_media.tpl");
2545 sub display_allmedia
2549 my $pool = $self->get_form('db_pools');
2551 foreach my $name (@{ $pool->{db_pools} }) {
2552 CGI::param('pool', $name->{name});
2553 $self->display_media();
2557 sub display_media_zoom
2561 my $media = $self->get_form('jmedias');
2563 unless ($media->{jmedias}) {
2564 return $self->error("Can't get media selection");
2568 SELECT InChanger AS online,
2569 Media.Enabled AS enabled,
2570 VolBytes AS nb_bytes,
2571 VolumeName AS volumename,
2572 VolStatus AS volstatus,
2573 VolMounts AS nb_mounts,
2574 Media.VolUseDuration AS voluseduration,
2575 Media.MaxVolJobs AS maxvoljobs,
2576 Media.MaxVolFiles AS maxvolfiles,
2577 Media.MaxVolBytes AS maxvolbytes,
2578 VolErrors AS nb_errors,
2579 Pool.Name AS poolname,
2580 Location.Location AS location,
2581 Media.Recycle AS recycle,
2582 Media.VolRetention AS volretention,
2583 Media.LastWritten AS lastwritten,
2584 Media.VolReadTime/1000000 AS volreadtime,
2585 Media.VolWriteTime/1000000 AS volwritetime,
2586 Media.RecycleCount AS recyclecount,
2587 Media.Comment AS comment,
2588 $self->{sql}->{FROM_UNIXTIME}(
2589 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2590 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2593 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2594 WHERE Pool.PoolId = Media.PoolId
2595 AND VolumeName IN ($media->{jmedias})
2598 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2600 foreach my $media (values %$all) {
2601 my $mq = $self->dbh_quote($media->{volumename});
2604 SELECT DISTINCT Job.JobId AS jobid,
2606 Job.StartTime AS starttime,
2609 Job.JobFiles AS files,
2610 Job.JobBytes AS bytes,
2611 Job.jobstatus AS status
2612 FROM Media,JobMedia,Job
2613 WHERE Media.VolumeName=$mq
2614 AND Media.MediaId=JobMedia.MediaId
2615 AND JobMedia.JobId=Job.JobId
2618 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2621 SELECT LocationLog.Date AS date,
2622 Location.Location AS location,
2623 LocationLog.Comment AS comment
2624 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2625 WHERE Media.MediaId = LocationLog.MediaId
2626 AND Media.VolumeName = $mq
2630 my $log = $self->dbh_selectall_arrayref($query) ;
2632 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2635 $self->display({ jobs => [ values %$jobs ],
2636 LocationLog => $logtxt,
2638 "display_media_zoom.tpl");
2645 $self->can_do('r_location_mgnt');
2647 my $loc = $self->get_form('qlocation');
2648 unless ($loc->{qlocation}) {
2649 return $self->error("Can't get location");
2653 SELECT Location.Location AS location,
2654 Location.Cost AS cost,
2655 Location.Enabled AS enabled
2657 WHERE Location.Location = $loc->{qlocation}
2660 my $row = $self->dbh_selectrow_hashref($query);
2661 $row->{enabled} = human_enabled($row->{enabled});
2662 $self->display({ ID => $cur_id++,
2663 %$row }, "location_edit.tpl") ;
2669 $self->can_do('r_location_mgnt');
2671 my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
2672 unless ($arg->{qlocation}) {
2673 return $self->error("Can't get location");
2675 unless ($arg->{qnewlocation}) {
2676 return $self->error("Can't get new location name");
2678 unless ($arg->{cost}) {
2679 return $self->error("Can't get new cost");
2682 my $enabled = from_human_enabled($arg->{enabled});
2685 UPDATE Location SET Cost = $arg->{cost},
2686 Location = $arg->{qnewlocation},
2688 WHERE Location.Location = $arg->{qlocation}
2691 $self->dbh_do($query);
2693 $self->location_display();
2699 $self->can_do('r_location_mgnt');
2701 my $arg = $self->get_form(qw/qlocation/) ;
2703 unless ($arg->{qlocation}) {
2704 return $self->error("Can't get location");
2708 SELECT count(Media.MediaId) AS nb
2709 FROM Media INNER JOIN Location USING (LocationID)
2710 WHERE Location = $arg->{qlocation}
2713 my $res = $self->dbh_selectrow_hashref($query);
2716 return $self->error("Sorry, the location must be empty");
2720 DELETE FROM Location WHERE Location = $arg->{qlocation}
2723 $self->dbh_do($query);
2725 $self->location_display();
2731 $self->can_do('r_location_mgnt');
2733 my $arg = $self->get_form(qw/qlocation cost/) ;
2735 unless ($arg->{qlocation}) {
2736 $self->display({}, "location_add.tpl");
2739 unless ($arg->{cost}) {
2740 return $self->error("Can't get new cost");
2743 my $enabled = CGI::param('enabled') || '';
2744 $enabled = from_human_enabled($enabled);
2747 INSERT INTO Location (Location, Cost, Enabled)
2748 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2751 $self->dbh_do($query);
2753 $self->location_display();
2756 sub location_display
2761 SELECT Location.Location AS location,
2762 Location.Cost AS cost,
2763 Location.Enabled AS enabled,
2764 (SELECT count(Media.MediaId)
2766 WHERE Media.LocationId = Location.LocationId
2771 my $location = $self->dbh_selectall_hashref($query, 'location');
2773 $self->display({ ID => $cur_id++,
2774 Locations => [ values %$location ] },
2775 "display_location.tpl");
2782 my $media = $self->get_selected_media_location();
2787 my $arg = $self->get_form('db_locations', 'qnewlocation');
2789 $self->display({ email => $self->{info}->{email_media},
2791 media => [ values %$media ],
2793 "update_location.tpl");
2796 ###########################################################
2801 my $grp = $self->get_form(qw/qclient_group db_clients/);
2803 unless ($grp->{qclient_group}) {
2804 $self->can_do('r_group_mgnt');
2805 $self->display({ ID => $cur_id++,
2806 client_group => "''",
2808 }, "groups_edit.tpl");
2812 unless ($self->cant_do('r_group_mgnt')) {
2813 $self->can_do('r_view_group');
2818 FROM Client JOIN client_group_member using (clientid)
2819 JOIN client_group using (client_group_id)
2820 WHERE client_group_name = $grp->{qclient_group}
2823 my $row = $self->dbh_selectall_hashref($query, "name");
2825 $self->display({ ID => $cur_id++,
2826 client_group => $grp->{qclient_group},
2828 client_group_member => [ values %$row]},
2835 $self->can_do('r_group_mgnt');
2837 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2839 if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
2841 INSERT INTO client_group (client_group_name)
2842 VALUES ($arg->{qnewgroup})
2844 $self->dbh_do($query);
2845 $arg->{qclient_group} = $arg->{qnewgroup};
2848 unless ($arg->{qclient_group}) {
2849 return $self->error("Can't get groups");
2852 $self->{dbh}->begin_work();
2855 DELETE FROM client_group_member
2856 WHERE client_group_id IN
2857 (SELECT client_group_id
2859 WHERE client_group_name = $arg->{qclient_group})
2861 $self->dbh_do($query);
2863 if ($arg->{jclients}) {
2865 INSERT INTO client_group_member (clientid, client_group_id)
2867 (SELECT client_group_id
2869 WHERE client_group_name = $arg->{qclient_group})
2870 FROM Client WHERE Name IN ($arg->{jclients})
2873 $self->dbh_do($query);
2875 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2878 SET client_group_name = $arg->{qnewgroup}
2879 WHERE client_group_name = $arg->{qclient_group}
2882 $self->dbh_do($query);
2885 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2887 $self->display_groups();
2893 $self->can_do('r_group_mgnt');
2895 my $arg = $self->get_form(qw/qclient_group/);
2897 unless ($arg->{qclient_group}) {
2898 return $self->error("Can't get groups");
2901 $self->{dbh}->begin_work();
2904 DELETE FROM client_group_member
2905 WHERE client_group_id IN
2906 (SELECT client_group_id
2908 WHERE client_group_name = $arg->{qclient_group})");
2911 DELETE FROM bweb_client_group_acl
2912 WHERE client_group_id IN
2913 (SELECT client_group_id
2915 WHERE client_group_name = $arg->{qclient_group})");
2918 DELETE FROM client_group
2919 WHERE client_group_name = $arg->{qclient_group}");
2921 $self->{dbh}->commit();
2922 $self->display_groups();
2930 if ($self->cant_do('r_group_mgnt')) {
2931 $arg = $self->get_form(qw/db_client_groups filter/) ;
2933 $arg = $self->get_form(qw/db_client_groups/) ;
2936 if ($self->{dbh}->errstr) {
2937 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2942 $self->display({ ID => $cur_id++,
2944 "display_groups.tpl");
2947 ###########################################################
2952 if (not $self->{info}->{enable_security}) {
2955 # admin is a special user that can do everything
2956 if ($self->{loginname} eq 'admin') {
2959 if (!$self->{loginname}) {
2960 $self->error("Can't get your login name");
2961 $self->display_end();
2965 if (defined $self->{security}) {
2968 $self->{security} = {};
2969 my $u = $self->dbh_quote($self->{loginname});
2972 SELECT use_acl, rolename, tpl
2974 JOIN bweb_role_member USING (userid)
2975 JOIN bweb_role USING (roleid)
2978 my $rows = $self->dbh_selectall_arrayref($query);
2979 # do cache with this role
2980 if (!$rows or !scalar(@$rows)) {
2981 $self->error("Can't get $self->{loginname}'s roles");
2982 $self->display_end();
2985 foreach my $r (@$rows) {
2986 $self->{security}->{$r->[1]}=1;
2988 $self->{security}->{use_acl} = $rows->[0]->[0];
2989 if ($rows->[0]->[2] =~ /^(\w\w)$/) {
2997 my ($self, $client) = @_;
2999 my $filter = $self->get_client_filter();
3003 my $cont = $self->dbh_selectrow_hashref("
3006 WHERE Name = '$client'
3008 return defined $cont;
3013 my ($self, $action) = @_;
3014 # is security enabled in configuration ?
3015 if (not $self->{info}->{enable_security}) {
3018 # admin is a special user that can do everything
3019 if ($self->{loginname} eq 'admin') {
3023 if (!$self->{loginname}) {
3024 $self->{error} = "Can't do $action, your are not logged. " .
3025 "Check security with your administrator";
3028 if (!$self->get_roles()) {
3031 if (!$self->{security}->{$action}) {
3033 "$self->{loginname} sorry, but this action ($action) " .
3034 "is not permited. " .
3035 "Check security with your administrator";
3041 # make like an assert (program die)
3044 my ($self, $action) = @_;
3045 if ($self->cant_do($action)) {
3046 $self->error($self->{error});
3047 $self->display_end();
3057 if (!$self->{info}->{enable_security} or
3058 !$self->{info}->{enable_security_acl})
3063 if ($self->get_roles()) {
3064 return $self->{security}->{use_acl};
3070 # JOIN Client USING (ClientId) " . $b->get_client_filter() . "
3071 sub get_client_filter
3073 my ($self, $login) = @_;
3076 $u = $self->dbh_quote($login);
3077 } elsif ($self->use_filter()) {
3078 $u = $self->dbh_quote($self->{loginname});
3083 JOIN (SELECT ClientId FROM client_group_member
3084 JOIN client_group USING (client_group_id)
3085 JOIN bweb_client_group_acl USING (client_group_id)
3086 JOIN bweb_user USING (userid)
3087 WHERE bweb_user.username = $u
3088 ) AS filter USING (ClientId)";
3091 #JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
3092 sub get_client_group_filter
3094 my ($self, $login) = @_;
3097 $u = $self->dbh_quote($login);
3098 } elsif ($self->use_filter()) {
3099 $u = $self->dbh_quote($self->{loginname});
3104 JOIN (SELECT client_group_id
3105 FROM bweb_client_group_acl
3106 JOIN bweb_user USING (userid)
3107 WHERE bweb_user.username = $u
3108 ) AS filter USING (client_group_id)";
3111 # role and username have to be quoted before
3112 # role and username can be a quoted list
3115 my ($self, $role, $username) = @_;
3116 $self->can_do("r_user_mgnt");
3118 my $nb = $self->dbh_do("
3119 DELETE FROM bweb_role_member
3120 WHERE roleid = (SELECT roleid FROM bweb_role
3121 WHERE rolename IN ($role))
3122 AND userid = (SELECT userid FROM bweb_user
3123 WHERE username IN ($username))");
3127 # role and username have to be quoted before
3128 # role and username can be a quoted list
3131 my ($self, $role, $username) = @_;
3132 $self->can_do("r_user_mgnt");
3134 my $nb = $self->dbh_do("
3135 INSERT INTO bweb_role_member (roleid, userid)
3136 SELECT roleid, userid FROM bweb_role, bweb_user
3137 WHERE rolename IN ($role)
3138 AND username IN ($username)
3143 # role and username have to be quoted before
3144 # role and username can be a quoted list
3147 my ($self, $copy, $user) = @_;
3148 $self->can_do("r_user_mgnt");
3150 my $nb = $self->dbh_do("
3151 INSERT INTO bweb_role_member (roleid, userid)
3152 SELECT roleid, a.userid
3153 FROM bweb_user AS a, bweb_role_member
3154 JOIN bweb_user USING (userid)
3155 WHERE bweb_user.username = $copy
3156 AND a.username = $user");
3160 # username can be a join quoted list of usernames
3163 my ($self, $username) = @_;
3164 $self->can_do("r_user_mgnt");
3167 DELETE FROM bweb_role_member
3171 WHERE username in ($username))");
3173 DELETE FROM bweb_client_group_acl
3177 WHERE username IN ($username))");
3184 $self->can_do("r_user_mgnt");
3186 my $arg = $self->get_form(qw/jusernames/);
3188 unless ($arg->{jusernames}) {
3189 return $self->error("Can't get user");
3192 $self->{dbh}->begin_work();
3194 $self->revoke_all($arg->{jusernames});
3196 DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
3198 $self->{dbh}->commit();
3200 $self->display_users();
3206 $self->can_do("r_user_mgnt");
3208 # we don't quote username directly to check that it is conform
3209 my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate
3210 lang qcopy_username jclient_groups/) ;
3212 if (not $arg->{qcreate}) {
3213 $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
3214 $self->display($arg, "display_user.tpl");
3218 my $u = $self->dbh_quote($arg->{username});
3220 $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
3222 if (!$arg->{qpasswd}) {
3223 $arg->{qpasswd} = "''";
3225 if (!$arg->{qcomment}) {
3226 $arg->{qcomment} = "''";
3229 # will fail if user already exists
3230 # UPDATE with mysql dbi does not return if update is ok
3233 SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment},
3234 use_acl=$arg->{use_acl}, tpl='$arg->{lang}'
3235 WHERE username = $u")
3236 # and (! $self->dbh_is_mysql() )
3239 INSERT INTO bweb_user (username, passwd, use_acl, comment, tpl)
3240 VALUES ($u, $arg->{qpasswd}, $arg->{use_acl},
3241 $arg->{qcomment}, '$arg->{lang}')");
3243 $self->{dbh}->begin_work();
3245 $self->revoke_all($u);
3247 if ($arg->{qcopy_username}) {
3248 $self->grant_like($arg->{qcopy_username}, $u);
3250 $self->grant($arg->{jrolenames}, $u);
3253 if ($arg->{jclient_groups}) {
3255 INSERT INTO bweb_client_group_acl (client_group_id, userid)
3256 SELECT client_group_id, userid
3257 FROM client_group, bweb_user
3258 WHERE client_group_name IN ($arg->{jclient_groups})
3263 $self->{dbh}->commit();
3265 $self->display_users();
3268 # TODO: we miss a matrix with all user/roles
3272 $self->can_do("r_user_mgnt");
3274 my $arg = $self->get_form(qw/db_usernames/) ;
3276 if ($self->{dbh}->errstr) {
3277 return $self->error("Can't use users with bweb, read INSTALL to enable them");
3280 $self->display({ ID => $cur_id++,
3282 "display_users.tpl");
3288 $self->can_do("r_user_mgnt");
3290 my $arg = $self->get_form('username');
3291 my $user = $self->dbh_quote($arg->{username});
3293 my $userp = $self->dbh_selectrow_hashref("
3294 SELECT username, passwd, comment, use_acl, tpl
3296 WHERE username = $user
3299 return $self->error("Can't find $user in catalog");
3301 my $filter = $self->get_client_group_filter($arg->{username});
3302 my $scg = $self->dbh_selectall_hashref("
3303 SELECT client_group_name AS name
3304 FROM client_group $filter
3308 #------------+--------
3313 my $role = $self->dbh_selectall_hashref("
3314 SELECT rolename, temp.userid
3316 LEFT JOIN (SELECT roleid, userid
3317 FROM bweb_user JOIN bweb_role_member USING (userid)
3318 WHERE username = $user) AS temp USING (roleid)
3322 $arg = $self->get_form(qw/db_usernames db_client_groups/);
3325 db_usernames => $arg->{db_usernames},
3326 username => $userp->{username},
3327 comment => $userp->{comment},
3328 passwd => $userp->{passwd},
3329 lang => $userp->{tpl},
3330 use_acl => $userp->{use_acl},
3331 db_client_groups => $arg->{db_client_groups},
3332 client_group => [ values %$scg ],
3333 db_roles => [ values %$role],
3334 }, "display_user.tpl");
3338 ###########################################################
3340 sub get_media_max_size
3342 my ($self, $type) = @_;
3344 "SELECT avg(VolBytes) AS size
3346 WHERE Media.VolStatus = 'Full'
3347 AND Media.MediaType = '$type'
3350 my $res = $self->selectrow_hashref($query);
3353 return $res->{size};
3363 my $media = $self->get_form('qmedia');
3365 unless ($media->{qmedia}) {
3366 return $self->error("Can't get media");
3370 SELECT Media.Slot AS slot,
3371 PoolMedia.Name AS poolname,
3372 Media.VolStatus AS volstatus,
3373 Media.InChanger AS inchanger,
3374 Location.Location AS location,
3375 Media.VolumeName AS volumename,
3376 Media.MaxVolBytes AS maxvolbytes,
3377 Media.MaxVolJobs AS maxvoljobs,
3378 Media.MaxVolFiles AS maxvolfiles,
3379 Media.VolUseDuration AS voluseduration,
3380 Media.VolRetention AS volretention,
3381 Media.Comment AS comment,
3382 PoolRecycle.Name AS poolrecycle,
3383 Media.Enabled AS enabled
3385 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
3386 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
3387 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3389 WHERE Media.VolumeName = $media->{qmedia}
3392 my $row = $self->dbh_selectrow_hashref($query);
3393 $row->{volretention} = human_sec($row->{volretention});
3394 $row->{voluseduration} = human_sec($row->{voluseduration});
3395 $row->{enabled} = human_enabled($row->{enabled});
3397 my $elt = $self->get_form(qw/db_pools db_locations/);
3402 }, "update_media.tpl");
3408 $self->can_do('r_media_mgnt');
3410 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
3412 unless ($arg->{jmedias}) {
3413 return $self->error("Can't get selected media");
3416 unless ($arg->{qnewlocation}) {
3417 return $self->error("Can't get new location");
3422 SET LocationId = (SELECT LocationId
3424 WHERE Location = $arg->{qnewlocation})
3425 WHERE Media.VolumeName IN ($arg->{jmedias})
3428 my $nb = $self->dbh_do($query);
3430 print "$nb media updated, you may have to update your autochanger.";
3432 $self->display_media();
3438 $self->can_do('r_media_mgnt');
3440 my $media = $self->get_selected_media_location();
3442 return $self->error("Can't get media selection");
3444 my $newloc = CGI::param('newlocation');
3446 my $user = CGI::param('user') || 'unknown';
3447 my $comm = CGI::param('comment') || '';
3448 $comm = $self->dbh_quote("$user: $comm");
3450 my $arg = $self->get_form('enabled');
3451 my $en = from_human_enabled($arg->{enabled});
3452 my $b = $self->get_bconsole();
3455 foreach my $vol (keys %$media) {
3457 INSERT INTO LocationLog (Date,Comment,MediaId,LocationId,NewEnabled,NewVolStatus)
3458 SELECT NOW(), $comm, Media.MediaId, Location.LocationId, $en, VolStatus
3459 FROM Media, Location
3460 WHERE Media.VolumeName = '$vol'
3461 AND Location.Location = '$media->{$vol}->{location}'
3463 $self->dbh_do($query);
3464 $self->debug($query);
3465 $b->send_cmd("update volume=\"$vol\" enabled=$en");
3470 $q->param('action', 'update_location');
3471 my $url = $q->url(-full => 1, -query=>1);
3473 $self->display({ email => $self->{info}->{email_media},
3475 newlocation => $newloc,
3476 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
3477 media => [ values %$media ],
3479 "change_location.tpl");
3483 sub display_client_stats
3485 my ($self, %arg) = @_ ;
3486 $self->can_do('r_view_stat');
3488 my $client = $self->dbh_quote($arg{clientname});
3489 # get security filter
3490 my $filter = $self->get_client_filter();
3492 my ($limit, $label) = $self->get_limit(%arg);
3495 count(Job.JobId) AS nb_jobs,
3496 sum(Job.JobBytes) AS nb_bytes,
3497 sum(Job.JobErrors) AS nb_err,
3498 sum(Job.JobFiles) AS nb_files,
3499 Client.Name AS clientname
3500 FROM Job JOIN Client USING (ClientId) $filter
3502 Client.Name = $client
3504 GROUP BY Client.Name
3507 my $row = $self->dbh_selectrow_hashref($query);
3509 $row->{ID} = $cur_id++;
3510 $row->{label} = $label;
3511 $row->{grapharg} = "client";
3513 $self->display($row, "display_client_stats.tpl");
3517 sub display_group_stats
3519 my ($self, %arg) = @_ ;
3521 my $carg = $self->get_form(qw/qclient_group/);
3523 unless ($carg->{qclient_group}) {
3524 return $self->error("Can't get group");
3527 my ($limit, $label) = $self->get_limit(%arg);
3531 count(Job.JobId) AS nb_jobs,
3532 sum(Job.JobBytes) AS nb_bytes,
3533 sum(Job.JobErrors) AS nb_err,
3534 sum(Job.JobFiles) AS nb_files,
3535 client_group.client_group_name AS clientname
3536 FROM Job JOIN Client USING (ClientId)
3537 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
3538 JOIN client_group USING (client_group_id)
3540 client_group.client_group_name = $carg->{qclient_group}
3542 GROUP BY client_group.client_group_name
3545 my $row = $self->dbh_selectrow_hashref($query);
3547 $row->{ID} = $cur_id++;
3548 $row->{label} = $label;
3549 $row->{grapharg} = "client_group";
3551 $self->display($row, "display_client_stats.tpl");
3554 # [ name, num, value, joberrors, nb_job ] =>
3556 # [ { name => 'ALL',
3557 # events => [ { num => 1, label => '2007-01',
3558 # value => 'T', title => 10 },
3559 # { num => 2, label => '2007-02',
3560 # value => 'R', title => 11 },
3563 # { name => 'Other',
3567 sub make_overview_tab
3569 my ($self, $q) = @_;
3570 my $ret = $self->dbh_selectall_arrayref($q);
3574 for my $elt (@$ret) {
3575 if ($cur_name and $cur_name ne $elt->[0]) { # order by name, num
3576 push @items, { name => $cur_name, events => $events};
3579 $cur_name = $elt->[0];
3581 { num => $elt->[1], status => $elt->[2],
3582 joberrors => $elt->[3], title => "$elt->[4] jobs"};
3584 push @items, { name => $cur_name, events => $events};
3588 sub get_time_overview
3590 my ($self, $arg) = @_; # want since et age from get_form();
3591 my $type = $arg->{type} || 'day';
3592 if ($type =~ /^(day|week|hour|month)$/) {
3598 my $jobt = $self->{info}->{stat_job_table} || 'Job';
3599 my $stime1 = $self->{sql}->{"STARTTIME_P" . $type}; # get 1,2,3
3600 $stime1 =~ s/Job.StartTime/date/;
3601 my $stime2 = $self->{sql}->{"STARTTIME_" . $type}; # get 2007-01-03, 2007-01-23
3603 my ($limit, $label) = $self->get_limit('since' => $arg->{since},
3604 'age' => $arg->{age});
3605 return ($stime1, $stime2, $limit, $label, $jobt);
3608 # lu ma me je ve sa di
3609 # groupe1 v v x w v v v overview
3610 # |-- s1 v v v v v v v overview_zoom
3611 # |-- s2 v v x v v v v
3612 # `-- s3 v v v w v v v
3613 sub display_overview_zoom
3616 $self->can_do('r_view_stat');
3618 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3620 if (!$arg->{jclient_groups}) {
3621 return $self->error("Can't get client_group selection");
3623 my ($filter2, undef) = $self->get_param(qw/level jobtype/);
3624 my ($stime1, $stime2, $limit, $label, $jobt) = $self->get_time_overview($arg);
3626 my $filter = $self->get_client_filter();
3628 SELECT name, $stime1 AS num,
3629 JobStatus AS value, joberrors, nb_job
3631 SELECT $stime2 AS date,
3632 Client.Name AS name,
3633 MAX(severity) AS severity,
3635 SUM(JobErrors) AS joberrors
3637 JOIN client_group_member USING (ClientId)
3638 JOIN client_group USING (client_group_id)
3639 JOIN Client USING (ClientId) $filter
3640 JOIN Status USING (JobStatus)
3641 WHERE client_group_name IN ($arg->{jclient_groups})
3644 GROUP BY Client.Name, date
3645 ) AS sub JOIN Status USING (severity)
3648 my $items = $self->make_overview_tab($q);
3649 $self->display({label => $label,
3650 action => "job;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client=",
3651 items => $items}, "overview.tpl");
3654 sub display_overview
3657 $self->can_do('r_view_stat');
3659 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3660 my ($filter2, undef) = $self->get_param(qw/client_groups level jobtype/);
3661 my $filter3 = $self->get_client_group_filter();
3662 my ($stime1, $stime2, $filter1, $label, $jobt) = $self->get_time_overview($arg);
3665 SELECT name, $stime1 AS num,
3666 JobStatus AS value, joberrors, nb_job
3668 SELECT $stime2 AS date,
3669 client_group_name AS name,
3670 MAX(severity) AS severity,
3672 SUM(JobErrors) AS joberrors
3674 JOIN client_group_member USING (ClientId)
3675 JOIN client_group USING (client_group_id) $filter3
3676 JOIN Status USING (JobStatus)
3677 WHERE true $filter1 $filter2
3678 GROUP BY client_group_name, date
3679 ) AS sub JOIN Status USING (severity)
3682 my $items = $self->make_overview_tab($q);
3683 $self->display({label=>$label,
3684 action => "overview_zoom;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client_group=",
3685 items => $items}, "overview.tpl");
3689 # poolname can be undef
3692 my ($self, $poolname) = @_ ;
3693 $self->can_do('r_view_media');
3698 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
3699 if ($arg->{jmediatypes}) {
3700 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
3701 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
3704 # TODO : afficher les tailles et les dates
3707 SELECT subq.volmax AS volmax,
3708 subq.volnum AS volnum,
3709 subq.voltotal AS voltotal,
3711 Pool.Recycle AS recycle,
3712 Pool.VolRetention AS volretention,
3713 Pool.VolUseDuration AS voluseduration,
3714 Pool.MaxVolJobs AS maxvoljobs,
3715 Pool.MaxVolFiles AS maxvolfiles,
3716 Pool.MaxVolBytes AS maxvolbytes,
3717 subq.PoolId AS PoolId,
3718 subq.MediaType AS mediatype,
3719 $self->{sql}->{CAT_POOL_TYPE} AS uniq
3722 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
3723 count(Media.MediaId) AS volnum,
3724 sum(Media.VolBytes) AS voltotal,
3725 Media.PoolId AS PoolId,
3726 Media.MediaType AS MediaType
3728 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
3729 Media.MediaType AS MediaType
3731 WHERE Media.VolStatus = 'Full'
3732 GROUP BY Media.MediaType
3733 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
3734 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
3736 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
3740 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
3743 SELECT Pool.Name AS name,
3744 sum(VolBytes) AS size
3745 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
3746 WHERE Media.VolStatus IN ('Recycled', 'Purged')
3750 my $empty = $self->dbh_selectall_hashref($query, 'name');
3752 foreach my $p (values %$all) {
3753 if ($p->{volmax} > 0) { # mysql returns 0.0000
3754 # we remove Recycled/Purged media from pool usage
3755 if (defined $empty->{$p->{name}}) {
3756 $p->{voltotal} -= $empty->{$p->{name}}->{size};
3758 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
3760 $p->{poolusage} = 0;
3764 SELECT VolStatus AS volstatus, count(MediaId) AS nb
3766 WHERE PoolId=$p->{poolid}
3767 AND Media.MediaType = '$p->{mediatype}'
3771 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
3772 foreach my $t (values %$content) {
3773 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
3778 $self->display({ ID => $cur_id++,
3779 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
3780 Pools => [ values %$all ]},
3781 "display_pool.tpl");
3784 sub display_running_job
3787 return if $self->cant_do('r_view_running_job');
3789 my $arg = $self->get_form('client', 'jobid');
3791 if (!$arg->{client} and $arg->{jobid}) {
3792 # get security filter
3793 my $filter = $self->get_client_filter();
3796 SELECT Client.Name AS name
3797 FROM Job INNER JOIN Client USING (ClientId) $filter
3798 WHERE Job.JobId = $arg->{jobid}
3801 my $row = $self->dbh_selectrow_hashref($query);
3804 $arg->{client} = $row->{name};
3805 CGI::param('client', $arg->{client});
3809 if ($arg->{client}) {
3810 my $cli = new Bweb::Client(name => $arg->{client});
3811 $cli->display_running_job($self->{info}, $arg->{jobid});
3812 if ($arg->{jobid}) {
3813 $self->get_job_log();
3816 $self->error("Can't get client or jobid");
3820 sub display_running_jobs
3822 my ($self, $display_action) = @_;
3823 return if $self->cant_do('r_view_running_job');
3825 # get security filter
3826 my $filter = $self->get_client_filter();
3829 SELECT Job.JobId AS jobid,
3830 Job.Name AS jobname,
3832 Job.StartTime AS starttime,
3833 Job.JobFiles AS jobfiles,
3834 Job.JobBytes AS jobbytes,
3835 Job.JobStatus AS jobstatus,
3836 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
3837 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
3839 Client.Name AS clientname
3840 FROM Job INNER JOIN Client USING (ClientId) $filter
3842 JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
3844 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
3846 $self->display({ ID => $cur_id++,
3847 display_action => $display_action,
3848 Jobs => [ values %$all ]},
3849 "running_job.tpl") ;
3852 # return the autochanger list to update
3856 $self->can_do('r_media_mgnt');
3859 my $arg = $self->get_form('jmedias');
3861 unless ($arg->{jmedias}) {
3862 return $self->error("Can't get media selection");
3866 SELECT Media.VolumeName AS volumename,
3867 Storage.Name AS storage,
3868 Location.Location AS location,
3870 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
3871 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3872 WHERE Media.VolumeName IN ($arg->{jmedias})
3873 AND Media.InChanger = 1
3876 my $all = $self->dbh_selectall_hashref($query, 'volumename');
3878 foreach my $vol (values %$all) {
3879 my $a = $self->ach_get($vol->{location});
3881 $ret{$vol->{location}} = 1;
3883 unless ($a->{have_status}) {
3885 $a->{have_status} = 1;
3888 print "eject $vol->{volumename} from $vol->{storage} : ";
3889 if ($a->send_to_io($vol->{slot})) {
3890 print "<img src='/bweb/T.png' alt='ok'><br/>";
3892 print "<img src='/bweb/E.png' alt='err'><br/>";
3902 my ($to, $subject, $content) = (CGI::param('email'),
3903 CGI::param('subject'),
3904 CGI::param('content'));
3905 $to =~ s/[^\w\d\.\@<>,]//;
3906 $subject =~ s/[^\w\d\.\[\]]/ /;
3908 open(MAIL, "|mail -s '$subject' '$to'") ;
3909 print MAIL $content;
3919 my $arg = $self->get_form('jobid', 'client');
3921 print CGI::header('text/brestore');
3922 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3923 print "client=$arg->{client}\n" if ($arg->{client});
3924 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3928 # TODO : move this to Bweb::Autochanger ?
3929 # TODO : make this internal to not eject tape ?
3935 my ($self, $name) = @_;
3938 return $self->error("Can't get your autochanger name ach");
3941 unless ($self->{info}->{ach_list}) {
3942 return $self->error("Could not find any autochanger");
3945 my $a = $self->{info}->{ach_list}->{$name};
3948 $self->error("Can't get your autochanger $name from your ach_list");
3953 $a->{debug} = $self->{debug};
3960 my ($self, $ach) = @_;
3961 $self->can_do('r_configure');
3963 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3965 $self->{info}->save();
3973 $self->can_do('r_configure');
3975 my $arg = $self->get_form('ach');
3977 or !$self->{info}->{ach_list}
3978 or !$self->{info}->{ach_list}->{$arg->{ach}})
3980 return $self->error("Can't get autochanger name");
3983 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3987 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3989 my $b = $self->get_bconsole();
3991 my @storages = $b->list_storage() ;
3993 $ach->{devices} = [ map { { name => $_ } } @storages ];
3995 $self->display($ach, "ach_add.tpl");
3996 delete $ach->{drives};
3997 delete $ach->{devices};
4004 $self->can_do('r_configure');
4006 my $arg = $self->get_form('ach');
4009 or !$self->{info}->{ach_list}
4010 or !$self->{info}->{ach_list}->{$arg->{ach}})
4012 return $self->error("Can't get autochanger name");
4015 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
4017 $self->{info}->save();
4018 $self->{info}->view();
4024 $self->can_do('r_configure');
4026 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
4028 my $b = $self->get_bconsole();
4029 my @storages = $b->list_storage() ;
4031 unless ($arg->{ach}) {
4032 $arg->{devices} = [ map { { name => $_ } } @storages ];
4033 return $self->display($arg, "ach_add.tpl");
4037 foreach my $drive (CGI::param('drives'))
4039 unless (grep(/^$drive$/,@storages)) {
4040 return $self->error("Can't find $drive in storage list");
4043 my $index = CGI::param("index_$drive");
4044 unless (defined $index and $index =~ /^(\d+)$/) {
4045 return $self->error("Can't get $drive index");
4048 $drives[$index] = $drive;
4052 return $self->error("Can't get drives from Autochanger");
4055 my $a = new Bweb::Autochanger(name => $arg->{ach},
4056 precmd => $arg->{precmd},
4057 drive_name => \@drives,
4058 device => $arg->{device},
4059 mtxcmd => $arg->{mtxcmd});
4061 $self->ach_register($a) ;
4063 $self->{info}->view();
4069 $self->can_do('r_delete_job');
4071 my $arg = $self->get_form('jobid');
4073 if ($arg->{jobid}) {
4074 my $b = $self->get_bconsole();
4075 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
4079 title => "Delete a job ",
4080 name => "delete jobid=$arg->{jobid}",
4088 $self->can_do('r_media_mgnt');
4090 my $arg = $self->get_form(qw/media volstatus inchanger pool
4091 slot volretention voluseduration
4092 maxvoljobs maxvolfiles maxvolbytes
4093 qcomment poolrecycle enabled
4096 unless ($arg->{media}) {
4097 return $self->error("Can't find media selection");
4100 my $update = "update volume=$arg->{media} ";
4102 if ($arg->{volstatus}) {
4103 $update .= " volstatus=$arg->{volstatus} ";
4106 if ($arg->{inchanger}) {
4107 $update .= " inchanger=yes " ;
4109 $update .= " slot=$arg->{slot} ";
4112 $update .= " slot=0 inchanger=no ";
4115 if ($arg->{enabled}) {
4116 $update .= " enabled=$arg->{enabled} ";
4120 $update .= " pool=$arg->{pool} " ;
4123 if (defined $arg->{volretention}) {
4124 $update .= " volretention=\"$arg->{volretention}\" " ;
4127 if (defined $arg->{voluseduration}) {
4128 $update .= " voluse=\"$arg->{voluseduration}\" " ;
4131 if (defined $arg->{maxvoljobs}) {
4132 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
4135 if (defined $arg->{maxvolfiles}) {
4136 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
4139 if (defined $arg->{maxvolbytes}) {
4140 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
4143 if (defined $arg->{poolrecycle}) {
4144 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
4147 my $b = $self->get_bconsole();
4150 content => $b->send_cmd($update),
4151 title => "Update a volume ",
4157 my $media = $self->dbh_quote($arg->{media});
4159 my $loc = CGI::param('location') || '';
4161 $loc = $self->dbh_quote($loc); # is checked by db
4162 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
4164 if (!$arg->{qcomment}) {
4165 $arg->{qcomment} = "''";
4167 push @q, "Comment=$arg->{qcomment}";
4172 SET " . join (',', @q) . "
4173 WHERE Media.VolumeName = $media
4175 $self->dbh_do($query);
4177 $self->update_media();
4183 $self->can_do('r_autochanger_mgnt');
4185 my $ach = CGI::param('ach') ;
4186 $ach = $self->ach_get($ach);
4188 return $self->error("Bad autochanger name");
4192 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
4193 $b->update_slots($ach->{name});
4200 $self->can_do('r_view_log');
4202 my $arg = $self->get_form('jobid', 'limit', 'offset');
4203 unless ($arg->{jobid}) {
4204 return $self->error("Can't get jobid");
4207 if ($arg->{limit} == 100) {
4208 $arg->{limit} = 1000;
4210 # get security filter
4211 my $filter = $self->get_client_filter();
4214 SELECT Job.Name as name, Client.Name as clientname
4215 FROM Job INNER JOIN Client USING (ClientId) $filter
4216 WHERE JobId = $arg->{jobid}
4219 my $row = $self->dbh_selectrow_hashref($query);
4222 return $self->error("Can't find $arg->{jobid} in catalog");
4225 # display only Error and Warning messages
4227 if (CGI::param('error')) {
4228 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning|ERR=' ";
4232 if (CGI::param('time') || $self->{info}->{display_log_time}) {
4233 $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
4235 $logtext = 'LogText';
4239 SELECT count(1) AS nbline, JobId AS jobid,
4240 GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
4242 SELECT JobId, Time, LogText
4244 WHERE ( Log.JobId = $arg->{jobid}
4246 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
4247 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
4251 OFFSET $arg->{offset}
4257 my $log = $self->dbh_selectrow_hashref($query);
4259 return $self->error("Can't get log for jobid $arg->{jobid}");
4262 $self->display({ lines=> $log->{logtxt},
4263 nbline => $log->{nbline},
4264 jobid => $arg->{jobid},
4265 name => $row->{name},
4266 client => $row->{clientname},
4267 offset => $arg->{offset},
4268 limit => $arg->{limit},
4269 }, 'display_log.tpl');
4275 $self->can_do('r_media_mgnt');
4276 my $arg = $self->get_form('storage', 'pool', 'nb', 'media', 'offset');
4277 my $b = $self->get_bconsole();
4279 if (!$arg->{storage} || !$arg->{pool} || not defined $arg->{nb} || !$arg->{media} || !$arg->{offset}) {
4280 CGI::param(offset => 0);
4281 $arg = $self->get_form('db_pools');
4282 $arg->{storage} = [ map { { name => $_ } }$b->list_storage()];
4283 $self->display($arg, 'add_media.tpl');
4288 if ($arg->{nb} > 0) {
4289 $arg->{offset} = $arg->{offset}?$arg->{offset}:1;
4290 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n$arg->{nb}\n$arg->{media}\n$arg->{offset}\n";
4292 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n0\n$arg->{media}\n";
4298 CGI::param('media', '');
4299 CGI::param('re_media', $arg->{media});
4300 $self->display_media();
4306 $self->can_do('r_autochanger_mgnt');
4308 my $arg = $self->get_form('ach', 'slots', 'drive');
4310 unless ($arg->{ach}) {
4311 return $self->error("Can't find autochanger name");
4314 my $a = $self->ach_get($arg->{ach});
4316 return $self->error("Can't find autochanger name in configuration");
4319 my $storage = $a->get_drive_name($arg->{drive});
4321 return $self->error("Can't get your drive name");
4327 if ($arg->{slots}) {
4328 $slots = join(",", @{ $arg->{slots} });
4329 $slots_sql = " AND Slot IN ($slots) ";
4330 $t += 60*scalar( @{ $arg->{slots} }) ;
4333 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
4334 print "<h1>This command can take long time, be patient...</h1>";
4336 $b->label_barcodes(storage => $storage,
4337 drive => $arg->{drive},
4345 SET LocationId = (SELECT LocationId
4347 WHERE Location = '$arg->{ach}')
4349 WHERE (LocationId = 0 OR LocationId IS NULL)
4358 $self->can_do('r_purge');
4360 my @volume = CGI::param('media');
4363 return $self->error("Can't get media selection");
4366 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4368 foreach my $v (@volume) {
4370 content => $b->purge_volume($v),
4371 title => "Purge media",
4372 name => "purge volume=$v",
4381 $self->can_do('r_prune');
4383 my @volume = CGI::param('media');
4385 return $self->error("Can't get media selection");
4388 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4390 foreach my $v (@volume) {
4392 content => $b->prune_volume($v),
4393 title => "Prune volume",
4394 name => "prune volume=$v",
4403 $self->can_do('r_cancel_job');
4405 my $arg = $self->get_form('jobid');
4406 unless ($arg->{jobid}) {
4407 return $self->error("Can't get jobid");
4410 my $b = $self->get_bconsole();
4412 content => $b->cancel($arg->{jobid}),
4413 title => "Cancel job",
4414 name => "cancel jobid=$arg->{jobid}",
4420 # Warning, we display current fileset
4423 my $arg = $self->get_form('fileset');
4425 if ($arg->{fileset}) {
4426 my $b = $self->get_bconsole();
4427 my $ret = $b->get_fileset($arg->{fileset});
4428 $self->display({ fileset => $arg->{fileset},
4430 }, "fileset_view.tpl");
4432 $self->error("Can't get fileset name");
4436 sub director_show_sched
4439 $self->can_do('r_view_job');
4440 my $arg = $self->get_form('days');
4442 my $b = $self->get_bconsole();
4443 my $ret = $b->director_get_sched( $arg->{days} );
4448 }, "scheduled_job.tpl");
4451 sub enable_disable_job
4453 my ($self, $what) = @_ ;
4454 $self->can_do('r_run_job');
4456 my $name = CGI::param('job') || '';
4457 unless ($name =~ /^[\w\d\.\-\s]+$/) {
4458 return $self->error("Can't find job name");
4461 my $b = $self->get_bconsole();
4471 content => $b->send_cmd("$cmd job=\"$name\""),
4472 title => "$cmd $name",
4473 name => "$cmd job=\"$name\"",
4480 return new Bconsole(pref => $self->{info});
4486 $self->can_do('r_storage_mgnt');
4487 my $arg = $self->get_form(qw/storage storage_cmd drive/);
4488 my $b = $self->get_bconsole();
4490 if ($arg->{storage} and $arg->{storage_cmd}) {
4491 my $cmd = "$arg->{storage_cmd} storage=\"$arg->{storage}\" drive=$arg->{drive}";
4492 my $ret = $b->send_cmd($cmd);
4496 title => "Storage ",
4500 my $storages= [ map { { name => $_ } } $b->list_storage()];
4501 $self->display({ storage => $storages}, "cmd_storage.tpl");
4508 $self->can_do('r_run_job');
4510 my $b = $self->get_bconsole();
4512 my $joblist = [ map { { name => $_ } } $b->list_job() ];
4514 $self->display({ Jobs => $joblist }, "run_job.tpl");
4519 my ($self, $ouput) = @_;
4522 foreach my $l (split(/\r\n/, $ouput)) {
4523 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
4529 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
4535 foreach my $k (keys %arg) {
4536 $lowcase{lc($k)} = $arg{$k} ;
4545 $self->can_do('r_run_job');
4547 my $b = $self->get_bconsole();
4549 my $job = CGI::param('job') || '';
4551 # we take informations from director, and we overwrite with user wish
4552 my $info = $b->send_cmd("show job=\"$job\"");
4553 my $attr = $self->run_parse_job($info);
4555 my $arg = $self->get_form(qw/pool level client fileset storage media/);
4557 if (!$arg->{pool} and $arg->{media}) {
4558 my $r = $self->dbh_selectrow_hashref("
4559 SELECT Pool.Name AS name
4560 FROM Media JOIN Pool USING (PoolId)
4561 WHERE Media.VolumeName = '$arg->{media}'
4562 AND Pool.Name != 'Scratch'
4565 $arg->{pool} = $r->{name};
4569 my %job_opt = (%$attr, %$arg);
4571 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4573 my $pools = [ map { { name => $_ } } $b->list_pool() ];
4574 my $clients = [ map { { name => $_ } }$b->list_client()];
4575 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
4576 my $storages= [ map { { name => $_ } }$b->list_storage()];
4581 clients => $clients,
4582 filesets => $filesets,
4583 storages => $storages,
4585 }, "run_job_mod.tpl");
4591 $self->can_do('r_run_job');
4593 my $b = $self->get_bconsole();
4595 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4605 $self->can_do('r_run_job');
4607 my $b = $self->get_bconsole();
4609 # TODO: check input (don't use pool, level)
4611 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
4612 my $job = CGI::param('job') || '';
4613 my $storage = CGI::param('storage') || '';
4615 my $jobid = $b->run(job => $job,
4616 client => $arg->{client},
4617 priority => $arg->{priority},
4618 level => $arg->{level},
4619 storage => $storage,
4620 pool => $arg->{pool},
4621 fileset => $arg->{fileset},
4622 when => $arg->{when},
4627 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>";
4630 sub display_next_job
4634 my $arg = $self->get_form(qw/job begin end/);
4636 return $self->error("Can't get job name");
4639 my $b = $self->get_bconsole();
4641 my $job = $b->send_cmd("show job=\"$arg->{job}\"");
4642 my $attr = $self->run_parse_job($job);
4644 if (!$attr->{schedule}) {
4645 return $self->error("Can't get $arg->{job} schedule");
4647 my $jpool=$attr->{pool} || '';
4649 my $sched = new Bweb::Sched(bconsole => $b, name => $attr->{schedule},
4650 begin => $arg->{begin}, end => $arg->{end});
4652 my $ss = $sched->get_scheds($attr->{schedule});
4655 foreach my $s (@$ss) {
4656 my $level = $sched->get_level($s);
4657 my $pool = $sched->get_pool($s) || $jpool;
4658 my $evt = $sched->get_event($s);
4659 push @ret, map { "$_ : $pool ($level)\n" } @$evt;
4662 print "<b>$arg->{job}:</b><pre>", sort @ret, "</pre><br>";
4665 # check jobs against their schedule
4668 my ($self, $sched, $schedname, $job, $job_pool, $client) = @_;
4669 return undef if (!$self->can_view_client($client));
4671 my $sch = $sched->get_scheds($schedname);
4672 return undef if (!$sch);
4674 my $end = $sched->{end}; # this backup must have start before the next one
4676 foreach my $s (@$sch) {
4677 my $pool = $sched->get_pool($s) || $job_pool;
4678 my $level = $sched->get_level($s);
4679 my ($l) = ($level =~ m/^(.)/); # we keep the first letter
4680 my $evts = $sched->get_event($s);
4682 foreach my $evt (reverse @$evts) {
4683 my $all = $self->dbh_selectrow_hashref("
4685 FROM Job JOIN Pool USING (PoolId) JOIN Client USING (ClientId)
4686 WHERE Job.StartTime >= '$evt'
4687 AND Job.StartTime < '$end'
4689 AND Job.Name = '$job'
4690 AND Job.JobStatus = 'T'
4691 AND Job.Level = '$l'
4692 " . ($pool?" AND Pool.Name = '$pool' ":'') . "
4693 AND Client.Name = '$client'
4699 push @{$self->{tmp}}, {date => $evt, level => $level,
4700 type => 'Backup', name => $job,
4701 pool => $pool, volume => $pool};
4708 sub display_missing_job
4711 my $arg = $self->get_form(qw/begin end/);
4713 if (!$arg->{begin}) { # TODO: change this
4714 $arg->{begin} = strftime('%F %T', localtime(time - 24*60*60 ));
4717 $arg->{end} = strftime('%F %T', localtime(time));
4719 $self->{tmp} = []; # check_job use this for result
4721 my $bconsole = $self->get_bconsole();
4723 my $sched = new Bweb::Sched(bconsole => $bconsole,
4724 begin => $arg->{begin},
4725 end => $arg->{end});
4727 my $job = $bconsole->send_cmd("show job");
4728 my ($jname, $jsched, $jclient, $jpool);
4729 foreach my $j (split(/\r?\n/, $job)) {
4730 if ($j =~ /Job: name=([\w\d\-]+?) JobType=/i) {
4731 if ($jname and $jsched) {
4732 $self->check_job($sched, $jsched, $jname, $jpool, $jclient);
4735 $jclient = $jpool = $jsched = undef;
4736 } elsif ($j =~ /Client: name=(.+?) address=/i) {
4738 } elsif ($j =~ /Pool: name=([\w\d\-]+) PoolType=/i) {
4740 } elsif ($j =~ /Schedule: name=([\w\d\-]+)/i) {
4746 title => "Missing Job (since $arg->{begin} to $arg->{end})",
4747 list => $self->{tmp},
4748 }, "scheduled_job.tpl");
4750 delete $self->{tmp};