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/en/tpl';
58 new - creation a of new Bweb object
62 This function take an hash of argument and place them
65 IE : $obj = new Obj(name => 'test', age => '10');
67 $obj->{name} eq 'test' and $obj->{age} eq 10
73 my ($class, %arg) = @_;
78 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
85 my ($self, $what) = @_;
89 print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
91 print "<pre>$what</pre>";
98 error - display an error to the user
102 this function set $self->{error} with arg, display a message with
103 error.tpl and return 0
108 return $self->error("Can't use this file");
115 my ($self, $what) = @_;
116 $self->{error} = $what;
117 $self->display($self, 'error.tpl');
123 display - display an html page with HTML::Template
127 this function is use to render all html codes. it takes an
128 ref hash as arg in which all param are usable in template.
130 it will use global template_dir to search the template file.
132 hash keys are not sensitive. See HTML::Template for more
133 explanations about the hash ref. (it's can be quiet hard to understand)
137 $ref = { name => 'me', age => 26 };
138 $self->display($ref, "people.tpl");
144 my ($self, $hash, $tpl) = @_ ;
146 my $template = HTML::Template->new(filename => $tpl,
147 path =>[$template_dir],
148 die_on_bad_params => 0,
149 case_sensitive => 0);
151 foreach my $var (qw/limit offset/) {
153 unless ($hash->{$var}) {
154 my $value = CGI::param($var) || '';
156 if ($value =~ /^(\d+)$/) {
157 $template->param($var, $1) ;
162 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
163 $template->param('loginname', CGI::remote_user());
165 $template->param($hash);
166 print $template->output();
170 ################################################################
172 package Bweb::Config;
174 use base q/Bweb::Gui/;
178 Bweb::Config - read, write, display, modify configuration
182 this package is used for manage configuration
186 $conf = new Bweb::Config(config_file => '/path/to/conf');
197 =head1 PACKAGE VARIABLE
199 %k_re - hash of all acceptable option.
203 this variable permit to check all option with a regexp.
207 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
208 user => qr/^([\w\d\.-]+)$/i,
209 password => qr/^(.*)$/,
210 fv_write_path => qr!^([/\w\d\.-]*)$!,
211 template_dir => qr!^([/\w\d\.-]+)$!,
212 debug => qr/^(on)?$/,
213 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
214 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
215 bconsole => qr!^(.+)?$!,
216 syslog_file => qr!^(.+)?$!,
217 log_dir => qr!^(.+)?$!,
218 wiki_url => qr!(.*)$!,
219 stat_job_table => qr!^(\w*)$!,
220 display_log_time => qr!^(on)?$!,
221 enable_security => qr/^(on)?$/,
222 enable_security_acl => qr/^(on)?$/,
227 load - load config_file
231 this function load the specified config_file.
239 unless (open(FP, $self->{config_file}))
241 return $self->error("can't load config_file $self->{config_file} : $!");
243 my $f=''; my $tmpbuffer;
244 while(read FP,$tmpbuffer,4096)
252 no strict; # I have no idea of the contents of the file
259 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...") ;
262 foreach my $k (keys %$VAR1) {
263 $self->{$k} = $VAR1->{$k};
271 load_old - load old configuration format
279 unless (open(FP, $self->{config_file}))
281 return $self->error("$self->{config_file} : $!");
284 while (my $line = <FP>)
287 my ($k, $v) = split(/\s*=\s*/, $line, 2);
299 save - save the current configuration to config_file
307 if ($self->{ach_list}) {
308 # shortcut for display_begin
309 $self->{achs} = [ map {{ name => $_ }}
310 keys %{$self->{ach_list}}
314 unless (open(FP, ">$self->{config_file}"))
316 return $self->error("$self->{config_file} : $!\n" .
317 "You must add this to your config file\n"
318 . Data::Dumper::Dumper($self));
321 print FP Data::Dumper::Dumper($self);
329 edit, view, modify - html form ouput
337 $self->display($self, "config_edit.tpl");
343 $self->display($self, "config_view.tpl");
351 # we need to reset checkbox first
353 $self->{display_log_time} = 0;
354 $self->{enable_security} = 0;
355 $self->{enable_security_acl} = 0;
357 foreach my $k (CGI::param())
359 next unless (exists $k_re{$k}) ;
360 my $val = CGI::param($k);
361 if ($val =~ $k_re{$k}) {
364 $self->{error} .= "bad parameter : $k = [$val]";
370 if ($self->{error}) { # an error as occured
371 $self->display($self, 'error.tpl');
379 ################################################################
381 package Bweb::Client;
383 use base q/Bweb::Gui/;
387 Bweb::Client - Bacula FD
391 this package is use to do all Client operations like, parse status etc...
395 $client = new Bweb::Client(name => 'zog-fd');
396 $client->status(); # do a 'status client=zog-fd'
402 display_running_job - Html display of a running job
406 this function is used to display information about a current job
410 sub display_running_job
412 my ($self, $conf, $jobid) = @_ ;
414 my $status = $self->status($conf);
417 if ($status->{$jobid}) {
418 $self->display($status->{$jobid}, "client_job_status.tpl");
421 for my $id (keys %$status) {
422 $self->display($status->{$id}, "client_job_status.tpl");
429 $client = new Bweb::Client(name => 'plume-fd');
431 $client->status($bweb);
435 dirty hack to parse "status client=xxx-fd"
439 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
440 Backup Job started: 06-jun-06 17:22
441 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
442 Files Examined=10,697
443 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
449 JobName => Full_plume.2006-06-06_17.22.23,
452 Bytes => 194,484,132,
462 my ($self, $conf) = @_ ;
464 if (defined $self->{cur_jobs}) {
465 return $self->{cur_jobs} ;
469 my $b = new Bconsole(pref => $conf);
470 my $ret = $b->send_cmd("st client=$self->{name}");
474 for my $r (split(/\n/, $ret)) {
476 $r =~ s/(^\s+|\s+$)//g;
477 if ($r =~ /JobId (\d+) Job (\S+)/) {
479 $arg->{$jobid} = { @param, JobId => $jobid } ;
483 @param = ( JobName => $2 );
485 } elsif ($r =~ /=.+=/) {
486 push @param, split(/\s+|\s*=\s*/, $r) ;
488 } elsif ($r =~ /=/) { # one per line
489 push @param, split(/\s*=\s*/, $r) ;
491 } elsif ($r =~ /:/) { # one per line
492 push @param, split(/\s*:\s*/, $r, 2) ;
496 if ($jobid and @param) {
497 $arg->{$jobid} = { @param,
499 Client => $self->{name},
503 $self->{cur_jobs} = $arg ;
509 ################################################################
511 package Bweb::Autochanger;
513 use base q/Bweb::Gui/;
517 Bweb::Autochanger - Object to manage Autochanger
521 this package will parse the mtx output and manage drives.
525 $auto = new Bweb::Autochanger(precmd => 'sudo');
527 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
531 $auto->slot_is_full(10);
532 $auto->transfer(10, 11);
538 my ($class, %arg) = @_;
541 name => '', # autochanger name
542 label => {}, # where are volume { label1 => 40, label2 => drive0 }
543 drive => [], # drive use [ 'media1', 'empty', ..]
544 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
545 io => [], # io slot number list [ 41, 42, 43...]
546 info => {slot => 0, # informations (slot, drive, io)
550 mtxcmd => '/usr/sbin/mtx',
552 device => '/dev/changer',
553 precmd => '', # ssh command
554 bweb => undef, # link to bacula web object (use for display)
557 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
564 status - parse the output of mtx status
568 this function will launch mtx status and parse the output. it will
569 give a perlish view of the autochanger content.
571 it uses ssh if the autochanger is on a other host.
578 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
580 # TODO : reset all infos
581 $self->{info}->{drive} = 0;
582 $self->{info}->{slot} = 0;
583 $self->{info}->{io} = 0;
585 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
588 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
589 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
590 #Data Transfer Element 1:Empty
591 # Storage Element 1:Empty
592 # Storage Element 2:Full :VolumeTag=000002
593 # Storage Element 3:Empty
594 # Storage Element 4:Full :VolumeTag=000004
595 # Storage Element 5:Full :VolumeTag=000001
596 # Storage Element 6:Full :VolumeTag=000003
597 # Storage Element 7:Empty
598 # Storage Element 41 IMPORT/EXPORT:Empty
599 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
604 # Storage Element 7:Empty
605 # Storage Element 2:Full :VolumeTag=000002
606 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
609 $self->set_empty_slot($1);
611 $self->set_slot($1, $4);
614 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
617 $self->set_empty_drive($1);
619 $self->set_drive($1, $4, $6);
622 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
625 $self->set_empty_io($1);
627 $self->set_io($1, $4);
630 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
632 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
633 $self->{info}->{drive} = $1;
634 $self->{info}->{slot} = $2;
635 if ($l =~ /(\d+)\s+Import/) {
636 $self->{info}->{io} = $1 ;
638 $self->{info}->{io} = 0;
643 $self->debug($self) ;
648 my ($self, $slot) = @_;
651 if ($self->{slot}->[$slot] eq 'loaded') {
655 my $label = $self->{slot}->[$slot] ;
657 return $self->is_media_loaded($label);
662 my ($self, $drive, $slot) = @_;
664 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
665 return 0 if ($self->slot_is_full($slot)) ;
667 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
670 my $content = $self->get_slot($slot);
671 print "content = $content<br/> $drive => $slot<br/>";
672 $self->set_empty_drive($drive);
673 $self->set_slot($slot, $content);
676 $self->{error} = $out;
681 # TODO: load/unload have to use mtx script from bacula
684 my ($self, $drive, $slot) = @_;
686 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
687 return 0 unless ($self->slot_is_full($slot)) ;
689 print "Loading drive $drive with slot $slot<br/>\n";
690 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
693 my $content = $self->get_slot($slot);
694 print "content = $content<br/> $slot => $drive<br/>";
695 $self->set_drive($drive, $slot, $content);
698 $self->{error} = $out;
706 my ($self, $media) = @_;
708 unless ($self->{label}->{$media}) {
712 if ($self->{label}->{$media} =~ /drive\d+/) {
722 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
727 my ($self, $slot, $tag) = @_;
728 $self->{slot}->[$slot] = $tag || 'full';
729 push @{ $self->{io} }, $slot;
732 $self->{label}->{$tag} = $slot;
738 my ($self, $slot) = @_;
740 push @{ $self->{io} }, $slot;
742 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
743 $self->{slot}->[$slot] = 'empty';
749 my ($self, $slot) = @_;
750 return $self->{slot}->[$slot];
755 my ($self, $slot, $tag) = @_;
756 $self->{slot}->[$slot] = $tag || 'full';
759 $self->{label}->{$tag} = $slot;
765 my ($self, $slot) = @_;
767 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
768 $self->{slot}->[$slot] = 'empty';
774 my ($self, $drive) = @_;
775 $self->{drive}->[$drive] = 'empty';
780 my ($self, $drive, $slot, $tag) = @_;
781 $self->{drive}->[$drive] = $tag || $slot;
783 $self->{slot}->[$slot] = $tag || 'loaded';
786 $self->{label}->{$tag} = "drive$drive";
792 my ($self, $slot) = @_;
794 # slot don't exists => full
795 if (not defined $self->{slot}->[$slot]) {
799 if ($self->{slot}->[$slot] eq 'empty') {
802 return 1; # vol, full, loaded
805 sub slot_get_first_free
808 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
809 return $slot unless ($self->slot_is_full($slot));
813 sub io_get_first_free
817 foreach my $slot (@{ $self->{io} }) {
818 return $slot unless ($self->slot_is_full($slot));
825 my ($self, $media) = @_;
827 return $self->{label}->{$media} ;
832 my ($self, $media) = @_;
834 return defined $self->{label}->{$media} ;
839 my ($self, $slot) = @_;
841 unless ($self->slot_is_full($slot)) {
842 print "Autochanger $self->{name} slot $slot is empty\n";
847 if ($self->is_slot_loaded($slot)) {
850 print "Autochanger $self->{name} $slot is currently in use\n";
854 # autochanger must have I/O
855 unless ($self->have_io()) {
856 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
860 my $dst = $self->io_get_first_free();
863 print "Autochanger $self->{name} you must empty I/O first\n";
866 $self->transfer($slot, $dst);
871 my ($self, $src, $dst) = @_ ;
872 if ($self->{debug}) {
873 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
875 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
878 my $content = $self->get_slot($src);
879 $self->{slot}->[$src] = 'empty';
880 $self->set_slot($dst, $content);
883 $self->{error} = $out;
890 my ($self, $index) = @_;
891 return $self->{drive_name}->[$index];
894 # TODO : do a tapeinfo request to get informations
904 for my $slot (@{$self->{io}})
906 if ($self->is_slot_loaded($slot)) {
907 print "$slot is currently loaded\n";
911 if ($self->slot_is_full($slot))
913 my $free = $self->slot_get_first_free() ;
914 print "move $slot to $free :\n";
917 if ($self->transfer($slot, $free)) {
918 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
920 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
924 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
930 # TODO : this is with mtx status output,
931 # we can do an other function from bacula view (with StorageId)
935 my $bweb = $self->{bweb};
937 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
938 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
941 SELECT Media.VolumeName AS volumename,
942 Media.VolStatus AS volstatus,
943 Media.LastWritten AS lastwritten,
944 Media.VolBytes AS volbytes,
945 Media.MediaType AS mediatype,
947 Media.InChanger AS inchanger,
949 $bweb->{sql}->{FROM_UNIXTIME}(
950 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
951 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
954 INNER JOIN Pool USING (PoolId)
956 WHERE Media.VolumeName IN ($media_list)
959 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
961 # TODO : verify slot and bacula slot
965 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
967 if ($self->slot_is_full($slot)) {
969 my $vol = $self->{slot}->[$slot];
970 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
972 my $bslot = $all->{$vol}->{slot} ;
973 my $inchanger = $all->{$vol}->{inchanger};
975 # if bacula slot or inchanger flag is bad, we display a message
976 if ($bslot != $slot or !$inchanger) {
977 push @to_update, $slot;
980 $all->{$vol}->{realslot} = $slot;
982 push @{ $param }, $all->{$vol};
984 } else { # empty or no label
985 push @{ $param }, {realslot => $slot,
986 volstatus => 'Unknown',
987 volumename => $self->{slot}->[$slot]} ;
990 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
994 my $i=0; my $drives = [] ;
995 foreach my $d (@{ $self->{drive} }) {
996 $drives->[$i] = { index => $i,
997 load => $self->{drive}->[$i],
998 name => $self->{drive_name}->[$i],
1003 $bweb->display({ Name => $self->{name},
1004 nb_drive => $self->{info}->{drive},
1005 nb_io => $self->{info}->{io},
1008 Update => scalar(@to_update) },
1015 ################################################################
1017 package Bweb::Sched;
1018 use base q/Bweb::Gui/;
1022 Bweb::Sched() - Bweb package that parse show schedule ouput
1024 new Bweb::Sched(format => '%Y-%m-%d', year => 2008);
1028 my $b = $bweb->get_bconsole();
1029 my $s = $b->send_cmd("show schedule");
1030 my $sched = new Bweb::Sched();
1031 $sched->parse_scheds(split(/\r?\n/, $s));
1042 'level' => 'Differential',
1047 # cleanup and add a schedule
1050 my ($self, $name, $info) = @_;
1051 # bacula uses dates that start from 0, we start from 1
1052 foreach (@{$info->{mday}},@{$info->{month}}) { $_++ };
1055 $info->{event} = [ $self->get_events($info, $self->{format}) ];
1057 foreach my $i (qw/hour mday month wday wom woy mins/) {
1061 push @{$self->{schedules}->{$name}}, $info;
1064 # return the name of all schedules
1067 my ($self, $name) = @_;
1069 return keys %{ $self->{schedules} };
1072 # return an array of all schedule
1075 my ($self, $sched) = @_;
1076 return $self->{schedules}->{$sched};
1079 # return an ref array of all events
1080 # [ '2007-12-19 04:05', '2007-12-19 04:05' ]
1083 my ($self, $sched) = @_;
1084 return $sched->{event};
1087 # return the pool name
1090 my ($self, $sched) = @_;
1091 return $sched->{pool} || '';
1094 # return the level name (Incremental, Differential, Full)
1097 my ($self, $sched) = @_;
1098 return $sched->{level};
1101 # parse bacula sched bitmap
1104 my ($self, @output) = @_;
1111 foreach my $ligne (@output) {
1112 if ($ligne =~ /Schedule: name=(.+)/) {
1113 if ($name and $elt) {
1114 $elt->{level} = $run;
1115 $self->add_sched($name, $elt);
1120 elsif ($ligne =~ /Run Level=(.+)/) {
1121 if ($name and $elt) {
1122 $elt->{level} = $run;
1123 $self->add_sched($name, $elt);
1128 elsif ($ligne =~ /(hour|mday|month|mins)=(.+)/) {
1129 # All theses lines have the same format
1131 my ($k,$v) = ($1,$2);
1132 # we get all values (0 1 4 9)
1133 $elt->{$k}=[split (/\s/,$v)];
1135 # we make a bitmap for this kind of data (0 0 1 0 0 0 1) for a quick access
1136 elsif ($ligne =~ /(wday|wom|woy)=(.+)/) {
1137 my ($k,$v) = ($1,$2);
1138 foreach my $e (split (/\s/,$v)) {
1142 elsif ($ligne =~ /Pool: name=(.+?) PoolType/) {
1147 if ($name and $elt) {
1148 $elt->{level} = $run;
1149 $self->add_sched($name, $elt);
1153 use Date::Calc qw(:all);
1155 # read bacula schedule bitmap and get $format date string
1159 my ($self, $s,$format) = @_;
1160 my $year = $self->{year} || ((localtime)[5] + 1900);
1161 $format = $format || '%u-%02u-%02u %02u:%02u';
1163 foreach my $m (@{$s->{month}}) # mois de l'annee
1165 foreach my $md (@{$s->{mday}}) # jour du mois
1167 # print " m=$m md=$md\n";
1168 # we check if this day exists (31 fev)
1169 next if (!check_date($year,$m,$md));
1170 # print " check_date ok\n";
1172 my $w = ($md-1)/7; # we use the same thing than bacula
1173 next if (! $s->{wom}->[$w]);
1174 # print " wom ok\n";
1176 # on recupere le jour de la semaine
1177 my $wd = Day_of_Week($year,$m,$md);
1179 my ($w1,undef) = Week_of_Year($year,$m,$wd);
1180 next if (! $s->{woy}->[$w1-1]); # bacula 0-51
1181 # print " woy ok\n";
1183 $wd = 0 if ($wd == 7) ; # sunday is 0 on bacula
1184 next if (! $s->{wday}->[$wd]);
1185 # print " wday ok\n";
1187 foreach my $h (@{$s->{hour}}) # hour of the day
1189 foreach my $min (@{$s->{mins}}) # minute
1191 push @ret, sprintf($format, $year,$m,$md,$h,$min);
1199 ################################################################
1203 use base q/Bweb::Gui/;
1207 Bweb - main Bweb package
1211 this package is use to compute and display informations
1216 use POSIX qw/strftime/;
1218 our $config_file='/etc/bacula/bweb.conf';
1224 %sql_func - hash to make query mysql/postgresql compliant
1230 UNIX_TIMESTAMP => '',
1231 FROM_UNIXTIME => '',
1232 TO_SEC => " interval '1 second' * ",
1233 SEC_TO_INT => "SEC_TO_INT",
1236 STARTTIME_SEC => " date_trunc('sec', Job.StartTime) ",
1237 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1238 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1239 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1240 STARTTIME_WEEK => " date_trunc('week', Job.StartTime) ",
1241 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1242 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1243 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1244 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1245 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1246 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1250 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1251 FROM_UNIXTIME => 'FROM_UNIXTIME',
1254 SEC_TO_TIME => 'SEC_TO_TIME',
1255 MATCH => " REGEXP ",
1256 STARTTIME_SEC => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %T') ",
1257 STARTTIME_DAY => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d') ",
1258 STARTTIME_HOUR => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %H') ",
1259 STARTTIME_MONTH => " DATE_FORMAT(Job.StartTime, '%Y-%m') ",
1260 STARTTIME_WEEK => " DATE_FORMAT(Job.StartTime, '%Y-%v') ",
1261 STARTTIME_PHOUR=> " DATE_FORMAT(Job.StartTime, '%H') ",
1262 STARTTIME_PDAY => " DATE_FORMAT(Job.StartTime, '%d') ",
1263 STARTTIME_PMONTH => " DATE_FORMAT(Job.StartTime, '%m') ",
1264 STARTTIME_PWEEK => " DATE_FORMAT(Job.StartTime, '%v') ",
1265 # with mysql < 5, you have to play with the ugly SHOW command
1266 DB_SIZE => " SELECT 0 ",
1267 # works only with mysql 5
1268 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1269 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1270 CONCAT_SEP => " SEPARATOR '' ",
1277 return $self->{info}->{dbi} =~ /dbi:mysql/i;
1284 $self->{dbh}->disconnect();
1289 sub dbh_selectall_arrayref
1291 my ($self, $query) = @_;
1292 $self->connect_db();
1293 $self->debug($query);
1294 return $self->{dbh}->selectall_arrayref($query);
1299 my ($self, @what) = @_;
1300 return join(',', $self->dbh_quote(@what)) ;
1305 my ($self, @what) = @_;
1307 $self->connect_db();
1309 return map { $self->{dbh}->quote($_) } @what;
1311 return $self->{dbh}->quote($what[0]) ;
1317 my ($self, $query) = @_ ;
1318 $self->connect_db();
1319 $self->debug($query);
1320 return $self->{dbh}->do($query);
1323 sub dbh_selectall_hashref
1325 my ($self, $query, $join) = @_;
1327 $self->connect_db();
1328 $self->debug($query);
1329 return $self->{dbh}->selectall_hashref($query, $join) ;
1332 sub dbh_selectrow_hashref
1334 my ($self, $query) = @_;
1336 $self->connect_db();
1337 $self->debug($query);
1338 return $self->{dbh}->selectrow_hashref($query) ;
1343 my ($self, @what) = @_;
1344 if ($self->dbh_is_mysql()) {
1345 return 'CONCAT(' . join(',', @what) . ')' ;
1347 return join(' || ', @what);
1353 my ($self, $query) = @_;
1354 $self->debug($query, up => 1);
1355 return $self->{dbh}->prepare($query);
1361 my @unit = qw(B KB MB GB TB);
1362 my $val = shift || 0;
1364 my $format = '%i %s';
1365 while ($val / 1024 > 1) {
1369 $format = ($i>0)?'%0.1f %s':'%i %s';
1370 return sprintf($format, $val, $unit[$i]);
1373 # display Day, Hour, Year
1379 $val /= 60; # sec -> min
1381 if ($val / 60 <= 1) {
1385 $val /= 60; # min -> hour
1386 if ($val / 24 <= 1) {
1387 return "$val hours";
1390 $val /= 24; # hour -> day
1391 if ($val / 365 < 2) {
1395 $val /= 365 ; # day -> year
1397 return "$val years";
1403 my $val = shift || 0;
1405 if ($val eq '1' or $val eq "yes") {
1407 } elsif ($val eq '2' or $val eq "archived") {
1415 sub from_human_enabled
1417 my $val = shift || 0;
1419 if ($val eq '1' or $val eq "yes") {
1421 } elsif ($val eq '2' or $val eq "archived") {
1428 # get Day, Hour, Year
1434 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1438 my %times = ( m => 60,
1444 my $mult = $times{$2} || 0;
1454 unless ($self->{dbh}) {
1456 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1457 $self->{info}->{user},
1458 $self->{info}->{password});
1460 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1461 unless ($self->{dbh});
1463 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1465 if ($self->dbh_is_mysql()) {
1466 $self->{dbh}->do("SET group_concat_max_len=1000000");
1468 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1475 my ($class, %arg) = @_;
1477 dbh => undef, # connect_db();
1479 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1485 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1487 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1488 $self->{sql} = $sql_func{$1};
1491 $self->{loginname} = CGI::remote_user();
1492 $self->{debug} = $self->{info}->{debug};
1493 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1501 $self->display($self->{info}, "begin.tpl");
1507 $self->display($self->{info}, "end.tpl");
1513 my $where=''; # by default
1515 my $arg = $self->get_form("client", "qre_client",
1516 "jclient_groups", "qnotingroup");
1518 if ($arg->{qre_client}) {
1519 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1520 } elsif ($arg->{client}) {
1521 $where = "WHERE Name = '$arg->{client}' ";
1522 } elsif ($arg->{jclient_groups}) {
1523 # $filter could already contains client_group_member
1525 JOIN client_group_member USING (ClientId)
1526 JOIN client_group USING (client_group_id)
1527 WHERE client_group_name IN ($arg->{jclient_groups}) ";
1528 } elsif ($arg->{qnotingroup}) {
1531 (SELECT 1 FROM client_group_member
1532 WHERE Client.ClientId = client_group_member.ClientId
1538 SELECT Name AS name,
1540 AutoPrune AS autoprune,
1541 FileRetention AS fileretention,
1542 JobRetention AS jobretention
1543 FROM Client " . $self->get_client_filter() .
1546 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1548 my $dsp = { ID => $cur_id++,
1549 clients => [ values %$all] };
1551 $self->display($dsp, "client_list.tpl") ;
1556 my ($self, %arg) = @_;
1561 if ($arg{since} and $arg{age}) {
1562 my $now = "$self->{sql}->{UNIX_TIMESTAMP}(TIMESTAMP '$arg{since}')";
1564 AND $self->{sql}->{UNIX_TIMESTAMP}(StartTime) > $now
1565 AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) < ($now + $self->{sql}->{TO_SEC}($arg{age}))";
1566 $label .= "since $arg{since} and during " . human_sec($arg{age});
1568 } elsif ($arg{age}) {
1570 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1572 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1574 $self->{sql}->{TO_SEC}($arg{age})
1577 $label = "last " . human_sec($arg{age});
1580 if ($arg{groupby}) {
1581 $limit .= " GROUP BY $arg{groupby} ";
1585 $limit .= " ORDER BY $arg{order} ";
1589 $limit .= " LIMIT $arg{limit} ";
1590 $label .= " limited to $arg{limit}";
1594 $limit .= " OFFSET $arg{offset} ";
1595 $label .= " with $arg{offset} offset ";
1599 $label = 'no filter';
1602 return ($limit, $label);
1607 $bweb->get_form(...) - Get useful stuff
1611 This function get and check parameters against regexp.
1613 If word begin with 'q', the return will be quoted or join quoted
1614 if it's end with 's'.
1619 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1622 qclient => 'plume-fd',
1623 qpools => "'plume-fd', 'test-fd', '...'",
1630 my ($self, @what) = @_;
1631 my %what = map { $_ => 1 } @what;
1654 my %opt_ss =( # string with space
1658 my %opt_s = ( # default to ''
1679 my %opt_p = ( # option with path
1686 my %opt_r = (regexwhere => 1);
1687 my %opt_d = ( # option with date
1692 foreach my $i (@what) {
1693 if (exists $opt_i{$i}) {# integer param
1694 my $value = CGI::param($i) || $opt_i{$i} ;
1695 if ($value =~ /^(\d+)$/) {
1698 } elsif ($opt_s{$i}) { # simple string param
1699 my $value = CGI::param($i) || '';
1700 if ($value =~ /^([\w\d\.-]+)$/) {
1703 } elsif ($opt_ss{$i}) { # simple string param (with space)
1704 my $value = CGI::param($i) || '';
1705 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1708 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1709 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1711 $ret{$i} = $self->dbh_join(@value) ;
1714 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1715 my $value = CGI::param($1) ;
1717 $ret{$i} = $self->dbh_quote($value);
1720 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1721 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1722 grep { ! /^\s*$/ } CGI::param($1) ];
1723 } elsif (exists $opt_p{$i}) {
1724 my $value = CGI::param($i) || '';
1725 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1728 } elsif (exists $opt_r{$i}) {
1729 my $value = CGI::param($i) || '';
1730 if ($value =~ /^([^'"']+)$/) {
1733 } elsif (exists $opt_d{$i}) {
1734 my $value = CGI::param($i) || '';
1735 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1742 foreach my $s (CGI::param('slot')) {
1743 if ($s =~ /^(\d+)$/) {
1744 push @{$ret{slots}}, $s;
1750 my $age = $ret{age} || $opt_i{age};
1751 my $since = CGI::param('since') || strftime('%F %T', localtime(time - $age));
1752 if ($since =~ /^(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)$/) {
1758 my $when = CGI::param('when') || '';
1759 if ($when =~ /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
1764 if ($what{db_clients}) {
1766 if ($what{filter}) {
1767 # get security filter only if asked
1768 $filter = $self->get_client_filter();
1772 SELECT Client.Name as clientname
1776 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1777 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1781 if ($what{db_client_groups}) {
1783 if ($what{filter}) {
1784 # get security filter only if asked
1785 $filter = $self->get_client_group_filter();
1789 SELECT client_group_name AS name
1790 FROM client_group $filter
1792 my $grps = $self->dbh_selectall_hashref($query, 'name');
1793 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1797 if ($what{db_usernames}) {
1802 my $users = $self->dbh_selectall_hashref($query, 'username');
1803 $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} }
1807 if ($what{db_roles}) {
1812 my $r = $self->dbh_selectall_hashref($query, 'rolename');
1813 $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} }
1817 if ($what{db_mediatypes}) {
1819 SELECT MediaType as mediatype
1822 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1823 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1827 if ($what{db_locations}) {
1829 SELECT Location as location, Cost as cost
1832 my $loc = $self->dbh_selectall_hashref($query, 'location');
1833 $ret{db_locations} = [ sort { $a->{location}
1839 if ($what{db_pools}) {
1840 my $query = "SELECT Name as name FROM Pool";
1842 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1843 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1846 if ($what{db_filesets}) {
1848 SELECT FileSet.FileSet AS fileset
1851 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1853 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1854 values %$filesets] ;
1857 if ($what{db_jobnames}) {
1859 if ($what{filter}) {
1860 $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
1863 SELECT DISTINCT Job.Name AS jobname
1866 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1868 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1869 values %$jobnames] ;
1872 if ($what{db_devices}) {
1874 SELECT Device.Name AS name
1877 my $devices = $self->dbh_selectall_hashref($query, 'name');
1879 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1889 $self->can_do('r_view_stat');
1890 my $fields = $self->get_form(qw/age level status clients filesets
1891 graph gtype type filter db_clients
1892 limit db_filesets width height
1893 qclients qfilesets qjobnames db_jobnames/);
1895 my $url = CGI::url(-full => 0,
1898 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1900 # this organisation is to keep user choice between 2 click
1901 # TODO : fileset and client selection doesn't work
1908 if ($fields->{gtype} eq 'balloon') {
1909 system("./bgraph.pl");
1913 sub get_selected_media_location
1917 my $media = $self->get_form('jmedias');
1919 unless ($media->{jmedias}) {
1924 SELECT Media.VolumeName AS volumename, Location.Location AS location
1925 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1926 WHERE Media.VolumeName IN ($media->{jmedias})
1929 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1931 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1940 my ($self, $in) = @_ ;
1941 $self->can_do('r_media_mgnt');
1942 my $media = $self->get_selected_media_location();
1948 my $elt = $self->get_form('db_locations');
1950 $self->display({ ID => $cur_id++,
1951 enabled => human_enabled($in),
1952 %$elt, # db_locations
1954 sort { $a->{volumename} cmp $b->{volumename} } values %$media
1963 $self->can_do('r_media_mgnt');
1965 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1967 $self->display($elt, "help_extern.tpl");
1970 sub help_extern_compute
1973 $self->can_do('r_media_mgnt');
1975 my $number = CGI::param('limit') || '' ;
1976 unless ($number =~ /^(\d+)$/) {
1977 return $self->error("Bad arg number : $number ");
1980 my ($sql, undef) = $self->get_param('pools',
1981 'locations', 'mediatypes');
1984 SELECT Media.VolumeName AS volumename,
1985 Media.VolStatus AS volstatus,
1986 Media.LastWritten AS lastwritten,
1987 Media.MediaType AS mediatype,
1988 Media.VolMounts AS volmounts,
1990 Media.Recycle AS recycle,
1991 $self->{sql}->{FROM_UNIXTIME}(
1992 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1993 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1996 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1997 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1999 WHERE Media.InChanger = 1
2000 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
2002 ORDER BY expire DESC, recycle, Media.VolMounts DESC
2006 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2008 $self->display({ Media => [ values %$all ] },
2009 "help_extern_compute.tpl");
2015 $self->can_do('r_media_mgnt');
2017 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
2018 $self->display($param, "help_intern.tpl");
2021 sub help_intern_compute
2024 $self->can_do('r_media_mgnt');
2026 my $number = CGI::param('limit') || '' ;
2027 unless ($number =~ /^(\d+)$/) {
2028 return $self->error("Bad arg number : $number ");
2031 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
2033 if (CGI::param('expired')) {
2035 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2036 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2042 SELECT Media.VolumeName AS volumename,
2043 Media.VolStatus AS volstatus,
2044 Media.LastWritten AS lastwritten,
2045 Media.MediaType AS mediatype,
2046 Media.VolMounts AS volmounts,
2048 $self->{sql}->{FROM_UNIXTIME}(
2049 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2050 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2053 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2054 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
2056 WHERE Media.InChanger <> 1
2057 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
2058 AND Media.Recycle = 1
2060 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
2064 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2066 $self->display({ Media => [ values %$all ] },
2067 "help_intern_compute.tpl");
2073 my ($self, %arg) = @_ ;
2075 my ($limit, $label) = $self->get_limit(%arg);
2079 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
2080 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
2081 (SELECT count(Job.JobId) FROM Job) AS nb_job,
2082 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
2083 ($self->{sql}->{DB_SIZE}) AS db_size,
2084 (SELECT count(Job.JobId)
2086 WHERE Job.JobStatus IN ('E','e','f','A')
2089 (SELECT count(Client.ClientId) FROM Client) AS nb_client
2092 my $row = $self->dbh_selectrow_hashref($query) ;
2094 $row->{nb_bytes} = human_size($row->{nb_bytes});
2096 $row->{db_size} = human_size($row->{db_size});
2097 $row->{label} = $label;
2099 $self->display($row, "general.tpl");
2104 my ($self, @what) = @_ ;
2105 my %elt = map { $_ => 1 } @what;
2110 if ($elt{clients}) {
2111 my @clients = grep { ! /^\s*$/ } CGI::param('client');
2113 $ret{clients} = \@clients;
2114 my $str = $self->dbh_join(@clients);
2115 $limit .= "AND Client.Name IN ($str) ";
2119 if ($elt{client_groups}) {
2120 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
2122 $ret{client_groups} = \@clients;
2123 my $str = $self->dbh_join(@clients);
2124 $limit .= "AND client_group_name IN ($str) ";
2128 if ($elt{filesets}) {
2129 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
2131 $ret{filesets} = \@filesets;
2132 my $str = $self->dbh_join(@filesets);
2133 $limit .= "AND FileSet.FileSet IN ($str) ";
2137 if ($elt{mediatypes}) {
2138 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
2140 $ret{mediatypes} = \@media;
2141 my $str = $self->dbh_join(@media);
2142 $limit .= "AND Media.MediaType IN ($str) ";
2147 my $client = CGI::param('client');
2148 $ret{client} = $client;
2149 $client = $self->dbh_join($client);
2150 $limit .= "AND Client.Name = $client ";
2154 my $level = CGI::param('level') || '';
2155 if ($level =~ /^(\w)$/) {
2157 $limit .= "AND Job.Level = '$1' ";
2162 my $jobid = CGI::param('jobid') || '';
2164 if ($jobid =~ /^(\d+)$/) {
2166 $limit .= "AND Job.JobId = '$1' ";
2171 my $status = CGI::param('status') || '';
2172 if ($status =~ /^(\w)$/) {
2175 $limit .= "AND Job.JobStatus IN ('f','E') ";
2176 } elsif ($1 eq 'W') {
2177 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
2179 $limit .= "AND Job.JobStatus = '$1' ";
2184 if ($elt{volstatus}) {
2185 my $status = CGI::param('volstatus') || '';
2186 if ($status =~ /^(\w+)$/) {
2188 $limit .= "AND Media.VolStatus = '$1' ";
2192 if ($elt{locations}) {
2193 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
2195 $ret{locations} = \@location;
2196 my $str = $self->dbh_join(@location);
2197 $limit .= "AND Location.Location IN ($str) ";
2202 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
2204 $ret{pools} = \@pool;
2205 my $str = $self->dbh_join(@pool);
2206 $limit .= "AND Pool.Name IN ($str) ";
2210 if ($elt{location}) {
2211 my $location = CGI::param('location') || '';
2213 $ret{location} = $location;
2214 $location = $self->dbh_quote($location);
2215 $limit .= "AND Location.Location = $location ";
2220 my $pool = CGI::param('pool') || '';
2223 $pool = $self->dbh_quote($pool);
2224 $limit .= "AND Pool.Name = $pool ";
2228 if ($elt{jobtype}) {
2229 my $jobtype = CGI::param('jobtype') || '';
2230 if ($jobtype =~ /^(\w)$/) {
2232 $limit .= "AND Job.Type = '$1' ";
2236 return ($limit, %ret);
2247 my ($self, %arg) = @_ ;
2248 return if $self->cant_do('r_view_job');
2250 $arg{order} = ' Job.JobId DESC ';
2252 my ($limit, $label) = $self->get_limit(%arg);
2253 my ($where, undef) = $self->get_param('clients',
2262 if (CGI::param('client_group')) {
2264 JOIN client_group_member USING (ClientId)
2265 JOIN client_group USING (client_group_id)
2268 my $filter = $self->get_client_filter();
2271 SELECT Job.JobId AS jobid,
2272 Client.Name AS client,
2273 FileSet.FileSet AS fileset,
2274 Job.Name AS jobname,
2276 StartTime AS starttime,
2278 Pool.Name AS poolname,
2279 JobFiles AS jobfiles,
2280 JobBytes AS jobbytes,
2281 JobStatus AS jobstatus,
2282 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2283 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2286 JobErrors AS joberrors
2288 FROM Client $filter $cgq,
2289 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2290 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2291 WHERE Client.ClientId=Job.ClientId
2292 AND Job.JobStatus NOT IN ('R', 'C')
2297 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2299 $self->display({ Filter => $label,
2303 sort { $a->{jobid} <=> $b->{jobid} }
2310 # display job informations
2311 sub display_job_zoom
2313 my ($self, $jobid) = @_ ;
2314 $self->can_do('r_view_job');
2316 $jobid = $self->dbh_quote($jobid);
2318 # get security filter
2319 my $filter = $self->get_client_filter();
2322 SELECT DISTINCT Job.JobId AS jobid,
2323 Client.Name AS client,
2324 Job.Name AS jobname,
2325 FileSet.FileSet AS fileset,
2327 Pool.Name AS poolname,
2328 StartTime AS starttime,
2329 JobFiles AS jobfiles,
2330 JobBytes AS jobbytes,
2331 JobStatus AS jobstatus,
2332 JobErrors AS joberrors,
2333 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2334 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2336 FROM Client $filter,
2337 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2338 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2339 WHERE Client.ClientId=Job.ClientId
2340 AND Job.JobId = $jobid
2343 my $row = $self->dbh_selectrow_hashref($query) ;
2345 # display all volumes associate with this job
2347 SELECT Media.VolumeName as volumename
2348 FROM Job,Media,JobMedia
2349 WHERE Job.JobId = $jobid
2350 AND JobMedia.JobId=Job.JobId
2351 AND JobMedia.MediaId=Media.MediaId
2354 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2356 $row->{volumes} = [ values %$all ] ;
2357 $row->{wiki_url} = $self->{info}->{wiki_url};
2359 $self->display($row, "display_job_zoom.tpl");
2362 sub display_job_group
2364 my ($self, %arg) = @_;
2365 $self->can_do('r_view_job');
2367 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2369 my ($where, undef) = $self->get_param('client_groups',
2372 my $filter = $self->get_client_group_filter();
2375 SELECT client_group_name AS client_group_name,
2376 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2377 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2378 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2379 COALESCE(jobok.nbjobs,0) AS nbjobok,
2380 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2381 COALESCE(jobok.duration, '0:0:0') AS duration
2383 FROM client_group $filter LEFT JOIN (
2384 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2385 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2386 SUM(JobErrors) AS joberrors,
2387 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2388 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2391 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2392 JOIN client_group USING (client_group_id)
2394 WHERE JobStatus = 'T'
2397 ) AS jobok USING (client_group_name) LEFT JOIN
2400 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2401 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2402 SUM(JobErrors) AS joberrors
2403 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2404 JOIN client_group USING (client_group_id)
2406 WHERE JobStatus IN ('f','E', 'A')
2409 ) AS joberr USING (client_group_name)
2413 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2415 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2418 $self->display($rep, "display_job_group.tpl");
2423 my ($self, %arg) = @_ ;
2424 $self->can_do('r_view_media');
2426 my ($limit, $label) = $self->get_limit(%arg);
2427 my ($where, %elt) = $self->get_param('pools',
2432 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2434 if ($arg->{jmedias}) {
2435 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2437 if ($arg->{qre_media}) {
2438 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2440 if ($arg->{expired}) {
2442 AND VolStatus = 'Full'
2443 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2444 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2445 ) < NOW() " . $where ;
2449 SELECT Media.VolumeName AS volumename,
2450 Media.VolBytes AS volbytes,
2451 Media.VolStatus AS volstatus,
2452 Media.MediaType AS mediatype,
2453 Media.InChanger AS online,
2454 Media.LastWritten AS lastwritten,
2455 Location.Location AS location,
2456 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2457 Pool.Name AS poolname,
2458 $self->{sql}->{FROM_UNIXTIME}(
2459 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2460 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2463 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2464 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2465 Media.MediaType AS MediaType
2467 WHERE Media.VolStatus = 'Full'
2468 GROUP BY Media.MediaType
2469 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2471 WHERE Media.PoolId=Pool.PoolId
2476 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2478 $self->display({ ID => $cur_id++,
2480 Location => $elt{location},
2481 Media => [ values %$all ],
2483 "display_media.tpl");
2486 sub display_allmedia
2490 my $pool = $self->get_form('db_pools');
2492 foreach my $name (@{ $pool->{db_pools} }) {
2493 CGI::param('pool', $name->{name});
2494 $self->display_media();
2498 sub display_media_zoom
2502 my $media = $self->get_form('jmedias');
2504 unless ($media->{jmedias}) {
2505 return $self->error("Can't get media selection");
2509 SELECT InChanger AS online,
2510 Media.Enabled AS enabled,
2511 VolBytes AS nb_bytes,
2512 VolumeName AS volumename,
2513 VolStatus AS volstatus,
2514 VolMounts AS nb_mounts,
2515 Media.VolUseDuration AS voluseduration,
2516 Media.MaxVolJobs AS maxvoljobs,
2517 Media.MaxVolFiles AS maxvolfiles,
2518 Media.MaxVolBytes AS maxvolbytes,
2519 VolErrors AS nb_errors,
2520 Pool.Name AS poolname,
2521 Location.Location AS location,
2522 Media.Recycle AS recycle,
2523 Media.VolRetention AS volretention,
2524 Media.LastWritten AS lastwritten,
2525 Media.VolReadTime/1000000 AS volreadtime,
2526 Media.VolWriteTime/1000000 AS volwritetime,
2527 Media.RecycleCount AS recyclecount,
2528 Media.Comment AS comment,
2529 $self->{sql}->{FROM_UNIXTIME}(
2530 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2531 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2534 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2535 WHERE Pool.PoolId = Media.PoolId
2536 AND VolumeName IN ($media->{jmedias})
2539 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2541 foreach my $media (values %$all) {
2542 my $mq = $self->dbh_quote($media->{volumename});
2545 SELECT DISTINCT Job.JobId AS jobid,
2547 Job.StartTime AS starttime,
2550 Job.JobFiles AS files,
2551 Job.JobBytes AS bytes,
2552 Job.jobstatus AS status
2553 FROM Media,JobMedia,Job
2554 WHERE Media.VolumeName=$mq
2555 AND Media.MediaId=JobMedia.MediaId
2556 AND JobMedia.JobId=Job.JobId
2559 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2562 SELECT LocationLog.Date AS date,
2563 Location.Location AS location,
2564 LocationLog.Comment AS comment
2565 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2566 WHERE Media.MediaId = LocationLog.MediaId
2567 AND Media.VolumeName = $mq
2571 my $log = $self->dbh_selectall_arrayref($query) ;
2573 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2576 $self->display({ jobs => [ values %$jobs ],
2577 LocationLog => $logtxt,
2579 "display_media_zoom.tpl");
2586 $self->can_do('r_location_mgnt');
2588 my $loc = $self->get_form('qlocation');
2589 unless ($loc->{qlocation}) {
2590 return $self->error("Can't get location");
2594 SELECT Location.Location AS location,
2595 Location.Cost AS cost,
2596 Location.Enabled AS enabled
2598 WHERE Location.Location = $loc->{qlocation}
2601 my $row = $self->dbh_selectrow_hashref($query);
2602 $row->{enabled} = human_enabled($row->{enabled});
2603 $self->display({ ID => $cur_id++,
2604 %$row }, "location_edit.tpl") ;
2610 $self->can_do('r_location_mgnt');
2612 my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
2613 unless ($arg->{qlocation}) {
2614 return $self->error("Can't get location");
2616 unless ($arg->{qnewlocation}) {
2617 return $self->error("Can't get new location name");
2619 unless ($arg->{cost}) {
2620 return $self->error("Can't get new cost");
2623 my $enabled = from_human_enabled($arg->{enabled});
2626 UPDATE Location SET Cost = $arg->{cost},
2627 Location = $arg->{qnewlocation},
2629 WHERE Location.Location = $arg->{qlocation}
2632 $self->dbh_do($query);
2634 $self->location_display();
2640 $self->can_do('r_location_mgnt');
2642 my $arg = $self->get_form(qw/qlocation/) ;
2644 unless ($arg->{qlocation}) {
2645 return $self->error("Can't get location");
2649 SELECT count(Media.MediaId) AS nb
2650 FROM Media INNER JOIN Location USING (LocationID)
2651 WHERE Location = $arg->{qlocation}
2654 my $res = $self->dbh_selectrow_hashref($query);
2657 return $self->error("Sorry, the location must be empty");
2661 DELETE FROM Location WHERE Location = $arg->{qlocation}
2664 $self->dbh_do($query);
2666 $self->location_display();
2672 $self->can_do('r_location_mgnt');
2674 my $arg = $self->get_form(qw/qlocation cost/) ;
2676 unless ($arg->{qlocation}) {
2677 $self->display({}, "location_add.tpl");
2680 unless ($arg->{cost}) {
2681 return $self->error("Can't get new cost");
2684 my $enabled = CGI::param('enabled') || '';
2685 $enabled = from_human_enabled($enabled);
2688 INSERT INTO Location (Location, Cost, Enabled)
2689 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2692 $self->dbh_do($query);
2694 $self->location_display();
2697 sub location_display
2702 SELECT Location.Location AS location,
2703 Location.Cost AS cost,
2704 Location.Enabled AS enabled,
2705 (SELECT count(Media.MediaId)
2707 WHERE Media.LocationId = Location.LocationId
2712 my $location = $self->dbh_selectall_hashref($query, 'location');
2714 $self->display({ ID => $cur_id++,
2715 Locations => [ values %$location ] },
2716 "display_location.tpl");
2723 my $media = $self->get_selected_media_location();
2728 my $arg = $self->get_form('db_locations', 'qnewlocation');
2730 $self->display({ email => $self->{info}->{email_media},
2732 media => [ values %$media ],
2734 "update_location.tpl");
2737 ###########################################################
2742 $self->can_do('r_group_mgnt');
2744 my $grp = $self->get_form(qw/qclient_group db_clients/);
2746 unless ($grp->{qclient_group}) {
2747 $self->display({ ID => $cur_id++,
2748 client_group => "''",
2750 }, "groups_edit.tpl");
2756 FROM Client JOIN client_group_member using (clientid)
2757 JOIN client_group using (client_group_id)
2758 WHERE client_group_name = $grp->{qclient_group}
2761 my $row = $self->dbh_selectall_hashref($query, "name");
2763 $self->display({ ID => $cur_id++,
2764 client_group => $grp->{qclient_group},
2766 client_group_member => [ values %$row]},
2773 $self->can_do('r_group_mgnt');
2775 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2777 if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
2779 INSERT INTO client_group (client_group_name)
2780 VALUES ($arg->{qnewgroup})
2782 $self->dbh_do($query);
2783 $arg->{qclient_group} = $arg->{qnewgroup};
2786 unless ($arg->{qclient_group}) {
2787 return $self->error("Can't get groups");
2790 $self->{dbh}->begin_work();
2793 DELETE FROM client_group_member
2794 WHERE client_group_id IN
2795 (SELECT client_group_id
2797 WHERE client_group_name = $arg->{qclient_group})
2799 $self->dbh_do($query);
2801 if ($arg->{jclients}) {
2803 INSERT INTO client_group_member (clientid, client_group_id)
2805 (SELECT client_group_id
2807 WHERE client_group_name = $arg->{qclient_group})
2808 FROM Client WHERE Name IN ($arg->{jclients})
2811 $self->dbh_do($query);
2813 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2816 SET client_group_name = $arg->{qnewgroup}
2817 WHERE client_group_name = $arg->{qclient_group}
2820 $self->dbh_do($query);
2823 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2825 $self->display_groups();
2831 $self->can_do('r_group_mgnt');
2833 my $arg = $self->get_form(qw/qclient_group/);
2835 unless ($arg->{qclient_group}) {
2836 return $self->error("Can't get groups");
2839 $self->{dbh}->begin_work();
2842 DELETE FROM client_group_member
2843 WHERE client_group_id IN
2844 (SELECT client_group_id
2846 WHERE client_group_name = $arg->{qclient_group})");
2849 DELETE FROM bweb_client_group_acl
2850 WHERE client_group_id IN
2851 (SELECT client_group_id
2853 WHERE client_group_name = $arg->{qclient_group})");
2856 DELETE FROM client_group
2857 WHERE client_group_name = $arg->{qclient_group}");
2859 $self->{dbh}->commit();
2860 $self->display_groups();
2868 if ($self->cant_do('r_group_mgnt')) {
2869 $arg = $self->get_form(qw/db_client_groups filter/) ;
2871 $arg = $self->get_form(qw/db_client_groups/) ;
2874 if ($self->{dbh}->errstr) {
2875 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2880 $self->display({ ID => $cur_id++,
2882 "display_groups.tpl");
2885 ###########################################################
2890 if (not $self->{info}->{enable_security}) {
2893 # admin is a special user that can do everything
2894 if ($self->{loginname} eq 'admin') {
2897 if (!$self->{loginname}) {
2898 $self->error("Can't get your login name");
2899 $self->display_end();
2903 if (defined $self->{security}) {
2906 $self->{security} = {};
2907 my $u = $self->dbh_quote($self->{loginname});
2910 SELECT use_acl, rolename
2912 JOIN bweb_role_member USING (userid)
2913 JOIN bweb_role USING (roleid)
2916 my $rows = $self->dbh_selectall_arrayref($query);
2917 # do cache with this role
2918 if (!$rows or !scalar(@$rows)) {
2919 $self->error("Can't get $self->{loginname}'s roles");
2920 $self->display_end();
2923 foreach my $r (@$rows) {
2924 $self->{security}->{$r->[1]}=1;
2927 $self->{security}->{use_acl} = $rows->[0]->[0];
2933 my ($self, $action) = @_;
2934 # is security enabled in configuration ?
2935 if (not $self->{info}->{enable_security}) {
2938 # admin is a special user that can do everything
2939 if ($self->{loginname} eq 'admin') {
2943 if (!$self->{loginname}) {
2944 $self->{error} = "Can't do $action, your are not logged. " .
2945 "Check security with your administrator";
2948 if (!$self->get_roles()) {
2951 if (!$self->{security}->{$action}) {
2953 "$self->{loginname} sorry, but this action ($action) " .
2954 "is not permited. " .
2955 "Check security with your administrator";
2961 # make like an assert (program die)
2964 my ($self, $action) = @_;
2965 if ($self->cant_do($action)) {
2966 $self->error($self->{error});
2967 $self->display_end();
2977 if (!$self->{info}->{enable_security} or
2978 !$self->{info}->{enable_security_acl})
2983 if ($self->get_roles()) {
2984 return $self->{security}->{use_acl};
2990 # JOIN Client USING (ClientId) " . $b->get_client_filter() . "
2991 sub get_client_filter
2993 my ($self, $login) = @_;
2996 $u = $self->dbh_quote($login);
2997 } elsif ($self->use_filter()) {
2998 $u = $self->dbh_quote($self->{loginname});
3003 JOIN (SELECT ClientId FROM client_group_member
3004 JOIN client_group USING (client_group_id)
3005 JOIN bweb_client_group_acl USING (client_group_id)
3006 JOIN bweb_user USING (userid)
3007 WHERE bweb_user.username = $u
3008 ) AS filter USING (ClientId)";
3011 #JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
3012 sub get_client_group_filter
3014 my ($self, $login) = @_;
3017 $u = $self->dbh_quote($login);
3018 } elsif ($self->use_filter()) {
3019 $u = $self->dbh_quote($self->{loginname});
3024 JOIN (SELECT client_group_id
3025 FROM bweb_client_group_acl
3026 JOIN bweb_user USING (userid)
3027 WHERE bweb_user.username = $u
3028 ) AS filter USING (client_group_id)";
3031 # role and username have to be quoted before
3032 # role and username can be a quoted list
3035 my ($self, $role, $username) = @_;
3036 $self->can_do("r_user_mgnt");
3038 my $nb = $self->dbh_do("
3039 DELETE FROM bweb_role_member
3040 WHERE roleid = (SELECT roleid FROM bweb_role
3041 WHERE rolename IN ($role))
3042 AND userid = (SELECT userid FROM bweb_user
3043 WHERE username IN ($username))");
3047 # role and username have to be quoted before
3048 # role and username can be a quoted list
3051 my ($self, $role, $username) = @_;
3052 $self->can_do("r_user_mgnt");
3054 my $nb = $self->dbh_do("
3055 INSERT INTO bweb_role_member (roleid, userid)
3056 SELECT roleid, userid FROM bweb_role, bweb_user
3057 WHERE rolename IN ($role)
3058 AND username IN ($username)
3063 # role and username have to be quoted before
3064 # role and username can be a quoted list
3067 my ($self, $copy, $user) = @_;
3068 $self->can_do("r_user_mgnt");
3070 my $nb = $self->dbh_do("
3071 INSERT INTO bweb_role_member (roleid, userid)
3072 SELECT roleid, a.userid
3073 FROM bweb_user AS a, bweb_role_member
3074 JOIN bweb_user USING (userid)
3075 WHERE bweb_user.username = $copy
3076 AND a.username = $user");
3080 # username can be a join quoted list of usernames
3083 my ($self, $username) = @_;
3084 $self->can_do("r_user_mgnt");
3087 DELETE FROM bweb_role_member
3091 WHERE username in ($username))");
3093 DELETE FROM bweb_client_group_acl
3097 WHERE username IN ($username))");
3104 $self->can_do("r_user_mgnt");
3106 my $arg = $self->get_form(qw/jusernames/);
3108 unless ($arg->{jusernames}) {
3109 return $self->error("Can't get user");
3112 $self->{dbh}->begin_work();
3114 $self->revoke_all($arg->{jusernames});
3116 DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
3118 $self->{dbh}->commit();
3120 $self->display_users();
3126 $self->can_do("r_user_mgnt");
3128 # we don't quote username directly to check that it is conform
3129 my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate qcopy_username jclient_groups/) ;
3131 if (not $arg->{qcreate}) {
3132 $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
3133 $self->display($arg, "display_user.tpl");
3137 my $u = $self->dbh_quote($arg->{username});
3139 $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
3141 if (!$arg->{qpasswd}) {
3142 $arg->{qpasswd} = "''";
3144 if (!$arg->{qcomment}) {
3145 $arg->{qcomment} = "''";
3148 # will fail if user already exists
3149 # UPDATE with mysql dbi does not return if update is ok
3152 SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment},
3153 use_acl=$arg->{use_acl}
3154 WHERE username = $u")
3155 # and (! $self->dbh_is_mysql() )
3158 INSERT INTO bweb_user (username, passwd, use_acl, comment)
3159 VALUES ($u, $arg->{qpasswd}, $arg->{use_acl}, $arg->{qcomment})");
3161 $self->{dbh}->begin_work();
3163 $self->revoke_all($u);
3165 if ($arg->{qcopy_username}) {
3166 $self->grant_like($arg->{qcopy_username}, $u);
3168 $self->grant($arg->{jrolenames}, $u);
3171 if ($arg->{jclient_groups}) {
3173 INSERT INTO bweb_client_group_acl (client_group_id, userid)
3174 SELECT client_group_id, userid
3175 FROM client_group, bweb_user
3176 WHERE client_group_name IN ($arg->{jclient_groups})
3181 $self->{dbh}->commit();
3183 $self->display_users();
3186 # TODO: we miss a matrix with all user/roles
3190 $self->can_do("r_user_mgnt");
3192 my $arg = $self->get_form(qw/db_usernames/) ;
3194 if ($self->{dbh}->errstr) {
3195 return $self->error("Can't use users with bweb, read INSTALL to enable them");
3198 $self->display({ ID => $cur_id++,
3200 "display_users.tpl");
3206 $self->can_do("r_user_mgnt");
3208 my $arg = $self->get_form('username');
3209 my $user = $self->dbh_quote($arg->{username});
3211 my $userp = $self->dbh_selectrow_hashref("
3212 SELECT username, passwd, comment, use_acl
3214 WHERE username = $user
3217 return $self->error("Can't find $user in catalog");
3219 my $filter = $self->get_client_group_filter($arg->{username});
3220 my $scg = $self->dbh_selectall_hashref("
3221 SELECT client_group_name AS name
3222 FROM client_group $filter
3226 #------------+--------
3231 my $role = $self->dbh_selectall_hashref("
3232 SELECT rolename, temp.userid
3234 LEFT JOIN (SELECT roleid, userid
3235 FROM bweb_user JOIN bweb_role_member USING (userid)
3236 WHERE username = $user) AS temp USING (roleid)
3240 $arg = $self->get_form(qw/db_usernames db_client_groups/);
3243 db_usernames => $arg->{db_usernames},
3244 username => $userp->{username},
3245 comment => $userp->{comment},
3246 passwd => $userp->{passwd},
3247 use_acl => $userp->{use_acl},
3248 db_client_groups => $arg->{db_client_groups},
3249 client_group => [ values %$scg ],
3250 db_roles => [ values %$role],
3251 }, "display_user.tpl");
3255 ###########################################################
3257 sub get_media_max_size
3259 my ($self, $type) = @_;
3261 "SELECT avg(VolBytes) AS size
3263 WHERE Media.VolStatus = 'Full'
3264 AND Media.MediaType = '$type'
3267 my $res = $self->selectrow_hashref($query);
3270 return $res->{size};
3280 my $media = $self->get_form('qmedia');
3282 unless ($media->{qmedia}) {
3283 return $self->error("Can't get media");
3287 SELECT Media.Slot AS slot,
3288 PoolMedia.Name AS poolname,
3289 Media.VolStatus AS volstatus,
3290 Media.InChanger AS inchanger,
3291 Location.Location AS location,
3292 Media.VolumeName AS volumename,
3293 Media.MaxVolBytes AS maxvolbytes,
3294 Media.MaxVolJobs AS maxvoljobs,
3295 Media.MaxVolFiles AS maxvolfiles,
3296 Media.VolUseDuration AS voluseduration,
3297 Media.VolRetention AS volretention,
3298 Media.Comment AS comment,
3299 PoolRecycle.Name AS poolrecycle,
3300 Media.Enabled AS enabled
3302 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
3303 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
3304 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3306 WHERE Media.VolumeName = $media->{qmedia}
3309 my $row = $self->dbh_selectrow_hashref($query);
3310 $row->{volretention} = human_sec($row->{volretention});
3311 $row->{voluseduration} = human_sec($row->{voluseduration});
3312 $row->{enabled} = human_enabled($row->{enabled});
3314 my $elt = $self->get_form(qw/db_pools db_locations/);
3319 }, "update_media.tpl");
3325 $self->can_do('r_media_mgnt');
3327 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
3329 unless ($arg->{jmedias}) {
3330 return $self->error("Can't get selected media");
3333 unless ($arg->{qnewlocation}) {
3334 return $self->error("Can't get new location");
3339 SET LocationId = (SELECT LocationId
3341 WHERE Location = $arg->{qnewlocation})
3342 WHERE Media.VolumeName IN ($arg->{jmedias})
3345 my $nb = $self->dbh_do($query);
3347 print "$nb media updated, you may have to update your autochanger.";
3349 $self->display_media();
3355 $self->can_do('r_media_mgnt');
3357 my $media = $self->get_selected_media_location();
3359 return $self->error("Can't get media selection");
3361 my $newloc = CGI::param('newlocation');
3363 my $user = CGI::param('user') || 'unknown';
3364 my $comm = CGI::param('comment') || '';
3365 $comm = $self->dbh_quote("$user: $comm");
3367 my $arg = $self->get_form('enabled');
3368 my $en = from_human_enabled($arg->{enabled});
3369 my $b = $self->get_bconsole();
3372 foreach my $vol (keys %$media) {
3374 INSERT INTO LocationLog (Date,Comment,MediaId,LocationId,NewEnabled,NewVolStatus)
3375 SELECT NOW(), $comm, Media.MediaId, Location.LocationId, $en, VolStatus
3376 FROM Media, Location
3377 WHERE Media.VolumeName = '$vol'
3378 AND Location.Location = '$media->{$vol}->{location}'
3380 $self->dbh_do($query);
3381 $self->debug($query);
3382 $b->send_cmd("update volume=\"$vol\" enabled=$en");
3387 $q->param('action', 'update_location');
3388 my $url = $q->url(-full => 1, -query=>1);
3390 $self->display({ email => $self->{info}->{email_media},
3392 newlocation => $newloc,
3393 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
3394 media => [ values %$media ],
3396 "change_location.tpl");
3400 sub display_client_stats
3402 my ($self, %arg) = @_ ;
3403 $self->can_do('r_view_stat');
3405 my $client = $self->dbh_quote($arg{clientname});
3406 # get security filter
3407 my $filter = $self->get_client_filter();
3409 my ($limit, $label) = $self->get_limit(%arg);
3412 count(Job.JobId) AS nb_jobs,
3413 sum(Job.JobBytes) AS nb_bytes,
3414 sum(Job.JobErrors) AS nb_err,
3415 sum(Job.JobFiles) AS nb_files,
3416 Client.Name AS clientname
3417 FROM Job JOIN Client USING (ClientId) $filter
3419 Client.Name = $client
3421 GROUP BY Client.Name
3424 my $row = $self->dbh_selectrow_hashref($query);
3426 $row->{ID} = $cur_id++;
3427 $row->{label} = $label;
3428 $row->{grapharg} = "client";
3430 $self->display($row, "display_client_stats.tpl");
3434 sub display_group_stats
3436 my ($self, %arg) = @_ ;
3438 my $carg = $self->get_form(qw/qclient_group/);
3440 unless ($carg->{qclient_group}) {
3441 return $self->error("Can't get group");
3444 my ($limit, $label) = $self->get_limit(%arg);
3448 count(Job.JobId) AS nb_jobs,
3449 sum(Job.JobBytes) AS nb_bytes,
3450 sum(Job.JobErrors) AS nb_err,
3451 sum(Job.JobFiles) AS nb_files,
3452 client_group.client_group_name AS clientname
3453 FROM Job JOIN Client USING (ClientId)
3454 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
3455 JOIN client_group USING (client_group_id)
3457 client_group.client_group_name = $carg->{qclient_group}
3459 GROUP BY client_group.client_group_name
3462 my $row = $self->dbh_selectrow_hashref($query);
3464 $row->{ID} = $cur_id++;
3465 $row->{label} = $label;
3466 $row->{grapharg} = "client_group";
3468 $self->display($row, "display_client_stats.tpl");
3471 # [ name, num, value, joberrors, nb_job ] =>
3473 # [ { name => 'ALL',
3474 # events => [ { num => 1, label => '2007-01',
3475 # value => 'T', title => 10 },
3476 # { num => 2, label => '2007-02',
3477 # value => 'R', title => 11 },
3480 # { name => 'Other',
3484 sub make_overview_tab
3486 my ($self, $q) = @_;
3487 my $ret = $self->dbh_selectall_arrayref($q);
3491 for my $elt (@$ret) {
3492 if ($cur_name and $cur_name ne $elt->[0]) { # order by name, num
3493 push @items, { name => $cur_name, events => $events};
3496 $cur_name = $elt->[0];
3498 { num => $elt->[1], status => $elt->[2],
3499 joberrors => $elt->[3], title => "$elt->[4] jobs"};
3501 push @items, { name => $cur_name, events => $events};
3505 sub get_time_overview
3507 my ($self, $arg) = @_; # want since et age from get_form();
3508 my $type = $arg->{type} || 'day';
3509 if ($type =~ /^(day|week|hour|month)$/) {
3515 my $jobt = $self->{info}->{stat_job_table} || 'Job';
3516 my $stime1 = $self->{sql}->{"STARTTIME_P" . $type}; # get 1,2,3
3517 $stime1 =~ s/Job.StartTime/date/;
3518 my $stime2 = $self->{sql}->{"STARTTIME_" . $type}; # get 2007-01-03, 2007-01-23
3520 my ($limit, $label) = $self->get_limit('since' => $arg->{since},
3521 'age' => $arg->{age});
3522 return ($stime1, $stime2, $limit, $label, $jobt);
3525 # lu ma me je ve sa di
3526 # groupe1 v v x w v v v overview
3527 # |-- s1 v v v v v v v overview_zoom
3528 # |-- s2 v v x v v v v
3529 # `-- s3 v v v w v v v
3530 sub display_overview_zoom
3533 $self->can_do('r_view_stat');
3535 my $arg = $self->get_form(qw/jclient_groups age since type/);
3537 if (!$arg->{jclient_groups}) {
3538 return $self->error("Can't get client_group selection");
3540 my ($filter2, undef) = $self->get_param(qw/level jobtype/);
3541 my ($stime1, $stime2, $limit, $label, $jobt) = $self->get_time_overview($arg);
3543 my $filter = $self->get_client_filter();
3545 SELECT name, $stime1 AS num,
3546 JobStatus AS value, joberrors, nb_job
3548 SELECT $stime2 AS date,
3549 Client.Name AS name,
3550 MAX(severity) AS severity,
3552 SUM(JobErrors) AS joberrors
3554 JOIN client_group_member USING (ClientId)
3555 JOIN client_group USING (client_group_id)
3556 JOIN Client USING (ClientId) $filter
3557 JOIN Status USING (JobStatus)
3558 WHERE client_group_name IN ($arg->{jclient_groups})
3561 GROUP BY Client.Name, date
3562 ) AS sub JOIN Status USING (severity)
3565 my $items = $self->make_overview_tab($q);
3566 $self->display({label => $label,
3567 action => "job;since=$arg->{since};type=$arg->{type};age=$arg->{age};client=",
3568 items => $items}, "overview.tpl");
3571 sub display_overview
3574 $self->can_do('r_view_stat');
3576 my $arg = $self->get_form(qw/jclient_groups age since type/);
3577 my ($filter2, undef) = $self->get_param(qw/client_groups level jobtype/);
3578 my $filter3 = $self->get_client_group_filter();
3579 my ($stime1, $stime2, $filter1, $label, $jobt) = $self->get_time_overview($arg);
3582 SELECT name, $stime1 AS num,
3583 JobStatus AS value, joberrors, nb_job
3585 SELECT $stime2 AS date,
3586 client_group_name AS name,
3587 MAX(severity) AS severity,
3589 SUM(JobErrors) AS joberrors
3591 JOIN client_group_member USING (ClientId)
3592 JOIN client_group USING (client_group_id) $filter3
3593 JOIN Status USING (JobStatus)
3594 WHERE true $filter1 $filter2
3595 GROUP BY client_group_name, date
3596 ) AS sub JOIN Status USING (severity)
3599 my $items = $self->make_overview_tab($q);
3600 $self->display({label=>$label,
3601 action => "overview_zoom;since=$arg->{since};type=$arg->{type};age=$arg->{age};client_group=",
3602 items => $items}, "overview.tpl");
3606 # poolname can be undef
3609 my ($self, $poolname) = @_ ;
3610 $self->can_do('r_view_media');
3615 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
3616 if ($arg->{jmediatypes}) {
3617 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
3618 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
3621 # TODO : afficher les tailles et les dates
3624 SELECT subq.volmax AS volmax,
3625 subq.volnum AS volnum,
3626 subq.voltotal AS voltotal,
3628 Pool.Recycle AS recycle,
3629 Pool.VolRetention AS volretention,
3630 Pool.VolUseDuration AS voluseduration,
3631 Pool.MaxVolJobs AS maxvoljobs,
3632 Pool.MaxVolFiles AS maxvolfiles,
3633 Pool.MaxVolBytes AS maxvolbytes,
3634 subq.PoolId AS PoolId,
3635 subq.MediaType AS mediatype,
3636 $self->{sql}->{CAT_POOL_TYPE} AS uniq
3639 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
3640 count(Media.MediaId) AS volnum,
3641 sum(Media.VolBytes) AS voltotal,
3642 Media.PoolId AS PoolId,
3643 Media.MediaType AS MediaType
3645 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
3646 Media.MediaType AS MediaType
3648 WHERE Media.VolStatus = 'Full'
3649 GROUP BY Media.MediaType
3650 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
3651 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
3653 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
3657 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
3660 SELECT Pool.Name AS name,
3661 sum(VolBytes) AS size
3662 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
3663 WHERE Media.VolStatus IN ('Recycled', 'Purged')
3667 my $empty = $self->dbh_selectall_hashref($query, 'name');
3669 foreach my $p (values %$all) {
3670 if ($p->{volmax} > 0) { # mysql returns 0.0000
3671 # we remove Recycled/Purged media from pool usage
3672 if (defined $empty->{$p->{name}}) {
3673 $p->{voltotal} -= $empty->{$p->{name}}->{size};
3675 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
3677 $p->{poolusage} = 0;
3681 SELECT VolStatus AS volstatus, count(MediaId) AS nb
3683 WHERE PoolId=$p->{poolid}
3684 AND Media.MediaType = '$p->{mediatype}'
3688 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
3689 foreach my $t (values %$content) {
3690 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
3695 $self->display({ ID => $cur_id++,
3696 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
3697 Pools => [ values %$all ]},
3698 "display_pool.tpl");
3701 sub display_running_job
3704 return if $self->cant_do('r_view_running_job');
3706 my $arg = $self->get_form('client', 'jobid');
3708 if (!$arg->{client} and $arg->{jobid}) {
3709 # get security filter
3710 my $filter = $self->get_client_filter();
3713 SELECT Client.Name AS name
3714 FROM Job INNER JOIN Client USING (ClientId) $filter
3715 WHERE Job.JobId = $arg->{jobid}
3718 my $row = $self->dbh_selectrow_hashref($query);
3721 $arg->{client} = $row->{name};
3722 CGI::param('client', $arg->{client});
3726 if ($arg->{client}) {
3727 my $cli = new Bweb::Client(name => $arg->{client});
3728 $cli->display_running_job($self->{info}, $arg->{jobid});
3729 if ($arg->{jobid}) {
3730 $self->get_job_log();
3733 $self->error("Can't get client or jobid");
3737 sub display_running_jobs
3739 my ($self, $display_action) = @_;
3740 return if $self->cant_do('r_view_running_job');
3742 # get security filter
3743 my $filter = $self->get_client_filter();
3746 SELECT Job.JobId AS jobid,
3747 Job.Name AS jobname,
3749 Job.StartTime AS starttime,
3750 Job.JobFiles AS jobfiles,
3751 Job.JobBytes AS jobbytes,
3752 Job.JobStatus AS jobstatus,
3753 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
3754 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
3756 Client.Name AS clientname
3757 FROM Job INNER JOIN Client USING (ClientId) $filter
3759 JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
3761 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
3763 $self->display({ ID => $cur_id++,
3764 display_action => $display_action,
3765 Jobs => [ values %$all ]},
3766 "running_job.tpl") ;
3769 # return the autochanger list to update
3773 $self->can_do('r_media_mgnt');
3776 my $arg = $self->get_form('jmedias');
3778 unless ($arg->{jmedias}) {
3779 return $self->error("Can't get media selection");
3783 SELECT Media.VolumeName AS volumename,
3784 Storage.Name AS storage,
3785 Location.Location AS location,
3787 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
3788 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3789 WHERE Media.VolumeName IN ($arg->{jmedias})
3790 AND Media.InChanger = 1
3793 my $all = $self->dbh_selectall_hashref($query, 'volumename');
3795 foreach my $vol (values %$all) {
3796 my $a = $self->ach_get($vol->{location});
3798 $ret{$vol->{location}} = 1;
3800 unless ($a->{have_status}) {
3802 $a->{have_status} = 1;
3805 print "eject $vol->{volumename} from $vol->{storage} : ";
3806 if ($a->send_to_io($vol->{slot})) {
3807 print "<img src='/bweb/T.png' alt='ok'><br/>";
3809 print "<img src='/bweb/E.png' alt='err'><br/>";
3819 my ($to, $subject, $content) = (CGI::param('email'),
3820 CGI::param('subject'),
3821 CGI::param('content'));
3822 $to =~ s/[^\w\d\.\@<>,]//;
3823 $subject =~ s/[^\w\d\.\[\]]/ /;
3825 open(MAIL, "|mail -s '$subject' '$to'") ;
3826 print MAIL $content;
3836 my $arg = $self->get_form('jobid', 'client');
3838 print CGI::header('text/brestore');
3839 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3840 print "client=$arg->{client}\n" if ($arg->{client});
3841 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3845 # TODO : move this to Bweb::Autochanger ?
3846 # TODO : make this internal to not eject tape ?
3852 my ($self, $name) = @_;
3855 return $self->error("Can't get your autochanger name ach");
3858 unless ($self->{info}->{ach_list}) {
3859 return $self->error("Could not find any autochanger");
3862 my $a = $self->{info}->{ach_list}->{$name};
3865 $self->error("Can't get your autochanger $name from your ach_list");
3870 $a->{debug} = $self->{debug};
3877 my ($self, $ach) = @_;
3878 $self->can_do('r_configure');
3880 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3882 $self->{info}->save();
3890 $self->can_do('r_configure');
3892 my $arg = $self->get_form('ach');
3894 or !$self->{info}->{ach_list}
3895 or !$self->{info}->{ach_list}->{$arg->{ach}})
3897 return $self->error("Can't get autochanger name");
3900 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3904 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3906 my $b = $self->get_bconsole();
3908 my @storages = $b->list_storage() ;
3910 $ach->{devices} = [ map { { name => $_ } } @storages ];
3912 $self->display($ach, "ach_add.tpl");
3913 delete $ach->{drives};
3914 delete $ach->{devices};
3921 $self->can_do('r_configure');
3923 my $arg = $self->get_form('ach');
3926 or !$self->{info}->{ach_list}
3927 or !$self->{info}->{ach_list}->{$arg->{ach}})
3929 return $self->error("Can't get autochanger name");
3932 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3934 $self->{info}->save();
3935 $self->{info}->view();
3941 $self->can_do('r_configure');
3943 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3945 my $b = $self->get_bconsole();
3946 my @storages = $b->list_storage() ;
3948 unless ($arg->{ach}) {
3949 $arg->{devices} = [ map { { name => $_ } } @storages ];
3950 return $self->display($arg, "ach_add.tpl");
3954 foreach my $drive (CGI::param('drives'))
3956 unless (grep(/^$drive$/,@storages)) {
3957 return $self->error("Can't find $drive in storage list");
3960 my $index = CGI::param("index_$drive");
3961 unless (defined $index and $index =~ /^(\d+)$/) {
3962 return $self->error("Can't get $drive index");
3965 $drives[$index] = $drive;
3969 return $self->error("Can't get drives from Autochanger");
3972 my $a = new Bweb::Autochanger(name => $arg->{ach},
3973 precmd => $arg->{precmd},
3974 drive_name => \@drives,
3975 device => $arg->{device},
3976 mtxcmd => $arg->{mtxcmd});
3978 $self->ach_register($a) ;
3980 $self->{info}->view();
3986 $self->can_do('r_delete_job');
3988 my $arg = $self->get_form('jobid');
3990 if ($arg->{jobid}) {
3991 my $b = $self->get_bconsole();
3992 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3996 title => "Delete a job ",
3997 name => "delete jobid=$arg->{jobid}",
4005 $self->can_do('r_media_mgnt');
4007 my $arg = $self->get_form(qw/media volstatus inchanger pool
4008 slot volretention voluseduration
4009 maxvoljobs maxvolfiles maxvolbytes
4010 qcomment poolrecycle enabled
4013 unless ($arg->{media}) {
4014 return $self->error("Can't find media selection");
4017 my $update = "update volume=$arg->{media} ";
4019 if ($arg->{volstatus}) {
4020 $update .= " volstatus=$arg->{volstatus} ";
4023 if ($arg->{inchanger}) {
4024 $update .= " inchanger=yes " ;
4026 $update .= " slot=$arg->{slot} ";
4029 $update .= " slot=0 inchanger=no ";
4032 if ($arg->{enabled}) {
4033 $update .= " enabled=$arg->{enabled} ";
4037 $update .= " pool=$arg->{pool} " ;
4040 if (defined $arg->{volretention}) {
4041 $update .= " volretention=\"$arg->{volretention}\" " ;
4044 if (defined $arg->{voluseduration}) {
4045 $update .= " voluse=\"$arg->{voluseduration}\" " ;
4048 if (defined $arg->{maxvoljobs}) {
4049 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
4052 if (defined $arg->{maxvolfiles}) {
4053 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
4056 if (defined $arg->{maxvolbytes}) {
4057 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
4060 if (defined $arg->{poolrecycle}) {
4061 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
4064 my $b = $self->get_bconsole();
4067 content => $b->send_cmd($update),
4068 title => "Update a volume ",
4074 my $media = $self->dbh_quote($arg->{media});
4076 my $loc = CGI::param('location') || '';
4078 $loc = $self->dbh_quote($loc); # is checked by db
4079 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
4081 if (!$arg->{qcomment}) {
4082 $arg->{qcomment} = "''";
4084 push @q, "Comment=$arg->{qcomment}";
4089 SET " . join (',', @q) . "
4090 WHERE Media.VolumeName = $media
4092 $self->dbh_do($query);
4094 $self->update_media();
4100 $self->can_do('r_autochanger_mgnt');
4102 my $ach = CGI::param('ach') ;
4103 $ach = $self->ach_get($ach);
4105 return $self->error("Bad autochanger name");
4109 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
4110 $b->update_slots($ach->{name});
4117 $self->can_do('r_view_log');
4119 my $arg = $self->get_form('jobid', 'limit', 'offset');
4120 unless ($arg->{jobid}) {
4121 return $self->error("Can't get jobid");
4124 if ($arg->{limit} == 100) {
4125 $arg->{limit} = 1000;
4127 # get security filter
4128 my $filter = $self->get_client_filter();
4131 SELECT Job.Name as name, Client.Name as clientname
4132 FROM Job INNER JOIN Client USING (ClientId) $filter
4133 WHERE JobId = $arg->{jobid}
4136 my $row = $self->dbh_selectrow_hashref($query);
4139 return $self->error("Can't find $arg->{jobid} in catalog");
4142 # display only Error and Warning messages
4144 if (CGI::param('error')) {
4145 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning|ERR=' ";
4149 if (CGI::param('time') || $self->{info}->{display_log_time}) {
4150 $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
4152 $logtext = 'LogText';
4156 SELECT count(1) AS nbline, JobId AS jobid,
4157 GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
4159 SELECT JobId, Time, LogText
4161 WHERE ( Log.JobId = $arg->{jobid}
4163 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
4164 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
4168 OFFSET $arg->{offset}
4174 my $log = $self->dbh_selectrow_hashref($query);
4176 return $self->error("Can't get log for jobid $arg->{jobid}");
4179 $self->display({ lines=> $log->{logtxt},
4180 nbline => $log->{nbline},
4181 jobid => $arg->{jobid},
4182 name => $row->{name},
4183 client => $row->{clientname},
4184 offset => $arg->{offset},
4185 limit => $arg->{limit},
4186 }, 'display_log.tpl');
4192 $self->can_do('r_media_mgnt');
4193 my $arg = $self->get_form('storage', 'pool', 'nb', 'media', 'offset');
4194 my $b = $self->get_bconsole();
4196 if (!$arg->{storage} || !$arg->{pool} || not defined $arg->{nb} || !$arg->{media} || !$arg->{offset}) {
4197 CGI::param(offset => 0);
4198 $arg = $self->get_form('db_pools');
4199 $arg->{storage} = [ map { { name => $_ } }$b->list_storage()];
4200 $self->display($arg, 'add_media.tpl');
4205 if ($arg->{nb} > 0) {
4206 $arg->{offset} = $arg->{offset}?$arg->{offset}:1;
4207 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n$arg->{nb}\n$arg->{media}\n$arg->{offset}\n";
4209 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n0\n$arg->{media}\n";
4215 CGI::param('media', '');
4216 CGI::param('re_media', $arg->{media});
4217 $self->display_media();
4223 $self->can_do('r_autochanger_mgnt');
4225 my $arg = $self->get_form('ach', 'slots', 'drive');
4227 unless ($arg->{ach}) {
4228 return $self->error("Can't find autochanger name");
4231 my $a = $self->ach_get($arg->{ach});
4233 return $self->error("Can't find autochanger name in configuration");
4236 my $storage = $a->get_drive_name($arg->{drive});
4238 return $self->error("Can't get your drive name");
4244 if ($arg->{slots}) {
4245 $slots = join(",", @{ $arg->{slots} });
4246 $slots_sql = " AND Slot IN ($slots) ";
4247 $t += 60*scalar( @{ $arg->{slots} }) ;
4250 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
4251 print "<h1>This command can take long time, be patient...</h1>";
4253 $b->label_barcodes(storage => $storage,
4254 drive => $arg->{drive},
4262 SET LocationId = (SELECT LocationId
4264 WHERE Location = '$arg->{ach}')
4266 WHERE (LocationId = 0 OR LocationId IS NULL)
4275 $self->can_do('r_purge');
4277 my @volume = CGI::param('media');
4280 return $self->error("Can't get media selection");
4283 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4285 foreach my $v (@volume) {
4287 content => $b->purge_volume($v),
4288 title => "Purge media",
4289 name => "purge volume=$v",
4298 $self->can_do('r_prune');
4300 my @volume = CGI::param('media');
4302 return $self->error("Can't get media selection");
4305 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4307 foreach my $v (@volume) {
4309 content => $b->prune_volume($v),
4310 title => "Prune volume",
4311 name => "prune volume=$v",
4320 $self->can_do('r_cancel_job');
4322 my $arg = $self->get_form('jobid');
4323 unless ($arg->{jobid}) {
4324 return $self->error("Can't get jobid");
4327 my $b = $self->get_bconsole();
4329 content => $b->cancel($arg->{jobid}),
4330 title => "Cancel job",
4331 name => "cancel jobid=$arg->{jobid}",
4337 # Warning, we display current fileset
4340 my $arg = $self->get_form('fileset');
4342 if ($arg->{fileset}) {
4343 my $b = $self->get_bconsole();
4344 my $ret = $b->get_fileset($arg->{fileset});
4345 $self->display({ fileset => $arg->{fileset},
4347 }, "fileset_view.tpl");
4349 $self->error("Can't get fileset name");
4353 sub director_show_sched
4356 $self->can_do('r_view_job');
4357 my $arg = $self->get_form('days');
4359 my $b = $self->get_bconsole();
4360 my $ret = $b->director_get_sched( $arg->{days} );
4365 }, "scheduled_job.tpl");
4368 sub enable_disable_job
4370 my ($self, $what) = @_ ;
4371 $self->can_do('r_run_job');
4373 my $name = CGI::param('job') || '';
4374 unless ($name =~ /^[\w\d\.\-\s]+$/) {
4375 return $self->error("Can't find job name");
4378 my $b = $self->get_bconsole();
4388 content => $b->send_cmd("$cmd job=\"$name\""),
4389 title => "$cmd $name",
4390 name => "$cmd job=\"$name\"",
4397 return new Bconsole(pref => $self->{info});
4403 $self->can_do('r_run_job');
4405 my $b = $self->get_bconsole();
4407 my $joblist = [ map { { name => $_ } } $b->list_job() ];
4409 $self->display({ Jobs => $joblist }, "run_job.tpl");
4414 my ($self, $ouput) = @_;
4417 foreach my $l (split(/\r\n/, $ouput)) {
4418 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
4424 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
4430 foreach my $k (keys %arg) {
4431 $lowcase{lc($k)} = $arg{$k} ;
4440 $self->can_do('r_run_job');
4442 my $b = $self->get_bconsole();
4444 my $job = CGI::param('job') || '';
4446 # we take informations from director, and we overwrite with user wish
4447 my $info = $b->send_cmd("show job=\"$job\"");
4448 my $attr = $self->run_parse_job($info);
4450 my $arg = $self->get_form(qw/pool level client fileset storage media/);
4452 if (!$arg->{pool} and $arg->{media}) {
4453 my $r = $self->dbh_selectrow_hashref("
4454 SELECT Pool.Name AS name
4455 FROM Media JOIN Pool USING (PoolId)
4456 WHERE Media.VolumeName = '$arg->{media}'
4457 AND Pool.Name != 'Scratch'
4460 $arg->{pool} = $r->{name};
4464 my %job_opt = (%$attr, %$arg);
4466 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4468 my $pools = [ map { { name => $_ } } $b->list_pool() ];
4469 my $clients = [ map { { name => $_ } }$b->list_client()];
4470 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
4471 my $storages= [ map { { name => $_ } }$b->list_storage()];
4476 clients => $clients,
4477 filesets => $filesets,
4478 storages => $storages,
4480 }, "run_job_mod.tpl");
4486 $self->can_do('r_run_job');
4488 my $b = $self->get_bconsole();
4490 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4500 $self->can_do('r_run_job');
4502 my $b = $self->get_bconsole();
4504 # TODO: check input (don't use pool, level)
4506 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
4507 my $job = CGI::param('job') || '';
4508 my $storage = CGI::param('storage') || '';
4510 my $jobid = $b->run(job => $job,
4511 client => $arg->{client},
4512 priority => $arg->{priority},
4513 level => $arg->{level},
4514 storage => $storage,
4515 pool => $arg->{pool},
4516 fileset => $arg->{fileset},
4517 when => $arg->{when},
4522 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>";
4525 sub display_next_job
4528 my $arg = $self->get_form(qw/job/);
4530 return $self->error("Can't get job name");
4533 my $b = $self->get_bconsole();
4535 my $job = $b->send_cmd("show job=\"$arg->{job}\"");
4536 if ($job !~ /Schedule: name=([\w\d\-]+)/s) {
4537 return $self->error("Can't get $arg->{job} schedule");
4541 if ($job =~ /Pool: name=([\w\d\-]+) PoolType=/) {
4545 my $out = $b->send_cmd("show schedule=\"$jsched\"");
4546 my $sched = new Bweb::Sched();
4547 $sched->parse_scheds(split(/\r?\n/, $out));
4549 my $ss = $sched->get_scheds($jsched);
4552 foreach my $s (@$ss) {
4553 my $level = $sched->get_level($s);
4554 my $pool = $sched->get_pool($s) || $jpool;
4555 my $evt = $sched->get_event($s);
4556 push @ret, map { "$_ : $pool ($level)\n" } @$evt;
4559 print "<b>$arg->{job}:</b><pre>", sort @ret, "</pre><br>";