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 my ($self, $what) = @_;
100 my $old = $self->{debug};
103 $self->{debug} = $old;
108 error - display an error to the user
112 this function set $self->{error} with arg, display a message with
113 error.tpl and return 0
118 return $self->error("Can't use this file");
125 my ($self, $what) = @_;
126 $self->{error} = $what;
127 $self->display($self, 'error.tpl');
133 display - display an html page with HTML::Template
137 this function is use to render all html codes. it takes an
138 ref hash as arg in which all param are usable in template.
140 it will use user template_dir then global template_dir
141 to search the template file.
143 hash keys are not sensitive. See HTML::Template for more
144 explanations about the hash ref. (it's can be quiet hard to understand)
148 $ref = { name => 'me', age => 26 };
149 $self->display($ref, "people.tpl");
155 my ($self, $hash, $tpl) = @_ ;
156 my $dir = $self->{template_dir} || $template_dir;
157 my $lang = $self->{lang} || 'en';
158 my $template = HTML::Template->new(filename => $tpl,
159 path =>["$dir/$lang",
161 die_on_bad_params => 0,
162 case_sensitive => 0);
164 foreach my $var (qw/limit offset/) {
166 unless ($hash->{$var}) {
167 my $value = CGI::param($var) || '';
169 if ($value =~ /^(\d+)$/) {
170 $template->param($var, $1) ;
175 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
176 $template->param('loginname', CGI::remote_user());
178 $template->param($hash);
179 print $template->output();
183 ################################################################
185 package Bweb::Config;
187 use base q/Bweb::Gui/;
191 Bweb::Config - read, write, display, modify configuration
195 this package is used for manage configuration
199 $conf = new Bweb::Config(config_file => '/path/to/conf');
210 =head1 PACKAGE VARIABLE
212 %k_re - hash of all acceptable option.
216 this variable permit to check all option with a regexp.
220 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
221 user => qr/^([\w\d\.-]+)$/i,
222 password => qr/^(.*)$/,
223 fv_write_path => qr!^([/\w\d\.-]*)$!,
224 template_dir => qr!^([/\w\d\.-]+)$!,
225 debug => qr/^(on)?$/,
226 lang => qr/^(\w\w)?$/,
227 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
228 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
229 bconsole => qr!^(.+)?$!,
230 syslog_file => qr!^(.+)?$!,
231 log_dir => qr!^(.+)?$!,
232 wiki_url => qr!(.*)$!,
233 stat_job_table => qr!^(\w*)$!,
234 display_log_time => qr!^(on)?$!,
235 enable_security => qr/^(on)?$/,
236 enable_security_acl => qr/^(on)?$/,
241 load - load config_file
245 this function load the specified config_file.
253 unless (open(FP, $self->{config_file}))
255 return $self->error("can't load config_file $self->{config_file} : $!");
257 my $f=''; my $tmpbuffer;
258 while(read FP,$tmpbuffer,4096)
266 no strict; # I have no idea of the contents of the file
273 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...") ;
276 foreach my $k (keys %$VAR1) {
277 $self->{$k} = $VAR1->{$k};
285 load_old - load old configuration format
293 unless (open(FP, $self->{config_file}))
295 return $self->error("$self->{config_file} : $!");
298 while (my $line = <FP>)
301 my ($k, $v) = split(/\s*=\s*/, $line, 2);
313 save - save the current configuration to config_file
321 if ($self->{ach_list}) {
322 # shortcut for display_begin
323 $self->{achs} = [ map {{ name => $_ }}
324 keys %{$self->{ach_list}}
328 unless (open(FP, ">$self->{config_file}"))
330 return $self->error("$self->{config_file} : $!\n" .
331 "You must add this to your config file\n"
332 . Data::Dumper::Dumper($self));
335 print FP Data::Dumper::Dumper($self);
343 edit, view, modify - html form ouput
351 $self->display($self, "config_edit.tpl");
357 $self->display($self, "config_view.tpl");
365 # we need to reset checkbox first
367 $self->{display_log_time} = 0;
368 $self->{enable_security} = 0;
369 $self->{enable_security_acl} = 0;
371 foreach my $k (CGI::param())
373 next unless (exists $k_re{$k}) ;
374 my $val = CGI::param($k);
375 if ($val =~ $k_re{$k}) {
378 $self->{error} .= "bad parameter : $k = [$val]";
384 if ($self->{error}) { # an error as occured
385 $self->display($self, 'error.tpl');
393 ################################################################
395 package Bweb::Client;
397 use base q/Bweb::Gui/;
401 Bweb::Client - Bacula FD
405 this package is use to do all Client operations like, parse status etc...
409 $client = new Bweb::Client(name => 'zog-fd');
410 $client->status(); # do a 'status client=zog-fd'
416 display_running_job - Html display of a running job
420 this function is used to display information about a current job
424 sub display_running_job
426 my ($self, $conf, $jobid, $last_jobfiles, $last_jobbytes) = @_ ;
427 my $status = $self->status($conf);
430 if ($status->{$jobid}) {
431 $status = $status->{$jobid};
432 $status->{last_jobbytes} = $last_jobbytes;
433 $status->{last_jobfiles} = $last_jobfiles;
434 $status->{jobbytes}=$status->{Bytes};
435 $status->{jobbytes} =~ s![, B/s]!!g;
436 $status->{jobfiles}=$status->{Files};
437 $status->{jobfiles} =~ s/,//g;
438 $self->display($status, "client_job_status.tpl");
441 for my $id (keys %$status) {
442 $self->display($status->{$id}, "client_job_status.tpl");
449 $client = new Bweb::Client(name => 'plume-fd');
451 $client->status($bweb);
455 dirty hack to parse "status client=xxx-fd"
459 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
460 Backup Job started: 06-jun-06 17:22
461 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
462 Files Examined=10,697
463 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
469 JobName => Full_plume.2006-06-06_17.22.23,
472 Bytes => 194,484,132,
482 my ($self, $conf) = @_ ;
484 if (defined $self->{cur_jobs}) {
485 return $self->{cur_jobs} ;
489 my $b = new Bconsole(pref => $conf);
490 my $ret = $b->send_cmd("st client=$self->{name}");
494 for my $r (split(/\n/, $ret)) {
496 $r =~ s/(^\s+|\s+$)//g;
497 if ($r =~ /JobId (\d+) Job (\S+)/) {
499 $arg->{$jobid} = { @param, JobId => $jobid } ;
503 @param = ( JobName => $2 );
505 } elsif ($r =~ /=.+=/) {
506 push @param, split(/\s+|\s*=\s*/, $r) ;
508 } elsif ($r =~ /=/) { # one per line
509 push @param, split(/\s*=\s*/, $r) ;
511 } elsif ($r =~ /:/) { # one per line
512 push @param, split(/\s*:\s*/, $r, 2) ;
516 if ($jobid and @param) {
517 $arg->{$jobid} = { @param,
519 Client => $self->{name},
523 $self->{cur_jobs} = $arg ;
529 ################################################################
531 package Bweb::Autochanger;
533 use base q/Bweb::Gui/;
537 Bweb::Autochanger - Object to manage Autochanger
541 this package will parse the mtx output and manage drives.
545 $auto = new Bweb::Autochanger(precmd => 'sudo');
547 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
551 $auto->slot_is_full(10);
552 $auto->transfer(10, 11);
558 my ($class, %arg) = @_;
561 name => '', # autochanger name
562 label => {}, # where are volume { label1 => 40, label2 => drive0 }
563 drive => [], # drive use [ 'media1', 'empty', ..]
564 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
565 io => [], # io slot number list [ 41, 42, 43...]
566 info => {slot => 0, # informations (slot, drive, io)
570 mtxcmd => '/usr/sbin/mtx',
572 device => '/dev/changer',
573 precmd => '', # ssh command
574 bweb => undef, # link to bacula web object (use for display)
577 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
584 status - parse the output of mtx status
588 this function will launch mtx status and parse the output. it will
589 give a perlish view of the autochanger content.
591 it uses ssh if the autochanger is on a other host.
598 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
600 # TODO : reset all infos
601 $self->{info}->{drive} = 0;
602 $self->{info}->{slot} = 0;
603 $self->{info}->{io} = 0;
605 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
608 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
609 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
610 #Data Transfer Element 1:Empty
611 # Storage Element 1:Empty
612 # Storage Element 2:Full :VolumeTag=000002
613 # Storage Element 3:Empty
614 # Storage Element 4:Full :VolumeTag=000004
615 # Storage Element 5:Full :VolumeTag=000001
616 # Storage Element 6:Full :VolumeTag=000003
617 # Storage Element 7:Empty
618 # Storage Element 41 IMPORT/EXPORT:Empty
619 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
624 # Storage Element 7:Empty
625 # Storage Element 2:Full :VolumeTag=000002
626 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
629 $self->set_empty_slot($1);
631 $self->set_slot($1, $4);
634 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
637 $self->set_empty_drive($1);
639 $self->set_drive($1, $4, $6);
642 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
645 $self->set_empty_io($1);
647 $self->set_io($1, $4);
650 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
652 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
653 $self->{info}->{drive} = $1;
654 $self->{info}->{slot} = $2;
655 if ($l =~ /(\d+)\s+Import/) {
656 $self->{info}->{io} = $1 ;
658 $self->{info}->{io} = 0;
663 $self->debug($self) ;
668 my ($self, $slot) = @_;
671 if ($self->{slot}->[$slot] eq 'loaded') {
675 my $label = $self->{slot}->[$slot] ;
677 return $self->is_media_loaded($label);
682 my ($self, $drive, $slot) = @_;
684 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
685 return 0 if ($self->slot_is_full($slot)) ;
687 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
690 my $content = $self->get_slot($slot);
691 print "content = $content<br/> $drive => $slot<br/>";
692 $self->set_empty_drive($drive);
693 $self->set_slot($slot, $content);
696 $self->{error} = $out;
701 # TODO: load/unload have to use mtx script from bacula
704 my ($self, $drive, $slot) = @_;
706 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
707 return 0 unless ($self->slot_is_full($slot)) ;
709 print "Loading drive $drive with slot $slot<br/>\n";
710 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
713 my $content = $self->get_slot($slot);
714 print "content = $content<br/> $slot => $drive<br/>";
715 $self->set_drive($drive, $slot, $content);
718 $self->{error} = $out;
726 my ($self, $media) = @_;
728 unless ($self->{label}->{$media}) {
732 if ($self->{label}->{$media} =~ /drive\d+/) {
742 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
747 my ($self, $slot, $tag) = @_;
748 $self->{slot}->[$slot] = $tag || 'full';
749 push @{ $self->{io} }, $slot;
752 $self->{label}->{$tag} = $slot;
758 my ($self, $slot) = @_;
760 push @{ $self->{io} }, $slot;
762 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
763 $self->{slot}->[$slot] = 'empty';
769 my ($self, $slot) = @_;
770 return $self->{slot}->[$slot];
775 my ($self, $slot, $tag) = @_;
776 $self->{slot}->[$slot] = $tag || 'full';
779 $self->{label}->{$tag} = $slot;
785 my ($self, $slot) = @_;
787 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
788 $self->{slot}->[$slot] = 'empty';
794 my ($self, $drive) = @_;
795 $self->{drive}->[$drive] = 'empty';
800 my ($self, $drive, $slot, $tag) = @_;
801 $self->{drive}->[$drive] = $tag || $slot;
803 $self->{slot}->[$slot] = $tag || 'loaded';
806 $self->{label}->{$tag} = "drive$drive";
812 my ($self, $slot) = @_;
814 # slot don't exists => full
815 if (not defined $self->{slot}->[$slot]) {
819 if ($self->{slot}->[$slot] eq 'empty') {
822 return 1; # vol, full, loaded
825 sub slot_get_first_free
828 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
829 return $slot unless ($self->slot_is_full($slot));
833 sub io_get_first_free
837 foreach my $slot (@{ $self->{io} }) {
838 return $slot unless ($self->slot_is_full($slot));
845 my ($self, $media) = @_;
847 return $self->{label}->{$media} ;
852 my ($self, $media) = @_;
854 return defined $self->{label}->{$media} ;
859 my ($self, $slot) = @_;
861 unless ($self->slot_is_full($slot)) {
862 print "Autochanger $self->{name} slot $slot is empty\n";
867 if ($self->is_slot_loaded($slot)) {
870 print "Autochanger $self->{name} $slot is currently in use\n";
874 # autochanger must have I/O
875 unless ($self->have_io()) {
876 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
880 my $dst = $self->io_get_first_free();
883 print "Autochanger $self->{name} you must empty I/O first\n";
886 $self->transfer($slot, $dst);
891 my ($self, $src, $dst) = @_ ;
892 if ($self->{debug}) {
893 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
895 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
898 my $content = $self->get_slot($src);
899 $self->{slot}->[$src] = 'empty';
900 $self->set_slot($dst, $content);
903 $self->{error} = $out;
910 my ($self, $index) = @_;
911 return $self->{drive_name}->[$index];
914 # TODO : do a tapeinfo request to get informations
924 for my $slot (@{$self->{io}})
926 if ($self->is_slot_loaded($slot)) {
927 print "$slot is currently loaded\n";
931 if ($self->slot_is_full($slot))
933 my $free = $self->slot_get_first_free() ;
934 print "move $slot to $free :\n";
937 if ($self->transfer($slot, $free)) {
938 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
940 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
944 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
950 # TODO : this is with mtx status output,
951 # we can do an other function from bacula view (with StorageId)
955 my $bweb = $self->{bweb};
957 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
958 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
961 SELECT Media.VolumeName AS volumename,
962 Media.VolStatus AS volstatus,
963 Media.LastWritten AS lastwritten,
964 Media.VolBytes AS volbytes,
965 Media.MediaType AS mediatype,
967 Media.InChanger AS inchanger,
969 $bweb->{sql}->{FROM_UNIXTIME}(
970 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
971 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
974 INNER JOIN Pool USING (PoolId)
976 WHERE Media.VolumeName IN ($media_list)
979 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
981 # TODO : verify slot and bacula slot
985 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
987 if ($self->slot_is_full($slot)) {
989 my $vol = $self->{slot}->[$slot];
990 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
992 my $bslot = $all->{$vol}->{slot} ;
993 my $inchanger = $all->{$vol}->{inchanger};
995 # if bacula slot or inchanger flag is bad, we display a message
996 if ($bslot != $slot or !$inchanger) {
997 push @to_update, $slot;
1000 $all->{$vol}->{realslot} = $slot;
1002 push @{ $param }, $all->{$vol};
1004 } else { # empty or no label
1005 push @{ $param }, {realslot => $slot,
1006 volstatus => 'Unknown',
1007 volumename => $self->{slot}->[$slot]} ;
1010 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
1014 my $i=0; my $drives = [] ;
1015 foreach my $d (@{ $self->{drive} }) {
1016 $drives->[$i] = { index => $i,
1017 load => $self->{drive}->[$i],
1018 name => $self->{drive_name}->[$i],
1023 $bweb->display({ Name => $self->{name},
1024 nb_drive => $self->{info}->{drive},
1025 nb_io => $self->{info}->{io},
1028 Update => scalar(@to_update) },
1035 ################################################################
1037 package Bweb::Sched;
1038 use base q/Bweb::Gui/;
1042 Bweb::Sched() - Bweb package that parse show schedule ouput
1044 new Bweb::Sched(format => '%Y-%m-%d', year => 2008);
1048 my $b = $bweb->get_bconsole();
1049 my $s = $b->send_cmd("show schedule");
1050 my $sched = new Bweb::Sched(begin => '2007-01-01', end => '2007-01-02 12:00');
1051 $sched->parse_scheds(split(/\r?\n/, $s));
1062 'level' => 'Differential',
1069 my ($class, @arg) = @_;
1070 my $self = $class->SUPER::new(@arg);
1072 # we compare the current schedule date with begin and end
1073 # in a float form ex: 20071212.1243 > 20070101
1074 if ($self->{begin} and $self->{end}) {
1075 ($self->{fbegin}, $self->{fend}) = ($self->{begin}, $self->{end});
1076 $self->{fbegin} =~ s/(-|:)//g; $self->{fbegin} =~ s/ /./;
1077 $self->{fend} =~ s/(-|:)//g; $self->{fend} =~ s/ /./;
1080 bless($self,$class);
1082 if ($self->{bconsole}) {
1083 my $sel = $self->{name}?"=\"$self->{name}\"":'';
1084 my $b = $self->{bconsole};
1085 my $out = $b->send_cmd("show schedule$sel");
1086 $self->parse_scheds(split(/\r?\n/, $out));
1087 undef $self->{bconsole}; # useless now
1093 # cleanup and add a schedule
1096 my ($self, $name, $info) = @_;
1097 # bacula uses dates that start from 0, we start from 1
1098 foreach (@{$info->{mday}},@{$info->{month}}) { $_++ };
1101 $info->{event} = [ $self->get_events($info, $self->{format}) ];
1103 foreach my $i (qw/hour mday month wday wom woy mins/) {
1107 push @{$self->{schedules}->{$name}}, $info;
1110 # return the name of all schedules
1113 my ($self, $name) = @_;
1115 return keys %{ $self->{schedules} };
1118 # return an array of all schedule
1121 my ($self, $sched) = @_;
1122 return $self->{schedules}->{$sched};
1125 # return an ref array of all events
1126 # [ '2007-12-19 04:05', '2007-12-19 04:05' ]
1129 my ($self, $sched) = @_;
1130 return $sched->{event};
1133 # return the pool name
1136 my ($self, $sched) = @_;
1137 return $sched->{pool} || '';
1140 # return the level name (Incremental, Differential, Full)
1143 my ($self, $sched) = @_;
1144 return $sched->{level};
1147 # parse bacula sched bitmap
1150 my ($self, @output) = @_;
1157 foreach my $ligne (@output) {
1158 if ($ligne =~ /Schedule: name=(.+)/) {
1159 if ($name and $elt) {
1160 $elt->{level} = $run;
1161 $self->add_sched($name, $elt);
1166 elsif ($ligne =~ /Run Level=(.+)/) {
1167 if ($name and $elt) {
1168 $elt->{level} = $run;
1169 $self->add_sched($name, $elt);
1174 elsif ($ligne =~ /(hour|mday|month|mins)=(.+)/) {
1175 # All theses lines have the same format
1177 my ($k,$v) = ($1,$2);
1178 # we get all values (0 1 4 9)
1179 $elt->{$k}=[split (/\s/,$v)];
1181 # we make a bitmap for this kind of data (0 0 1 0 0 0 1) for a quick access
1182 elsif ($ligne =~ /(wday|wom|woy)=(.+)/) {
1183 my ($k,$v) = ($1,$2);
1184 foreach my $e (split (/\s/,$v)) {
1188 elsif ($ligne =~ /Pool: name=(.+?) PoolType/) {
1193 if ($name and $elt) {
1194 $elt->{level} = $run;
1195 $self->add_sched($name, $elt);
1199 use Date::Calc qw(:all);
1201 # read bacula schedule bitmap and get $format date string
1205 my ($self, $s,$format) = @_;
1206 my $year = $self->{year} || ((localtime)[5] + 1900);
1207 $format = $format || '%u-%02u-%02u %02u:%02u';
1209 foreach my $m (@{$s->{month}}) # mois de l'annee
1211 foreach my $md (@{$s->{mday}}) # jour du mois
1213 # print " m=$m md=$md\n";
1214 # we check if this day exists (31 fev)
1215 next if (!check_date($year,$m,$md));
1216 # print " check_date ok\n";
1218 my $w = ($md-1)/7; # we use the same thing than bacula
1219 next if (! $s->{wom}->[$w]);
1220 # print " wom ok\n";
1222 # on recupere le jour de la semaine
1223 my $wd = Day_of_Week($year,$m,$md);
1225 my ($w1,undef) = Week_of_Year($year,$m,$wd);
1226 next if (! $s->{woy}->[$w1-1]); # bacula 0-51
1227 # print " woy ok\n";
1229 $wd = 0 if ($wd == 7) ; # sunday is 0 on bacula
1230 next if (! $s->{wday}->[$wd]);
1231 # print " wday ok\n";
1233 foreach my $h (@{$s->{hour}}) # hour of the day
1235 foreach my $min (@{$s->{mins}}) # minute
1237 if ($self->{fbegin}) {
1239 my $d = sprintf('%d%0.2d%0.2d.%0.2d%0.2d',
1240 $year,$m,$md,$h,$min);
1241 next if ($d < $self->{fbegin} or $d > $self->{fend});
1243 push @ret, sprintf($format, $year,$m,$md,$h,$min);
1252 ################################################################
1256 use base q/Bweb::Gui/;
1260 Bweb - main Bweb package
1264 this package is use to compute and display informations
1269 use POSIX qw/strftime/;
1271 our $config_file='/etc/bacula/bweb.conf';
1277 %sql_func - hash to make query mysql/postgresql compliant
1283 UNIX_TIMESTAMP => '',
1284 FROM_UNIXTIME => '',
1285 TO_SEC => " interval '1 second' * ",
1286 SEC_TO_INT => "SEC_TO_INT",
1289 STARTTIME_SEC => " date_trunc('sec', Job.StartTime) ",
1290 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1291 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1292 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1293 STARTTIME_WEEK => " date_trunc('week', Job.StartTime) ",
1294 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1295 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1296 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1297 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1298 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1299 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1303 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1304 FROM_UNIXTIME => 'FROM_UNIXTIME',
1307 SEC_TO_TIME => 'SEC_TO_TIME',
1308 MATCH => " REGEXP ",
1309 STARTTIME_SEC => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %T') ",
1310 STARTTIME_DAY => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d') ",
1311 STARTTIME_HOUR => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %H') ",
1312 STARTTIME_MONTH => " DATE_FORMAT(Job.StartTime, '%Y-%m') ",
1313 STARTTIME_WEEK => " DATE_FORMAT(Job.StartTime, '%Y-%v') ",
1314 STARTTIME_PHOUR=> " DATE_FORMAT(Job.StartTime, '%H') ",
1315 STARTTIME_PDAY => " DATE_FORMAT(Job.StartTime, '%d') ",
1316 STARTTIME_PMONTH => " DATE_FORMAT(Job.StartTime, '%m') ",
1317 STARTTIME_PWEEK => " DATE_FORMAT(Job.StartTime, '%v') ",
1318 # with mysql < 5, you have to play with the ugly SHOW command
1319 DB_SIZE => " SELECT 0 ",
1320 # works only with mysql 5
1321 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1322 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1323 CONCAT_SEP => " SEPARATOR '' ",
1330 return $self->{info}->{dbi} =~ /dbi:mysql/i;
1337 $self->{dbh}->disconnect();
1342 sub dbh_selectall_arrayref
1344 my ($self, $query) = @_;
1345 $self->connect_db();
1346 $self->debug($query);
1347 return $self->{dbh}->selectall_arrayref($query);
1352 my ($self, @what) = @_;
1353 return join(',', $self->dbh_quote(@what)) ;
1358 my ($self, @what) = @_;
1360 $self->connect_db();
1362 return map { $self->{dbh}->quote($_) } @what;
1364 return $self->{dbh}->quote($what[0]) ;
1370 my ($self, $query) = @_ ;
1371 $self->connect_db();
1372 $self->debug($query);
1373 return $self->{dbh}->do($query);
1376 sub dbh_selectall_hashref
1378 my ($self, $query, $join) = @_;
1380 $self->connect_db();
1381 $self->debug($query);
1382 return $self->{dbh}->selectall_hashref($query, $join) ;
1385 sub dbh_selectrow_hashref
1387 my ($self, $query) = @_;
1389 $self->connect_db();
1390 $self->debug($query);
1391 return $self->{dbh}->selectrow_hashref($query) ;
1396 my ($self, @what) = @_;
1397 if ($self->dbh_is_mysql()) {
1398 return 'CONCAT(' . join(',', @what) . ')' ;
1400 return join(' || ', @what);
1406 my ($self, $query) = @_;
1407 $self->debug($query, up => 1);
1408 return $self->{dbh}->prepare($query);
1414 my @unit = qw(B KB MB GB TB);
1415 my $val = shift || 0;
1417 my $format = '%i %s';
1418 while ($val / 1024 > 1) {
1422 $format = ($i>0)?'%0.1f %s':'%i %s';
1423 return sprintf($format, $val, $unit[$i]);
1426 # display Day, Hour, Year
1432 $val /= 60; # sec -> min
1434 if ($val / 60 <= 1) {
1438 $val /= 60; # min -> hour
1439 if ($val / 24 <= 1) {
1440 return "$val hours";
1443 $val /= 24; # hour -> day
1444 if ($val / 365 < 2) {
1448 $val /= 365 ; # day -> year
1450 return "$val years";
1456 my $val = shift || 0;
1458 if ($val eq '1' or $val eq "yes") {
1460 } elsif ($val eq '2' or $val eq "archived") {
1468 sub from_human_enabled
1470 my $val = shift || 0;
1472 if ($val eq '1' or $val eq "yes") {
1474 } elsif ($val eq '2' or $val eq "archived") {
1481 # get Day, Hour, Year
1487 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1491 my %times = ( m => 60,
1497 my $mult = $times{$2} || 0;
1507 unless ($self->{dbh}) {
1509 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1510 $self->{info}->{user},
1511 $self->{info}->{password});
1513 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1514 unless ($self->{dbh});
1516 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1518 if ($self->dbh_is_mysql()) {
1519 $self->{dbh}->do("SET group_concat_max_len=1000000");
1521 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1528 my ($class, %arg) = @_;
1530 dbh => undef, # connect_db();
1532 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1538 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1540 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1541 $self->{sql} = $sql_func{$1};
1544 $self->{loginname} = CGI::remote_user();
1545 $self->{debug} = $self->{info}->{debug};
1546 $self->{lang} = $self->{info}->{lang};
1547 $self->{template_dir} = $self->{info}->{template_dir};
1555 if ($self->{info}->{enable_security}) {
1556 $self->get_roles(); # get lang
1558 $self->display($self->{info}, "begin.tpl");
1564 $self->display($self->{info}, "end.tpl");
1570 my $arg = $self->get_form("qclient");
1571 my $f1 = $self->get_client_group_filter();
1572 my $f2 = $self->get_client_filter();
1574 # client_group_name | here
1575 #-------------------+-----
1580 SELECT client_group_name, max(here) AS here FROM (
1581 SELECT client_group_name, 1 AS here
1583 JOIN client_group_member USING (client_group_id)
1584 JOIN Client USING (ClientId) $f2
1585 WHERE Name = $arg->{qclient}
1587 SELECT client_group_name, 0
1588 FROM client_group $f1
1590 GROUP by client_group_name";
1592 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
1594 $self->display({ client_group => [ values %$all ], %$arg }, "client_edit.tpl");
1600 my $where=''; # by default
1602 my $arg = $self->get_form("client", "qre_client",
1603 "jclient_groups", "qnotingroup");
1605 if ($arg->{qre_client}) {
1606 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1607 } elsif ($arg->{client}) {
1608 $where = "WHERE Name = '$arg->{client}' ";
1609 } elsif ($arg->{jclient_groups}) {
1610 # $filter could already contains client_group_member
1612 JOIN client_group_member USING (ClientId)
1613 JOIN client_group USING (client_group_id)
1614 WHERE client_group_name IN ($arg->{jclient_groups}) ";
1615 } elsif ($arg->{qnotingroup}) {
1618 (SELECT 1 FROM client_group_member
1619 WHERE Client.ClientId = client_group_member.ClientId
1625 SELECT Name AS name,
1627 AutoPrune AS autoprune,
1628 FileRetention AS fileretention,
1629 JobRetention AS jobretention
1630 FROM Client " . $self->get_client_filter() .
1633 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1635 my $dsp = { ID => $cur_id++,
1636 clients => [ values %$all] };
1638 $self->display($dsp, "client_list.tpl") ;
1643 my ($self, %arg) = @_;
1648 if ($arg{since} and $arg{age}) {
1649 my $now = "$self->{sql}->{UNIX_TIMESTAMP}(TIMESTAMP '$arg{since}')";
1651 AND $self->{sql}->{UNIX_TIMESTAMP}(StartTime) > $now
1652 AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) < ($now + $self->{sql}->{TO_SEC}($arg{age}))";
1653 $label .= "since $arg{since} and during " . human_sec($arg{age});
1655 } elsif ($arg{age}) {
1657 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1659 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1661 $self->{sql}->{TO_SEC}($arg{age})
1664 $label = "last " . human_sec($arg{age});
1667 if ($arg{groupby}) {
1668 $limit .= " GROUP BY $arg{groupby} ";
1672 $limit .= " ORDER BY $arg{order} ";
1676 $limit .= " LIMIT $arg{limit} ";
1677 $label .= " limited to $arg{limit}";
1681 $limit .= " OFFSET $arg{offset} ";
1682 $label .= " with $arg{offset} offset ";
1686 $label = 'no filter';
1689 return ($limit, $label);
1694 $bweb->get_form(...) - Get useful stuff
1698 This function get and check parameters against regexp.
1700 If word begin with 'q', the return will be quoted or join quoted
1701 if it's end with 's'.
1706 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1709 qclient => 'plume-fd',
1710 qpools => "'plume-fd', 'test-fd', '...'",
1717 my ($self, @what) = @_;
1718 my %what = map { $_ => 1 } @what;
1741 my %opt_ss =( # string with space
1745 my %opt_s = ( # default to ''
1767 my %opt_p = ( # option with path
1774 my %opt_r = (regexwhere => 1);
1775 my %opt_d = ( # option with date
1779 my %opt_t = (when => 2, # option with time
1780 begin => 1, # 1 hh:min are optionnal
1781 end => 1, # 2 hh:min are required
1784 foreach my $i (@what) {
1785 if (exists $opt_i{$i}) {# integer param
1786 my $value = CGI::param($i) || $opt_i{$i} ;
1787 if ($value =~ /^(\d+)$/) {
1790 } elsif ($opt_s{$i}) { # simple string param
1791 my $value = CGI::param($i) || '';
1792 if ($value =~ /^([\w\d\.-]+)$/) {
1795 } elsif ($opt_ss{$i}) { # simple string param (with space)
1796 my $value = CGI::param($i) || '';
1797 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1800 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1801 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1803 $ret{$i} = $self->dbh_join(@value) ;
1806 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1807 my $value = CGI::param($1) ;
1809 $ret{$i} = $self->dbh_quote($value);
1812 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1813 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1814 grep { ! /^\s*$/ } CGI::param($1) ];
1815 } elsif (exists $opt_p{$i}) {
1816 my $value = CGI::param($i) || '';
1817 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1820 } elsif (exists $opt_r{$i}) {
1821 my $value = CGI::param($i) || '';
1822 if ($value =~ /^([^'"']+)$/) {
1825 } elsif (exists $opt_d{$i}) {
1826 my $value = CGI::param($i) || '';
1827 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1830 } elsif (exists $opt_t{$i}) { # 1: hh:min optionnal, 2: hh:min required
1831 my $when = CGI::param($i) || '';
1832 if ($when =~ /(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)/) {
1833 if ($opt_t{$i} == 1 or defined $2) {
1840 if ($what{storage_cmd}) {
1841 if (!grep {/^$ret{storage_cmd}$/} ('mount', 'umount', 'release','status')) {
1842 delete $ret{storage_cmd};
1847 foreach my $s (CGI::param('slot')) {
1848 if ($s =~ /^(\d+)$/) {
1849 push @{$ret{slots}}, $s;
1855 my $age = $ret{age} || $opt_i{age};
1856 my $since = CGI::param('since') || strftime('%F %T', localtime(time - $age));
1857 if ($since =~ /^(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)$/) {
1863 my $lang = CGI::param('lang') || 'en';
1864 if ($lang =~ /^(\w\w)$/) {
1869 if ($what{db_clients}) {
1871 if ($what{filter}) {
1872 # get security filter only if asked
1873 $filter = $self->get_client_filter();
1877 SELECT Client.Name as clientname
1881 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1882 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1886 if ($what{db_client_groups}) {
1888 if ($what{filter}) {
1889 # get security filter only if asked
1890 $filter = $self->get_client_group_filter();
1894 SELECT client_group_name AS name, comment AS comment
1895 FROM client_group $filter
1897 my $grps = $self->dbh_selectall_hashref($query, 'name');
1898 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1902 if ($what{db_usernames}) {
1904 SELECT username, comment
1907 my $users = $self->dbh_selectall_hashref($query, 'username');
1908 $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} }
1912 if ($what{db_roles}) {
1914 SELECT rolename, comment
1917 my $r = $self->dbh_selectall_hashref($query, 'rolename');
1918 $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} }
1922 if ($what{db_mediatypes}) {
1924 SELECT MediaType as mediatype
1927 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1928 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1932 if ($what{db_locations}) {
1934 SELECT Location as location, Cost as cost
1937 my $loc = $self->dbh_selectall_hashref($query, 'location');
1938 $ret{db_locations} = [ sort { $a->{location}
1944 if ($what{db_pools}) {
1945 my $query = "SELECT Name as name FROM Pool";
1947 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1948 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1951 if ($what{db_filesets}) {
1953 SELECT FileSet.FileSet AS fileset
1956 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1958 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1959 values %$filesets] ;
1962 if ($what{db_jobnames}) {
1964 if ($what{filter}) {
1965 $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
1968 SELECT DISTINCT Job.Name AS jobname
1971 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1973 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1974 values %$jobnames] ;
1977 if ($what{db_devices}) {
1979 SELECT Device.Name AS name
1982 my $devices = $self->dbh_selectall_hashref($query, 'name');
1984 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1994 $self->can_do('r_view_stat');
1995 my $fields = $self->get_form(qw/age level status clients filesets
1996 graph gtype type filter db_clients
1997 limit db_filesets width height
1998 qclients qfilesets qjobnames db_jobnames/);
2000 my $url = CGI::url(-full => 0,
2003 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
2005 # this organisation is to keep user choice between 2 click
2006 # TODO : fileset and client selection doesn't work
2013 if ($fields->{gtype} eq 'balloon') {
2014 system("./bgraph.pl");
2018 sub get_selected_media_location
2022 my $media = $self->get_form('jmedias');
2024 unless ($media->{jmedias}) {
2029 SELECT Media.VolumeName AS volumename, Location.Location AS location
2030 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2031 WHERE Media.VolumeName IN ($media->{jmedias})
2034 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2036 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
2045 my ($self, $in) = @_ ;
2046 $self->can_do('r_media_mgnt');
2047 my $media = $self->get_selected_media_location();
2053 my $elt = $self->get_form('db_locations');
2055 $self->display({ ID => $cur_id++,
2056 enabled => human_enabled($in),
2057 %$elt, # db_locations
2059 sort { $a->{volumename} cmp $b->{volumename} } values %$media
2068 $self->can_do('r_media_mgnt');
2070 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
2072 $self->display($elt, "help_extern.tpl");
2075 sub help_extern_compute
2078 $self->can_do('r_media_mgnt');
2080 my $number = CGI::param('limit') || '' ;
2081 unless ($number =~ /^(\d+)$/) {
2082 return $self->error("Bad arg number : $number ");
2085 my ($sql, undef) = $self->get_param('pools',
2086 'locations', 'mediatypes');
2089 SELECT Media.VolumeName AS volumename,
2090 Media.VolStatus AS volstatus,
2091 Media.LastWritten AS lastwritten,
2092 Media.MediaType AS mediatype,
2093 Media.VolMounts AS volmounts,
2095 Media.Recycle AS recycle,
2096 $self->{sql}->{FROM_UNIXTIME}(
2097 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2098 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2101 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2102 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2104 WHERE Media.InChanger = 1
2105 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
2107 ORDER BY expire DESC, recycle, Media.VolMounts DESC
2111 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2113 $self->display({ Media => [ values %$all ] },
2114 "help_extern_compute.tpl");
2120 $self->can_do('r_media_mgnt');
2122 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
2123 $self->display($param, "help_intern.tpl");
2126 sub help_intern_compute
2129 $self->can_do('r_media_mgnt');
2131 my $number = CGI::param('limit') || '' ;
2132 unless ($number =~ /^(\d+)$/) {
2133 return $self->error("Bad arg number : $number ");
2136 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
2138 if (CGI::param('expired')) {
2140 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2141 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2147 SELECT Media.VolumeName AS volumename,
2148 Media.VolStatus AS volstatus,
2149 Media.LastWritten AS lastwritten,
2150 Media.MediaType AS mediatype,
2151 Media.VolMounts AS volmounts,
2153 $self->{sql}->{FROM_UNIXTIME}(
2154 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2155 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2158 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2159 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
2161 WHERE Media.InChanger <> 1
2162 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
2163 AND Media.Recycle = 1
2165 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
2169 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2171 $self->display({ Media => [ values %$all ] },
2172 "help_intern_compute.tpl");
2178 my ($self, %arg) = @_ ;
2180 my ($limit, $label) = $self->get_limit(%arg);
2184 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
2185 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
2186 (SELECT count(Job.JobId) FROM Job) AS nb_job,
2187 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
2188 ($self->{sql}->{DB_SIZE}) AS db_size,
2189 (SELECT count(Job.JobId)
2191 WHERE Job.JobStatus IN ('E','e','f','A')
2194 (SELECT count(Client.ClientId) FROM Client) AS nb_client
2197 my $row = $self->dbh_selectrow_hashref($query) ;
2199 $row->{nb_bytes} = human_size($row->{nb_bytes});
2201 $row->{db_size} = human_size($row->{db_size});
2202 $row->{label} = $label;
2204 $self->display($row, "general.tpl");
2209 my ($self, @what) = @_ ;
2210 my %elt = map { $_ => 1 } @what;
2215 if ($elt{clients}) {
2216 my @clients = grep { ! /^\s*$/ } CGI::param('client');
2218 $ret{clients} = \@clients;
2219 my $str = $self->dbh_join(@clients);
2220 $limit .= "AND Client.Name IN ($str) ";
2224 if ($elt{client_groups}) {
2225 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
2227 $ret{client_groups} = \@clients;
2228 my $str = $self->dbh_join(@clients);
2229 $limit .= "AND client_group_name IN ($str) ";
2233 if ($elt{filesets}) {
2234 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
2236 $ret{filesets} = \@filesets;
2237 my $str = $self->dbh_join(@filesets);
2238 $limit .= "AND FileSet.FileSet IN ($str) ";
2242 if ($elt{mediatypes}) {
2243 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
2245 $ret{mediatypes} = \@media;
2246 my $str = $self->dbh_join(@media);
2247 $limit .= "AND Media.MediaType IN ($str) ";
2252 my $client = CGI::param('client');
2253 $ret{client} = $client;
2254 $client = $self->dbh_join($client);
2255 $limit .= "AND Client.Name = $client ";
2259 my $level = CGI::param('level') || '';
2260 if ($level =~ /^(\w)$/) {
2262 $limit .= "AND Job.Level = '$1' ";
2267 my $jobid = CGI::param('jobid') || '';
2269 if ($jobid =~ /^(\d+)$/) {
2271 $limit .= "AND Job.JobId = '$1' ";
2276 my $status = CGI::param('status') || '';
2277 if ($status =~ /^(\w)$/) {
2280 $limit .= "AND Job.JobStatus IN ('f','E') ";
2281 } elsif ($1 eq 'W') {
2282 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
2284 $limit .= "AND Job.JobStatus = '$1' ";
2289 if ($elt{volstatus}) {
2290 my $status = CGI::param('volstatus') || '';
2291 if ($status =~ /^(\w+)$/) {
2293 $limit .= "AND Media.VolStatus = '$1' ";
2297 if ($elt{locations}) {
2298 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
2300 $ret{locations} = \@location;
2301 my $str = $self->dbh_join(@location);
2302 $limit .= "AND Location.Location IN ($str) ";
2307 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
2309 $ret{pools} = \@pool;
2310 my $str = $self->dbh_join(@pool);
2311 $limit .= "AND Pool.Name IN ($str) ";
2315 if ($elt{location}) {
2316 my $location = CGI::param('location') || '';
2318 $ret{location} = $location;
2319 $location = $self->dbh_quote($location);
2320 $limit .= "AND Location.Location = $location ";
2325 my $pool = CGI::param('pool') || '';
2328 $pool = $self->dbh_quote($pool);
2329 $limit .= "AND Pool.Name = $pool ";
2333 if ($elt{jobtype}) {
2334 my $jobtype = CGI::param('jobtype') || '';
2335 if ($jobtype =~ /^(\w)$/) {
2337 $limit .= "AND Job.Type = '$1' ";
2341 return ($limit, %ret);
2352 my ($self, %arg) = @_ ;
2353 return if $self->cant_do('r_view_job');
2355 $arg{order} = ' Job.JobId DESC ';
2357 my ($limit, $label) = $self->get_limit(%arg);
2358 my ($where, undef) = $self->get_param('clients',
2367 if (CGI::param('client_group')) {
2369 JOIN client_group_member USING (ClientId)
2370 JOIN client_group USING (client_group_id)
2373 my $filter = $self->get_client_filter();
2376 SELECT Job.JobId AS jobid,
2377 Client.Name AS client,
2378 FileSet.FileSet AS fileset,
2379 Job.Name AS jobname,
2381 StartTime AS starttime,
2383 Pool.Name AS poolname,
2384 JobFiles AS jobfiles,
2385 JobBytes AS jobbytes,
2386 JobStatus AS jobstatus,
2387 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2388 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2391 JobErrors AS joberrors
2393 FROM Client $filter $cgq,
2394 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2395 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2396 WHERE Client.ClientId=Job.ClientId
2397 AND Job.JobStatus NOT IN ('R', 'C')
2402 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2404 $self->display({ Filter => $label,
2408 sort { $a->{jobid} <=> $b->{jobid} }
2415 # display job informations
2416 sub display_job_zoom
2418 my ($self, $jobid) = @_ ;
2419 $self->can_do('r_view_job');
2421 $jobid = $self->dbh_quote($jobid);
2423 # get security filter
2424 my $filter = $self->get_client_filter();
2427 SELECT DISTINCT Job.JobId AS jobid,
2428 Client.Name AS client,
2429 Job.Name AS jobname,
2430 FileSet.FileSet AS fileset,
2432 Pool.Name AS poolname,
2433 StartTime AS starttime,
2434 JobFiles AS jobfiles,
2435 JobBytes AS jobbytes,
2436 JobStatus AS jobstatus,
2437 JobErrors AS joberrors,
2438 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2439 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2441 FROM Client $filter,
2442 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2443 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2444 WHERE Client.ClientId=Job.ClientId
2445 AND Job.JobId = $jobid
2448 my $row = $self->dbh_selectrow_hashref($query) ;
2450 # display all volumes associate with this job
2452 SELECT Media.VolumeName as volumename
2453 FROM Job,Media,JobMedia
2454 WHERE Job.JobId = $jobid
2455 AND JobMedia.JobId=Job.JobId
2456 AND JobMedia.MediaId=Media.MediaId
2459 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2461 $row->{volumes} = [ values %$all ] ;
2462 $row->{wiki_url} = $self->{info}->{wiki_url};
2464 $self->display($row, "display_job_zoom.tpl");
2467 sub display_job_group
2469 my ($self, %arg) = @_;
2470 $self->can_do('r_view_job');
2472 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2474 my ($where, undef) = $self->get_param('client_groups',
2477 my $filter = $self->get_client_group_filter();
2480 SELECT client_group_name AS client_group_name,
2481 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2482 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2483 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2484 COALESCE(jobok.nbjobs,0) AS nbjobok,
2485 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2486 COALESCE(jobok.duration, '0:0:0') AS duration
2488 FROM client_group $filter LEFT JOIN (
2489 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2490 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2491 SUM(JobErrors) AS joberrors,
2492 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2493 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2496 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2497 JOIN client_group USING (client_group_id)
2499 WHERE JobStatus = 'T'
2502 ) AS jobok USING (client_group_name) LEFT JOIN
2505 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2506 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2507 SUM(JobErrors) AS joberrors
2508 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2509 JOIN client_group USING (client_group_id)
2511 WHERE JobStatus IN ('f','E', 'A')
2514 ) AS joberr USING (client_group_name)
2518 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2520 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2523 $self->display($rep, "display_job_group.tpl");
2528 my ($self, %arg) = @_ ;
2529 $self->can_do('r_view_media');
2531 my ($limit, $label) = $self->get_limit(%arg);
2532 my ($where, %elt) = $self->get_param('pools',
2537 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2539 if ($arg->{jmedias}) {
2540 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2542 if ($arg->{qre_media}) {
2543 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2545 if ($arg->{expired}) {
2547 AND VolStatus = 'Full'
2548 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2549 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2550 ) < NOW() " . $where ;
2554 SELECT Media.VolumeName AS volumename,
2555 Media.VolBytes AS volbytes,
2556 Media.VolStatus AS volstatus,
2557 Media.MediaType AS mediatype,
2558 Media.InChanger AS online,
2559 Media.LastWritten AS lastwritten,
2560 Location.Location AS location,
2561 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2562 Pool.Name AS poolname,
2563 $self->{sql}->{FROM_UNIXTIME}(
2564 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2565 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2568 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2569 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2570 Media.MediaType AS MediaType
2572 WHERE Media.VolStatus = 'Full'
2573 GROUP BY Media.MediaType
2574 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2576 WHERE Media.PoolId=Pool.PoolId
2581 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2583 $self->display({ ID => $cur_id++,
2585 Location => $elt{location},
2586 Media => [ values %$all ],
2588 "display_media.tpl");
2591 sub display_allmedia
2595 my $pool = $self->get_form('db_pools');
2597 foreach my $name (@{ $pool->{db_pools} }) {
2598 CGI::param('pool', $name->{name});
2599 $self->display_media();
2603 sub display_media_zoom
2607 my $media = $self->get_form('jmedias');
2609 unless ($media->{jmedias}) {
2610 return $self->error("Can't get media selection");
2614 SELECT InChanger AS online,
2615 Media.Enabled AS enabled,
2616 VolBytes AS nb_bytes,
2617 VolumeName AS volumename,
2618 VolStatus AS volstatus,
2619 VolMounts AS nb_mounts,
2620 Media.VolUseDuration AS voluseduration,
2621 Media.MaxVolJobs AS maxvoljobs,
2622 Media.MaxVolFiles AS maxvolfiles,
2623 Media.MaxVolBytes AS maxvolbytes,
2624 VolErrors AS nb_errors,
2625 Pool.Name AS poolname,
2626 Location.Location AS location,
2627 Media.Recycle AS recycle,
2628 Media.VolRetention AS volretention,
2629 Media.LastWritten AS lastwritten,
2630 Media.VolReadTime/1000000 AS volreadtime,
2631 Media.VolWriteTime/1000000 AS volwritetime,
2632 Media.RecycleCount AS recyclecount,
2633 Media.Comment AS comment,
2634 $self->{sql}->{FROM_UNIXTIME}(
2635 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2636 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2639 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2640 WHERE Pool.PoolId = Media.PoolId
2641 AND VolumeName IN ($media->{jmedias})
2644 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2646 foreach my $media (values %$all) {
2647 my $mq = $self->dbh_quote($media->{volumename});
2650 SELECT DISTINCT Job.JobId AS jobid,
2652 Job.StartTime AS starttime,
2655 Job.JobFiles AS files,
2656 Job.JobBytes AS bytes,
2657 Job.jobstatus AS status
2658 FROM Media,JobMedia,Job
2659 WHERE Media.VolumeName=$mq
2660 AND Media.MediaId=JobMedia.MediaId
2661 AND JobMedia.JobId=Job.JobId
2664 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2667 SELECT LocationLog.Date AS date,
2668 Location.Location AS location,
2669 LocationLog.Comment AS comment
2670 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2671 WHERE Media.MediaId = LocationLog.MediaId
2672 AND Media.VolumeName = $mq
2676 my $log = $self->dbh_selectall_arrayref($query) ;
2678 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2681 $self->display({ jobs => [ values %$jobs ],
2682 LocationLog => $logtxt,
2684 "display_media_zoom.tpl");
2691 $self->can_do('r_location_mgnt');
2693 my $loc = $self->get_form('qlocation');
2694 unless ($loc->{qlocation}) {
2695 return $self->error("Can't get location");
2699 SELECT Location.Location AS location,
2700 Location.Cost AS cost,
2701 Location.Enabled AS enabled
2703 WHERE Location.Location = $loc->{qlocation}
2706 my $row = $self->dbh_selectrow_hashref($query);
2707 $row->{enabled} = human_enabled($row->{enabled});
2708 $self->display({ ID => $cur_id++,
2709 %$row }, "location_edit.tpl") ;
2715 $self->can_do('r_location_mgnt');
2717 my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
2718 unless ($arg->{qlocation}) {
2719 return $self->error("Can't get location");
2721 unless ($arg->{qnewlocation}) {
2722 return $self->error("Can't get new location name");
2724 unless ($arg->{cost}) {
2725 return $self->error("Can't get new cost");
2728 my $enabled = from_human_enabled($arg->{enabled});
2731 UPDATE Location SET Cost = $arg->{cost},
2732 Location = $arg->{qnewlocation},
2734 WHERE Location.Location = $arg->{qlocation}
2737 $self->dbh_do($query);
2739 $self->location_display();
2745 $self->can_do('r_location_mgnt');
2747 my $arg = $self->get_form(qw/qlocation/) ;
2749 unless ($arg->{qlocation}) {
2750 return $self->error("Can't get location");
2754 SELECT count(Media.MediaId) AS nb
2755 FROM Media INNER JOIN Location USING (LocationID)
2756 WHERE Location = $arg->{qlocation}
2759 my $res = $self->dbh_selectrow_hashref($query);
2762 return $self->error("Sorry, the location must be empty");
2766 DELETE FROM Location WHERE Location = $arg->{qlocation}
2769 $self->dbh_do($query);
2771 $self->location_display();
2777 $self->can_do('r_location_mgnt');
2779 my $arg = $self->get_form(qw/qlocation cost/) ;
2781 unless ($arg->{qlocation}) {
2782 $self->display({}, "location_add.tpl");
2785 unless ($arg->{cost}) {
2786 return $self->error("Can't get new cost");
2789 my $enabled = CGI::param('enabled') || '';
2790 $enabled = from_human_enabled($enabled);
2793 INSERT INTO Location (Location, Cost, Enabled)
2794 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2797 $self->dbh_do($query);
2799 $self->location_display();
2802 sub location_display
2807 SELECT Location.Location AS location,
2808 Location.Cost AS cost,
2809 Location.Enabled AS enabled,
2810 (SELECT count(Media.MediaId)
2812 WHERE Media.LocationId = Location.LocationId
2817 my $location = $self->dbh_selectall_hashref($query, 'location');
2819 $self->display({ ID => $cur_id++,
2820 Locations => [ values %$location ] },
2821 "display_location.tpl");
2828 my $media = $self->get_selected_media_location();
2833 my $arg = $self->get_form('db_locations', 'qnewlocation');
2835 $self->display({ email => $self->{info}->{email_media},
2837 media => [ values %$media ],
2839 "update_location.tpl");
2842 ###########################################################
2847 my $arg = $self->get_form(qw/jclient_groups qclient/);
2849 unless ($arg->{qclient}) {
2850 return $self->error("Can't get client name");
2853 $self->can_do('r_group_mgnt');
2855 my $f1 = $self->get_client_filter();
2856 my $f2 = $self->get_client_group_filter();
2858 $self->{dbh}->begin_work();
2861 DELETE FROM client_group_member
2865 WHERE Client.Name = $arg->{qclient})
2867 $self->dbh_do($query);
2869 if ($arg->{jclient_groups}) {
2871 INSERT INTO client_group_member (client_group_id, ClientId)
2872 (SELECT client_group_id, (SELECT ClientId
2874 WHERE Name = $arg->{qclient})
2875 FROM client_group $f2 WHERE client_group_name IN ($arg->{jclient_groups})
2878 $self->dbh_do($query);
2881 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2883 $self->display_clients();
2889 my $grp = $self->get_form(qw/qclient_group db_clients/);
2891 unless ($grp->{qclient_group}) {
2892 $self->can_do('r_group_mgnt');
2893 $self->display({ ID => $cur_id++,
2894 client_group => "''",
2896 }, "groups_edit.tpl");
2900 unless ($self->cant_do('r_group_mgnt')) {
2901 $self->can_do('r_view_group');
2906 FROM Client JOIN client_group_member using (ClientId)
2907 JOIN client_group using (client_group_id)
2908 WHERE client_group_name = $grp->{qclient_group}
2911 my $row = $self->dbh_selectall_hashref($query, "name");
2913 $self->display({ ID => $cur_id++,
2914 client_group => $grp->{qclient_group},
2916 client_group_member => [ values %$row]},
2923 $self->can_do('r_group_mgnt');
2925 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup qcomment/);
2927 if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
2929 INSERT INTO client_group (client_group_name, comment)
2930 VALUES ($arg->{qnewgroup}, $arg->{qcomment})
2932 $self->dbh_do($query);
2933 $arg->{qclient_group} = $arg->{qnewgroup};
2936 unless ($arg->{qclient_group}) {
2937 return $self->error("Can't get groups");
2940 $self->{dbh}->begin_work();
2943 DELETE FROM client_group_member
2944 WHERE client_group_id IN
2945 (SELECT client_group_id
2947 WHERE client_group_name = $arg->{qclient_group})
2949 $self->dbh_do($query);
2951 if ($arg->{jclients}) {
2953 INSERT INTO client_group_member (ClientId, client_group_id)
2955 (SELECT client_group_id
2957 WHERE client_group_name = $arg->{qclient_group})
2958 FROM Client WHERE Name IN ($arg->{jclients})
2961 $self->dbh_do($query);
2963 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2966 SET client_group_name = $arg->{qnewgroup}, comment = $arg->{qcomment}
2967 WHERE client_group_name = $arg->{qclient_group}
2970 $self->dbh_do($query);
2973 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2975 $self->display_groups();
2981 $self->can_do('r_group_mgnt');
2983 my $arg = $self->get_form(qw/qclient_group/);
2985 unless ($arg->{qclient_group}) {
2986 return $self->error("Can't get groups");
2989 $self->{dbh}->begin_work();
2992 DELETE FROM client_group_member
2993 WHERE client_group_id IN
2994 (SELECT client_group_id
2996 WHERE client_group_name = $arg->{qclient_group})");
2999 DELETE FROM bweb_client_group_acl
3000 WHERE client_group_id IN
3001 (SELECT client_group_id
3003 WHERE client_group_name = $arg->{qclient_group})");
3006 DELETE FROM client_group
3007 WHERE client_group_name = $arg->{qclient_group}");
3009 $self->{dbh}->commit();
3010 $self->display_groups();
3018 if ($self->cant_do('r_group_mgnt')) {
3019 $arg = $self->get_form(qw/db_client_groups filter/) ;
3021 $arg = $self->get_form(qw/db_client_groups/) ;
3024 if ($self->{dbh}->errstr) {
3025 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
3030 $self->display({ ID => $cur_id++,
3032 "display_groups.tpl");
3035 ###########################################################
3040 if (not $self->{info}->{enable_security}) {
3043 # admin is a special user that can do everything
3044 if ($self->{loginname} eq 'admin') {
3047 if (!$self->{loginname}) {
3048 $self->error("Can't get your login name");
3049 $self->display_end();
3053 if (defined $self->{security}) {
3056 $self->{security} = {};
3057 my $u = $self->dbh_quote($self->{loginname});
3060 SELECT use_acl, rolename, tpl
3062 JOIN bweb_role_member USING (userid)
3063 JOIN bweb_role USING (roleid)
3066 my $rows = $self->dbh_selectall_arrayref($query);
3067 # do cache with this role
3068 if (!$rows or !scalar(@$rows)) {
3069 $self->error("Can't get $self->{loginname}'s roles");
3070 $self->display_end();
3073 foreach my $r (@$rows) {
3074 $self->{security}->{$r->[1]}=1;
3076 $self->{security}->{use_acl} = $rows->[0]->[0];
3077 if ($rows->[0]->[2] =~ /^(\w\w)$/) {
3085 my ($self, $client) = @_;
3087 my $filter = $self->get_client_filter();
3091 my $cont = $self->dbh_selectrow_hashref("
3094 WHERE Name = '$client'
3096 return defined $cont;
3101 my ($self, $action) = @_;
3102 # is security enabled in configuration ?
3103 if (not $self->{info}->{enable_security}) {
3106 # admin is a special user that can do everything
3107 if ($self->{loginname} eq 'admin') {
3111 if (!$self->{loginname}) {
3112 $self->{error} = "Can't do $action, your are not logged. " .
3113 "Check security with your administrator";
3116 if (!$self->get_roles()) {
3119 if (!$self->{security}->{$action}) {
3121 "$self->{loginname} sorry, but this action ($action) " .
3122 "is not permited. " .
3123 "Check security with your administrator";
3129 # make like an assert (program die)
3132 my ($self, $action) = @_;
3133 if ($self->cant_do($action)) {
3134 $self->error($self->{error});
3135 $self->display_end();
3145 if (!$self->{info}->{enable_security} or
3146 !$self->{info}->{enable_security_acl})
3151 if ($self->get_roles()) {
3152 return $self->{security}->{use_acl};
3158 # JOIN Client USING (ClientId) " . $b->get_client_filter() . "
3159 sub get_client_filter
3161 my ($self, $login) = @_;
3164 $u = $self->dbh_quote($login);
3165 } elsif ($self->use_filter()) {
3166 $u = $self->dbh_quote($self->{loginname});
3171 JOIN (SELECT ClientId FROM client_group_member
3172 JOIN client_group USING (client_group_id)
3173 JOIN bweb_client_group_acl USING (client_group_id)
3174 JOIN bweb_user USING (userid)
3175 WHERE bweb_user.username = $u
3176 ) AS filter USING (ClientId)";
3179 #JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
3180 sub get_client_group_filter
3182 my ($self, $login) = @_;
3185 $u = $self->dbh_quote($login);
3186 } elsif ($self->use_filter()) {
3187 $u = $self->dbh_quote($self->{loginname});
3192 JOIN (SELECT client_group_id
3193 FROM bweb_client_group_acl
3194 JOIN bweb_user USING (userid)
3195 WHERE bweb_user.username = $u
3196 ) AS filter USING (client_group_id)";
3199 # role and username have to be quoted before
3200 # role and username can be a quoted list
3203 my ($self, $role, $username) = @_;
3204 $self->can_do("r_user_mgnt");
3206 my $nb = $self->dbh_do("
3207 DELETE FROM bweb_role_member
3208 WHERE roleid = (SELECT roleid FROM bweb_role
3209 WHERE rolename IN ($role))
3210 AND userid = (SELECT userid FROM bweb_user
3211 WHERE username IN ($username))");
3215 # role and username have to be quoted before
3216 # role and username can be a quoted list
3219 my ($self, $role, $username) = @_;
3220 $self->can_do("r_user_mgnt");
3222 my $nb = $self->dbh_do("
3223 INSERT INTO bweb_role_member (roleid, userid)
3224 SELECT roleid, userid FROM bweb_role, bweb_user
3225 WHERE rolename IN ($role)
3226 AND username IN ($username)
3231 # role and username have to be quoted before
3232 # role and username can be a quoted list
3235 my ($self, $copy, $user) = @_;
3236 $self->can_do("r_user_mgnt");
3238 my $nb = $self->dbh_do("
3239 INSERT INTO bweb_role_member (roleid, userid)
3240 SELECT roleid, a.userid
3241 FROM bweb_user AS a, bweb_role_member
3242 JOIN bweb_user USING (userid)
3243 WHERE bweb_user.username = $copy
3244 AND a.username = $user");
3248 # username can be a join quoted list of usernames
3251 my ($self, $username) = @_;
3252 $self->can_do("r_user_mgnt");
3255 DELETE FROM bweb_role_member
3259 WHERE username in ($username))");
3261 DELETE FROM bweb_client_group_acl
3265 WHERE username IN ($username))");
3272 $self->can_do("r_user_mgnt");
3274 my $arg = $self->get_form(qw/jusernames/);
3276 unless ($arg->{jusernames}) {
3277 return $self->error("Can't get user");
3280 $self->{dbh}->begin_work();
3282 $self->revoke_all($arg->{jusernames});
3284 DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
3286 $self->{dbh}->commit();
3288 $self->display_users();
3294 $self->can_do("r_user_mgnt");
3296 # we don't quote username directly to check that it is conform
3297 my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate
3298 lang qcopy_username jclient_groups/) ;
3300 if (not $arg->{qcreate}) {
3301 $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
3302 $self->display($arg, "display_user.tpl");
3306 my $u = $self->dbh_quote($arg->{username});
3308 $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
3310 if (!$arg->{qpasswd}) {
3311 $arg->{qpasswd} = "''";
3313 if (!$arg->{qcomment}) {
3314 $arg->{qcomment} = "''";
3317 # will fail if user already exists
3318 # UPDATE with mysql dbi does not return if update is ok
3321 SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment},
3322 use_acl=$arg->{use_acl}, tpl='$arg->{lang}'
3323 WHERE username = $u")
3324 # and (! $self->dbh_is_mysql() )
3327 INSERT INTO bweb_user (username, passwd, use_acl, comment, tpl)
3328 VALUES ($u, $arg->{qpasswd}, $arg->{use_acl},
3329 $arg->{qcomment}, '$arg->{lang}')");
3331 $self->{dbh}->begin_work();
3333 $self->revoke_all($u);
3335 if ($arg->{qcopy_username}) {
3336 $self->grant_like($arg->{qcopy_username}, $u);
3338 $self->grant($arg->{jrolenames}, $u);
3341 if ($arg->{jclient_groups}) {
3343 INSERT INTO bweb_client_group_acl (client_group_id, userid)
3344 SELECT client_group_id, userid
3345 FROM client_group, bweb_user
3346 WHERE client_group_name IN ($arg->{jclient_groups})
3351 $self->{dbh}->commit();
3353 $self->display_users();
3356 # TODO: we miss a matrix with all user/roles
3360 $self->can_do("r_user_mgnt");
3362 my $arg = $self->get_form(qw/db_usernames/) ;
3364 if ($self->{dbh}->errstr) {
3365 return $self->error("Can't use users with bweb, read INSTALL to enable them");
3368 $self->display({ ID => $cur_id++,
3370 "display_users.tpl");
3376 $self->can_do("r_user_mgnt");
3378 my $arg = $self->get_form('username');
3379 my $user = $self->dbh_quote($arg->{username});
3381 my $userp = $self->dbh_selectrow_hashref("
3382 SELECT username, passwd, comment, use_acl, tpl
3384 WHERE username = $user
3387 return $self->error("Can't find $user in catalog");
3389 my $filter = $self->get_client_group_filter($arg->{username});
3390 my $scg = $self->dbh_selectall_hashref("
3391 SELECT client_group_name AS name
3392 FROM client_group $filter
3396 #------------+--------
3401 my $role = $self->dbh_selectall_hashref("
3402 SELECT rolename, max(here) AS userid FROM (
3403 SELECT rolename, 1 AS here
3405 JOIN bweb_role_member USING (userid)
3406 JOIN bweb_role USING (roleid)
3407 WHERE username = $user
3412 GROUP by rolename", 'rolename');
3414 $arg = $self->get_form(qw/db_usernames db_client_groups/);
3417 db_usernames => $arg->{db_usernames},
3418 username => $userp->{username},
3419 comment => $userp->{comment},
3420 passwd => $userp->{passwd},
3421 lang => $userp->{tpl},
3422 use_acl => $userp->{use_acl},
3423 db_client_groups => $arg->{db_client_groups},
3424 client_group => [ values %$scg ],
3425 db_roles => [ values %$role],
3426 }, "display_user.tpl");
3430 ###########################################################
3432 sub get_media_max_size
3434 my ($self, $type) = @_;
3436 "SELECT avg(VolBytes) AS size
3438 WHERE Media.VolStatus = 'Full'
3439 AND Media.MediaType = '$type'
3442 my $res = $self->selectrow_hashref($query);
3445 return $res->{size};
3455 my $media = $self->get_form('qmedia');
3457 unless ($media->{qmedia}) {
3458 return $self->error("Can't get media");
3462 SELECT Media.Slot AS slot,
3463 PoolMedia.Name AS poolname,
3464 Media.VolStatus AS volstatus,
3465 Media.InChanger AS inchanger,
3466 Location.Location AS location,
3467 Media.VolumeName AS volumename,
3468 Media.MaxVolBytes AS maxvolbytes,
3469 Media.MaxVolJobs AS maxvoljobs,
3470 Media.MaxVolFiles AS maxvolfiles,
3471 Media.VolUseDuration AS voluseduration,
3472 Media.VolRetention AS volretention,
3473 Media.Comment AS comment,
3474 PoolRecycle.Name AS poolrecycle,
3475 Media.Enabled AS enabled
3477 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
3478 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
3479 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3481 WHERE Media.VolumeName = $media->{qmedia}
3484 my $row = $self->dbh_selectrow_hashref($query);
3485 $row->{volretention} = human_sec($row->{volretention});
3486 $row->{voluseduration} = human_sec($row->{voluseduration});
3487 $row->{enabled} = human_enabled($row->{enabled});
3489 my $elt = $self->get_form(qw/db_pools db_locations/);
3494 }, "update_media.tpl");
3500 $self->can_do('r_media_mgnt');
3502 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
3504 unless ($arg->{jmedias}) {
3505 return $self->error("Can't get selected media");
3508 unless ($arg->{qnewlocation}) {
3509 return $self->error("Can't get new location");
3514 SET LocationId = (SELECT LocationId
3516 WHERE Location = $arg->{qnewlocation})
3517 WHERE Media.VolumeName IN ($arg->{jmedias})
3520 my $nb = $self->dbh_do($query);
3522 print "$nb media updated, you may have to update your autochanger.";
3524 $self->display_media();
3530 $self->can_do('r_media_mgnt');
3532 my $media = $self->get_selected_media_location();
3534 return $self->error("Can't get media selection");
3536 my $newloc = CGI::param('newlocation');
3538 my $user = CGI::param('user') || 'unknown';
3539 my $comm = CGI::param('comment') || '';
3540 $comm = $self->dbh_quote("$user: $comm");
3542 my $arg = $self->get_form('enabled');
3543 my $en = from_human_enabled($arg->{enabled});
3544 my $b = $self->get_bconsole();
3547 foreach my $vol (keys %$media) {
3549 INSERT INTO LocationLog (Date,Comment,MediaId,LocationId,NewEnabled,NewVolStatus)
3550 SELECT NOW(), $comm, Media.MediaId, Location.LocationId, $en, VolStatus
3551 FROM Media, Location
3552 WHERE Media.VolumeName = '$vol'
3553 AND Location.Location = '$media->{$vol}->{location}'
3555 $self->dbh_do($query);
3556 $self->debug($query);
3557 $b->send_cmd("update volume=\"$vol\" enabled=$en");
3562 $q->param('action', 'update_location');
3563 my $url = $q->url(-full => 1, -query=>1);
3565 $self->display({ email => $self->{info}->{email_media},
3567 newlocation => $newloc,
3568 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
3569 media => [ values %$media ],
3571 "change_location.tpl");
3575 sub display_client_stats
3577 my ($self, %arg) = @_ ;
3578 $self->can_do('r_view_stat');
3580 my $client = $self->dbh_quote($arg{clientname});
3581 # get security filter
3582 my $filter = $self->get_client_filter();
3584 my ($limit, $label) = $self->get_limit(%arg);
3587 count(Job.JobId) AS nb_jobs,
3588 sum(Job.JobBytes) AS nb_bytes,
3589 sum(Job.JobErrors) AS nb_err,
3590 sum(Job.JobFiles) AS nb_files,
3591 Client.Name AS clientname
3592 FROM Job JOIN Client USING (ClientId) $filter
3594 Client.Name = $client
3596 GROUP BY Client.Name
3599 my $row = $self->dbh_selectrow_hashref($query);
3601 $row->{ID} = $cur_id++;
3602 $row->{label} = $label;
3603 $row->{grapharg} = "client";
3605 $self->display($row, "display_client_stats.tpl");
3609 sub display_group_stats
3611 my ($self, %arg) = @_ ;
3613 my $carg = $self->get_form(qw/qclient_group/);
3615 unless ($carg->{qclient_group}) {
3616 return $self->error("Can't get group");
3619 my ($limit, $label) = $self->get_limit(%arg);
3623 count(Job.JobId) AS nb_jobs,
3624 sum(Job.JobBytes) AS nb_bytes,
3625 sum(Job.JobErrors) AS nb_err,
3626 sum(Job.JobFiles) AS nb_files,
3627 client_group.client_group_name AS clientname
3628 FROM Job JOIN Client USING (ClientId)
3629 JOIN client_group_member ON (Client.ClientId = client_group_member.ClientId)
3630 JOIN client_group USING (client_group_id)
3632 client_group.client_group_name = $carg->{qclient_group}
3634 GROUP BY client_group.client_group_name
3637 my $row = $self->dbh_selectrow_hashref($query);
3639 $row->{ID} = $cur_id++;
3640 $row->{label} = $label;
3641 $row->{grapharg} = "client_group";
3643 $self->display($row, "display_client_stats.tpl");
3646 # [ name, num, value, joberrors, nb_job ] =>
3648 # [ { name => 'ALL',
3649 # events => [ { num => 1, label => '2007-01',
3650 # value => 'T', title => 10 },
3651 # { num => 2, label => '2007-02',
3652 # value => 'R', title => 11 },
3655 # { name => 'Other',
3659 sub make_overview_tab
3661 my ($self, $q) = @_;
3662 my $ret = $self->dbh_selectall_arrayref($q);
3666 for my $elt (@$ret) {
3667 if ($cur_name and $cur_name ne $elt->[0]) { # order by name, num
3668 push @items, { name => $cur_name, events => $events};
3671 $cur_name = $elt->[0];
3673 { num => $elt->[1], status => $elt->[2],
3674 joberrors => $elt->[3], title => "$elt->[4] jobs"};
3676 push @items, { name => $cur_name, events => $events};
3680 sub get_time_overview
3682 my ($self, $arg) = @_; # want since et age from get_form();
3683 my $type = $arg->{type} || 'day';
3684 if ($type =~ /^(day|week|hour|month)$/) {
3690 my $jobt = $self->{info}->{stat_job_table} || 'Job';
3691 my $stime1 = $self->{sql}->{"STARTTIME_P" . $type}; # get 1,2,3
3692 $stime1 =~ s/Job.StartTime/date/;
3693 my $stime2 = $self->{sql}->{"STARTTIME_" . $type}; # get 2007-01-03, 2007-01-23
3695 my ($limit, $label) = $self->get_limit('since' => $arg->{since},
3696 'age' => $arg->{age});
3697 return ($stime1, $stime2, $limit, $label, $jobt);
3700 # lu ma me je ve sa di
3701 # groupe1 v v x w v v v overview
3702 # |-- s1 v v v v v v v overview_zoom
3703 # |-- s2 v v x v v v v
3704 # `-- s3 v v v w v v v
3705 sub display_overview_zoom
3708 $self->can_do('r_view_stat');
3710 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3712 if (!$arg->{jclient_groups}) {
3713 return $self->error("Can't get client_group selection");
3715 my ($filter2, undef) = $self->get_param(qw/level jobtype/);
3716 my ($stime1, $stime2, $limit, $label, $jobt) = $self->get_time_overview($arg);
3718 my $filter = $self->get_client_filter();
3720 SELECT name, $stime1 AS num,
3721 JobStatus AS value, joberrors, nb_job
3723 SELECT $stime2 AS date,
3724 Client.Name AS name,
3725 MAX(severity) AS severity,
3727 SUM(JobErrors) AS joberrors
3729 JOIN client_group_member USING (ClientId)
3730 JOIN client_group USING (client_group_id)
3731 JOIN Client USING (ClientId) $filter
3732 JOIN Status USING (JobStatus)
3733 WHERE client_group_name IN ($arg->{jclient_groups})
3736 GROUP BY Client.Name, date
3737 ) AS sub JOIN Status USING (severity)
3740 my $items = $self->make_overview_tab($q);
3741 $self->display({label => $label,
3742 action => "job;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client=",
3743 items => $items}, "overview.tpl");
3746 sub display_overview
3749 $self->can_do('r_view_stat');
3751 my $arg = $self->get_form(qw/jclient_groups age since type level/);
3752 my ($filter2, undef) = $self->get_param(qw/client_groups level jobtype/);
3753 my $filter3 = $self->get_client_group_filter();
3754 my ($stime1, $stime2, $filter1, $label, $jobt) = $self->get_time_overview($arg);
3757 SELECT name, $stime1 AS num,
3758 JobStatus AS value, joberrors, nb_job
3760 SELECT $stime2 AS date,
3761 client_group_name AS name,
3762 MAX(severity) AS severity,
3764 SUM(JobErrors) AS joberrors
3766 JOIN client_group_member USING (ClientId)
3767 JOIN client_group USING (client_group_id) $filter3
3768 JOIN Status USING (JobStatus)
3769 WHERE true $filter1 $filter2
3770 GROUP BY client_group_name, date
3771 ) AS sub JOIN Status USING (severity)
3774 my $items = $self->make_overview_tab($q);
3775 $self->display({label=>$label,
3776 action => "overview_zoom;since=$arg->{since};level=$arg->{level};type=$arg->{type};age=$arg->{age};client_group=",
3777 items => $items}, "overview.tpl");
3781 # poolname can be undef
3784 my ($self, $poolname) = @_ ;
3785 $self->can_do('r_view_media');
3790 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
3791 if ($arg->{jmediatypes}) {
3792 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
3793 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
3796 # TODO : afficher les tailles et les dates
3799 SELECT subq.volmax AS volmax,
3800 subq.volnum AS volnum,
3801 subq.voltotal AS voltotal,
3803 Pool.Recycle AS recycle,
3804 Pool.VolRetention AS volretention,
3805 Pool.VolUseDuration AS voluseduration,
3806 Pool.MaxVolJobs AS maxvoljobs,
3807 Pool.MaxVolFiles AS maxvolfiles,
3808 Pool.MaxVolBytes AS maxvolbytes,
3809 subq.PoolId AS PoolId,
3810 subq.MediaType AS mediatype,
3811 $self->{sql}->{CAT_POOL_TYPE} AS uniq
3814 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
3815 count(Media.MediaId) AS volnum,
3816 sum(Media.VolBytes) AS voltotal,
3817 Media.PoolId AS PoolId,
3818 Media.MediaType AS MediaType
3820 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
3821 Media.MediaType AS MediaType
3823 WHERE Media.VolStatus = 'Full'
3824 GROUP BY Media.MediaType
3825 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
3826 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
3828 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
3832 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
3835 SELECT Pool.Name AS name,
3836 sum(VolBytes) AS size
3837 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
3838 WHERE Media.VolStatus IN ('Recycled', 'Purged')
3842 my $empty = $self->dbh_selectall_hashref($query, 'name');
3844 foreach my $p (values %$all) {
3845 if ($p->{volmax} > 0) { # mysql returns 0.0000
3846 # we remove Recycled/Purged media from pool usage
3847 if (defined $empty->{$p->{name}}) {
3848 $p->{voltotal} -= $empty->{$p->{name}}->{size};
3850 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
3852 $p->{poolusage} = 0;
3856 SELECT VolStatus AS volstatus, count(MediaId) AS nb
3858 WHERE PoolId=$p->{poolid}
3859 AND Media.MediaType = '$p->{mediatype}'
3863 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
3864 foreach my $t (values %$content) {
3865 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
3870 $self->display({ ID => $cur_id++,
3871 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
3872 Pools => [ values %$all ]},
3873 "display_pool.tpl");
3876 sub display_running_job
3879 return if $self->cant_do('r_view_running_job');
3881 my $arg = $self->get_form('jobid');
3883 return $self->error("Can't get jobid") unless ($arg->{jobid});
3885 # get security filter
3886 my $filter = $self->get_client_filter();
3889 SELECT Client.Name AS name, Job.Name AS jobname,
3891 FROM Job INNER JOIN Client USING (ClientId) $filter
3892 WHERE Job.JobId = $arg->{jobid}
3895 my $row = $self->dbh_selectrow_hashref($query);
3898 $arg->{client} = $row->{name};
3900 return $self->error("Can't get client");
3904 SELECT temp.jobname AS jobname, AVG(JobBytes) AS jobbytes,
3905 AVG(JobFiles) AS jobfiles, COUNT(1) AS nb
3907 SELECT Job.Name AS jobname,
3908 JobBytes AS jobbytes,
3909 JobFiles AS jobfiles
3910 FROM Job INNER JOIN Client USING (ClientId) $filter
3911 WHERE Job.Name = '$row->{jobname}'
3912 AND Job.Level = '$row->{level}'
3913 AND Job.JobStatus = 'T'
3914 ORDER BY StartTime DESC
3916 ) AS temp GROUP BY temp.jobname
3919 $row = $self->dbh_selectrow_hashref($query);
3922 if ($row->{nb} >= 1) {
3923 $arg->{jobbytes} = $row->{jobbytes};
3924 $arg->{jobfiles} = $row->{jobfiles};
3926 $arg->{jobbytes} = $arg->{jobfiles} = 0;
3929 my $cli = new Bweb::Client(name => $arg->{client});
3930 $cli->display_running_job($self->{info}, $arg->{jobid},
3931 $arg->{jobfiles}, $arg->{jobbytes});
3932 if ($arg->{jobid}) {
3933 $self->get_job_log();
3937 sub display_running_jobs
3939 my ($self, $display_action) = @_;
3940 return if $self->cant_do('r_view_running_job');
3942 # get security filter
3943 my $filter = $self->get_client_filter();
3946 SELECT Job.JobId AS jobid,
3947 Job.Name AS jobname,
3949 Job.StartTime AS starttime,
3950 Job.JobFiles AS jobfiles,
3951 Job.JobBytes AS jobbytes,
3952 Job.JobStatus AS jobstatus,
3953 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
3954 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
3956 Client.Name AS clientname
3957 FROM Job INNER JOIN Client USING (ClientId) $filter
3959 JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
3961 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
3963 $self->display({ ID => $cur_id++,
3964 display_action => $display_action,
3965 Jobs => [ values %$all ]},
3966 "running_job.tpl") ;
3969 # return the autochanger list to update
3973 $self->can_do('r_media_mgnt');
3976 my $arg = $self->get_form('jmedias');
3978 unless ($arg->{jmedias}) {
3979 return $self->error("Can't get media selection");
3983 SELECT Media.VolumeName AS volumename,
3984 Storage.Name AS storage,
3985 Location.Location AS location,
3987 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
3988 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3989 WHERE Media.VolumeName IN ($arg->{jmedias})
3990 AND Media.InChanger = 1
3993 my $all = $self->dbh_selectall_hashref($query, 'volumename');
3995 foreach my $vol (values %$all) {
3996 my $a = $self->ach_get($vol->{location});
3998 $ret{$vol->{location}} = 1;
4000 unless ($a->{have_status}) {
4002 $a->{have_status} = 1;
4005 print "eject $vol->{volumename} from $vol->{storage} : ";
4006 if ($a->send_to_io($vol->{slot})) {
4007 print "<img src='/bweb/T.png' alt='ok'><br/>";
4009 print "<img src='/bweb/E.png' alt='err'><br/>";
4019 my ($to, $subject, $content) = (CGI::param('email'),
4020 CGI::param('subject'),
4021 CGI::param('content'));
4022 $to =~ s/[^\w\d\.\@<>,]//;
4023 $subject =~ s/[^\w\d\.\[\]]/ /;
4025 open(MAIL, "|mail -s '$subject' '$to'") ;
4026 print MAIL $content;
4036 my $arg = $self->get_form('jobid', 'client');
4038 print CGI::header('text/brestore');
4039 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
4040 print "client=$arg->{client}\n" if ($arg->{client});
4041 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
4045 # TODO : move this to Bweb::Autochanger ?
4046 # TODO : make this internal to not eject tape ?
4052 my ($self, $name) = @_;
4055 return $self->error("Can't get your autochanger name ach");
4058 unless ($self->{info}->{ach_list}) {
4059 return $self->error("Could not find any autochanger");
4062 my $a = $self->{info}->{ach_list}->{$name};
4065 $self->error("Can't get your autochanger $name from your ach_list");
4070 $a->{debug} = $self->{debug};
4077 my ($self, $ach) = @_;
4078 $self->can_do('r_configure');
4080 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
4082 $self->{info}->save();
4090 $self->can_do('r_configure');
4092 my $arg = $self->get_form('ach');
4094 or !$self->{info}->{ach_list}
4095 or !$self->{info}->{ach_list}->{$arg->{ach}})
4097 return $self->error("Can't get autochanger name");
4100 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
4104 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
4106 my $b = $self->get_bconsole();
4108 my @storages = $b->list_storage() ;
4110 $ach->{devices} = [ map { { name => $_ } } @storages ];
4112 $self->display($ach, "ach_add.tpl");
4113 delete $ach->{drives};
4114 delete $ach->{devices};
4121 $self->can_do('r_configure');
4123 my $arg = $self->get_form('ach');
4126 or !$self->{info}->{ach_list}
4127 or !$self->{info}->{ach_list}->{$arg->{ach}})
4129 return $self->error("Can't get autochanger name");
4132 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
4134 $self->{info}->save();
4135 $self->{info}->view();
4141 $self->can_do('r_configure');
4143 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
4145 my $b = $self->get_bconsole();
4146 my @storages = $b->list_storage() ;
4148 unless ($arg->{ach}) {
4149 $arg->{devices} = [ map { { name => $_ } } @storages ];
4150 return $self->display($arg, "ach_add.tpl");
4154 foreach my $drive (CGI::param('drives'))
4156 unless (grep(/^$drive$/,@storages)) {
4157 return $self->error("Can't find $drive in storage list");
4160 my $index = CGI::param("index_$drive");
4161 unless (defined $index and $index =~ /^(\d+)$/) {
4162 return $self->error("Can't get $drive index");
4165 $drives[$index] = $drive;
4169 return $self->error("Can't get drives from Autochanger");
4172 my $a = new Bweb::Autochanger(name => $arg->{ach},
4173 precmd => $arg->{precmd},
4174 drive_name => \@drives,
4175 device => $arg->{device},
4176 mtxcmd => $arg->{mtxcmd});
4178 $self->ach_register($a) ;
4180 $self->{info}->view();
4186 $self->can_do('r_delete_job');
4188 my $arg = $self->get_form('jobid');
4190 if ($arg->{jobid}) {
4191 my $b = $self->get_bconsole();
4192 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
4196 title => "Delete a job ",
4197 name => "delete jobid=$arg->{jobid}",
4205 $self->can_do('r_media_mgnt');
4207 my $arg = $self->get_form(qw/media volstatus inchanger pool
4208 slot volretention voluseduration
4209 maxvoljobs maxvolfiles maxvolbytes
4210 qcomment poolrecycle enabled
4213 unless ($arg->{media}) {
4214 return $self->error("Can't find media selection");
4217 my $update = "update volume=$arg->{media} ";
4219 if ($arg->{volstatus}) {
4220 $update .= " volstatus=$arg->{volstatus} ";
4223 if ($arg->{inchanger}) {
4224 $update .= " inchanger=yes " ;
4226 $update .= " slot=$arg->{slot} ";
4229 $update .= " slot=0 inchanger=no ";
4232 if ($arg->{enabled}) {
4233 $update .= " enabled=$arg->{enabled} ";
4237 $update .= " pool=$arg->{pool} " ;
4240 if (defined $arg->{volretention}) {
4241 $update .= " volretention=\"$arg->{volretention}\" " ;
4244 if (defined $arg->{voluseduration}) {
4245 $update .= " voluse=\"$arg->{voluseduration}\" " ;
4248 if (defined $arg->{maxvoljobs}) {
4249 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
4252 if (defined $arg->{maxvolfiles}) {
4253 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
4256 if (defined $arg->{maxvolbytes}) {
4257 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
4260 if (defined $arg->{poolrecycle}) {
4261 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
4264 my $b = $self->get_bconsole();
4267 content => $b->send_cmd($update),
4268 title => "Update a volume ",
4274 my $media = $self->dbh_quote($arg->{media});
4276 my $loc = CGI::param('location') || '';
4278 $loc = $self->dbh_quote($loc); # is checked by db
4279 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
4281 if (!$arg->{qcomment}) {
4282 $arg->{qcomment} = "''";
4284 push @q, "Comment=$arg->{qcomment}";
4289 SET " . join (',', @q) . "
4290 WHERE Media.VolumeName = $media
4292 $self->dbh_do($query);
4294 $self->update_media();
4300 $self->can_do('r_autochanger_mgnt');
4302 my $ach = CGI::param('ach') ;
4303 $ach = $self->ach_get($ach);
4305 return $self->error("Bad autochanger name");
4309 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
4310 $b->update_slots($ach->{name});
4317 $self->can_do('r_view_log');
4319 my $arg = $self->get_form('jobid', 'limit', 'offset');
4320 unless ($arg->{jobid}) {
4321 return $self->error("Can't get jobid");
4324 if ($arg->{limit} == 100) {
4325 $arg->{limit} = 1000;
4327 # get security filter
4328 my $filter = $self->get_client_filter();
4331 SELECT Job.Name as name, Client.Name as clientname
4332 FROM Job INNER JOIN Client USING (ClientId) $filter
4333 WHERE JobId = $arg->{jobid}
4336 my $row = $self->dbh_selectrow_hashref($query);
4339 return $self->error("Can't find $arg->{jobid} in catalog");
4342 # display only Error and Warning messages
4344 if (CGI::param('error')) {
4345 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning|ERR=' ";
4349 if (CGI::param('time') || $self->{info}->{display_log_time}) {
4350 $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
4352 $logtext = 'LogText';
4356 SELECT count(1) AS nbline, JobId AS jobid,
4357 GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
4359 SELECT JobId, Time, LogText
4361 WHERE ( Log.JobId = $arg->{jobid}
4363 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
4364 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
4368 OFFSET $arg->{offset}
4374 my $log = $self->dbh_selectrow_hashref($query);
4376 return $self->error("Can't get log for jobid $arg->{jobid}");
4379 $self->display({ lines=> $log->{logtxt},
4380 nbline => $log->{nbline},
4381 jobid => $arg->{jobid},
4382 name => $row->{name},
4383 client => $row->{clientname},
4384 offset => $arg->{offset},
4385 limit => $arg->{limit},
4386 }, 'display_log.tpl');
4392 $self->can_do('r_media_mgnt');
4393 my $arg = $self->get_form('storage', 'pool', 'nb', 'media', 'offset');
4394 my $b = $self->get_bconsole();
4396 if (!$arg->{storage} || !$arg->{pool} || not defined $arg->{nb} || !$arg->{media} || !$arg->{offset}) {
4397 CGI::param(offset => 0);
4398 $arg = $self->get_form('db_pools');
4399 $arg->{storage} = [ map { { name => $_ } }$b->list_storage()];
4400 $self->display($arg, 'add_media.tpl');
4405 if ($arg->{nb} > 0) {
4406 $arg->{offset} = $arg->{offset}?$arg->{offset}:1;
4407 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n$arg->{nb}\n$arg->{media}\n$arg->{offset}\n";
4409 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n0\n$arg->{media}\n";
4415 CGI::param('media', '');
4416 CGI::param('re_media', $arg->{media});
4417 $self->display_media();
4423 $self->can_do('r_autochanger_mgnt');
4425 my $arg = $self->get_form('ach', 'slots', 'drive');
4427 unless ($arg->{ach}) {
4428 return $self->error("Can't find autochanger name");
4431 my $a = $self->ach_get($arg->{ach});
4433 return $self->error("Can't find autochanger name in configuration");
4436 my $storage = $a->get_drive_name($arg->{drive});
4438 return $self->error("Can't get your drive name");
4444 if ($arg->{slots}) {
4445 $slots = join(",", @{ $arg->{slots} });
4446 $slots_sql = " AND Slot IN ($slots) ";
4447 $t += 60*scalar( @{ $arg->{slots} }) ;
4450 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
4451 print "<h1>This command can take long time, be patient...</h1>";
4453 $b->label_barcodes(storage => $storage,
4454 drive => $arg->{drive},
4462 SET LocationId = (SELECT LocationId
4464 WHERE Location = '$arg->{ach}')
4466 WHERE (LocationId = 0 OR LocationId IS NULL)
4475 $self->can_do('r_purge');
4477 my @volume = CGI::param('media');
4480 return $self->error("Can't get media selection");
4483 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4485 foreach my $v (@volume) {
4487 content => $b->purge_volume($v),
4488 title => "Purge media",
4489 name => "purge volume=$v",
4498 $self->can_do('r_prune');
4500 my @volume = CGI::param('media');
4502 return $self->error("Can't get media selection");
4505 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4507 foreach my $v (@volume) {
4509 content => $b->prune_volume($v),
4510 title => "Prune volume",
4511 name => "prune volume=$v",
4520 $self->can_do('r_cancel_job');
4522 my $arg = $self->get_form('jobid');
4523 unless ($arg->{jobid}) {
4524 return $self->error("Can't get jobid");
4527 my $b = $self->get_bconsole();
4529 content => $b->cancel($arg->{jobid}),
4530 title => "Cancel job",
4531 name => "cancel jobid=$arg->{jobid}",
4537 # Warning, we display current fileset
4540 my $arg = $self->get_form('fileset');
4542 if ($arg->{fileset}) {
4543 my $b = $self->get_bconsole();
4544 my $ret = $b->get_fileset($arg->{fileset});
4545 $self->display({ fileset => $arg->{fileset},
4547 }, "fileset_view.tpl");
4549 $self->error("Can't get fileset name");
4553 sub director_show_sched
4556 $self->can_do('r_view_job');
4557 my $arg = $self->get_form('days');
4559 my $b = $self->get_bconsole();
4560 my $ret = $b->director_get_sched( $arg->{days} );
4565 }, "scheduled_job.tpl");
4568 sub enable_disable_job
4570 my ($self, $what) = @_ ;
4571 $self->can_do('r_run_job');
4573 my $name = CGI::param('job') || '';
4574 unless ($name =~ /^[\w\d\.\-\s]+$/) {
4575 return $self->error("Can't find job name");
4578 my $b = $self->get_bconsole();
4588 content => $b->send_cmd("$cmd job=\"$name\""),
4589 title => "$cmd $name",
4590 name => "$cmd job=\"$name\"",
4597 return new Bconsole(pref => $self->{info});
4603 $self->can_do('r_storage_mgnt');
4604 my $arg = $self->get_form(qw/storage storage_cmd drive/);
4605 my $b = $self->get_bconsole();
4607 if ($arg->{storage} and $arg->{storage_cmd}) {
4608 my $cmd = "$arg->{storage_cmd} storage=\"$arg->{storage}\" drive=$arg->{drive}";
4609 my $ret = $b->send_cmd($cmd);
4613 title => "Storage ",
4617 my $storages= [ map { { name => $_ } } $b->list_storage()];
4618 $self->display({ storage => $storages}, "cmd_storage.tpl");
4625 $self->can_do('r_run_job');
4627 my $b = $self->get_bconsole();
4629 my $joblist = [ map { { name => $_ } } $b->list_job() ];
4631 $self->display({ Jobs => $joblist }, "run_job.tpl");
4636 my ($self, $ouput) = @_;
4639 foreach my $l (split(/\r\n/, $ouput)) {
4640 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
4646 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
4652 foreach my $k (keys %arg) {
4653 $lowcase{lc($k)} = $arg{$k} ;
4662 $self->can_do('r_run_job');
4664 my $b = $self->get_bconsole();
4666 my $job = CGI::param('job') || '';
4668 # we take informations from director, and we overwrite with user wish
4669 my $info = $b->send_cmd("show job=\"$job\"");
4670 my $attr = $self->run_parse_job($info);
4672 my $arg = $self->get_form(qw/pool level client fileset storage media/);
4674 if (!$arg->{pool} and $arg->{media}) {
4675 my $r = $self->dbh_selectrow_hashref("
4676 SELECT Pool.Name AS name
4677 FROM Media JOIN Pool USING (PoolId)
4678 WHERE Media.VolumeName = '$arg->{media}'
4679 AND Pool.Name != 'Scratch'
4682 $arg->{pool} = $r->{name};
4686 my %job_opt = (%$attr, %$arg);
4688 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4690 my $pools = [ map { { name => $_ } } $b->list_pool() ];
4691 my $clients = [ map { { name => $_ } }$b->list_client()];
4692 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
4693 my $storages= [ map { { name => $_ } }$b->list_storage()];
4698 clients => $clients,
4699 filesets => $filesets,
4700 storages => $storages,
4702 }, "run_job_mod.tpl");
4708 $self->can_do('r_run_job');
4710 my $b = $self->get_bconsole();
4712 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4722 $self->can_do('r_run_job');
4724 my $b = $self->get_bconsole();
4726 # TODO: check input (don't use pool, level)
4728 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
4729 my $job = CGI::param('job') || '';
4730 my $storage = CGI::param('storage') || '';
4732 my $jobid = $b->run(job => $job,
4733 client => $arg->{client},
4734 priority => $arg->{priority},
4735 level => $arg->{level},
4736 storage => $storage,
4737 pool => $arg->{pool},
4738 fileset => $arg->{fileset},
4739 when => $arg->{when},
4744 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>";
4747 sub display_next_job
4751 my $arg = $self->get_form(qw/job begin end/);
4753 return $self->error("Can't get job name");
4756 my $b = $self->get_bconsole();
4758 my $job = $b->send_cmd("show job=\"$arg->{job}\"");
4759 my $attr = $self->run_parse_job($job);
4761 if (!$attr->{schedule}) {
4762 return $self->error("Can't get $arg->{job} schedule");
4764 my $jpool=$attr->{pool} || '';
4766 my $sched = new Bweb::Sched(bconsole => $b, name => $attr->{schedule},
4767 begin => $arg->{begin}, end => $arg->{end});
4769 my $ss = $sched->get_scheds($attr->{schedule});
4772 foreach my $s (@$ss) {
4773 my $level = $sched->get_level($s);
4774 my $pool = $sched->get_pool($s) || $jpool;
4775 my $evt = $sched->get_event($s);
4776 push @ret, map { "$_ : $pool ($level)\n" } @$evt;
4779 print "<b>$arg->{job}:</b><pre>", sort @ret, "</pre><br>";
4782 # check jobs against their schedule
4785 my ($self, $sched, $schedname, $job, $job_pool, $client) = @_;
4786 return undef if (!$self->can_view_client($client));
4788 my $sch = $sched->get_scheds($schedname);
4789 return undef if (!$sch);
4791 my $end = $sched->{end}; # this backup must have start before the next one
4793 foreach my $s (@$sch) {
4794 my $pool = $sched->get_pool($s) || $job_pool;
4795 my $level = $sched->get_level($s);
4796 my ($l) = ($level =~ m/^(.)/); # we keep the first letter
4797 my $evts = $sched->get_event($s);
4799 foreach my $evt (reverse @$evts) {
4800 my $all = $self->dbh_selectrow_hashref("
4802 FROM Job JOIN Pool USING (PoolId) JOIN Client USING (ClientId)
4803 WHERE Job.StartTime >= '$evt'
4804 AND Job.StartTime < '$end'
4806 AND Job.Name = '$job'
4807 AND Job.JobStatus = 'T'
4808 AND Job.Level = '$l'
4809 " . ($pool?" AND Pool.Name = '$pool' ":'') . "
4810 AND Client.Name = '$client'
4816 push @{$self->{tmp}}, {date => $evt, level => $level,
4817 type => 'Backup', name => $job,
4818 pool => $pool, volume => $pool};
4825 sub display_missing_job
4828 my $arg = $self->get_form(qw/begin end/);
4830 if (!$arg->{begin}) { # TODO: change this
4831 $arg->{begin} = strftime('%F %T', localtime(time - 24*60*60 ));
4834 $arg->{end} = strftime('%F %T', localtime(time));
4836 $self->{tmp} = []; # check_job use this for result
4838 my $bconsole = $self->get_bconsole();
4840 my $sched = new Bweb::Sched(bconsole => $bconsole,
4841 begin => $arg->{begin},
4842 end => $arg->{end});
4844 my $job = $bconsole->send_cmd("show job");
4845 my ($jname, $jsched, $jclient, $jpool);
4846 foreach my $j (split(/\r?\n/, $job)) {
4847 if ($j =~ /Job: name=([\w\d\-]+?) JobType=/i) {
4848 if ($jname and $jsched) {
4849 $self->check_job($sched, $jsched, $jname, $jpool, $jclient);
4852 $jclient = $jpool = $jsched = undef;
4853 } elsif ($j =~ /Client: name=(.+?) address=/i) {
4855 } elsif ($j =~ /Pool: name=([\w\d\-]+) PoolType=/i) {
4857 } elsif ($j =~ /Schedule: name=([\w\d\-]+)/i) {
4863 title => "Missing Job (since $arg->{begin} to $arg->{end})",
4864 list => $self->{tmp},
4865 }, "scheduled_job.tpl");
4867 delete $self->{tmp};