1 ################################################################
6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2006 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 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/^(.*)$/i,
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 stat_job_table => qr!^(\w*)$!,
219 display_log_time => qr!^(on)?$!,
224 load - load config_file
228 this function load the specified config_file.
236 unless (open(FP, $self->{config_file}))
238 return $self->error("can't load config_file $self->{config_file} : $!");
240 my $f=''; my $tmpbuffer;
241 while(read FP,$tmpbuffer,4096)
249 no strict; # I have no idea of the contents of the file
256 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...") ;
259 foreach my $k (keys %$VAR1) {
260 $self->{$k} = $VAR1->{$k};
268 load_old - load old configuration format
276 unless (open(FP, $self->{config_file}))
278 return $self->error("$self->{config_file} : $!");
281 while (my $line = <FP>)
284 my ($k, $v) = split(/\s*=\s*/, $line, 2);
296 save - save the current configuration to config_file
304 if ($self->{ach_list}) {
305 # shortcut for display_begin
306 $self->{achs} = [ map {{ name => $_ }}
307 keys %{$self->{ach_list}}
311 unless (open(FP, ">$self->{config_file}"))
313 return $self->error("$self->{config_file} : $!\n" .
314 "You must add this to your config file\n"
315 . Data::Dumper::Dumper($self));
318 print FP Data::Dumper::Dumper($self);
326 edit, view, modify - html form ouput
334 $self->display($self, "config_edit.tpl");
340 $self->display($self, "config_view.tpl");
350 foreach my $k (CGI::param())
352 next unless (exists $k_re{$k}) ;
353 my $val = CGI::param($k);
354 if ($val =~ $k_re{$k}) {
357 $self->{error} .= "bad parameter : $k = [$val]";
363 if ($self->{error}) { # an error as occured
364 $self->display($self, 'error.tpl');
372 ################################################################
374 package Bweb::Client;
376 use base q/Bweb::Gui/;
380 Bweb::Client - Bacula FD
384 this package is use to do all Client operations like, parse status etc...
388 $client = new Bweb::Client(name => 'zog-fd');
389 $client->status(); # do a 'status client=zog-fd'
395 display_running_job - Html display of a running job
399 this function is used to display information about a current job
403 sub display_running_job
405 my ($self, $conf, $jobid) = @_ ;
407 my $status = $self->status($conf);
410 if ($status->{$jobid}) {
411 $self->display($status->{$jobid}, "client_job_status.tpl");
414 for my $id (keys %$status) {
415 $self->display($status->{$id}, "client_job_status.tpl");
422 $client = new Bweb::Client(name => 'plume-fd');
424 $client->status($bweb);
428 dirty hack to parse "status client=xxx-fd"
432 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
433 Backup Job started: 06-jun-06 17:22
434 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
435 Files Examined=10,697
436 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
442 JobName => Full_plume.2006-06-06_17.22.23,
445 Bytes => 194,484,132,
455 my ($self, $conf) = @_ ;
457 if (defined $self->{cur_jobs}) {
458 return $self->{cur_jobs} ;
462 my $b = new Bconsole(pref => $conf);
463 my $ret = $b->send_cmd("st client=$self->{name}");
467 for my $r (split(/\n/, $ret)) {
469 $r =~ s/(^\s+|\s+$)//g;
470 if ($r =~ /JobId (\d+) Job (\S+)/) {
472 $arg->{$jobid} = { @param, JobId => $jobid } ;
476 @param = ( JobName => $2 );
478 } elsif ($r =~ /=.+=/) {
479 push @param, split(/\s+|\s*=\s*/, $r) ;
481 } elsif ($r =~ /=/) { # one per line
482 push @param, split(/\s*=\s*/, $r) ;
484 } elsif ($r =~ /:/) { # one per line
485 push @param, split(/\s*:\s*/, $r, 2) ;
489 if ($jobid and @param) {
490 $arg->{$jobid} = { @param,
492 Client => $self->{name},
496 $self->{cur_jobs} = $arg ;
502 ################################################################
504 package Bweb::Autochanger;
506 use base q/Bweb::Gui/;
510 Bweb::Autochanger - Object to manage Autochanger
514 this package will parse the mtx output and manage drives.
518 $auto = new Bweb::Autochanger(precmd => 'sudo');
520 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
524 $auto->slot_is_full(10);
525 $auto->transfer(10, 11);
531 my ($class, %arg) = @_;
534 name => '', # autochanger name
535 label => {}, # where are volume { label1 => 40, label2 => drive0 }
536 drive => [], # drive use [ 'media1', 'empty', ..]
537 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
538 io => [], # io slot number list [ 41, 42, 43...]
539 info => {slot => 0, # informations (slot, drive, io)
543 mtxcmd => '/usr/sbin/mtx',
545 device => '/dev/changer',
546 precmd => '', # ssh command
547 bweb => undef, # link to bacula web object (use for display)
550 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
557 status - parse the output of mtx status
561 this function will launch mtx status and parse the output. it will
562 give a perlish view of the autochanger content.
564 it uses ssh if the autochanger is on a other host.
571 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
573 # TODO : reset all infos
574 $self->{info}->{drive} = 0;
575 $self->{info}->{slot} = 0;
576 $self->{info}->{io} = 0;
578 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
581 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
582 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
583 #Data Transfer Element 1:Empty
584 # Storage Element 1:Empty
585 # Storage Element 2:Full :VolumeTag=000002
586 # Storage Element 3:Empty
587 # Storage Element 4:Full :VolumeTag=000004
588 # Storage Element 5:Full :VolumeTag=000001
589 # Storage Element 6:Full :VolumeTag=000003
590 # Storage Element 7:Empty
591 # Storage Element 41 IMPORT/EXPORT:Empty
592 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
597 # Storage Element 7:Empty
598 # Storage Element 2:Full :VolumeTag=000002
599 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
602 $self->set_empty_slot($1);
604 $self->set_slot($1, $4);
607 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
610 $self->set_empty_drive($1);
612 $self->set_drive($1, $4, $6);
615 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
618 $self->set_empty_io($1);
620 $self->set_io($1, $4);
623 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
625 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
626 $self->{info}->{drive} = $1;
627 $self->{info}->{slot} = $2;
628 if ($l =~ /(\d+)\s+Import/) {
629 $self->{info}->{io} = $1 ;
631 $self->{info}->{io} = 0;
636 $self->debug($self) ;
641 my ($self, $slot) = @_;
644 if ($self->{slot}->[$slot] eq 'loaded') {
648 my $label = $self->{slot}->[$slot] ;
650 return $self->is_media_loaded($label);
655 my ($self, $drive, $slot) = @_;
657 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
658 return 0 if ($self->slot_is_full($slot)) ;
660 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
663 my $content = $self->get_slot($slot);
664 print "content = $content<br/> $drive => $slot<br/>";
665 $self->set_empty_drive($drive);
666 $self->set_slot($slot, $content);
669 $self->{error} = $out;
674 # TODO: load/unload have to use mtx script from bacula
677 my ($self, $drive, $slot) = @_;
679 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
680 return 0 unless ($self->slot_is_full($slot)) ;
682 print "Loading drive $drive with slot $slot<br/>\n";
683 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
686 my $content = $self->get_slot($slot);
687 print "content = $content<br/> $slot => $drive<br/>";
688 $self->set_drive($drive, $slot, $content);
691 $self->{error} = $out;
699 my ($self, $media) = @_;
701 unless ($self->{label}->{$media}) {
705 if ($self->{label}->{$media} =~ /drive\d+/) {
715 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
720 my ($self, $slot, $tag) = @_;
721 $self->{slot}->[$slot] = $tag || 'full';
722 push @{ $self->{io} }, $slot;
725 $self->{label}->{$tag} = $slot;
731 my ($self, $slot) = @_;
733 push @{ $self->{io} }, $slot;
735 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
736 $self->{slot}->[$slot] = 'empty';
742 my ($self, $slot) = @_;
743 return $self->{slot}->[$slot];
748 my ($self, $slot, $tag) = @_;
749 $self->{slot}->[$slot] = $tag || 'full';
752 $self->{label}->{$tag} = $slot;
758 my ($self, $slot) = @_;
760 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
761 $self->{slot}->[$slot] = 'empty';
767 my ($self, $drive) = @_;
768 $self->{drive}->[$drive] = 'empty';
773 my ($self, $drive, $slot, $tag) = @_;
774 $self->{drive}->[$drive] = $tag || $slot;
776 $self->{slot}->[$slot] = $tag || 'loaded';
779 $self->{label}->{$tag} = "drive$drive";
785 my ($self, $slot) = @_;
787 # slot don't exists => full
788 if (not defined $self->{slot}->[$slot]) {
792 if ($self->{slot}->[$slot] eq 'empty') {
795 return 1; # vol, full, loaded
798 sub slot_get_first_free
801 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
802 return $slot unless ($self->slot_is_full($slot));
806 sub io_get_first_free
810 foreach my $slot (@{ $self->{io} }) {
811 return $slot unless ($self->slot_is_full($slot));
818 my ($self, $media) = @_;
820 return $self->{label}->{$media} ;
825 my ($self, $media) = @_;
827 return defined $self->{label}->{$media} ;
832 my ($self, $slot) = @_;
834 unless ($self->slot_is_full($slot)) {
835 print "Autochanger $self->{name} slot $slot is empty\n";
840 if ($self->is_slot_loaded($slot)) {
843 print "Autochanger $self->{name} $slot is currently in use\n";
847 # autochanger must have I/O
848 unless ($self->have_io()) {
849 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
853 my $dst = $self->io_get_first_free();
856 print "Autochanger $self->{name} you must empty I/O first\n";
859 $self->transfer($slot, $dst);
864 my ($self, $src, $dst) = @_ ;
865 if ($self->{debug}) {
866 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
868 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
871 my $content = $self->get_slot($src);
872 $self->{slot}->[$src] = 'empty';
873 $self->set_slot($dst, $content);
876 $self->{error} = $out;
883 my ($self, $index) = @_;
884 return $self->{drive_name}->[$index];
887 # TODO : do a tapeinfo request to get informations
897 for my $slot (@{$self->{io}})
899 if ($self->is_slot_loaded($slot)) {
900 print "$slot is currently loaded\n";
904 if ($self->slot_is_full($slot))
906 my $free = $self->slot_get_first_free() ;
907 print "move $slot to $free :\n";
910 if ($self->transfer($slot, $free)) {
911 print "<img src='/bweb/T.png' alt='ok'><br/>\n";
913 print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
917 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
923 # TODO : this is with mtx status output,
924 # we can do an other function from bacula view (with StorageId)
928 my $bweb = $self->{bweb};
930 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
931 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
934 SELECT Media.VolumeName AS volumename,
935 Media.VolStatus AS volstatus,
936 Media.LastWritten AS lastwritten,
937 Media.VolBytes AS volbytes,
938 Media.MediaType AS mediatype,
940 Media.InChanger AS inchanger,
942 $bweb->{sql}->{FROM_UNIXTIME}(
943 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
944 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
947 INNER JOIN Pool USING (PoolId)
949 WHERE Media.VolumeName IN ($media_list)
952 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
954 # TODO : verify slot and bacula slot
958 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
960 if ($self->slot_is_full($slot)) {
962 my $vol = $self->{slot}->[$slot];
963 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
965 my $bslot = $all->{$vol}->{slot} ;
966 my $inchanger = $all->{$vol}->{inchanger};
968 # if bacula slot or inchanger flag is bad, we display a message
969 if ($bslot != $slot or !$inchanger) {
970 push @to_update, $slot;
973 $all->{$vol}->{realslot} = $slot;
975 push @{ $param }, $all->{$vol};
977 } else { # empty or no label
978 push @{ $param }, {realslot => $slot,
979 volstatus => 'Unknown',
980 volumename => $self->{slot}->[$slot]} ;
983 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
987 my $i=0; my $drives = [] ;
988 foreach my $d (@{ $self->{drive} }) {
989 $drives->[$i] = { index => $i,
990 load => $self->{drive}->[$i],
991 name => $self->{drive_name}->[$i],
996 $bweb->display({ Name => $self->{name},
997 nb_drive => $self->{info}->{drive},
998 nb_io => $self->{info}->{io},
1001 Update => scalar(@to_update) },
1009 ################################################################
1013 use base q/Bweb::Gui/;
1017 Bweb - main Bweb package
1021 this package is use to compute and display informations
1026 use POSIX qw/strftime/;
1028 our $config_file='/etc/bacula/bweb.conf';
1034 %sql_func - hash to make query mysql/postgresql compliant
1040 UNIX_TIMESTAMP => '',
1041 FROM_UNIXTIME => '',
1042 TO_SEC => " interval '1 second' * ",
1043 SEC_TO_INT => "SEC_TO_INT",
1046 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1047 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1048 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1049 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1050 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1051 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1052 DB_SIZE => " SELECT pg_database_size(current_database()) ",
1053 CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
1056 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1057 FROM_UNIXTIME => 'FROM_UNIXTIME',
1060 SEC_TO_TIME => 'SEC_TO_TIME',
1061 MATCH => " REGEXP ",
1062 STARTTIME_DAY => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1063 STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1064 STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1065 STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1066 STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1067 STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1068 # with mysql < 5, you have to play with the ugly SHOW command
1069 DB_SIZE => " SELECT 0 ",
1070 # works only with mysql 5
1071 # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1072 CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
1080 $self->{dbh}->disconnect();
1085 sub dbh_selectall_arrayref
1087 my ($self, $query) = @_;
1088 $self->connect_db();
1089 $self->debug($query);
1090 return $self->{dbh}->selectall_arrayref($query);
1095 my ($self, @what) = @_;
1096 return join(',', $self->dbh_quote(@what)) ;
1101 my ($self, @what) = @_;
1103 $self->connect_db();
1105 return map { $self->{dbh}->quote($_) } @what;
1107 return $self->{dbh}->quote($what[0]) ;
1113 my ($self, $query) = @_ ;
1114 $self->connect_db();
1115 $self->debug($query);
1116 return $self->{dbh}->do($query);
1119 sub dbh_selectall_hashref
1121 my ($self, $query, $join) = @_;
1123 $self->connect_db();
1124 $self->debug($query);
1125 return $self->{dbh}->selectall_hashref($query, $join) ;
1128 sub dbh_selectrow_hashref
1130 my ($self, $query) = @_;
1132 $self->connect_db();
1133 $self->debug($query);
1134 return $self->{dbh}->selectrow_hashref($query) ;
1140 my @unit = qw(b Kb Mb Gb Tb);
1141 my $val = shift || 0;
1143 my $format = '%i %s';
1144 while ($val / 1024 > 1) {
1148 $format = ($i>0)?'%0.1f %s':'%i %s';
1149 return sprintf($format, $val, $unit[$i]);
1152 # display Day, Hour, Year
1158 $val /= 60; # sec -> min
1160 if ($val / 60 <= 1) {
1164 $val /= 60; # min -> hour
1165 if ($val / 24 <= 1) {
1166 return "$val hours";
1169 $val /= 24; # hour -> day
1170 if ($val / 365 < 2) {
1174 $val /= 365 ; # day -> year
1176 return "$val years";
1179 # get Day, Hour, Year
1185 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1189 my %times = ( m => 60,
1195 my $mult = $times{$2} || 0;
1205 unless ($self->{dbh}) {
1206 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1207 $self->{info}->{user},
1208 $self->{info}->{password});
1210 $self->error("Can't connect to your database:\n$DBI::errstr\n")
1211 unless ($self->{dbh});
1213 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1215 if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
1216 $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1223 my ($class, %arg) = @_;
1225 dbh => undef, # connect_db();
1227 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1233 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1235 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1236 $self->{sql} = $sql_func{$1};
1239 $self->{debug} = $self->{info}->{debug};
1240 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1248 $self->display($self->{info}, "begin.tpl");
1254 $self->display($self->{info}, "end.tpl");
1262 my $arg = $self->get_form("client", "qre_client", "jclient_groups");
1264 if ($arg->{qre_client}) {
1265 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1266 } elsif ($arg->{client}) {
1267 $where = "WHERE Name = '$arg->{client}' ";
1268 } elsif ($arg->{jclient_groups}) {
1269 $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
1270 JOIN client_group USING (client_group_id)
1271 WHERE client_group_name IN ($arg->{jclient_groups})";
1275 SELECT Name AS name,
1277 AutoPrune AS autoprune,
1278 FileRetention AS fileretention,
1279 JobRetention AS jobretention
1284 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1286 my $dsp = { ID => $cur_id++,
1287 clients => [ values %$all] };
1289 $self->display($dsp, "client_list.tpl") ;
1294 my ($self, %arg) = @_;
1301 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1303 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1305 $self->{sql}->{TO_SEC}($arg{age})
1308 $label = "last " . human_sec($arg{age});
1311 if ($arg{groupby}) {
1312 $limit .= " GROUP BY $arg{groupby} ";
1316 $limit .= " ORDER BY $arg{order} ";
1320 $limit .= " LIMIT $arg{limit} ";
1321 $label .= " limited to $arg{limit}";
1325 $limit .= " OFFSET $arg{offset} ";
1326 $label .= " with $arg{offset} offset ";
1330 $label = 'no filter';
1333 return ($limit, $label);
1338 $bweb->get_form(...) - Get useful stuff
1342 This function get and check parameters against regexp.
1344 If word begin with 'q', the return will be quoted or join quoted
1345 if it's end with 's'.
1350 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1353 qclient => 'plume-fd',
1354 qpools => "'plume-fd', 'test-fd', '...'",
1361 my ($self, @what) = @_;
1362 my %what = map { $_ => 1 } @what;
1382 my %opt_ss =( # string with space
1386 my %opt_s = ( # default to ''
1403 my %opt_p = ( # option with path
1410 my %opt_r = (regexwhere => 1);
1412 my %opt_d = ( # option with date
1417 foreach my $i (@what) {
1418 if (exists $opt_i{$i}) {# integer param
1419 my $value = CGI::param($i) || $opt_i{$i} ;
1420 if ($value =~ /^(\d+)$/) {
1423 } elsif ($opt_s{$i}) { # simple string param
1424 my $value = CGI::param($i) || '';
1425 if ($value =~ /^([\w\d\.-]+)$/) {
1428 } elsif ($opt_ss{$i}) { # simple string param (with space)
1429 my $value = CGI::param($i) || '';
1430 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1433 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1434 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1436 $ret{$i} = $self->dbh_join(@value) ;
1439 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1440 my $value = CGI::param($1) ;
1442 $ret{$i} = $self->dbh_quote($value);
1445 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1446 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1447 grep { ! /^\s*$/ } CGI::param($1) ];
1448 } elsif (exists $opt_p{$i}) {
1449 my $value = CGI::param($i) || '';
1450 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1453 } elsif (exists $opt_r{$i}) {
1454 my $value = CGI::param($i) || '';
1455 if ($value =~ /^([^'"']+)$/) {
1458 } elsif (exists $opt_d{$i}) {
1459 my $value = CGI::param($i) || '';
1460 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1467 foreach my $s (CGI::param('slot')) {
1468 if ($s =~ /^(\d+)$/) {
1469 push @{$ret{slots}}, $s;
1475 my $when = CGI::param('when') || '';
1476 if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1481 if ($what{db_clients}) {
1483 SELECT Client.Name as clientname
1487 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1488 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1492 if ($what{db_client_groups}) {
1494 SELECT client_group_name AS name
1498 my $grps = $self->dbh_selectall_hashref($query, 'name');
1499 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1503 if ($what{db_mediatypes}) {
1505 SELECT MediaType as mediatype
1509 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1510 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1514 if ($what{db_locations}) {
1516 SELECT Location as location, Cost as cost
1519 my $loc = $self->dbh_selectall_hashref($query, 'location');
1520 $ret{db_locations} = [ sort { $a->{location}
1526 if ($what{db_pools}) {
1527 my $query = "SELECT Name as name FROM Pool";
1529 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1530 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1533 if ($what{db_filesets}) {
1535 SELECT FileSet.FileSet AS fileset
1539 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1541 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1542 values %$filesets] ;
1545 if ($what{db_jobnames}) {
1547 SELECT DISTINCT Job.Name AS jobname
1551 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1553 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1554 values %$jobnames] ;
1557 if ($what{db_devices}) {
1559 SELECT Device.Name AS name
1563 my $devices = $self->dbh_selectall_hashref($query, 'name');
1565 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1576 my $fields = $self->get_form(qw/age level status clients filesets
1578 db_clients limit db_filesets width height
1579 qclients qfilesets qjobnames db_jobnames/);
1582 my $url = CGI::url(-full => 0,
1585 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1587 # this organisation is to keep user choice between 2 click
1588 # TODO : fileset and client selection doesn't work
1597 sub display_client_job
1599 my ($self, %arg) = @_ ;
1601 $arg{order} = ' Job.JobId DESC ';
1602 my ($limit, $label) = $self->get_limit(%arg);
1604 my $clientname = $self->dbh_quote($arg{clientname});
1607 SELECT DISTINCT Job.JobId AS jobid,
1608 Job.Name AS jobname,
1609 FileSet.FileSet AS fileset,
1611 StartTime AS starttime,
1612 JobFiles AS jobfiles,
1613 JobBytes AS jobbytes,
1614 JobStatus AS jobstatus,
1615 JobErrors AS joberrors
1617 FROM Client,Job,FileSet
1618 WHERE Client.Name=$clientname
1619 AND Client.ClientId=Job.ClientId
1620 AND Job.FileSetId=FileSet.FileSetId
1624 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1626 $self->display({ clientname => $arg{clientname},
1629 Jobs => [ values %$all ],
1631 "display_client_job.tpl") ;
1634 sub get_selected_media_location
1638 my $medias = $self->get_form('jmedias');
1640 unless ($medias->{jmedias}) {
1645 SELECT Media.VolumeName AS volumename, Location.Location AS location
1646 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1647 WHERE Media.VolumeName IN ($medias->{jmedias})
1650 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1652 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1663 my $medias = $self->get_selected_media_location();
1669 my $elt = $self->get_form('db_locations');
1671 $self->display({ ID => $cur_id++,
1672 %$elt, # db_locations
1674 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1684 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1686 $self->display($elt, "help_extern.tpl");
1689 sub help_extern_compute
1693 my $number = CGI::param('limit') || '' ;
1694 unless ($number =~ /^(\d+)$/) {
1695 return $self->error("Bad arg number : $number ");
1698 my ($sql, undef) = $self->get_param('pools',
1699 'locations', 'mediatypes');
1702 SELECT Media.VolumeName AS volumename,
1703 Media.VolStatus AS volstatus,
1704 Media.LastWritten AS lastwritten,
1705 Media.MediaType AS mediatype,
1706 Media.VolMounts AS volmounts,
1708 Media.Recycle AS recycle,
1709 $self->{sql}->{FROM_UNIXTIME}(
1710 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1711 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1714 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1715 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1717 WHERE Media.InChanger = 1
1718 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1720 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1724 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1726 $self->display({ Medias => [ values %$all ] },
1727 "help_extern_compute.tpl");
1734 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1735 $self->display($param, "help_intern.tpl");
1738 sub help_intern_compute
1742 my $number = CGI::param('limit') || '' ;
1743 unless ($number =~ /^(\d+)$/) {
1744 return $self->error("Bad arg number : $number ");
1747 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1749 if (CGI::param('expired')) {
1751 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1752 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1758 SELECT Media.VolumeName AS volumename,
1759 Media.VolStatus AS volstatus,
1760 Media.LastWritten AS lastwritten,
1761 Media.MediaType AS mediatype,
1762 Media.VolMounts AS volmounts,
1764 $self->{sql}->{FROM_UNIXTIME}(
1765 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1766 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1769 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1770 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1772 WHERE Media.InChanger <> 1
1773 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1774 AND Media.Recycle = 1
1776 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1780 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1782 $self->display({ Medias => [ values %$all ] },
1783 "help_intern_compute.tpl");
1789 my ($self, %arg) = @_ ;
1791 my ($limit, $label) = $self->get_limit(%arg);
1795 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1796 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1797 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1798 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1799 ($self->{sql}->{DB_SIZE}) AS db_size,
1800 (SELECT count(Job.JobId)
1802 WHERE Job.JobStatus IN ('E','e','f','A')
1805 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1808 my $row = $self->dbh_selectrow_hashref($query) ;
1810 $row->{nb_bytes} = human_size($row->{nb_bytes});
1812 $row->{db_size} = human_size($row->{db_size});
1813 $row->{label} = $label;
1815 $self->display($row, "general.tpl");
1820 my ($self, @what) = @_ ;
1821 my %elt = map { $_ => 1 } @what;
1826 if ($elt{clients}) {
1827 my @clients = grep { ! /^\s*$/ } CGI::param('client');
1829 $ret{clients} = \@clients;
1830 my $str = $self->dbh_join(@clients);
1831 $limit .= "AND Client.Name IN ($str) ";
1835 if ($elt{client_groups}) {
1836 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1838 $ret{client_groups} = \@clients;
1839 my $str = $self->dbh_join(@clients);
1840 $limit .= "AND client_group_name IN ($str) ";
1844 if ($elt{filesets}) {
1845 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1847 $ret{filesets} = \@filesets;
1848 my $str = $self->dbh_join(@filesets);
1849 $limit .= "AND FileSet.FileSet IN ($str) ";
1853 if ($elt{mediatypes}) {
1854 my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
1856 $ret{mediatypes} = \@medias;
1857 my $str = $self->dbh_join(@medias);
1858 $limit .= "AND Media.MediaType IN ($str) ";
1863 my $client = CGI::param('client');
1864 $ret{client} = $client;
1865 $client = $self->dbh_join($client);
1866 $limit .= "AND Client.Name = $client ";
1870 my $level = CGI::param('level') || '';
1871 if ($level =~ /^(\w)$/) {
1873 $limit .= "AND Job.Level = '$1' ";
1878 my $jobid = CGI::param('jobid') || '';
1880 if ($jobid =~ /^(\d+)$/) {
1882 $limit .= "AND Job.JobId = '$1' ";
1887 my $status = CGI::param('status') || '';
1888 if ($status =~ /^(\w)$/) {
1891 $limit .= "AND Job.JobStatus IN ('f','E') ";
1892 } elsif ($1 eq 'W') {
1893 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
1895 $limit .= "AND Job.JobStatus = '$1' ";
1900 if ($elt{volstatus}) {
1901 my $status = CGI::param('volstatus') || '';
1902 if ($status =~ /^(\w+)$/) {
1904 $limit .= "AND Media.VolStatus = '$1' ";
1908 if ($elt{locations}) {
1909 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1911 $ret{locations} = \@location;
1912 my $str = $self->dbh_join(@location);
1913 $limit .= "AND Location.Location IN ($str) ";
1918 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1920 $ret{pools} = \@pool;
1921 my $str = $self->dbh_join(@pool);
1922 $limit .= "AND Pool.Name IN ($str) ";
1926 if ($elt{location}) {
1927 my $location = CGI::param('location') || '';
1929 $ret{location} = $location;
1930 $location = $self->dbh_quote($location);
1931 $limit .= "AND Location.Location = $location ";
1936 my $pool = CGI::param('pool') || '';
1939 $pool = $self->dbh_quote($pool);
1940 $limit .= "AND Pool.Name = $pool ";
1944 if ($elt{jobtype}) {
1945 my $jobtype = CGI::param('jobtype') || '';
1946 if ($jobtype =~ /^(\w)$/) {
1948 $limit .= "AND Job.Type = '$1' ";
1952 return ($limit, %ret);
1963 my ($self, %arg) = @_ ;
1965 $arg{order} = ' Job.JobId DESC ';
1967 my ($limit, $label) = $self->get_limit(%arg);
1968 my ($where, undef) = $self->get_param('clients',
1978 if (CGI::param('client_group')) {
1980 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
1981 LEFT JOIN client_group USING (client_group_id)
1986 SELECT Job.JobId AS jobid,
1987 Client.Name AS client,
1988 FileSet.FileSet AS fileset,
1989 Job.Name AS jobname,
1991 StartTime AS starttime,
1993 Pool.Name AS poolname,
1994 JobFiles AS jobfiles,
1995 JobBytes AS jobbytes,
1996 JobStatus AS jobstatus,
1997 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1998 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2001 JobErrors AS joberrors
2004 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2005 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2007 WHERE Client.ClientId=Job.ClientId
2008 AND Job.JobStatus != 'R'
2013 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2015 $self->display({ Filter => $label,
2019 sort { $a->{jobid} <=> $b->{jobid} }
2026 # display job informations
2027 sub display_job_zoom
2029 my ($self, $jobid) = @_ ;
2031 $jobid = $self->dbh_quote($jobid);
2034 SELECT DISTINCT Job.JobId AS jobid,
2035 Client.Name AS client,
2036 Job.Name AS jobname,
2037 FileSet.FileSet AS fileset,
2039 Pool.Name AS poolname,
2040 StartTime AS starttime,
2041 JobFiles AS jobfiles,
2042 JobBytes AS jobbytes,
2043 JobStatus AS jobstatus,
2044 JobErrors AS joberrors,
2045 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2046 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2049 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2050 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2051 WHERE Client.ClientId=Job.ClientId
2052 AND Job.JobId = $jobid
2055 my $row = $self->dbh_selectrow_hashref($query) ;
2057 # display all volumes associate with this job
2059 SELECT Media.VolumeName as volumename
2060 FROM Job,Media,JobMedia
2061 WHERE Job.JobId = $jobid
2062 AND JobMedia.JobId=Job.JobId
2063 AND JobMedia.MediaId=Media.MediaId
2066 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2068 $row->{volumes} = [ values %$all ] ;
2070 $self->display($row, "display_job_zoom.tpl");
2073 sub display_job_group
2075 my ($self, %arg) = @_;
2077 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2079 my ($where, undef) = $self->get_param('client_groups',
2085 SELECT client_group_name AS client_group_name,
2086 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2087 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2088 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2089 COALESCE(jobok.nbjobs,0) AS nbjobok,
2090 COALESCE(joberr.nbjobs,0) AS nbjoberr
2093 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2094 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2095 SUM(JobErrors) AS joberrors
2096 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2097 JOIN client_group USING (client_group_id)
2099 WHERE JobStatus = 'T'
2102 ) AS jobok LEFT JOIN
2105 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2106 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2107 SUM(JobErrors) AS joberrors
2108 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2109 JOIN client_group USING (client_group_id)
2111 WHERE JobStatus IN ('f','E', 'A')
2114 ) AS joberr USING (client_group_name)
2118 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2120 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2123 $self->display($rep, "display_job_group.tpl");
2128 my ($self, %arg) = @_ ;
2130 my ($limit, $label) = $self->get_limit(%arg);
2131 my ($where, %elt) = $self->get_param('pools',
2136 my $arg = $self->get_form('jmedias', 'qre_media');
2138 if ($arg->{jmedias}) {
2139 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2141 if ($arg->{qre_media}) {
2142 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2146 SELECT Media.VolumeName AS volumename,
2147 Media.VolBytes AS volbytes,
2148 Media.VolStatus AS volstatus,
2149 Media.MediaType AS mediatype,
2150 Media.InChanger AS online,
2151 Media.LastWritten AS lastwritten,
2152 Location.Location AS location,
2153 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2154 Pool.Name AS poolname,
2155 $self->{sql}->{FROM_UNIXTIME}(
2156 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2157 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2160 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2161 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2162 Media.MediaType AS MediaType
2164 WHERE Media.VolStatus = 'Full'
2165 GROUP BY Media.MediaType
2166 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2168 WHERE Media.PoolId=Pool.PoolId
2173 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2175 $self->display({ ID => $cur_id++,
2177 Location => $elt{location},
2178 Medias => [ values %$all ]
2180 "display_media.tpl");
2187 my $pool = $self->get_form('db_pools');
2189 foreach my $name (@{ $pool->{db_pools} }) {
2190 CGI::param('pool', $name->{name});
2191 $self->display_media();
2195 sub display_media_zoom
2199 my $medias = $self->get_form('jmedias');
2201 unless ($medias->{jmedias}) {
2202 return $self->error("Can't get media selection");
2206 SELECT InChanger AS online,
2207 VolBytes AS nb_bytes,
2208 VolumeName AS volumename,
2209 VolStatus AS volstatus,
2210 VolMounts AS nb_mounts,
2211 Media.VolUseDuration AS voluseduration,
2212 Media.MaxVolJobs AS maxvoljobs,
2213 Media.MaxVolFiles AS maxvolfiles,
2214 Media.MaxVolBytes AS maxvolbytes,
2215 VolErrors AS nb_errors,
2216 Pool.Name AS poolname,
2217 Location.Location AS location,
2218 Media.Recycle AS recycle,
2219 Media.VolRetention AS volretention,
2220 Media.LastWritten AS lastwritten,
2221 Media.VolReadTime/1000000 AS volreadtime,
2222 Media.VolWriteTime/1000000 AS volwritetime,
2223 Media.RecycleCount AS recyclecount,
2224 Media.Comment AS comment,
2225 $self->{sql}->{FROM_UNIXTIME}(
2226 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2227 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2230 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2231 WHERE Pool.PoolId = Media.PoolId
2232 AND VolumeName IN ($medias->{jmedias})
2235 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2237 foreach my $media (values %$all) {
2238 my $mq = $self->dbh_quote($media->{volumename});
2241 SELECT DISTINCT Job.JobId AS jobid,
2243 Job.StartTime AS starttime,
2246 Job.JobFiles AS files,
2247 Job.JobBytes AS bytes,
2248 Job.jobstatus AS status
2249 FROM Media,JobMedia,Job
2250 WHERE Media.VolumeName=$mq
2251 AND Media.MediaId=JobMedia.MediaId
2252 AND JobMedia.JobId=Job.JobId
2255 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2258 SELECT LocationLog.Date AS date,
2259 Location.Location AS location,
2260 LocationLog.Comment AS comment
2261 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2262 WHERE Media.MediaId = LocationLog.MediaId
2263 AND Media.VolumeName = $mq
2267 my $log = $self->dbh_selectall_arrayref($query) ;
2269 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2272 $self->display({ jobs => [ values %$jobs ],
2273 LocationLog => $logtxt,
2275 "display_media_zoom.tpl");
2283 my $loc = $self->get_form('qlocation');
2284 unless ($loc->{qlocation}) {
2285 return $self->error("Can't get location");
2289 SELECT Location.Location AS location,
2290 Location.Cost AS cost,
2291 Location.Enabled AS enabled
2293 WHERE Location.Location = $loc->{qlocation}
2296 my $row = $self->dbh_selectrow_hashref($query);
2298 $self->display({ ID => $cur_id++,
2299 %$row }, "location_edit.tpl") ;
2307 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2308 unless ($arg->{qlocation}) {
2309 return $self->error("Can't get location");
2311 unless ($arg->{qnewlocation}) {
2312 return $self->error("Can't get new location name");
2314 unless ($arg->{cost}) {
2315 return $self->error("Can't get new cost");
2318 my $enabled = CGI::param('enabled') || '';
2319 $enabled = $enabled?1:0;
2322 UPDATE Location SET Cost = $arg->{cost},
2323 Location = $arg->{qnewlocation},
2325 WHERE Location.Location = $arg->{qlocation}
2328 $self->dbh_do($query);
2330 $self->location_display();
2336 my $arg = $self->get_form(qw/qlocation/) ;
2338 unless ($arg->{qlocation}) {
2339 return $self->error("Can't get location");
2343 SELECT count(Media.MediaId) AS nb
2344 FROM Media INNER JOIN Location USING (LocationID)
2345 WHERE Location = $arg->{qlocation}
2348 my $res = $self->dbh_selectrow_hashref($query);
2351 return $self->error("Sorry, the location must be empty");
2355 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2358 $self->dbh_do($query);
2360 $self->location_display();
2367 my $arg = $self->get_form(qw/qlocation cost/) ;
2369 unless ($arg->{qlocation}) {
2370 $self->display({}, "location_add.tpl");
2373 unless ($arg->{cost}) {
2374 return $self->error("Can't get new cost");
2377 my $enabled = CGI::param('enabled') || '';
2378 $enabled = $enabled?1:0;
2381 INSERT INTO Location (Location, Cost, Enabled)
2382 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2385 $self->dbh_do($query);
2387 $self->location_display();
2390 sub location_display
2395 SELECT Location.Location AS location,
2396 Location.Cost AS cost,
2397 Location.Enabled AS enabled,
2398 (SELECT count(Media.MediaId)
2400 WHERE Media.LocationId = Location.LocationId
2405 my $location = $self->dbh_selectall_hashref($query, 'location');
2407 $self->display({ ID => $cur_id++,
2408 Locations => [ values %$location ] },
2409 "display_location.tpl");
2416 my $medias = $self->get_selected_media_location();
2421 my $arg = $self->get_form('db_locations', 'qnewlocation');
2423 $self->display({ email => $self->{info}->{email_media},
2425 medias => [ values %$medias ],
2427 "update_location.tpl");
2430 ###########################################################
2436 my $grp = $self->get_form(qw/qclient_group db_clients/);
2439 unless ($grp->{qclient_group}) {
2440 return $self->error("Can't get group");
2445 FROM Client JOIN client_group_member using (clientid)
2446 JOIN client_group using (client_group_id)
2447 WHERE client_group_name = $grp->{qclient_group}
2450 my $row = $self->dbh_selectall_hashref($query, "name");
2452 $self->display({ ID => $cur_id++,
2453 client_group => $grp->{qclient_group},
2455 client_group_member => [ values %$row]},
2463 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2464 unless ($arg->{qclient_group}) {
2465 return $self->error("Can't get groups");
2468 $self->{dbh}->begin_work();
2471 DELETE FROM client_group_member
2472 WHERE client_group_id IN
2473 (SELECT client_group_id
2475 WHERE client_group_name = $arg->{qclient_group})
2477 $self->dbh_do($query);
2480 INSERT INTO client_group_member (clientid, client_group_id)
2482 (SELECT client_group_id
2484 WHERE client_group_name = $arg->{qclient_group})
2485 FROM Client WHERE Name IN ($arg->{jclients})
2488 $self->dbh_do($query);
2490 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2493 SET client_group_name = $arg->{qnewgroup}
2494 WHERE client_group_name = $arg->{qclient_group}
2497 $self->dbh_do($query);
2500 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2502 $self->display_groups();
2508 my $arg = $self->get_form(qw/qclient_group/);
2510 unless ($arg->{qclient_group}) {
2511 return $self->error("Can't get groups");
2514 $self->{dbh}->begin_work();
2517 DELETE FROM client_group_member
2518 WHERE client_group_id IN
2519 (SELECT client_group_id
2521 WHERE client_group_name = $arg->{qclient_group});
2523 DELETE FROM client_group
2524 WHERE client_group_name = $arg->{qclient_group};
2526 $self->dbh_do($query);
2528 $self->{dbh}->commit();
2530 $self->display_groups();
2537 my $arg = $self->get_form(qw/qclient_group/) ;
2539 unless ($arg->{qclient_group}) {
2540 $self->display({}, "groups_add.tpl");
2545 INSERT INTO client_group (client_group_name)
2546 VALUES ($arg->{qclient_group})
2549 $self->dbh_do($query);
2551 $self->display_groups();
2558 my $arg = $self->get_form(qw/db_client_groups/) ;
2560 if ($self->{dbh}->errstr) {
2561 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2566 $self->display({ ID => $cur_id++,
2568 "display_groups.tpl");
2571 ###########################################################
2573 sub get_media_max_size
2575 my ($self, $type) = @_;
2577 "SELECT avg(VolBytes) AS size
2579 WHERE Media.VolStatus = 'Full'
2580 AND Media.MediaType = '$type'
2583 my $res = $self->selectrow_hashref($query);
2586 return $res->{size};
2596 my $media = $self->get_form('qmedia');
2598 unless ($media->{qmedia}) {
2599 return $self->error("Can't get media");
2603 SELECT Media.Slot AS slot,
2604 PoolMedia.Name AS poolname,
2605 Media.VolStatus AS volstatus,
2606 Media.InChanger AS inchanger,
2607 Location.Location AS location,
2608 Media.VolumeName AS volumename,
2609 Media.MaxVolBytes AS maxvolbytes,
2610 Media.MaxVolJobs AS maxvoljobs,
2611 Media.MaxVolFiles AS maxvolfiles,
2612 Media.VolUseDuration AS voluseduration,
2613 Media.VolRetention AS volretention,
2614 Media.Comment AS comment,
2615 PoolRecycle.Name AS poolrecycle
2617 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2618 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2619 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2621 WHERE Media.VolumeName = $media->{qmedia}
2624 my $row = $self->dbh_selectrow_hashref($query);
2625 $row->{volretention} = human_sec($row->{volretention});
2626 $row->{voluseduration} = human_sec($row->{voluseduration});
2628 my $elt = $self->get_form(qw/db_pools db_locations/);
2633 }, "update_media.tpl");
2640 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2642 unless ($arg->{jmedias}) {
2643 return $self->error("Can't get selected media");
2646 unless ($arg->{qnewlocation}) {
2647 return $self->error("Can't get new location");
2652 SET LocationId = (SELECT LocationId
2654 WHERE Location = $arg->{qnewlocation})
2655 WHERE Media.VolumeName IN ($arg->{jmedias})
2658 my $nb = $self->dbh_do($query);
2660 print "$nb media updated, you may have to update your autochanger.";
2662 $self->display_media();
2669 my $medias = $self->get_selected_media_location();
2671 return $self->error("Can't get media selection");
2673 my $newloc = CGI::param('newlocation');
2675 my $user = CGI::param('user') || 'unknown';
2676 my $comm = CGI::param('comment') || '';
2677 $comm = $self->dbh_quote("$user: $comm");
2681 foreach my $media (keys %$medias) {
2683 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2685 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2686 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2687 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2690 $self->dbh_do($query);
2691 $self->debug($query);
2695 $q->param('action', 'update_location');
2696 my $url = $q->url(-full => 1, -query=>1);
2698 $self->display({ email => $self->{info}->{email_media},
2700 newlocation => $newloc,
2701 # [ { volumename => 'vol1' }, { volumename => 'vol2'
\81\81 },..]
2702 medias => [ values %$medias ],
2704 "change_location.tpl");
2708 sub display_client_stats
2710 my ($self, %arg) = @_ ;
2712 my $client = $self->dbh_quote($arg{clientname});
2714 my ($limit, $label) = $self->get_limit(%arg);
2718 count(Job.JobId) AS nb_jobs,
2719 sum(Job.JobBytes) AS nb_bytes,
2720 sum(Job.JobErrors) AS nb_err,
2721 sum(Job.JobFiles) AS nb_files,
2722 Client.Name AS clientname
2723 FROM Job JOIN Client USING (ClientId)
2725 Client.Name = $client
2727 GROUP BY Client.Name
2730 my $row = $self->dbh_selectrow_hashref($query);
2732 $row->{ID} = $cur_id++;
2733 $row->{label} = $label;
2734 $row->{grapharg} = "client";
2736 $self->display($row, "display_client_stats.tpl");
2740 sub display_group_stats
2742 my ($self, %arg) = @_ ;
2744 my $carg = $self->get_form(qw/qclient_group/);
2746 unless ($carg->{qclient_group}) {
2747 return $self->error("Can't get group");
2750 my ($limit, $label) = $self->get_limit(%arg);
2754 count(Job.JobId) AS nb_jobs,
2755 sum(Job.JobBytes) AS nb_bytes,
2756 sum(Job.JobErrors) AS nb_err,
2757 sum(Job.JobFiles) AS nb_files,
2758 client_group.client_group_name AS clientname
2759 FROM Job JOIN Client USING (ClientId)
2760 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
2761 JOIN client_group USING (client_group_id)
2763 client_group.client_group_name = $carg->{qclient_group}
2765 GROUP BY client_group.client_group_name
2768 my $row = $self->dbh_selectrow_hashref($query);
2770 $row->{ID} = $cur_id++;
2771 $row->{label} = $label;
2772 $row->{grapharg} = "client_group";
2774 $self->display($row, "display_client_stats.tpl");
2777 # poolname can be undef
2780 my ($self, $poolname) = @_ ;
2784 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2785 if ($arg->{jmediatypes}) {
2786 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2787 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
2790 # TODO : afficher les tailles et les dates
2793 SELECT subq.volmax AS volmax,
2794 subq.volnum AS volnum,
2795 subq.voltotal AS voltotal,
2797 Pool.Recycle AS recycle,
2798 Pool.VolRetention AS volretention,
2799 Pool.VolUseDuration AS voluseduration,
2800 Pool.MaxVolJobs AS maxvoljobs,
2801 Pool.MaxVolFiles AS maxvolfiles,
2802 Pool.MaxVolBytes AS maxvolbytes,
2803 subq.PoolId AS PoolId,
2804 subq.MediaType AS mediatype,
2805 $self->{sql}->{CAT_POOL_TYPE} AS uniq
2808 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2809 count(Media.MediaId) AS volnum,
2810 sum(Media.VolBytes) AS voltotal,
2811 Media.PoolId AS PoolId,
2812 Media.MediaType AS MediaType
2814 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2815 Media.MediaType AS MediaType
2817 WHERE Media.VolStatus = 'Full'
2818 GROUP BY Media.MediaType
2819 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2820 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2822 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2826 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
2829 SELECT Pool.Name AS name,
2830 sum(VolBytes) AS size
2831 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2832 WHERE Media.VolStatus IN ('Recycled', 'Purged')
2836 my $empty = $self->dbh_selectall_hashref($query, 'name');
2838 foreach my $p (values %$all) {
2839 if ($p->{volmax} > 0) { # mysql returns 0.0000
2840 # we remove Recycled/Purged media from pool usage
2841 if (defined $empty->{$p->{name}}) {
2842 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2844 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2846 $p->{poolusage} = 0;
2850 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2852 WHERE PoolId=$p->{poolid}
2853 AND Media.MediaType = '$p->{mediatype}'
2857 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2858 foreach my $t (values %$content) {
2859 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2864 $self->display({ ID => $cur_id++,
2865 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2866 Pools => [ values %$all ]},
2867 "display_pool.tpl");
2870 sub display_running_job
2874 my $arg = $self->get_form('client', 'jobid');
2876 if (!$arg->{client} and $arg->{jobid}) {
2879 SELECT Client.Name AS name
2880 FROM Job INNER JOIN Client USING (ClientId)
2881 WHERE Job.JobId = $arg->{jobid}
2884 my $row = $self->dbh_selectrow_hashref($query);
2887 $arg->{client} = $row->{name};
2888 CGI::param('client', $arg->{client});
2892 if ($arg->{client}) {
2893 my $cli = new Bweb::Client(name => $arg->{client});
2894 $cli->display_running_job($self->{info}, $arg->{jobid});
2895 if ($arg->{jobid}) {
2896 $self->get_job_log();
2899 $self->error("Can't get client or jobid");
2903 sub display_running_jobs
2905 my ($self, $display_action) = @_;
2908 SELECT Job.JobId AS jobid,
2909 Job.Name AS jobname,
2911 Job.StartTime AS starttime,
2912 Job.JobFiles AS jobfiles,
2913 Job.JobBytes AS jobbytes,
2914 Job.JobStatus AS jobstatus,
2915 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2916 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2918 Client.Name AS clientname
2919 FROM Job INNER JOIN Client USING (ClientId)
2920 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2922 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2924 $self->display({ ID => $cur_id++,
2925 display_action => $display_action,
2926 Jobs => [ values %$all ]},
2927 "running_job.tpl") ;
2930 # return the autochanger list to update
2935 my $arg = $self->get_form('jmedias');
2937 unless ($arg->{jmedias}) {
2938 return $self->error("Can't get media selection");
2942 SELECT Media.VolumeName AS volumename,
2943 Storage.Name AS storage,
2944 Location.Location AS location,
2946 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2947 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2948 WHERE Media.VolumeName IN ($arg->{jmedias})
2949 AND Media.InChanger = 1
2952 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2954 foreach my $vol (values %$all) {
2955 my $a = $self->ach_get($vol->{location});
2957 $ret{$vol->{location}} = 1;
2959 unless ($a->{have_status}) {
2961 $a->{have_status} = 1;
2964 print "eject $vol->{volumename} from $vol->{storage} : ";
2965 if ($a->send_to_io($vol->{slot})) {
2966 print "<img src='/bweb/T.png' alt='ok'><br/>";
2968 print "<img src='/bweb/E.png' alt='err'><br/>";
2978 my ($to, $subject, $content) = (CGI::param('email'),
2979 CGI::param('subject'),
2980 CGI::param('content'));
2981 $to =~ s/[^\w\d\.\@<>,]//;
2982 $subject =~ s/[^\w\d\.\[\]]/ /;
2984 open(MAIL, "|mail -s '$subject' '$to'") ;
2985 print MAIL $content;
2995 my $arg = $self->get_form('jobid', 'client');
2997 print CGI::header('text/brestore');
2998 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
2999 print "client=$arg->{client}\n" if ($arg->{client});
3000 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3004 # TODO : move this to Bweb::Autochanger ?
3005 # TODO : make this internal to not eject tape ?
3011 my ($self, $name) = @_;
3014 return $self->error("Can't get your autochanger name ach");
3017 unless ($self->{info}->{ach_list}) {
3018 return $self->error("Could not find any autochanger");
3021 my $a = $self->{info}->{ach_list}->{$name};
3024 $self->error("Can't get your autochanger $name from your ach_list");
3029 $a->{debug} = $self->{debug};
3036 my ($self, $ach) = @_;
3038 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3040 $self->{info}->save();
3048 my $arg = $self->get_form('ach');
3050 or !$self->{info}->{ach_list}
3051 or !$self->{info}->{ach_list}->{$arg->{ach}})
3053 return $self->error("Can't get autochanger name");
3056 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3060 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3062 my $b = $self->get_bconsole();
3064 my @storages = $b->list_storage() ;
3066 $ach->{devices} = [ map { { name => $_ } } @storages ];
3068 $self->display($ach, "ach_add.tpl");
3069 delete $ach->{drives};
3070 delete $ach->{devices};
3077 my $arg = $self->get_form('ach');
3080 or !$self->{info}->{ach_list}
3081 or !$self->{info}->{ach_list}->{$arg->{ach}})
3083 return $self->error("Can't get autochanger name");
3086 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3088 $self->{info}->save();
3089 $self->{info}->view();
3095 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3097 my $b = $self->get_bconsole();
3098 my @storages = $b->list_storage() ;
3100 unless ($arg->{ach}) {
3101 $arg->{devices} = [ map { { name => $_ } } @storages ];
3102 return $self->display($arg, "ach_add.tpl");
3106 foreach my $drive (CGI::param('drives'))
3108 unless (grep(/^$drive$/,@storages)) {
3109 return $self->error("Can't find $drive in storage list");
3112 my $index = CGI::param("index_$drive");
3113 unless (defined $index and $index =~ /^(\d+)$/) {
3114 return $self->error("Can't get $drive index");
3117 $drives[$index] = $drive;
3121 return $self->error("Can't get drives from Autochanger");
3124 my $a = new Bweb::Autochanger(name => $arg->{ach},
3125 precmd => $arg->{precmd},
3126 drive_name => \@drives,
3127 device => $arg->{device},
3128 mtxcmd => $arg->{mtxcmd});
3130 $self->ach_register($a) ;
3132 $self->{info}->view();
3138 my $arg = $self->get_form('jobid');
3140 if ($arg->{jobid}) {
3141 my $b = $self->get_bconsole();
3142 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3146 title => "Delete a job ",
3147 name => "delete jobid=$arg->{jobid}",
3156 my $arg = $self->get_form(qw/media volstatus inchanger pool
3157 slot volretention voluseduration
3158 maxvoljobs maxvolfiles maxvolbytes
3159 qcomment poolrecycle
3162 unless ($arg->{media}) {
3163 return $self->error("Can't find media selection");
3166 my $update = "update volume=$arg->{media} ";
3168 if ($arg->{volstatus}) {
3169 $update .= " volstatus=$arg->{volstatus} ";
3172 if ($arg->{inchanger}) {
3173 $update .= " inchanger=yes " ;
3175 $update .= " slot=$arg->{slot} ";
3178 $update .= " slot=0 inchanger=no ";
3182 $update .= " pool=$arg->{pool} " ;
3185 if (defined $arg->{volretention}) {
3186 $update .= " volretention=\"$arg->{volretention}\" " ;
3189 if (defined $arg->{voluseduration}) {
3190 $update .= " voluse=\"$arg->{voluseduration}\" " ;
3193 if (defined $arg->{maxvoljobs}) {
3194 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3197 if (defined $arg->{maxvolfiles}) {
3198 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3201 if (defined $arg->{maxvolbytes}) {
3202 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3205 my $b = $self->get_bconsole();
3208 content => $b->send_cmd($update),
3209 title => "Update a volume ",
3215 my $media = $self->dbh_quote($arg->{media});
3217 my $loc = CGI::param('location') || '';
3219 $loc = $self->dbh_quote($loc); # is checked by db
3220 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3222 if ($arg->{poolrecycle}) {
3223 push @q, "RecyclePoolId=(SELECT PoolId FROM Pool WHERE Name='$arg->{poolrecycle}')";
3225 if (!$arg->{qcomment}) {
3226 $arg->{qcomment} = "''";
3228 push @q, "Comment=$arg->{qcomment}";
3233 SET " . join (',', @q) . "
3234 WHERE Media.VolumeName = $media
3236 $self->dbh_do($query);
3238 $self->update_media();
3245 my $ach = CGI::param('ach') ;
3246 $ach = $self->ach_get($ach);
3248 return $self->error("Bad autochanger name");
3252 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3253 $b->update_slots($ach->{name});
3261 my $arg = $self->get_form('jobid', 'limit', 'offset');
3262 unless ($arg->{jobid}) {
3263 return $self->error("Can't get jobid");
3266 if ($arg->{limit} == 100) {
3267 $arg->{limit} = 1000;
3270 my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3273 SELECT Job.Name as name, Client.Name as clientname
3274 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3275 WHERE JobId = $arg->{jobid}
3278 my $row = $self->dbh_selectrow_hashref($query);
3281 return $self->error("Can't find $arg->{jobid} in catalog");
3285 SELECT Time AS time, LogText AS log
3287 WHERE Log.JobId = $arg->{jobid}
3288 OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
3289 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3293 OFFSET $arg->{offset}
3296 my $log = $self->dbh_selectall_arrayref($query);
3298 return $self->error("Can't get log for jobid $arg->{jobid}");
3304 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
3306 $logtxt = join("", map { $_->[1] } @$log ) ;
3309 $self->display({ lines=> $logtxt,
3310 jobid => $arg->{jobid},
3311 name => $row->{name},
3312 client => $row->{clientname},
3313 offset => $arg->{offset},
3314 limit => $arg->{limit},
3315 }, 'display_log.tpl');
3323 my $arg = $self->get_form('ach', 'slots', 'drive');
3325 unless ($arg->{ach}) {
3326 return $self->error("Can't find autochanger name");
3329 my $a = $self->ach_get($arg->{ach});
3331 return $self->error("Can't find autochanger name in configuration");
3334 my $storage = $a->get_drive_name($arg->{drive});
3336 return $self->error("Can't get your drive name");
3342 if ($arg->{slots}) {
3343 $slots = join(",", @{ $arg->{slots} });
3344 $slots_sql = " AND Slot IN ($slots) ";
3345 $t += 60*scalar( @{ $arg->{slots} }) ;
3350 SET LocationId = (SELECT LocationId
3352 WHERE Location = '$arg->{ach}'),
3354 RecyclePoolId = (SELECT PoolId
3356 WHERE Name = 'Scratch')
3358 WHERE (LocationId = 0 OR LocationId IS NULL)
3362 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3363 print "<h1>This command can take long time, be patient...</h1>";
3365 $b->label_barcodes(storage => $storage,
3366 drive => $arg->{drive},
3377 my @volume = CGI::param('media');
3380 return $self->error("Can't get media selection");
3383 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3386 content => $b->purge_volume(@volume),
3387 title => "Purge media",
3388 name => "purge volume=" . join(' volume=', @volume),
3397 my @volume = CGI::param('media');
3399 return $self->error("Can't get media selection");
3402 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3405 content => $b->prune_volume(@volume),
3406 title => "Prune media",
3407 name => "prune volume=" . join(' volume=', @volume),
3417 my $arg = $self->get_form('jobid');
3418 unless ($arg->{jobid}) {
3419 return $self->error("Can't get jobid");
3422 my $b = $self->get_bconsole();
3424 content => $b->cancel($arg->{jobid}),
3425 title => "Cancel job",
3426 name => "cancel jobid=$arg->{jobid}",
3432 # Warning, we display current fileset
3435 my $arg = $self->get_form('fileset');
3437 if ($arg->{fileset}) {
3438 my $b = $self->get_bconsole();
3439 my $ret = $b->get_fileset($arg->{fileset});
3440 $self->display({ fileset => $arg->{fileset},
3442 }, "fileset_view.tpl");
3444 $self->error("Can't get fileset name");
3448 sub director_show_sched
3452 my $arg = $self->get_form('days');
3454 my $b = $self->get_bconsole();
3455 my $ret = $b->director_get_sched( $arg->{days} );
3460 }, "scheduled_job.tpl");
3463 sub enable_disable_job
3465 my ($self, $what) = @_ ;
3467 my $name = CGI::param('job') || '';
3468 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3469 return $self->error("Can't find job name");
3472 my $b = $self->get_bconsole();
3482 content => $b->send_cmd("$cmd job=\"$name\""),
3483 title => "$cmd $name",
3484 name => "$cmd job=\"$name\"",
3491 return new Bconsole(pref => $self->{info});
3497 my $b = $self->get_bconsole();
3499 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3501 $self->display({ Jobs => $joblist }, "run_job.tpl");
3506 my ($self, $ouput) = @_;
3509 foreach my $l (split(/\r\n/, $ouput)) {
3510 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3516 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3522 foreach my $k (keys %arg) {
3523 $lowcase{lc($k)} = $arg{$k} ;
3532 my $b = $self->get_bconsole();
3534 my $job = CGI::param('job') || '';
3536 # we take informations from director, and we overwrite with user wish
3537 my $info = $b->send_cmd("show job=\"$job\"");
3538 my $attr = $self->run_parse_job($info);
3540 my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3541 my %job_opt = (%$attr, %$arg);
3543 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3545 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3546 my $clients = [ map { { name => $_ } }$b->list_client()];
3547 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3548 my $storages= [ map { { name => $_ } }$b->list_storage()];
3553 clients => $clients,
3554 filesets => $filesets,
3555 storages => $storages,
3557 }, "run_job_mod.tpl");
3563 my $b = $self->get_bconsole();
3565 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3575 my $b = $self->get_bconsole();
3577 # TODO: check input (don't use pool, level)
3579 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3580 my $job = CGI::param('job') || '';
3581 my $storage = CGI::param('storage') || '';
3583 my $jobid = $b->run(job => $job,
3584 client => $arg->{client},
3585 priority => $arg->{priority},
3586 level => $arg->{level},
3587 storage => $storage,
3588 pool => $arg->{pool},
3589 fileset => $arg->{fileset},
3590 when => $arg->{when},
3593 print $jobid, $b->{error};
3595 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";