1 ################################################################
6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
11 The main author of Bweb is Eric Bollengier.
12 The main author of Bacula is Kern Sibbald, with contributions from
13 many others, a complete list can be found in the file AUTHORS.
15 This program is Free Software; you can redistribute it and/or
16 modify it under the terms of version two of the GNU General Public
17 License as published by the Free Software Foundation plus additions
18 that are listed in the file LICENSE.
20 This program is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 Bacula® is a registered trademark of John Walker.
31 The licensor of Bacula is the Free Software Foundation Europe
32 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
33 Switzerland, email:ftf@fsfeurope.org.
45 Bweb::Gui - Base package for all Bweb object
49 This package define base fonction like new, display, etc..
54 our $template_dir='/usr/share/bweb/tpl';
58 new - creation a of new Bweb object
62 This function take an hash of argument and place them
65 IE : $obj = new Obj(name => 'test', age => '10');
67 $obj->{name} eq 'test' and $obj->{age} eq 10
73 my ($class, %arg) = @_;
78 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
85 my ($self, $what) = @_;
89 print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
91 print "<pre>$what</pre>";
98 error - display an error to the user
102 this function set $self->{error} with arg, display a message with
103 error.tpl and return 0
108 return $self->error("Can't use this file");
115 my ($self, $what) = @_;
116 $self->{error} = $what;
117 $self->display($self, 'error.tpl');
123 display - display an html page with HTML::Template
127 this function is use to render all html codes. it takes an
128 ref hash as arg in which all param are usable in template.
130 it will use user template_dir then global template_dir
131 to search the template file.
133 hash keys are not sensitive. See HTML::Template for more
134 explanations about the hash ref. (it's can be quiet hard to understand)
138 $ref = { name => 'me', age => 26 };
139 $self->display($ref, "people.tpl");
145 my ($self, $hash, $tpl) = @_ ;
146 my $dir = $self->{template_dir} || $template_dir;
147 my $lang = $self->{lang} || 'en';
148 my $template = HTML::Template->new(filename => $tpl,
149 path =>["$dir/$lang",
151 die_on_bad_params => 0,
152 case_sensitive => 0);
154 foreach my $var (qw/limit offset/) {
156 unless ($hash->{$var}) {
157 my $value = CGI::param($var) || '';
159 if ($value =~ /^(\d+)$/) {
160 $template->param($var, $1) ;
165 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
166 $template->param('loginname', CGI::remote_user());
168 $template->param($hash);
169 print $template->output();
173 ################################################################
175 package Bweb::Config;
177 use base q/Bweb::Gui/;
181 Bweb::Config - read, write, display, modify configuration
185 this package is used for manage configuration
189 $conf = new Bweb::Config(config_file => '/path/to/conf');
200 =head1 PACKAGE VARIABLE
202 %k_re - hash of all acceptable option.
206 this variable permit to check all option with a regexp.
210 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
211 user => qr/^([\w\d\.-]+)$/i,
212 password => qr/^(.*)$/,
213 fv_write_path => qr!^([/\w\d\.-]*)$!,
214 template_dir => qr!^([/\w\d\.-]+)$!,
215 debug => qr/^(on)?$/,
216 lang => qr/^(\w\w)?$/,
217 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
218 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
219 bconsole => qr!^(.+)?$!,
220 syslog_file => qr!^(.+)?$!,
221 log_dir => qr!^(.+)?$!,
222 wiki_url => qr!(.*)$!,
223 stat_job_table => qr!^(\w*)$!,
224 display_log_time => qr!^(on)?$!,
225 enable_security => qr/^(on)?$/,
226 enable_security_acl => qr/^(on)?$/,
231 load - load config_file
235 this function load the specified config_file.
243 unless (open(FP, $self->{config_file}))
245 return $self->error("can't load config_file $self->{config_file} : $!");
247 my $f=''; my $tmpbuffer;
248 while(read FP,$tmpbuffer,4096)
256 no strict; # I have no idea of the contents of the file
263 return $self->error("If you update from an old bweb install, your must reload this page and if it's fail again, you have to configure bweb again...") ;
266 foreach my $k (keys %$VAR1) {
267 $self->{$k} = $VAR1->{$k};
275 load_old - load old configuration format
283 unless (open(FP, $self->{config_file}))
285 return $self->error("$self->{config_file} : $!");
288 while (my $line = <FP>)
291 my ($k, $v) = split(/\s*=\s*/, $line, 2);
303 save - save the current configuration to config_file
311 if ($self->{ach_list}) {
312 # shortcut for display_begin
313 $self->{achs} = [ map {{ name => $_ }}
314 keys %{$self->{ach_list}}
318 unless (open(FP, ">$self->{config_file}"))
320 return $self->error("$self->{config_file} : $!\n" .
321 "You must add this to your config file\n"
322 . Data::Dumper::Dumper($self));
325 print FP Data::Dumper::Dumper($self);
333 edit, view, modify - html form ouput
341 $self->display($self, "config_edit.tpl");
347 $self->display($self, "config_view.tpl");
355 # we need to reset checkbox first
357 $self->{display_log_time} = 0;
358 $self->{enable_security} = 0;
359 $self->{enable_security_acl} = 0;
361 foreach my $k (CGI::param())
363 next unless (exists $k_re{$k}) ;
364 my $val = CGI::param($k);
365 if ($val =~ $k_re{$k}) {
368 $self->{error} .= "bad parameter : $k = [$val]";
374 if ($self->{error}) { # an error as occured
375 $self->display($self, 'error.tpl');
383 ################################################################
385 package Bweb::Client;
387 use base q/Bweb::Gui/;
391 Bweb::Client - Bacula FD
395 this package is use to do all Client operations like, parse status etc...
399 $client = new Bweb::Client(name => 'zog-fd');
400 $client->status(); # do a 'status client=zog-fd'
406 display_running_job - Html display of a running job
410 this function is used to display information about a current job
414 sub display_running_job
416 my ($self, $conf, $jobid) = @_ ;
418 my $status = $self->status($conf);
421 if ($status->{$jobid}) {
422 $self->display($status->{$jobid}, "client_job_status.tpl");
425 for my $id (keys %$status) {
426 $self->display($status->{$id}, "client_job_status.tpl");
433 $client = new Bweb::Client(name => 'plume-fd');
435 $client->status($bweb);
439 dirty hack to parse "status client=xxx-fd"
443 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
444 Backup Job started: 06-jun-06 17:22
445 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
446 Files Examined=10,697
447 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
453 JobName => Full_plume.2006-06-06_17.22.23,
456 Bytes => 194,484,132,
466 my ($self, $conf) = @_ ;
468 if (defined $self->{cur_jobs}) {
469 return $self->{cur_jobs} ;
473 my $b = new Bconsole(pref => $conf);
474 my $ret = $b->send_cmd("st client=$self->{name}");
478 for my $r (split(/\n/, $ret)) {
480 $r =~ s/(^\s+|\s+$)//g;
481 if ($r =~ /JobId (\d+) Job (\S+)/) {
483 $arg->{$jobid} = { @param, JobId => $jobid } ;
487 @param = ( JobName => $2 );
489 } elsif ($r =~ /=.+=/) {
490 push @param, split(/\s+|\s*=\s*/, $r) ;
492 } elsif ($r =~ /=/) { # one per line
493 push @param, split(/\s*=\s*/, $r) ;
495 } elsif ($r =~ /:/) { # one per line
496 push @param, split(/\s*:\s*/, $r, 2) ;
500 if ($jobid and @param) {
501 $arg->{$jobid} = { @param,
503 Client => $self->{name},
507 $self->{cur_jobs} = $arg ;
513 ################################################################
515 package Bweb::Autochanger;
517 use base q/Bweb::Gui/;
521 Bweb::Autochanger - Object to manage Autochanger
525 this package will parse the mtx output and manage drives.
529 $auto = new Bweb::Autochanger(precmd => 'sudo');
531 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
535 $auto->slot_is_full(10);
536 $auto->transfer(10, 11);
542 my ($class, %arg) = @_;
545 name => '', # autochanger name
546 label => {}, # where are volume { label1 => 40, label2 => drive0 }
547 drive => [], # drive use [ 'media1', 'empty', ..]
548 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
549 io => [], # io slot number list [ 41, 42, 43...]
550 info => {slot => 0, # informations (slot, drive, io)
554 mtxcmd => '/usr/sbin/mtx',
556 device => '/dev/changer',
557 precmd => '', # ssh command
558 bweb => undef, # link to bacula web object (use for display)
561 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
568 status - parse the output of mtx status
572 this function will launch mtx status and parse the output. it will
573 give a perlish view of the autochanger content.
575 it uses ssh if the autochanger is on a other host.
582 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
584 # TODO : reset all infos
585 $self->{info}->{drive} = 0;
586 $self->{info}->{slot} = 0;
587 $self->{info}->{io} = 0;
589 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
592 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
593 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
594 #Data Transfer Element 1:Empty
595 # Storage Element 1:Empty
596 # Storage Element 2:Full :VolumeTag=000002
597 # Storage Element 3:Empty
598 # Storage Element 4:Full :VolumeTag=000004
599 # Storage Element 5:Full :VolumeTag=000001
600 # Storage Element 6:Full :VolumeTag=000003
601 # Storage Element 7:Empty
602 # Storage Element 41 IMPORT/EXPORT:Empty
603 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
608 # Storage Element 7:Empty
609 # Storage Element 2:Full :VolumeTag=000002
610 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
613 $self->set_empty_slot($1);
615 $self->set_slot($1, $4);
618 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
621 $self->set_empty_drive($1);
623 $self->set_drive($1, $4, $6);
626 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
629 $self->set_empty_io($1);
631 $self->set_io($1, $4);
634 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
636 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
637 $self->{info}->{drive} = $1;
638 $self->{info}->{slot} = $2;
639 if ($l =~ /(\d+)\s+Import/) {
640 $self->{info}->{io} = $1 ;
642 $self->{info}->{io} = 0;
647 $self->debug($self) ;
652 my ($self, $slot) = @_;
655 if ($self->{slot}->[$slot] eq 'loaded') {
659 my $label = $self->{slot}->[$slot] ;
661 return $self->is_media_loaded($label);
666 my ($self, $drive, $slot) = @_;
668 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
669 return 0 if ($self->slot_is_full($slot)) ;
671 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
674 my $content = $self->get_slot($slot);
675 print "content = $content<br/> $drive => $slot<br/>";
676 $self->set_empty_drive($drive);
677 $self->set_slot($slot, $content);
680 $self->{error} = $out;
685 # TODO: load/unload have to use mtx script from bacula
688 my ($self, $drive, $slot) = @_;
690 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
691 return 0 unless ($self->slot_is_full($slot)) ;
693 print "Loading drive $drive with slot $slot<br/>\n";
694 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
697 my $content = $self->get_slot($slot);
698 print "content = $content<br/> $slot => $drive<br/>";
699 $self->set_drive($drive, $slot, $content);
702 $self->{error} = $out;
710 my ($self, $media) = @_;
712 unless ($self->{label}->{$media}) {
716 if ($self->{label}->{$media} =~ /drive\d+/) {
726 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
731 my ($self, $slot, $tag) = @_;
732 $self->{slot}->[$slot] = $tag || 'full';
733 push @{ $self->{io} }, $slot;
736 $self->{label}->{$tag} = $slot;
742 my ($self, $slot) = @_;
744 push @{ $self->{io} }, $slot;
746 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
747 $self->{slot}->[$slot] = 'empty';
753 my ($self, $slot) = @_;
754 return $self->{slot}->[$slot];
759 my ($self, $slot, $tag) = @_;
760 $self->{slot}->[$slot] = $tag || 'full';
763 $self->{label}->{$tag} = $slot;
769 my ($self, $slot) = @_;
771 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
772 $self->{slot}->[$slot] = 'empty';
778 my ($self, $drive) = @_;
779 $self->{drive}->[$drive] = 'empty';
784 my ($self, $drive, $slot, $tag) = @_;
785 $self->{drive}->[$drive] = $tag || $slot;
787 $self->{slot}->[$slot] = $tag || 'loaded';
790 $self->{label}->{$tag} = "drive$drive";
796 my ($self, $slot) = @_;
798 # slot don't exists => full
799 if (not defined $self->{slot}->[$slot]) {
803 if ($self->{slot}->[$slot] eq 'empty') {
806 return 1; # vol, full, loaded
809 sub slot_get_first_free
812 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
813 return $slot unless ($self->slot_is_full($slot));
817 sub io_get_first_free
821 foreach my $slot (@{ $self->{io} }) {
822 return $slot unless ($self->slot_is_full($slot));
829 my ($self, $media) = @_;
831 return $self->{label}->{$media} ;
836 my ($self, $media) = @_;
838 return defined $self->{label}->{$media} ;
843 my ($self, $slot) = @_;
845 unless ($self->slot_is_full($slot)) {
846 print "Autochanger $self->{name} slot $slot is empty\n";
851 if ($self->is_slot_loaded($slot)) {
854 print "Autochanger $self->{name} $slot is currently in use\n";
858 # autochanger must have I/O
859 unless ($self->have_io()) {
860 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
864 my $dst = $self->io_get_first_free();
867 print "Autochanger $self->{name} you must empty I/O first\n";
870 $self->transfer($slot, $dst);
875 my ($self, $src, $dst) = @_ ;
876 if ($self->{debug}) {
877 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
879 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
882 my $content = $self->get_slot($src);
883 $self->{slot}->[$src] = 'empty';
884 $self->set_slot($dst, $content);
887 $self->{error} = $out;
894 my ($self, $index) = @_;
895 return $self->{drive_name}->[$index];
898 # TODO : do a tapeinfo request to get informations
908 for my $slot (@{$self->{io}})
910 if ($self->is_slot_loaded($slot)) {
911 print "$slot is currently loaded\n";
915 if ($self->slot_is_full($slot))
917 my $free = $self->slot_get_first_free() ;
918 print "move $slot to $free :\n";
921 if ($self->transfer($slot, $free)) {
922 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
924 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
928 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
934 # TODO : this is with mtx status output,
935 # we can do an other function from bacula view (with StorageId)
939 my $bweb = $self->{bweb};
941 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
942 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
945 SELECT Media.VolumeName AS volumename,
946 Media.VolStatus AS volstatus,
947 Media.LastWritten AS lastwritten,
948 Media.VolBytes AS volbytes,
949 Media.MediaType AS mediatype,
951 Media.InChanger AS inchanger,
953 $bweb->{sql}->{FROM_UNIXTIME}(
954 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
955 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
958 INNER JOIN Pool USING (PoolId)
960 WHERE Media.VolumeName IN ($media_list)
963 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
965 # TODO : verify slot and bacula slot
969 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
971 if ($self->slot_is_full($slot)) {
973 my $vol = $self->{slot}->[$slot];
974 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
976 my $bslot = $all->{$vol}->{slot} ;
977 my $inchanger = $all->{$vol}->{inchanger};
979 # if bacula slot or inchanger flag is bad, we display a message
980 if ($bslot != $slot or !$inchanger) {
981 push @to_update, $slot;
984 $all->{$vol}->{realslot} = $slot;
986 push @{ $param }, $all->{$vol};
988 } else { # empty or no label
989 push @{ $param }, {realslot => $slot,
990 volstatus => 'Unknown',
991 volumename => $self->{slot}->[$slot]} ;
994 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
998 my $i=0; my $drives = [] ;
999 foreach my $d (@{ $self->{drive} }) {
1000 $drives->[$i] = { index => $i,
1001 load => $self->{drive}->[$i],
1002 name => $self->{drive_name}->[$i],
1007 $bweb->display({ Name => $self->{name},
1008 nb_drive => $self->{info}->{drive},
1009 nb_io => $self->{info}->{io},
1012 Update => scalar(@to_update) },
1019 ################################################################
1021 package Bweb::Sched;
1022 use base q/Bweb::Gui/;
1026 Bweb::Sched() - Bweb package that parse show schedule ouput
1028 new Bweb::Sched(format => '%Y-%m-%d', year => 2008);
1032 my $b = $bweb->get_bconsole();
1033 my $s = $b->send_cmd("show schedule");
1034 my $sched = new Bweb::Sched();
1035 $sched->parse_scheds(split(/\r?\n/, $s));
1046 'level' => 'Differential',
1051 # cleanup and add a schedule
1054 my ($self, $name, $info) = @_;
1055 # bacula uses dates that start from 0, we start from 1
1056 foreach (@{$info->{mday}},@{$info->{month}}) { $_++ };
1059 $info->{event} = [ $self->get_events($info, $self->{format}) ];
1061 foreach my $i (qw/hour mday month wday wom woy mins/) {
1065 push @{$self->{schedules}->{$name}}, $info;
1068 # return the name of all schedules
1071 my ($self, $name) = @_;
1073 return keys %{ $self->{schedules} };
1076 # return an array of all schedule
1079 my ($self, $sched) = @_;
1080 return $self->{schedules}->{$sched};
1083 # return an ref array of all events
1084 # [ '2007-12-19 04:05', '2007-12-19 04:05' ]
1087 my ($self, $sched) = @_;
1088 return $sched->{event};
1091 # return the pool name
1094 my ($self, $sched) = @_;
1095 return $sched->{pool} || '';
1098 # return the level name (Incremental, Differential, Full)
1101 my ($self, $sched) = @_;
1102 return $sched->{level};
1105 # parse bacula sched bitmap
1108 my ($self, @output) = @_;
1115 foreach my $ligne (@output) {
1116 if ($ligne =~ /Schedule: name=(.+)/) {
1117 if ($name and $elt) {
1118 $elt->{level} = $run;
1119 $self->add_sched($name, $elt);
1124 elsif ($ligne =~ /Run Level=(.+)/) {
1125 if ($name and $elt) {
1126 $elt->{level} = $run;
1127 $self->add_sched($name, $elt);
1132 elsif ($ligne =~ /(hour|mday|month|mins)=(.+)/) {
1133 # All theses lines have the same format
1135 my ($k,$v) = ($1,$2);
1136 # we get all values (0 1 4 9)
1137 $elt->{$k}=[split (/\s/,$v)];
1139 # we make a bitmap for this kind of data (0 0 1 0 0 0 1) for a quick access
1140 elsif ($ligne =~ /(wday|wom|woy)=(.+)/) {
1141 my ($k,$v) = ($1,$2);
1142 foreach my $e (split (/\s/,$v)) {
1146 elsif ($ligne =~ /Pool: name=(.+?) PoolType/) {
1151 if ($name and $elt) {
1152 $elt->{level} = $run;
1153 $self->add_sched($name, $elt);
1157 use Date::Calc qw(:all);
1159 # read bacula schedule bitmap and get $format date string
1163 my ($self, $s,$format) = @_;
1164 my $year = $self->{year} || ((localtime)[5] + 1900);
1165 $format = $format || '%u-%02u-%02u %02u:%02u';
1167 foreach my $m (@{$s->{month}}) # mois de l'annee
1169 foreach my $md (@{$s->{mday}}) # jour du mois
1171 # print " m=$m md=$md\n";
1172 # we check if this day exists (31 fev)
1173 next if (!check_date($year,$m,$md));
1174 # print " check_date ok\n";
1176 my $w = ($md-1)/7; # we use the same thing than bacula
1177 next if (! $s->{wom}->[$w]);
1178 # print " wom ok\n";
1180 # on recupere le jour de la semaine
1181 my $wd = Day_of_Week($year,$m,$md);
1183 my ($w1,undef) = Week_of_Year($year,$m,$wd);
1184 next if (! $s->{woy}->[$w1-1]); # bacula 0-51
1185 # print " woy ok\n";
1187 $wd = 0 if ($wd == 7) ; # sunday is 0 on bacula
1188 next if (! $s->{wday}->[$wd]);
1189 # print " wday ok\n";
1191 foreach my $h (@{$s->{hour}}) # hour of the day
1193 foreach my $min (@{$s->{mins}}) # minute
1195 push @ret, sprintf($format, $year,$m,$md,$h,$min);
1203 ################################################################
1207 use base q/Bweb::Gui/;
1211 Bweb - main Bweb package
1215 this package is use to compute and display informations
1220 use POSIX qw/strftime/;
1222 our $config_file='/etc/bacula/bweb.conf';
1228 %sql_func - hash to make query mysql/postgresql compliant
1234 UNIX_TIMESTAMP => '',
1235 FROM_UNIXTIME => '',
1236 TO_SEC => " interval '1 second' * ",
1237 SEC_TO_INT => "SEC_TO_INT",
1240 STARTTIME_SEC => " date_trunc('sec', Job.StartTime) ",
1241 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1242 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1243 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1244 STARTTIME_WEEK => " date_trunc('week', Job.StartTime) ",
1245 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1246 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1247 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1248 STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
1249 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1250 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1254 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1255 FROM_UNIXTIME => 'FROM_UNIXTIME',
1258 SEC_TO_TIME => 'SEC_TO_TIME',
1259 MATCH => " REGEXP ",
1260 STARTTIME_SEC => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %T') ",
1261 STARTTIME_DAY => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d') ",
1262 STARTTIME_HOUR => " DATE_FORMAT(Job.StartTime, '%Y-%m-%d %H') ",
1263 STARTTIME_MONTH => " DATE_FORMAT(Job.StartTime, '%Y-%m') ",
1264 STARTTIME_WEEK => " DATE_FORMAT(Job.StartTime, '%Y-%v') ",
1265 STARTTIME_PHOUR=> " DATE_FORMAT(Job.StartTime, '%H') ",
1266 STARTTIME_PDAY => " DATE_FORMAT(Job.StartTime, '%d') ",
1267 STARTTIME_PMONTH => " DATE_FORMAT(Job.StartTime, '%m') ",
1268 STARTTIME_PWEEK => " DATE_FORMAT(Job.StartTime, '%v') ",
1269 # with mysql < 5, you have to play with the ugly SHOW command
1270 DB_SIZE => " SELECT 0 ",
1271 # works only with mysql 5
1272 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1273 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1274 CONCAT_SEP => " SEPARATOR '' ",
1281 return $self->{info}->{dbi} =~ /dbi:mysql/i;
1288 $self->{dbh}->disconnect();
1293 sub dbh_selectall_arrayref
1295 my ($self, $query) = @_;
1296 $self->connect_db();
1297 $self->debug($query);
1298 return $self->{dbh}->selectall_arrayref($query);
1303 my ($self, @what) = @_;
1304 return join(',', $self->dbh_quote(@what)) ;
1309 my ($self, @what) = @_;
1311 $self->connect_db();
1313 return map { $self->{dbh}->quote($_) } @what;
1315 return $self->{dbh}->quote($what[0]) ;
1321 my ($self, $query) = @_ ;
1322 $self->connect_db();
1323 $self->debug($query);
1324 return $self->{dbh}->do($query);
1327 sub dbh_selectall_hashref
1329 my ($self, $query, $join) = @_;
1331 $self->connect_db();
1332 $self->debug($query);
1333 return $self->{dbh}->selectall_hashref($query, $join) ;
1336 sub dbh_selectrow_hashref
1338 my ($self, $query) = @_;
1340 $self->connect_db();
1341 $self->debug($query);
1342 return $self->{dbh}->selectrow_hashref($query) ;
1347 my ($self, @what) = @_;
1348 if ($self->dbh_is_mysql()) {
1349 return 'CONCAT(' . join(',', @what) . ')' ;
1351 return join(' || ', @what);
1357 my ($self, $query) = @_;
1358 $self->debug($query, up => 1);
1359 return $self->{dbh}->prepare($query);
1365 my @unit = qw(B KB MB GB TB);
1366 my $val = shift || 0;
1368 my $format = '%i %s';
1369 while ($val / 1024 > 1) {
1373 $format = ($i>0)?'%0.1f %s':'%i %s';
1374 return sprintf($format, $val, $unit[$i]);
1377 # display Day, Hour, Year
1383 $val /= 60; # sec -> min
1385 if ($val / 60 <= 1) {
1389 $val /= 60; # min -> hour
1390 if ($val / 24 <= 1) {
1391 return "$val hours";
1394 $val /= 24; # hour -> day
1395 if ($val / 365 < 2) {
1399 $val /= 365 ; # day -> year
1401 return "$val years";
1407 my $val = shift || 0;
1409 if ($val eq '1' or $val eq "yes") {
1411 } elsif ($val eq '2' or $val eq "archived") {
1419 sub from_human_enabled
1421 my $val = shift || 0;
1423 if ($val eq '1' or $val eq "yes") {
1425 } elsif ($val eq '2' or $val eq "archived") {
1432 # get Day, Hour, Year
1438 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1442 my %times = ( m => 60,
1448 my $mult = $times{$2} || 0;
1458 unless ($self->{dbh}) {
1460 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1461 $self->{info}->{user},
1462 $self->{info}->{password});
1464 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1465 unless ($self->{dbh});
1467 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1469 if ($self->dbh_is_mysql()) {
1470 $self->{dbh}->do("SET group_concat_max_len=1000000");
1472 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1479 my ($class, %arg) = @_;
1481 dbh => undef, # connect_db();
1483 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1489 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1491 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1492 $self->{sql} = $sql_func{$1};
1495 $self->{loginname} = CGI::remote_user();
1496 $self->{debug} = $self->{info}->{debug};
1497 $self->{lang} = $self->{info}->{lang};
1498 $self->{template_dir} = $self->{info}->{template_dir};
1506 if ($self->{info}->{enable_security}) {
1507 $self->get_roles(); # get lang
1509 $self->display($self->{info}, "begin.tpl");
1515 $self->display($self->{info}, "end.tpl");
1521 my $where=''; # by default
1523 my $arg = $self->get_form("client", "qre_client",
1524 "jclient_groups", "qnotingroup");
1526 if ($arg->{qre_client}) {
1527 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1528 } elsif ($arg->{client}) {
1529 $where = "WHERE Name = '$arg->{client}' ";
1530 } elsif ($arg->{jclient_groups}) {
1531 # $filter could already contains client_group_member
1533 JOIN client_group_member USING (ClientId)
1534 JOIN client_group USING (client_group_id)
1535 WHERE client_group_name IN ($arg->{jclient_groups}) ";
1536 } elsif ($arg->{qnotingroup}) {
1539 (SELECT 1 FROM client_group_member
1540 WHERE Client.ClientId = client_group_member.ClientId
1546 SELECT Name AS name,
1548 AutoPrune AS autoprune,
1549 FileRetention AS fileretention,
1550 JobRetention AS jobretention
1551 FROM Client " . $self->get_client_filter() .
1554 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1556 my $dsp = { ID => $cur_id++,
1557 clients => [ values %$all] };
1559 $self->display($dsp, "client_list.tpl") ;
1564 my ($self, %arg) = @_;
1569 if ($arg{since} and $arg{age}) {
1570 my $now = "$self->{sql}->{UNIX_TIMESTAMP}(TIMESTAMP '$arg{since}')";
1572 AND $self->{sql}->{UNIX_TIMESTAMP}(StartTime) > $now
1573 AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) < ($now + $self->{sql}->{TO_SEC}($arg{age}))";
1574 $label .= "since $arg{since} and during " . human_sec($arg{age});
1576 } elsif ($arg{age}) {
1578 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1580 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1582 $self->{sql}->{TO_SEC}($arg{age})
1585 $label = "last " . human_sec($arg{age});
1588 if ($arg{groupby}) {
1589 $limit .= " GROUP BY $arg{groupby} ";
1593 $limit .= " ORDER BY $arg{order} ";
1597 $limit .= " LIMIT $arg{limit} ";
1598 $label .= " limited to $arg{limit}";
1602 $limit .= " OFFSET $arg{offset} ";
1603 $label .= " with $arg{offset} offset ";
1607 $label = 'no filter';
1610 return ($limit, $label);
1615 $bweb->get_form(...) - Get useful stuff
1619 This function get and check parameters against regexp.
1621 If word begin with 'q', the return will be quoted or join quoted
1622 if it's end with 's'.
1627 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1630 qclient => 'plume-fd',
1631 qpools => "'plume-fd', 'test-fd', '...'",
1638 my ($self, @what) = @_;
1639 my %what = map { $_ => 1 } @what;
1662 my %opt_ss =( # string with space
1666 my %opt_s = ( # default to ''
1687 my %opt_p = ( # option with path
1694 my %opt_r = (regexwhere => 1);
1695 my %opt_d = ( # option with date
1700 foreach my $i (@what) {
1701 if (exists $opt_i{$i}) {# integer param
1702 my $value = CGI::param($i) || $opt_i{$i} ;
1703 if ($value =~ /^(\d+)$/) {
1706 } elsif ($opt_s{$i}) { # simple string param
1707 my $value = CGI::param($i) || '';
1708 if ($value =~ /^([\w\d\.-]+)$/) {
1711 } elsif ($opt_ss{$i}) { # simple string param (with space)
1712 my $value = CGI::param($i) || '';
1713 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1716 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1717 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1719 $ret{$i} = $self->dbh_join(@value) ;
1722 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1723 my $value = CGI::param($1) ;
1725 $ret{$i} = $self->dbh_quote($value);
1728 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1729 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1730 grep { ! /^\s*$/ } CGI::param($1) ];
1731 } elsif (exists $opt_p{$i}) {
1732 my $value = CGI::param($i) || '';
1733 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1736 } elsif (exists $opt_r{$i}) {
1737 my $value = CGI::param($i) || '';
1738 if ($value =~ /^([^'"']+)$/) {
1741 } elsif (exists $opt_d{$i}) {
1742 my $value = CGI::param($i) || '';
1743 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1750 foreach my $s (CGI::param('slot')) {
1751 if ($s =~ /^(\d+)$/) {
1752 push @{$ret{slots}}, $s;
1758 my $age = $ret{age} || $opt_i{age};
1759 my $since = CGI::param('since') || strftime('%F %T', localtime(time - $age));
1760 if ($since =~ /^(\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?)$/) {
1766 my $when = CGI::param('when') || '';
1767 if ($when =~ /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
1773 my $lang = CGI::param('lang') || 'en';
1774 if ($lang =~ /^(\w\w)$/) {
1779 if ($what{db_clients}) {
1781 if ($what{filter}) {
1782 # get security filter only if asked
1783 $filter = $self->get_client_filter();
1787 SELECT Client.Name as clientname
1791 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1792 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1796 if ($what{db_client_groups}) {
1798 if ($what{filter}) {
1799 # get security filter only if asked
1800 $filter = $self->get_client_group_filter();
1804 SELECT client_group_name AS name
1805 FROM client_group $filter
1807 my $grps = $self->dbh_selectall_hashref($query, 'name');
1808 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1812 if ($what{db_usernames}) {
1817 my $users = $self->dbh_selectall_hashref($query, 'username');
1818 $ret{db_usernames} = [sort {$a->{username} cmp $b->{username} }
1822 if ($what{db_roles}) {
1827 my $r = $self->dbh_selectall_hashref($query, 'rolename');
1828 $ret{db_roles} = [sort {$a->{rolename} cmp $b->{rolename} }
1832 if ($what{db_mediatypes}) {
1834 SELECT MediaType as mediatype
1837 my $media = $self->dbh_selectall_hashref($query, 'mediatype');
1838 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1842 if ($what{db_locations}) {
1844 SELECT Location as location, Cost as cost
1847 my $loc = $self->dbh_selectall_hashref($query, 'location');
1848 $ret{db_locations} = [ sort { $a->{location}
1854 if ($what{db_pools}) {
1855 my $query = "SELECT Name as name FROM Pool";
1857 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1858 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1861 if ($what{db_filesets}) {
1863 SELECT FileSet.FileSet AS fileset
1866 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1868 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1869 values %$filesets] ;
1872 if ($what{db_jobnames}) {
1874 if ($what{filter}) {
1875 $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
1878 SELECT DISTINCT Job.Name AS jobname
1881 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1883 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1884 values %$jobnames] ;
1887 if ($what{db_devices}) {
1889 SELECT Device.Name AS name
1892 my $devices = $self->dbh_selectall_hashref($query, 'name');
1894 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1904 $self->can_do('r_view_stat');
1905 my $fields = $self->get_form(qw/age level status clients filesets
1906 graph gtype type filter db_clients
1907 limit db_filesets width height
1908 qclients qfilesets qjobnames db_jobnames/);
1910 my $url = CGI::url(-full => 0,
1913 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1915 # this organisation is to keep user choice between 2 click
1916 # TODO : fileset and client selection doesn't work
1923 if ($fields->{gtype} eq 'balloon') {
1924 system("./bgraph.pl");
1928 sub get_selected_media_location
1932 my $media = $self->get_form('jmedias');
1934 unless ($media->{jmedias}) {
1939 SELECT Media.VolumeName AS volumename, Location.Location AS location
1940 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1941 WHERE Media.VolumeName IN ($media->{jmedias})
1944 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1946 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1955 my ($self, $in) = @_ ;
1956 $self->can_do('r_media_mgnt');
1957 my $media = $self->get_selected_media_location();
1963 my $elt = $self->get_form('db_locations');
1965 $self->display({ ID => $cur_id++,
1966 enabled => human_enabled($in),
1967 %$elt, # db_locations
1969 sort { $a->{volumename} cmp $b->{volumename} } values %$media
1978 $self->can_do('r_media_mgnt');
1980 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1982 $self->display($elt, "help_extern.tpl");
1985 sub help_extern_compute
1988 $self->can_do('r_media_mgnt');
1990 my $number = CGI::param('limit') || '' ;
1991 unless ($number =~ /^(\d+)$/) {
1992 return $self->error("Bad arg number : $number ");
1995 my ($sql, undef) = $self->get_param('pools',
1996 'locations', 'mediatypes');
1999 SELECT Media.VolumeName AS volumename,
2000 Media.VolStatus AS volstatus,
2001 Media.LastWritten AS lastwritten,
2002 Media.MediaType AS mediatype,
2003 Media.VolMounts AS volmounts,
2005 Media.Recycle AS recycle,
2006 $self->{sql}->{FROM_UNIXTIME}(
2007 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2008 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2011 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2012 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2014 WHERE Media.InChanger = 1
2015 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
2017 ORDER BY expire DESC, recycle, Media.VolMounts DESC
2021 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2023 $self->display({ Media => [ values %$all ] },
2024 "help_extern_compute.tpl");
2030 $self->can_do('r_media_mgnt');
2032 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
2033 $self->display($param, "help_intern.tpl");
2036 sub help_intern_compute
2039 $self->can_do('r_media_mgnt');
2041 my $number = CGI::param('limit') || '' ;
2042 unless ($number =~ /^(\d+)$/) {
2043 return $self->error("Bad arg number : $number ");
2046 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
2048 if (CGI::param('expired')) {
2050 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2051 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2057 SELECT Media.VolumeName AS volumename,
2058 Media.VolStatus AS volstatus,
2059 Media.LastWritten AS lastwritten,
2060 Media.MediaType AS mediatype,
2061 Media.VolMounts AS volmounts,
2063 $self->{sql}->{FROM_UNIXTIME}(
2064 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2065 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2068 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
2069 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
2071 WHERE Media.InChanger <> 1
2072 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
2073 AND Media.Recycle = 1
2075 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
2079 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2081 $self->display({ Media => [ values %$all ] },
2082 "help_intern_compute.tpl");
2088 my ($self, %arg) = @_ ;
2090 my ($limit, $label) = $self->get_limit(%arg);
2094 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
2095 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
2096 (SELECT count(Job.JobId) FROM Job) AS nb_job,
2097 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
2098 ($self->{sql}->{DB_SIZE}) AS db_size,
2099 (SELECT count(Job.JobId)
2101 WHERE Job.JobStatus IN ('E','e','f','A')
2104 (SELECT count(Client.ClientId) FROM Client) AS nb_client
2107 my $row = $self->dbh_selectrow_hashref($query) ;
2109 $row->{nb_bytes} = human_size($row->{nb_bytes});
2111 $row->{db_size} = human_size($row->{db_size});
2112 $row->{label} = $label;
2114 $self->display($row, "general.tpl");
2119 my ($self, @what) = @_ ;
2120 my %elt = map { $_ => 1 } @what;
2125 if ($elt{clients}) {
2126 my @clients = grep { ! /^\s*$/ } CGI::param('client');
2128 $ret{clients} = \@clients;
2129 my $str = $self->dbh_join(@clients);
2130 $limit .= "AND Client.Name IN ($str) ";
2134 if ($elt{client_groups}) {
2135 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
2137 $ret{client_groups} = \@clients;
2138 my $str = $self->dbh_join(@clients);
2139 $limit .= "AND client_group_name IN ($str) ";
2143 if ($elt{filesets}) {
2144 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
2146 $ret{filesets} = \@filesets;
2147 my $str = $self->dbh_join(@filesets);
2148 $limit .= "AND FileSet.FileSet IN ($str) ";
2152 if ($elt{mediatypes}) {
2153 my @media = grep { ! /^\s*$/ } CGI::param('mediatype');
2155 $ret{mediatypes} = \@media;
2156 my $str = $self->dbh_join(@media);
2157 $limit .= "AND Media.MediaType IN ($str) ";
2162 my $client = CGI::param('client');
2163 $ret{client} = $client;
2164 $client = $self->dbh_join($client);
2165 $limit .= "AND Client.Name = $client ";
2169 my $level = CGI::param('level') || '';
2170 if ($level =~ /^(\w)$/) {
2172 $limit .= "AND Job.Level = '$1' ";
2177 my $jobid = CGI::param('jobid') || '';
2179 if ($jobid =~ /^(\d+)$/) {
2181 $limit .= "AND Job.JobId = '$1' ";
2186 my $status = CGI::param('status') || '';
2187 if ($status =~ /^(\w)$/) {
2190 $limit .= "AND Job.JobStatus IN ('f','E') ";
2191 } elsif ($1 eq 'W') {
2192 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
2194 $limit .= "AND Job.JobStatus = '$1' ";
2199 if ($elt{volstatus}) {
2200 my $status = CGI::param('volstatus') || '';
2201 if ($status =~ /^(\w+)$/) {
2203 $limit .= "AND Media.VolStatus = '$1' ";
2207 if ($elt{locations}) {
2208 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
2210 $ret{locations} = \@location;
2211 my $str = $self->dbh_join(@location);
2212 $limit .= "AND Location.Location IN ($str) ";
2217 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
2219 $ret{pools} = \@pool;
2220 my $str = $self->dbh_join(@pool);
2221 $limit .= "AND Pool.Name IN ($str) ";
2225 if ($elt{location}) {
2226 my $location = CGI::param('location') || '';
2228 $ret{location} = $location;
2229 $location = $self->dbh_quote($location);
2230 $limit .= "AND Location.Location = $location ";
2235 my $pool = CGI::param('pool') || '';
2238 $pool = $self->dbh_quote($pool);
2239 $limit .= "AND Pool.Name = $pool ";
2243 if ($elt{jobtype}) {
2244 my $jobtype = CGI::param('jobtype') || '';
2245 if ($jobtype =~ /^(\w)$/) {
2247 $limit .= "AND Job.Type = '$1' ";
2251 return ($limit, %ret);
2262 my ($self, %arg) = @_ ;
2263 return if $self->cant_do('r_view_job');
2265 $arg{order} = ' Job.JobId DESC ';
2267 my ($limit, $label) = $self->get_limit(%arg);
2268 my ($where, undef) = $self->get_param('clients',
2277 if (CGI::param('client_group')) {
2279 JOIN client_group_member USING (ClientId)
2280 JOIN client_group USING (client_group_id)
2283 my $filter = $self->get_client_filter();
2286 SELECT Job.JobId AS jobid,
2287 Client.Name AS client,
2288 FileSet.FileSet AS fileset,
2289 Job.Name AS jobname,
2291 StartTime AS starttime,
2293 Pool.Name AS poolname,
2294 JobFiles AS jobfiles,
2295 JobBytes AS jobbytes,
2296 JobStatus AS jobstatus,
2297 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2298 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2301 JobErrors AS joberrors
2303 FROM Client $filter $cgq,
2304 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2305 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2306 WHERE Client.ClientId=Job.ClientId
2307 AND Job.JobStatus NOT IN ('R', 'C')
2312 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2314 $self->display({ Filter => $label,
2318 sort { $a->{jobid} <=> $b->{jobid} }
2325 # display job informations
2326 sub display_job_zoom
2328 my ($self, $jobid) = @_ ;
2329 $self->can_do('r_view_job');
2331 $jobid = $self->dbh_quote($jobid);
2333 # get security filter
2334 my $filter = $self->get_client_filter();
2337 SELECT DISTINCT Job.JobId AS jobid,
2338 Client.Name AS client,
2339 Job.Name AS jobname,
2340 FileSet.FileSet AS fileset,
2342 Pool.Name AS poolname,
2343 StartTime AS starttime,
2344 JobFiles AS jobfiles,
2345 JobBytes AS jobbytes,
2346 JobStatus AS jobstatus,
2347 JobErrors AS joberrors,
2348 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2349 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2351 FROM Client $filter,
2352 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2353 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2354 WHERE Client.ClientId=Job.ClientId
2355 AND Job.JobId = $jobid
2358 my $row = $self->dbh_selectrow_hashref($query) ;
2360 # display all volumes associate with this job
2362 SELECT Media.VolumeName as volumename
2363 FROM Job,Media,JobMedia
2364 WHERE Job.JobId = $jobid
2365 AND JobMedia.JobId=Job.JobId
2366 AND JobMedia.MediaId=Media.MediaId
2369 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2371 $row->{volumes} = [ values %$all ] ;
2372 $row->{wiki_url} = $self->{info}->{wiki_url};
2374 $self->display($row, "display_job_zoom.tpl");
2377 sub display_job_group
2379 my ($self, %arg) = @_;
2380 $self->can_do('r_view_job');
2382 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2384 my ($where, undef) = $self->get_param('client_groups',
2387 my $filter = $self->get_client_group_filter();
2390 SELECT client_group_name AS client_group_name,
2391 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2392 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2393 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2394 COALESCE(jobok.nbjobs,0) AS nbjobok,
2395 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2396 COALESCE(jobok.duration, '0:0:0') AS duration
2398 FROM client_group $filter LEFT JOIN (
2399 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2400 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2401 SUM(JobErrors) AS joberrors,
2402 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2403 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2406 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2407 JOIN client_group USING (client_group_id)
2409 WHERE JobStatus = 'T'
2412 ) AS jobok USING (client_group_name) LEFT JOIN
2415 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2416 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2417 SUM(JobErrors) AS joberrors
2418 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2419 JOIN client_group USING (client_group_id)
2421 WHERE JobStatus IN ('f','E', 'A')
2424 ) AS joberr USING (client_group_name)
2428 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2430 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2433 $self->display($rep, "display_job_group.tpl");
2438 my ($self, %arg) = @_ ;
2439 $self->can_do('r_view_media');
2441 my ($limit, $label) = $self->get_limit(%arg);
2442 my ($where, %elt) = $self->get_param('pools',
2447 my $arg = $self->get_form('jmedias', 'qre_media', 'expired');
2449 if ($arg->{jmedias}) {
2450 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2452 if ($arg->{qre_media}) {
2453 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2455 if ($arg->{expired}) {
2457 AND VolStatus = 'Full'
2458 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2459 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2460 ) < NOW() " . $where ;
2464 SELECT Media.VolumeName AS volumename,
2465 Media.VolBytes AS volbytes,
2466 Media.VolStatus AS volstatus,
2467 Media.MediaType AS mediatype,
2468 Media.InChanger AS online,
2469 Media.LastWritten AS lastwritten,
2470 Location.Location AS location,
2471 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2472 Pool.Name AS poolname,
2473 $self->{sql}->{FROM_UNIXTIME}(
2474 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2475 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2478 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2479 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2480 Media.MediaType AS MediaType
2482 WHERE Media.VolStatus = 'Full'
2483 GROUP BY Media.MediaType
2484 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2486 WHERE Media.PoolId=Pool.PoolId
2491 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2493 $self->display({ ID => $cur_id++,
2495 Location => $elt{location},
2496 Media => [ values %$all ],
2498 "display_media.tpl");
2501 sub display_allmedia
2505 my $pool = $self->get_form('db_pools');
2507 foreach my $name (@{ $pool->{db_pools} }) {
2508 CGI::param('pool', $name->{name});
2509 $self->display_media();
2513 sub display_media_zoom
2517 my $media = $self->get_form('jmedias');
2519 unless ($media->{jmedias}) {
2520 return $self->error("Can't get media selection");
2524 SELECT InChanger AS online,
2525 Media.Enabled AS enabled,
2526 VolBytes AS nb_bytes,
2527 VolumeName AS volumename,
2528 VolStatus AS volstatus,
2529 VolMounts AS nb_mounts,
2530 Media.VolUseDuration AS voluseduration,
2531 Media.MaxVolJobs AS maxvoljobs,
2532 Media.MaxVolFiles AS maxvolfiles,
2533 Media.MaxVolBytes AS maxvolbytes,
2534 VolErrors AS nb_errors,
2535 Pool.Name AS poolname,
2536 Location.Location AS location,
2537 Media.Recycle AS recycle,
2538 Media.VolRetention AS volretention,
2539 Media.LastWritten AS lastwritten,
2540 Media.VolReadTime/1000000 AS volreadtime,
2541 Media.VolWriteTime/1000000 AS volwritetime,
2542 Media.RecycleCount AS recyclecount,
2543 Media.Comment AS comment,
2544 $self->{sql}->{FROM_UNIXTIME}(
2545 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2546 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2549 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2550 WHERE Pool.PoolId = Media.PoolId
2551 AND VolumeName IN ($media->{jmedias})
2554 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2556 foreach my $media (values %$all) {
2557 my $mq = $self->dbh_quote($media->{volumename});
2560 SELECT DISTINCT Job.JobId AS jobid,
2562 Job.StartTime AS starttime,
2565 Job.JobFiles AS files,
2566 Job.JobBytes AS bytes,
2567 Job.jobstatus AS status
2568 FROM Media,JobMedia,Job
2569 WHERE Media.VolumeName=$mq
2570 AND Media.MediaId=JobMedia.MediaId
2571 AND JobMedia.JobId=Job.JobId
2574 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2577 SELECT LocationLog.Date AS date,
2578 Location.Location AS location,
2579 LocationLog.Comment AS comment
2580 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2581 WHERE Media.MediaId = LocationLog.MediaId
2582 AND Media.VolumeName = $mq
2586 my $log = $self->dbh_selectall_arrayref($query) ;
2588 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2591 $self->display({ jobs => [ values %$jobs ],
2592 LocationLog => $logtxt,
2594 "display_media_zoom.tpl");
2601 $self->can_do('r_location_mgnt');
2603 my $loc = $self->get_form('qlocation');
2604 unless ($loc->{qlocation}) {
2605 return $self->error("Can't get location");
2609 SELECT Location.Location AS location,
2610 Location.Cost AS cost,
2611 Location.Enabled AS enabled
2613 WHERE Location.Location = $loc->{qlocation}
2616 my $row = $self->dbh_selectrow_hashref($query);
2617 $row->{enabled} = human_enabled($row->{enabled});
2618 $self->display({ ID => $cur_id++,
2619 %$row }, "location_edit.tpl") ;
2625 $self->can_do('r_location_mgnt');
2627 my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
2628 unless ($arg->{qlocation}) {
2629 return $self->error("Can't get location");
2631 unless ($arg->{qnewlocation}) {
2632 return $self->error("Can't get new location name");
2634 unless ($arg->{cost}) {
2635 return $self->error("Can't get new cost");
2638 my $enabled = from_human_enabled($arg->{enabled});
2641 UPDATE Location SET Cost = $arg->{cost},
2642 Location = $arg->{qnewlocation},
2644 WHERE Location.Location = $arg->{qlocation}
2647 $self->dbh_do($query);
2649 $self->location_display();
2655 $self->can_do('r_location_mgnt');
2657 my $arg = $self->get_form(qw/qlocation/) ;
2659 unless ($arg->{qlocation}) {
2660 return $self->error("Can't get location");
2664 SELECT count(Media.MediaId) AS nb
2665 FROM Media INNER JOIN Location USING (LocationID)
2666 WHERE Location = $arg->{qlocation}
2669 my $res = $self->dbh_selectrow_hashref($query);
2672 return $self->error("Sorry, the location must be empty");
2676 DELETE FROM Location WHERE Location = $arg->{qlocation}
2679 $self->dbh_do($query);
2681 $self->location_display();
2687 $self->can_do('r_location_mgnt');
2689 my $arg = $self->get_form(qw/qlocation cost/) ;
2691 unless ($arg->{qlocation}) {
2692 $self->display({}, "location_add.tpl");
2695 unless ($arg->{cost}) {
2696 return $self->error("Can't get new cost");
2699 my $enabled = CGI::param('enabled') || '';
2700 $enabled = from_human_enabled($enabled);
2703 INSERT INTO Location (Location, Cost, Enabled)
2704 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2707 $self->dbh_do($query);
2709 $self->location_display();
2712 sub location_display
2717 SELECT Location.Location AS location,
2718 Location.Cost AS cost,
2719 Location.Enabled AS enabled,
2720 (SELECT count(Media.MediaId)
2722 WHERE Media.LocationId = Location.LocationId
2727 my $location = $self->dbh_selectall_hashref($query, 'location');
2729 $self->display({ ID => $cur_id++,
2730 Locations => [ values %$location ] },
2731 "display_location.tpl");
2738 my $media = $self->get_selected_media_location();
2743 my $arg = $self->get_form('db_locations', 'qnewlocation');
2745 $self->display({ email => $self->{info}->{email_media},
2747 media => [ values %$media ],
2749 "update_location.tpl");
2752 ###########################################################
2757 $self->can_do('r_group_mgnt');
2759 my $grp = $self->get_form(qw/qclient_group db_clients/);
2761 unless ($grp->{qclient_group}) {
2762 $self->display({ ID => $cur_id++,
2763 client_group => "''",
2765 }, "groups_edit.tpl");
2771 FROM Client JOIN client_group_member using (clientid)
2772 JOIN client_group using (client_group_id)
2773 WHERE client_group_name = $grp->{qclient_group}
2776 my $row = $self->dbh_selectall_hashref($query, "name");
2778 $self->display({ ID => $cur_id++,
2779 client_group => $grp->{qclient_group},
2781 client_group_member => [ values %$row]},
2788 $self->can_do('r_group_mgnt');
2790 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2792 if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
2794 INSERT INTO client_group (client_group_name)
2795 VALUES ($arg->{qnewgroup})
2797 $self->dbh_do($query);
2798 $arg->{qclient_group} = $arg->{qnewgroup};
2801 unless ($arg->{qclient_group}) {
2802 return $self->error("Can't get groups");
2805 $self->{dbh}->begin_work();
2808 DELETE FROM client_group_member
2809 WHERE client_group_id IN
2810 (SELECT client_group_id
2812 WHERE client_group_name = $arg->{qclient_group})
2814 $self->dbh_do($query);
2816 if ($arg->{jclients}) {
2818 INSERT INTO client_group_member (clientid, client_group_id)
2820 (SELECT client_group_id
2822 WHERE client_group_name = $arg->{qclient_group})
2823 FROM Client WHERE Name IN ($arg->{jclients})
2826 $self->dbh_do($query);
2828 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2831 SET client_group_name = $arg->{qnewgroup}
2832 WHERE client_group_name = $arg->{qclient_group}
2835 $self->dbh_do($query);
2838 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2840 $self->display_groups();
2846 $self->can_do('r_group_mgnt');
2848 my $arg = $self->get_form(qw/qclient_group/);
2850 unless ($arg->{qclient_group}) {
2851 return $self->error("Can't get groups");
2854 $self->{dbh}->begin_work();
2857 DELETE FROM client_group_member
2858 WHERE client_group_id IN
2859 (SELECT client_group_id
2861 WHERE client_group_name = $arg->{qclient_group})");
2864 DELETE FROM bweb_client_group_acl
2865 WHERE client_group_id IN
2866 (SELECT client_group_id
2868 WHERE client_group_name = $arg->{qclient_group})");
2871 DELETE FROM client_group
2872 WHERE client_group_name = $arg->{qclient_group}");
2874 $self->{dbh}->commit();
2875 $self->display_groups();
2883 if ($self->cant_do('r_group_mgnt')) {
2884 $arg = $self->get_form(qw/db_client_groups filter/) ;
2886 $arg = $self->get_form(qw/db_client_groups/) ;
2889 if ($self->{dbh}->errstr) {
2890 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2895 $self->display({ ID => $cur_id++,
2897 "display_groups.tpl");
2900 ###########################################################
2905 if (not $self->{info}->{enable_security}) {
2908 # admin is a special user that can do everything
2909 if ($self->{loginname} eq 'admin') {
2912 if (!$self->{loginname}) {
2913 $self->error("Can't get your login name");
2914 $self->display_end();
2918 if (defined $self->{security}) {
2921 $self->{security} = {};
2922 my $u = $self->dbh_quote($self->{loginname});
2925 SELECT use_acl, rolename, tpl
2927 JOIN bweb_role_member USING (userid)
2928 JOIN bweb_role USING (roleid)
2931 my $rows = $self->dbh_selectall_arrayref($query);
2932 # do cache with this role
2933 if (!$rows or !scalar(@$rows)) {
2934 $self->error("Can't get $self->{loginname}'s roles");
2935 $self->display_end();
2938 foreach my $r (@$rows) {
2939 $self->{security}->{$r->[1]}=1;
2941 $self->{security}->{use_acl} = $rows->[0]->[0];
2942 if ($rows->[0]->[2] =~ /^(\w\w)$/) {
2950 my ($self, $action) = @_;
2951 # is security enabled in configuration ?
2952 if (not $self->{info}->{enable_security}) {
2955 # admin is a special user that can do everything
2956 if ($self->{loginname} eq 'admin') {
2960 if (!$self->{loginname}) {
2961 $self->{error} = "Can't do $action, your are not logged. " .
2962 "Check security with your administrator";
2965 if (!$self->get_roles()) {
2968 if (!$self->{security}->{$action}) {
2970 "$self->{loginname} sorry, but this action ($action) " .
2971 "is not permited. " .
2972 "Check security with your administrator";
2978 # make like an assert (program die)
2981 my ($self, $action) = @_;
2982 if ($self->cant_do($action)) {
2983 $self->error($self->{error});
2984 $self->display_end();
2994 if (!$self->{info}->{enable_security} or
2995 !$self->{info}->{enable_security_acl})
3000 if ($self->get_roles()) {
3001 return $self->{security}->{use_acl};
3007 # JOIN Client USING (ClientId) " . $b->get_client_filter() . "
3008 sub get_client_filter
3010 my ($self, $login) = @_;
3013 $u = $self->dbh_quote($login);
3014 } elsif ($self->use_filter()) {
3015 $u = $self->dbh_quote($self->{loginname});
3020 JOIN (SELECT ClientId FROM client_group_member
3021 JOIN client_group USING (client_group_id)
3022 JOIN bweb_client_group_acl USING (client_group_id)
3023 JOIN bweb_user USING (userid)
3024 WHERE bweb_user.username = $u
3025 ) AS filter USING (ClientId)";
3028 #JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
3029 sub get_client_group_filter
3031 my ($self, $login) = @_;
3034 $u = $self->dbh_quote($login);
3035 } elsif ($self->use_filter()) {
3036 $u = $self->dbh_quote($self->{loginname});
3041 JOIN (SELECT client_group_id
3042 FROM bweb_client_group_acl
3043 JOIN bweb_user USING (userid)
3044 WHERE bweb_user.username = $u
3045 ) AS filter USING (client_group_id)";
3048 # role and username have to be quoted before
3049 # role and username can be a quoted list
3052 my ($self, $role, $username) = @_;
3053 $self->can_do("r_user_mgnt");
3055 my $nb = $self->dbh_do("
3056 DELETE FROM bweb_role_member
3057 WHERE roleid = (SELECT roleid FROM bweb_role
3058 WHERE rolename IN ($role))
3059 AND userid = (SELECT userid FROM bweb_user
3060 WHERE username IN ($username))");
3064 # role and username have to be quoted before
3065 # role and username can be a quoted list
3068 my ($self, $role, $username) = @_;
3069 $self->can_do("r_user_mgnt");
3071 my $nb = $self->dbh_do("
3072 INSERT INTO bweb_role_member (roleid, userid)
3073 SELECT roleid, userid FROM bweb_role, bweb_user
3074 WHERE rolename IN ($role)
3075 AND username IN ($username)
3080 # role and username have to be quoted before
3081 # role and username can be a quoted list
3084 my ($self, $copy, $user) = @_;
3085 $self->can_do("r_user_mgnt");
3087 my $nb = $self->dbh_do("
3088 INSERT INTO bweb_role_member (roleid, userid)
3089 SELECT roleid, a.userid
3090 FROM bweb_user AS a, bweb_role_member
3091 JOIN bweb_user USING (userid)
3092 WHERE bweb_user.username = $copy
3093 AND a.username = $user");
3097 # username can be a join quoted list of usernames
3100 my ($self, $username) = @_;
3101 $self->can_do("r_user_mgnt");
3104 DELETE FROM bweb_role_member
3108 WHERE username in ($username))");
3110 DELETE FROM bweb_client_group_acl
3114 WHERE username IN ($username))");
3121 $self->can_do("r_user_mgnt");
3123 my $arg = $self->get_form(qw/jusernames/);
3125 unless ($arg->{jusernames}) {
3126 return $self->error("Can't get user");
3129 $self->{dbh}->begin_work();
3131 $self->revoke_all($arg->{jusernames});
3133 DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
3135 $self->{dbh}->commit();
3137 $self->display_users();
3143 $self->can_do("r_user_mgnt");
3145 # we don't quote username directly to check that it is conform
3146 my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate
3147 lang qcopy_username jclient_groups/) ;
3149 if (not $arg->{qcreate}) {
3150 $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
3151 $self->display($arg, "display_user.tpl");
3155 my $u = $self->dbh_quote($arg->{username});
3157 $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
3159 if (!$arg->{qpasswd}) {
3160 $arg->{qpasswd} = "''";
3162 if (!$arg->{qcomment}) {
3163 $arg->{qcomment} = "''";
3166 # will fail if user already exists
3167 # UPDATE with mysql dbi does not return if update is ok
3170 SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment},
3171 use_acl=$arg->{use_acl}, tpl='$arg->{lang}'
3172 WHERE username = $u")
3173 # and (! $self->dbh_is_mysql() )
3176 INSERT INTO bweb_user (username, passwd, use_acl, comment, tpl)
3177 VALUES ($u, $arg->{qpasswd}, $arg->{use_acl},
3178 $arg->{qcomment}, '$arg->{lang}')");
3180 $self->{dbh}->begin_work();
3182 $self->revoke_all($u);
3184 if ($arg->{qcopy_username}) {
3185 $self->grant_like($arg->{qcopy_username}, $u);
3187 $self->grant($arg->{jrolenames}, $u);
3190 if ($arg->{jclient_groups}) {
3192 INSERT INTO bweb_client_group_acl (client_group_id, userid)
3193 SELECT client_group_id, userid
3194 FROM client_group, bweb_user
3195 WHERE client_group_name IN ($arg->{jclient_groups})
3200 $self->{dbh}->commit();
3202 $self->display_users();
3205 # TODO: we miss a matrix with all user/roles
3209 $self->can_do("r_user_mgnt");
3211 my $arg = $self->get_form(qw/db_usernames/) ;
3213 if ($self->{dbh}->errstr) {
3214 return $self->error("Can't use users with bweb, read INSTALL to enable them");
3217 $self->display({ ID => $cur_id++,
3219 "display_users.tpl");
3225 $self->can_do("r_user_mgnt");
3227 my $arg = $self->get_form('username');
3228 my $user = $self->dbh_quote($arg->{username});
3230 my $userp = $self->dbh_selectrow_hashref("
3231 SELECT username, passwd, comment, use_acl, tpl
3233 WHERE username = $user
3236 return $self->error("Can't find $user in catalog");
3238 my $filter = $self->get_client_group_filter($arg->{username});
3239 my $scg = $self->dbh_selectall_hashref("
3240 SELECT client_group_name AS name
3241 FROM client_group $filter
3245 #------------+--------
3250 my $role = $self->dbh_selectall_hashref("
3251 SELECT rolename, temp.userid
3253 LEFT JOIN (SELECT roleid, userid
3254 FROM bweb_user JOIN bweb_role_member USING (userid)
3255 WHERE username = $user) AS temp USING (roleid)
3259 $arg = $self->get_form(qw/db_usernames db_client_groups/);
3262 db_usernames => $arg->{db_usernames},
3263 username => $userp->{username},
3264 comment => $userp->{comment},
3265 passwd => $userp->{passwd},
3266 lang => $userp->{lang},
3267 use_acl => $userp->{use_acl},
3268 db_client_groups => $arg->{db_client_groups},
3269 client_group => [ values %$scg ],
3270 db_roles => [ values %$role],
3271 }, "display_user.tpl");
3275 ###########################################################
3277 sub get_media_max_size
3279 my ($self, $type) = @_;
3281 "SELECT avg(VolBytes) AS size
3283 WHERE Media.VolStatus = 'Full'
3284 AND Media.MediaType = '$type'
3287 my $res = $self->selectrow_hashref($query);
3290 return $res->{size};
3300 my $media = $self->get_form('qmedia');
3302 unless ($media->{qmedia}) {
3303 return $self->error("Can't get media");
3307 SELECT Media.Slot AS slot,
3308 PoolMedia.Name AS poolname,
3309 Media.VolStatus AS volstatus,
3310 Media.InChanger AS inchanger,
3311 Location.Location AS location,
3312 Media.VolumeName AS volumename,
3313 Media.MaxVolBytes AS maxvolbytes,
3314 Media.MaxVolJobs AS maxvoljobs,
3315 Media.MaxVolFiles AS maxvolfiles,
3316 Media.VolUseDuration AS voluseduration,
3317 Media.VolRetention AS volretention,
3318 Media.Comment AS comment,
3319 PoolRecycle.Name AS poolrecycle,
3320 Media.Enabled AS enabled
3322 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
3323 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
3324 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3326 WHERE Media.VolumeName = $media->{qmedia}
3329 my $row = $self->dbh_selectrow_hashref($query);
3330 $row->{volretention} = human_sec($row->{volretention});
3331 $row->{voluseduration} = human_sec($row->{voluseduration});
3332 $row->{enabled} = human_enabled($row->{enabled});
3334 my $elt = $self->get_form(qw/db_pools db_locations/);
3339 }, "update_media.tpl");
3345 $self->can_do('r_media_mgnt');
3347 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
3349 unless ($arg->{jmedias}) {
3350 return $self->error("Can't get selected media");
3353 unless ($arg->{qnewlocation}) {
3354 return $self->error("Can't get new location");
3359 SET LocationId = (SELECT LocationId
3361 WHERE Location = $arg->{qnewlocation})
3362 WHERE Media.VolumeName IN ($arg->{jmedias})
3365 my $nb = $self->dbh_do($query);
3367 print "$nb media updated, you may have to update your autochanger.";
3369 $self->display_media();
3375 $self->can_do('r_media_mgnt');
3377 my $media = $self->get_selected_media_location();
3379 return $self->error("Can't get media selection");
3381 my $newloc = CGI::param('newlocation');
3383 my $user = CGI::param('user') || 'unknown';
3384 my $comm = CGI::param('comment') || '';
3385 $comm = $self->dbh_quote("$user: $comm");
3387 my $arg = $self->get_form('enabled');
3388 my $en = from_human_enabled($arg->{enabled});
3389 my $b = $self->get_bconsole();
3392 foreach my $vol (keys %$media) {
3394 INSERT INTO LocationLog (Date,Comment,MediaId,LocationId,NewEnabled,NewVolStatus)
3395 SELECT NOW(), $comm, Media.MediaId, Location.LocationId, $en, VolStatus
3396 FROM Media, Location
3397 WHERE Media.VolumeName = '$vol'
3398 AND Location.Location = '$media->{$vol}->{location}'
3400 $self->dbh_do($query);
3401 $self->debug($query);
3402 $b->send_cmd("update volume=\"$vol\" enabled=$en");
3407 $q->param('action', 'update_location');
3408 my $url = $q->url(-full => 1, -query=>1);
3410 $self->display({ email => $self->{info}->{email_media},
3412 newlocation => $newloc,
3413 # [ { volumename => 'vol1' }, { volumename => 'vol2'},..]
3414 media => [ values %$media ],
3416 "change_location.tpl");
3420 sub display_client_stats
3422 my ($self, %arg) = @_ ;
3423 $self->can_do('r_view_stat');
3425 my $client = $self->dbh_quote($arg{clientname});
3426 # get security filter
3427 my $filter = $self->get_client_filter();
3429 my ($limit, $label) = $self->get_limit(%arg);
3432 count(Job.JobId) AS nb_jobs,
3433 sum(Job.JobBytes) AS nb_bytes,
3434 sum(Job.JobErrors) AS nb_err,
3435 sum(Job.JobFiles) AS nb_files,
3436 Client.Name AS clientname
3437 FROM Job JOIN Client USING (ClientId) $filter
3439 Client.Name = $client
3441 GROUP BY Client.Name
3444 my $row = $self->dbh_selectrow_hashref($query);
3446 $row->{ID} = $cur_id++;
3447 $row->{label} = $label;
3448 $row->{grapharg} = "client";
3450 $self->display($row, "display_client_stats.tpl");
3454 sub display_group_stats
3456 my ($self, %arg) = @_ ;
3458 my $carg = $self->get_form(qw/qclient_group/);
3460 unless ($carg->{qclient_group}) {
3461 return $self->error("Can't get group");
3464 my ($limit, $label) = $self->get_limit(%arg);
3468 count(Job.JobId) AS nb_jobs,
3469 sum(Job.JobBytes) AS nb_bytes,
3470 sum(Job.JobErrors) AS nb_err,
3471 sum(Job.JobFiles) AS nb_files,
3472 client_group.client_group_name AS clientname
3473 FROM Job JOIN Client USING (ClientId)
3474 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
3475 JOIN client_group USING (client_group_id)
3477 client_group.client_group_name = $carg->{qclient_group}
3479 GROUP BY client_group.client_group_name
3482 my $row = $self->dbh_selectrow_hashref($query);
3484 $row->{ID} = $cur_id++;
3485 $row->{label} = $label;
3486 $row->{grapharg} = "client_group";
3488 $self->display($row, "display_client_stats.tpl");
3491 # [ name, num, value, joberrors, nb_job ] =>
3493 # [ { name => 'ALL',
3494 # events => [ { num => 1, label => '2007-01',
3495 # value => 'T', title => 10 },
3496 # { num => 2, label => '2007-02',
3497 # value => 'R', title => 11 },
3500 # { name => 'Other',
3504 sub make_overview_tab
3506 my ($self, $q) = @_;
3507 my $ret = $self->dbh_selectall_arrayref($q);
3511 for my $elt (@$ret) {
3512 if ($cur_name and $cur_name ne $elt->[0]) { # order by name, num
3513 push @items, { name => $cur_name, events => $events};
3516 $cur_name = $elt->[0];
3518 { num => $elt->[1], status => $elt->[2],
3519 joberrors => $elt->[3], title => "$elt->[4] jobs"};
3521 push @items, { name => $cur_name, events => $events};
3525 sub get_time_overview
3527 my ($self, $arg) = @_; # want since et age from get_form();
3528 my $type = $arg->{type} || 'day';
3529 if ($type =~ /^(day|week|hour|month)$/) {
3535 my $jobt = $self->{info}->{stat_job_table} || 'Job';
3536 my $stime1 = $self->{sql}->{"STARTTIME_P" . $type}; # get 1,2,3
3537 $stime1 =~ s/Job.StartTime/date/;
3538 my $stime2 = $self->{sql}->{"STARTTIME_" . $type}; # get 2007-01-03, 2007-01-23
3540 my ($limit, $label) = $self->get_limit('since' => $arg->{since},
3541 'age' => $arg->{age});
3542 return ($stime1, $stime2, $limit, $label, $jobt);
3545 # lu ma me je ve sa di
3546 # groupe1 v v x w v v v overview
3547 # |-- s1 v v v v v v v overview_zoom
3548 # |-- s2 v v x v v v v
3549 # `-- s3 v v v w v v v
3550 sub display_overview_zoom
3553 $self->can_do('r_view_stat');
3555 my $arg = $self->get_form(qw/jclient_groups age since type/);
3557 if (!$arg->{jclient_groups}) {
3558 return $self->error("Can't get client_group selection");
3560 my ($filter2, undef) = $self->get_param(qw/level jobtype/);
3561 my ($stime1, $stime2, $limit, $label, $jobt) = $self->get_time_overview($arg);
3563 my $filter = $self->get_client_filter();
3565 SELECT name, $stime1 AS num,
3566 JobStatus AS value, joberrors, nb_job
3568 SELECT $stime2 AS date,
3569 Client.Name AS name,
3570 MAX(severity) AS severity,
3572 SUM(JobErrors) AS joberrors
3574 JOIN client_group_member USING (ClientId)
3575 JOIN client_group USING (client_group_id)
3576 JOIN Client USING (ClientId) $filter
3577 JOIN Status USING (JobStatus)
3578 WHERE client_group_name IN ($arg->{jclient_groups})
3581 GROUP BY Client.Name, date
3582 ) AS sub JOIN Status USING (severity)
3585 my $items = $self->make_overview_tab($q);
3586 $self->display({label => $label,
3587 action => "job;since=$arg->{since};type=$arg->{type};age=$arg->{age};client=",
3588 items => $items}, "overview.tpl");
3591 sub display_overview
3594 $self->can_do('r_view_stat');
3596 my $arg = $self->get_form(qw/jclient_groups age since type/);
3597 my ($filter2, undef) = $self->get_param(qw/client_groups level jobtype/);
3598 my $filter3 = $self->get_client_group_filter();
3599 my ($stime1, $stime2, $filter1, $label, $jobt) = $self->get_time_overview($arg);
3602 SELECT name, $stime1 AS num,
3603 JobStatus AS value, joberrors, nb_job
3605 SELECT $stime2 AS date,
3606 client_group_name AS name,
3607 MAX(severity) AS severity,
3609 SUM(JobErrors) AS joberrors
3611 JOIN client_group_member USING (ClientId)
3612 JOIN client_group USING (client_group_id) $filter3
3613 JOIN Status USING (JobStatus)
3614 WHERE true $filter1 $filter2
3615 GROUP BY client_group_name, date
3616 ) AS sub JOIN Status USING (severity)
3619 my $items = $self->make_overview_tab($q);
3620 $self->display({label=>$label,
3621 action => "overview_zoom;since=$arg->{since};type=$arg->{type};age=$arg->{age};client_group=",
3622 items => $items}, "overview.tpl");
3626 # poolname can be undef
3629 my ($self, $poolname) = @_ ;
3630 $self->can_do('r_view_media');
3635 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
3636 if ($arg->{jmediatypes}) {
3637 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
3638 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
3641 # TODO : afficher les tailles et les dates
3644 SELECT subq.volmax AS volmax,
3645 subq.volnum AS volnum,
3646 subq.voltotal AS voltotal,
3648 Pool.Recycle AS recycle,
3649 Pool.VolRetention AS volretention,
3650 Pool.VolUseDuration AS voluseduration,
3651 Pool.MaxVolJobs AS maxvoljobs,
3652 Pool.MaxVolFiles AS maxvolfiles,
3653 Pool.MaxVolBytes AS maxvolbytes,
3654 subq.PoolId AS PoolId,
3655 subq.MediaType AS mediatype,
3656 $self->{sql}->{CAT_POOL_TYPE} AS uniq
3659 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
3660 count(Media.MediaId) AS volnum,
3661 sum(Media.VolBytes) AS voltotal,
3662 Media.PoolId AS PoolId,
3663 Media.MediaType AS MediaType
3665 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
3666 Media.MediaType AS MediaType
3668 WHERE Media.VolStatus = 'Full'
3669 GROUP BY Media.MediaType
3670 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
3671 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
3673 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
3677 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
3680 SELECT Pool.Name AS name,
3681 sum(VolBytes) AS size
3682 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
3683 WHERE Media.VolStatus IN ('Recycled', 'Purged')
3687 my $empty = $self->dbh_selectall_hashref($query, 'name');
3689 foreach my $p (values %$all) {
3690 if ($p->{volmax} > 0) { # mysql returns 0.0000
3691 # we remove Recycled/Purged media from pool usage
3692 if (defined $empty->{$p->{name}}) {
3693 $p->{voltotal} -= $empty->{$p->{name}}->{size};
3695 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
3697 $p->{poolusage} = 0;
3701 SELECT VolStatus AS volstatus, count(MediaId) AS nb
3703 WHERE PoolId=$p->{poolid}
3704 AND Media.MediaType = '$p->{mediatype}'
3708 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
3709 foreach my $t (values %$content) {
3710 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
3715 $self->display({ ID => $cur_id++,
3716 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
3717 Pools => [ values %$all ]},
3718 "display_pool.tpl");
3721 sub display_running_job
3724 return if $self->cant_do('r_view_running_job');
3726 my $arg = $self->get_form('client', 'jobid');
3728 if (!$arg->{client} and $arg->{jobid}) {
3729 # get security filter
3730 my $filter = $self->get_client_filter();
3733 SELECT Client.Name AS name
3734 FROM Job INNER JOIN Client USING (ClientId) $filter
3735 WHERE Job.JobId = $arg->{jobid}
3738 my $row = $self->dbh_selectrow_hashref($query);
3741 $arg->{client} = $row->{name};
3742 CGI::param('client', $arg->{client});
3746 if ($arg->{client}) {
3747 my $cli = new Bweb::Client(name => $arg->{client});
3748 $cli->display_running_job($self->{info}, $arg->{jobid});
3749 if ($arg->{jobid}) {
3750 $self->get_job_log();
3753 $self->error("Can't get client or jobid");
3757 sub display_running_jobs
3759 my ($self, $display_action) = @_;
3760 return if $self->cant_do('r_view_running_job');
3762 # get security filter
3763 my $filter = $self->get_client_filter();
3766 SELECT Job.JobId AS jobid,
3767 Job.Name AS jobname,
3769 Job.StartTime AS starttime,
3770 Job.JobFiles AS jobfiles,
3771 Job.JobBytes AS jobbytes,
3772 Job.JobStatus AS jobstatus,
3773 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
3774 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
3776 Client.Name AS clientname
3777 FROM Job INNER JOIN Client USING (ClientId) $filter
3779 JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
3781 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
3783 $self->display({ ID => $cur_id++,
3784 display_action => $display_action,
3785 Jobs => [ values %$all ]},
3786 "running_job.tpl") ;
3789 # return the autochanger list to update
3793 $self->can_do('r_media_mgnt');
3796 my $arg = $self->get_form('jmedias');
3798 unless ($arg->{jmedias}) {
3799 return $self->error("Can't get media selection");
3803 SELECT Media.VolumeName AS volumename,
3804 Storage.Name AS storage,
3805 Location.Location AS location,
3807 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
3808 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
3809 WHERE Media.VolumeName IN ($arg->{jmedias})
3810 AND Media.InChanger = 1
3813 my $all = $self->dbh_selectall_hashref($query, 'volumename');
3815 foreach my $vol (values %$all) {
3816 my $a = $self->ach_get($vol->{location});
3818 $ret{$vol->{location}} = 1;
3820 unless ($a->{have_status}) {
3822 $a->{have_status} = 1;
3825 print "eject $vol->{volumename} from $vol->{storage} : ";
3826 if ($a->send_to_io($vol->{slot})) {
3827 print "<img src='/bweb/T.png' alt='ok'><br/>";
3829 print "<img src='/bweb/E.png' alt='err'><br/>";
3839 my ($to, $subject, $content) = (CGI::param('email'),
3840 CGI::param('subject'),
3841 CGI::param('content'));
3842 $to =~ s/[^\w\d\.\@<>,]//;
3843 $subject =~ s/[^\w\d\.\[\]]/ /;
3845 open(MAIL, "|mail -s '$subject' '$to'") ;
3846 print MAIL $content;
3856 my $arg = $self->get_form('jobid', 'client');
3858 print CGI::header('text/brestore');
3859 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3860 print "client=$arg->{client}\n" if ($arg->{client});
3861 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3865 # TODO : move this to Bweb::Autochanger ?
3866 # TODO : make this internal to not eject tape ?
3872 my ($self, $name) = @_;
3875 return $self->error("Can't get your autochanger name ach");
3878 unless ($self->{info}->{ach_list}) {
3879 return $self->error("Could not find any autochanger");
3882 my $a = $self->{info}->{ach_list}->{$name};
3885 $self->error("Can't get your autochanger $name from your ach_list");
3890 $a->{debug} = $self->{debug};
3897 my ($self, $ach) = @_;
3898 $self->can_do('r_configure');
3900 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3902 $self->{info}->save();
3910 $self->can_do('r_configure');
3912 my $arg = $self->get_form('ach');
3914 or !$self->{info}->{ach_list}
3915 or !$self->{info}->{ach_list}->{$arg->{ach}})
3917 return $self->error("Can't get autochanger name");
3920 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3924 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3926 my $b = $self->get_bconsole();
3928 my @storages = $b->list_storage() ;
3930 $ach->{devices} = [ map { { name => $_ } } @storages ];
3932 $self->display($ach, "ach_add.tpl");
3933 delete $ach->{drives};
3934 delete $ach->{devices};
3941 $self->can_do('r_configure');
3943 my $arg = $self->get_form('ach');
3946 or !$self->{info}->{ach_list}
3947 or !$self->{info}->{ach_list}->{$arg->{ach}})
3949 return $self->error("Can't get autochanger name");
3952 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3954 $self->{info}->save();
3955 $self->{info}->view();
3961 $self->can_do('r_configure');
3963 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3965 my $b = $self->get_bconsole();
3966 my @storages = $b->list_storage() ;
3968 unless ($arg->{ach}) {
3969 $arg->{devices} = [ map { { name => $_ } } @storages ];
3970 return $self->display($arg, "ach_add.tpl");
3974 foreach my $drive (CGI::param('drives'))
3976 unless (grep(/^$drive$/,@storages)) {
3977 return $self->error("Can't find $drive in storage list");
3980 my $index = CGI::param("index_$drive");
3981 unless (defined $index and $index =~ /^(\d+)$/) {
3982 return $self->error("Can't get $drive index");
3985 $drives[$index] = $drive;
3989 return $self->error("Can't get drives from Autochanger");
3992 my $a = new Bweb::Autochanger(name => $arg->{ach},
3993 precmd => $arg->{precmd},
3994 drive_name => \@drives,
3995 device => $arg->{device},
3996 mtxcmd => $arg->{mtxcmd});
3998 $self->ach_register($a) ;
4000 $self->{info}->view();
4006 $self->can_do('r_delete_job');
4008 my $arg = $self->get_form('jobid');
4010 if ($arg->{jobid}) {
4011 my $b = $self->get_bconsole();
4012 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
4016 title => "Delete a job ",
4017 name => "delete jobid=$arg->{jobid}",
4025 $self->can_do('r_media_mgnt');
4027 my $arg = $self->get_form(qw/media volstatus inchanger pool
4028 slot volretention voluseduration
4029 maxvoljobs maxvolfiles maxvolbytes
4030 qcomment poolrecycle enabled
4033 unless ($arg->{media}) {
4034 return $self->error("Can't find media selection");
4037 my $update = "update volume=$arg->{media} ";
4039 if ($arg->{volstatus}) {
4040 $update .= " volstatus=$arg->{volstatus} ";
4043 if ($arg->{inchanger}) {
4044 $update .= " inchanger=yes " ;
4046 $update .= " slot=$arg->{slot} ";
4049 $update .= " slot=0 inchanger=no ";
4052 if ($arg->{enabled}) {
4053 $update .= " enabled=$arg->{enabled} ";
4057 $update .= " pool=$arg->{pool} " ;
4060 if (defined $arg->{volretention}) {
4061 $update .= " volretention=\"$arg->{volretention}\" " ;
4064 if (defined $arg->{voluseduration}) {
4065 $update .= " voluse=\"$arg->{voluseduration}\" " ;
4068 if (defined $arg->{maxvoljobs}) {
4069 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
4072 if (defined $arg->{maxvolfiles}) {
4073 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
4076 if (defined $arg->{maxvolbytes}) {
4077 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
4080 if (defined $arg->{poolrecycle}) {
4081 $update .= " recyclepool=\"$arg->{poolrecycle}\" " ;
4084 my $b = $self->get_bconsole();
4087 content => $b->send_cmd($update),
4088 title => "Update a volume ",
4094 my $media = $self->dbh_quote($arg->{media});
4096 my $loc = CGI::param('location') || '';
4098 $loc = $self->dbh_quote($loc); # is checked by db
4099 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
4101 if (!$arg->{qcomment}) {
4102 $arg->{qcomment} = "''";
4104 push @q, "Comment=$arg->{qcomment}";
4109 SET " . join (',', @q) . "
4110 WHERE Media.VolumeName = $media
4112 $self->dbh_do($query);
4114 $self->update_media();
4120 $self->can_do('r_autochanger_mgnt');
4122 my $ach = CGI::param('ach') ;
4123 $ach = $self->ach_get($ach);
4125 return $self->error("Bad autochanger name");
4129 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
4130 $b->update_slots($ach->{name});
4137 $self->can_do('r_view_log');
4139 my $arg = $self->get_form('jobid', 'limit', 'offset');
4140 unless ($arg->{jobid}) {
4141 return $self->error("Can't get jobid");
4144 if ($arg->{limit} == 100) {
4145 $arg->{limit} = 1000;
4147 # get security filter
4148 my $filter = $self->get_client_filter();
4151 SELECT Job.Name as name, Client.Name as clientname
4152 FROM Job INNER JOIN Client USING (ClientId) $filter
4153 WHERE JobId = $arg->{jobid}
4156 my $row = $self->dbh_selectrow_hashref($query);
4159 return $self->error("Can't find $arg->{jobid} in catalog");
4162 # display only Error and Warning messages
4164 if (CGI::param('error')) {
4165 $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning|ERR=' ";
4169 if (CGI::param('time') || $self->{info}->{display_log_time}) {
4170 $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
4172 $logtext = 'LogText';
4176 SELECT count(1) AS nbline, JobId AS jobid,
4177 GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
4179 SELECT JobId, Time, LogText
4181 WHERE ( Log.JobId = $arg->{jobid}
4183 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
4184 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
4188 OFFSET $arg->{offset}
4194 my $log = $self->dbh_selectrow_hashref($query);
4196 return $self->error("Can't get log for jobid $arg->{jobid}");
4199 $self->display({ lines=> $log->{logtxt},
4200 nbline => $log->{nbline},
4201 jobid => $arg->{jobid},
4202 name => $row->{name},
4203 client => $row->{clientname},
4204 offset => $arg->{offset},
4205 limit => $arg->{limit},
4206 }, 'display_log.tpl');
4212 $self->can_do('r_media_mgnt');
4213 my $arg = $self->get_form('storage', 'pool', 'nb', 'media', 'offset');
4214 my $b = $self->get_bconsole();
4216 if (!$arg->{storage} || !$arg->{pool} || not defined $arg->{nb} || !$arg->{media} || !$arg->{offset}) {
4217 CGI::param(offset => 0);
4218 $arg = $self->get_form('db_pools');
4219 $arg->{storage} = [ map { { name => $_ } }$b->list_storage()];
4220 $self->display($arg, 'add_media.tpl');
4225 if ($arg->{nb} > 0) {
4226 $arg->{offset} = $arg->{offset}?$arg->{offset}:1;
4227 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n$arg->{nb}\n$arg->{media}\n$arg->{offset}\n";
4229 $cmd = "add pool=\"$arg->{pool}\" storage=\"$arg->{storage}\"\n0\n$arg->{media}\n";
4235 CGI::param('media', '');
4236 CGI::param('re_media', $arg->{media});
4237 $self->display_media();
4243 $self->can_do('r_autochanger_mgnt');
4245 my $arg = $self->get_form('ach', 'slots', 'drive');
4247 unless ($arg->{ach}) {
4248 return $self->error("Can't find autochanger name");
4251 my $a = $self->ach_get($arg->{ach});
4253 return $self->error("Can't find autochanger name in configuration");
4256 my $storage = $a->get_drive_name($arg->{drive});
4258 return $self->error("Can't get your drive name");
4264 if ($arg->{slots}) {
4265 $slots = join(",", @{ $arg->{slots} });
4266 $slots_sql = " AND Slot IN ($slots) ";
4267 $t += 60*scalar( @{ $arg->{slots} }) ;
4270 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
4271 print "<h1>This command can take long time, be patient...</h1>";
4273 $b->label_barcodes(storage => $storage,
4274 drive => $arg->{drive},
4282 SET LocationId = (SELECT LocationId
4284 WHERE Location = '$arg->{ach}')
4286 WHERE (LocationId = 0 OR LocationId IS NULL)
4295 $self->can_do('r_purge');
4297 my @volume = CGI::param('media');
4300 return $self->error("Can't get media selection");
4303 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4305 foreach my $v (@volume) {
4307 content => $b->purge_volume($v),
4308 title => "Purge media",
4309 name => "purge volume=$v",
4318 $self->can_do('r_prune');
4320 my @volume = CGI::param('media');
4322 return $self->error("Can't get media selection");
4325 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
4327 foreach my $v (@volume) {
4329 content => $b->prune_volume($v),
4330 title => "Prune volume",
4331 name => "prune volume=$v",
4340 $self->can_do('r_cancel_job');
4342 my $arg = $self->get_form('jobid');
4343 unless ($arg->{jobid}) {
4344 return $self->error("Can't get jobid");
4347 my $b = $self->get_bconsole();
4349 content => $b->cancel($arg->{jobid}),
4350 title => "Cancel job",
4351 name => "cancel jobid=$arg->{jobid}",
4357 # Warning, we display current fileset
4360 my $arg = $self->get_form('fileset');
4362 if ($arg->{fileset}) {
4363 my $b = $self->get_bconsole();
4364 my $ret = $b->get_fileset($arg->{fileset});
4365 $self->display({ fileset => $arg->{fileset},
4367 }, "fileset_view.tpl");
4369 $self->error("Can't get fileset name");
4373 sub director_show_sched
4376 $self->can_do('r_view_job');
4377 my $arg = $self->get_form('days');
4379 my $b = $self->get_bconsole();
4380 my $ret = $b->director_get_sched( $arg->{days} );
4385 }, "scheduled_job.tpl");
4388 sub enable_disable_job
4390 my ($self, $what) = @_ ;
4391 $self->can_do('r_run_job');
4393 my $name = CGI::param('job') || '';
4394 unless ($name =~ /^[\w\d\.\-\s]+$/) {
4395 return $self->error("Can't find job name");
4398 my $b = $self->get_bconsole();
4408 content => $b->send_cmd("$cmd job=\"$name\""),
4409 title => "$cmd $name",
4410 name => "$cmd job=\"$name\"",
4417 return new Bconsole(pref => $self->{info});
4423 $self->can_do('r_run_job');
4425 my $b = $self->get_bconsole();
4427 my $joblist = [ map { { name => $_ } } $b->list_job() ];
4429 $self->display({ Jobs => $joblist }, "run_job.tpl");
4434 my ($self, $ouput) = @_;
4437 foreach my $l (split(/\r\n/, $ouput)) {
4438 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
4444 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
4450 foreach my $k (keys %arg) {
4451 $lowcase{lc($k)} = $arg{$k} ;
4460 $self->can_do('r_run_job');
4462 my $b = $self->get_bconsole();
4464 my $job = CGI::param('job') || '';
4466 # we take informations from director, and we overwrite with user wish
4467 my $info = $b->send_cmd("show job=\"$job\"");
4468 my $attr = $self->run_parse_job($info);
4470 my $arg = $self->get_form(qw/pool level client fileset storage media/);
4472 if (!$arg->{pool} and $arg->{media}) {
4473 my $r = $self->dbh_selectrow_hashref("
4474 SELECT Pool.Name AS name
4475 FROM Media JOIN Pool USING (PoolId)
4476 WHERE Media.VolumeName = '$arg->{media}'
4477 AND Pool.Name != 'Scratch'
4480 $arg->{pool} = $r->{name};
4484 my %job_opt = (%$attr, %$arg);
4486 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4488 my $pools = [ map { { name => $_ } } $b->list_pool() ];
4489 my $clients = [ map { { name => $_ } }$b->list_client()];
4490 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
4491 my $storages= [ map { { name => $_ } }$b->list_storage()];
4496 clients => $clients,
4497 filesets => $filesets,
4498 storages => $storages,
4500 }, "run_job_mod.tpl");
4506 $self->can_do('r_run_job');
4508 my $b = $self->get_bconsole();
4510 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
4520 $self->can_do('r_run_job');
4522 my $b = $self->get_bconsole();
4524 # TODO: check input (don't use pool, level)
4526 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
4527 my $job = CGI::param('job') || '';
4528 my $storage = CGI::param('storage') || '';
4530 my $jobid = $b->run(job => $job,
4531 client => $arg->{client},
4532 priority => $arg->{priority},
4533 level => $arg->{level},
4534 storage => $storage,
4535 pool => $arg->{pool},
4536 fileset => $arg->{fileset},
4537 when => $arg->{when},
4542 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>";
4545 sub display_next_job
4548 my $arg = $self->get_form(qw/job/);
4550 return $self->error("Can't get job name");
4553 my $b = $self->get_bconsole();
4555 my $job = $b->send_cmd("show job=\"$arg->{job}\"");
4556 if ($job !~ /Schedule: name=([\w\d\-]+)/s) {
4557 return $self->error("Can't get $arg->{job} schedule");
4561 if ($job =~ /Pool: name=([\w\d\-]+) PoolType=/) {
4565 my $out = $b->send_cmd("show schedule=\"$jsched\"");
4566 my $sched = new Bweb::Sched();
4567 $sched->parse_scheds(split(/\r?\n/, $out));
4569 my $ss = $sched->get_scheds($jsched);
4572 foreach my $s (@$ss) {
4573 my $level = $sched->get_level($s);
4574 my $pool = $sched->get_pool($s) || $jpool;
4575 my $evt = $sched->get_event($s);
4576 push @ret, map { "$_ : $pool ($level)\n" } @$evt;
4579 print "<b>$arg->{job}:</b><pre>", sort @ret, "</pre><br>";