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", "qnotingroup");
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})";
1272 } elsif ($arg->{qnotingroup}) {
1275 (SELECT 1 FROM client_group_member
1276 WHERE Client.ClientId = client_group_member.ClientId
1283 SELECT Name AS name,
1285 AutoPrune AS autoprune,
1286 FileRetention AS fileretention,
1287 JobRetention AS jobretention
1292 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1294 my $dsp = { ID => $cur_id++,
1295 clients => [ values %$all] };
1297 $self->display($dsp, "client_list.tpl") ;
1302 my ($self, %arg) = @_;
1309 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1311 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1313 $self->{sql}->{TO_SEC}($arg{age})
1316 $label = "last " . human_sec($arg{age});
1319 if ($arg{groupby}) {
1320 $limit .= " GROUP BY $arg{groupby} ";
1324 $limit .= " ORDER BY $arg{order} ";
1328 $limit .= " LIMIT $arg{limit} ";
1329 $label .= " limited to $arg{limit}";
1333 $limit .= " OFFSET $arg{offset} ";
1334 $label .= " with $arg{offset} offset ";
1338 $label = 'no filter';
1341 return ($limit, $label);
1346 $bweb->get_form(...) - Get useful stuff
1350 This function get and check parameters against regexp.
1352 If word begin with 'q', the return will be quoted or join quoted
1353 if it's end with 's'.
1358 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1361 qclient => 'plume-fd',
1362 qpools => "'plume-fd', 'test-fd', '...'",
1369 my ($self, @what) = @_;
1370 my %what = map { $_ => 1 } @what;
1391 my %opt_ss =( # string with space
1395 my %opt_s = ( # default to ''
1412 my %opt_p = ( # option with path
1419 my %opt_r = (regexwhere => 1);
1421 my %opt_d = ( # option with date
1426 foreach my $i (@what) {
1427 if (exists $opt_i{$i}) {# integer param
1428 my $value = CGI::param($i) || $opt_i{$i} ;
1429 if ($value =~ /^(\d+)$/) {
1432 } elsif ($opt_s{$i}) { # simple string param
1433 my $value = CGI::param($i) || '';
1434 if ($value =~ /^([\w\d\.-]+)$/) {
1437 } elsif ($opt_ss{$i}) { # simple string param (with space)
1438 my $value = CGI::param($i) || '';
1439 if ($value =~ /^([\w\d\.\-\s]+)$/) {
1442 } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1443 my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1445 $ret{$i} = $self->dbh_join(@value) ;
1448 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1449 my $value = CGI::param($1) ;
1451 $ret{$i} = $self->dbh_quote($value);
1454 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1455 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1456 grep { ! /^\s*$/ } CGI::param($1) ];
1457 } elsif (exists $opt_p{$i}) {
1458 my $value = CGI::param($i) || '';
1459 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1462 } elsif (exists $opt_r{$i}) {
1463 my $value = CGI::param($i) || '';
1464 if ($value =~ /^([^'"']+)$/) {
1467 } elsif (exists $opt_d{$i}) {
1468 my $value = CGI::param($i) || '';
1469 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1476 foreach my $s (CGI::param('slot')) {
1477 if ($s =~ /^(\d+)$/) {
1478 push @{$ret{slots}}, $s;
1484 my $when = CGI::param('when') || '';
1485 if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1490 if ($what{db_clients}) {
1492 SELECT Client.Name as clientname
1496 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1497 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1501 if ($what{db_client_groups}) {
1503 SELECT client_group_name AS name
1507 my $grps = $self->dbh_selectall_hashref($query, 'name');
1508 $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} }
1512 if ($what{db_mediatypes}) {
1514 SELECT MediaType as mediatype
1518 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1519 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1523 if ($what{db_locations}) {
1525 SELECT Location as location, Cost as cost
1528 my $loc = $self->dbh_selectall_hashref($query, 'location');
1529 $ret{db_locations} = [ sort { $a->{location}
1535 if ($what{db_pools}) {
1536 my $query = "SELECT Name as name FROM Pool";
1538 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1539 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1542 if ($what{db_filesets}) {
1544 SELECT FileSet.FileSet AS fileset
1548 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1550 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1551 values %$filesets] ;
1554 if ($what{db_jobnames}) {
1556 SELECT DISTINCT Job.Name AS jobname
1560 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1562 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1563 values %$jobnames] ;
1566 if ($what{db_devices}) {
1568 SELECT Device.Name AS name
1572 my $devices = $self->dbh_selectall_hashref($query, 'name');
1574 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1585 my $fields = $self->get_form(qw/age level status clients filesets
1587 db_clients limit db_filesets width height
1588 qclients qfilesets qjobnames db_jobnames/);
1591 my $url = CGI::url(-full => 0,
1594 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1596 # this organisation is to keep user choice between 2 click
1597 # TODO : fileset and client selection doesn't work
1606 sub display_client_job
1608 my ($self, %arg) = @_ ;
1610 $arg{order} = ' Job.JobId DESC ';
1611 my ($limit, $label) = $self->get_limit(%arg);
1613 my $clientname = $self->dbh_quote($arg{clientname});
1616 SELECT DISTINCT Job.JobId AS jobid,
1617 Job.Name AS jobname,
1618 FileSet.FileSet AS fileset,
1620 StartTime AS starttime,
1621 JobFiles AS jobfiles,
1622 JobBytes AS jobbytes,
1623 JobStatus AS jobstatus,
1624 JobErrors AS joberrors
1626 FROM Client,Job,FileSet
1627 WHERE Client.Name=$clientname
1628 AND Client.ClientId=Job.ClientId
1629 AND Job.FileSetId=FileSet.FileSetId
1633 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1635 $self->display({ clientname => $arg{clientname},
1638 Jobs => [ values %$all ],
1640 "display_client_job.tpl") ;
1643 sub get_selected_media_location
1647 my $medias = $self->get_form('jmedias');
1649 unless ($medias->{jmedias}) {
1654 SELECT Media.VolumeName AS volumename, Location.Location AS location
1655 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1656 WHERE Media.VolumeName IN ($medias->{jmedias})
1659 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1661 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1672 my $medias = $self->get_selected_media_location();
1678 my $elt = $self->get_form('db_locations');
1680 $self->display({ ID => $cur_id++,
1681 %$elt, # db_locations
1683 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1693 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1695 $self->display($elt, "help_extern.tpl");
1698 sub help_extern_compute
1702 my $number = CGI::param('limit') || '' ;
1703 unless ($number =~ /^(\d+)$/) {
1704 return $self->error("Bad arg number : $number ");
1707 my ($sql, undef) = $self->get_param('pools',
1708 'locations', 'mediatypes');
1711 SELECT Media.VolumeName AS volumename,
1712 Media.VolStatus AS volstatus,
1713 Media.LastWritten AS lastwritten,
1714 Media.MediaType AS mediatype,
1715 Media.VolMounts AS volmounts,
1717 Media.Recycle AS recycle,
1718 $self->{sql}->{FROM_UNIXTIME}(
1719 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1720 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1723 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1724 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1726 WHERE Media.InChanger = 1
1727 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1729 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1733 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1735 $self->display({ Medias => [ values %$all ] },
1736 "help_extern_compute.tpl");
1743 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1744 $self->display($param, "help_intern.tpl");
1747 sub help_intern_compute
1751 my $number = CGI::param('limit') || '' ;
1752 unless ($number =~ /^(\d+)$/) {
1753 return $self->error("Bad arg number : $number ");
1756 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1758 if (CGI::param('expired')) {
1760 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1761 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1767 SELECT Media.VolumeName AS volumename,
1768 Media.VolStatus AS volstatus,
1769 Media.LastWritten AS lastwritten,
1770 Media.MediaType AS mediatype,
1771 Media.VolMounts AS volmounts,
1773 $self->{sql}->{FROM_UNIXTIME}(
1774 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1775 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1778 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1779 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1781 WHERE Media.InChanger <> 1
1782 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1783 AND Media.Recycle = 1
1785 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1789 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1791 $self->display({ Medias => [ values %$all ] },
1792 "help_intern_compute.tpl");
1798 my ($self, %arg) = @_ ;
1800 my ($limit, $label) = $self->get_limit(%arg);
1804 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1805 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1806 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1807 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1808 ($self->{sql}->{DB_SIZE}) AS db_size,
1809 (SELECT count(Job.JobId)
1811 WHERE Job.JobStatus IN ('E','e','f','A')
1814 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1817 my $row = $self->dbh_selectrow_hashref($query) ;
1819 $row->{nb_bytes} = human_size($row->{nb_bytes});
1821 $row->{db_size} = human_size($row->{db_size});
1822 $row->{label} = $label;
1824 $self->display($row, "general.tpl");
1829 my ($self, @what) = @_ ;
1830 my %elt = map { $_ => 1 } @what;
1835 if ($elt{clients}) {
1836 my @clients = grep { ! /^\s*$/ } CGI::param('client');
1838 $ret{clients} = \@clients;
1839 my $str = $self->dbh_join(@clients);
1840 $limit .= "AND Client.Name IN ($str) ";
1844 if ($elt{client_groups}) {
1845 my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1847 $ret{client_groups} = \@clients;
1848 my $str = $self->dbh_join(@clients);
1849 $limit .= "AND client_group_name IN ($str) ";
1853 if ($elt{filesets}) {
1854 my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1856 $ret{filesets} = \@filesets;
1857 my $str = $self->dbh_join(@filesets);
1858 $limit .= "AND FileSet.FileSet IN ($str) ";
1862 if ($elt{mediatypes}) {
1863 my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
1865 $ret{mediatypes} = \@medias;
1866 my $str = $self->dbh_join(@medias);
1867 $limit .= "AND Media.MediaType IN ($str) ";
1872 my $client = CGI::param('client');
1873 $ret{client} = $client;
1874 $client = $self->dbh_join($client);
1875 $limit .= "AND Client.Name = $client ";
1879 my $level = CGI::param('level') || '';
1880 if ($level =~ /^(\w)$/) {
1882 $limit .= "AND Job.Level = '$1' ";
1887 my $jobid = CGI::param('jobid') || '';
1889 if ($jobid =~ /^(\d+)$/) {
1891 $limit .= "AND Job.JobId = '$1' ";
1896 my $status = CGI::param('status') || '';
1897 if ($status =~ /^(\w)$/) {
1900 $limit .= "AND Job.JobStatus IN ('f','E') ";
1901 } elsif ($1 eq 'W') {
1902 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";
1904 $limit .= "AND Job.JobStatus = '$1' ";
1909 if ($elt{volstatus}) {
1910 my $status = CGI::param('volstatus') || '';
1911 if ($status =~ /^(\w+)$/) {
1913 $limit .= "AND Media.VolStatus = '$1' ";
1917 if ($elt{locations}) {
1918 my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1920 $ret{locations} = \@location;
1921 my $str = $self->dbh_join(@location);
1922 $limit .= "AND Location.Location IN ($str) ";
1927 my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1929 $ret{pools} = \@pool;
1930 my $str = $self->dbh_join(@pool);
1931 $limit .= "AND Pool.Name IN ($str) ";
1935 if ($elt{location}) {
1936 my $location = CGI::param('location') || '';
1938 $ret{location} = $location;
1939 $location = $self->dbh_quote($location);
1940 $limit .= "AND Location.Location = $location ";
1945 my $pool = CGI::param('pool') || '';
1948 $pool = $self->dbh_quote($pool);
1949 $limit .= "AND Pool.Name = $pool ";
1953 if ($elt{jobtype}) {
1954 my $jobtype = CGI::param('jobtype') || '';
1955 if ($jobtype =~ /^(\w)$/) {
1957 $limit .= "AND Job.Type = '$1' ";
1961 return ($limit, %ret);
1972 my ($self, %arg) = @_ ;
1974 $arg{order} = ' Job.JobId DESC ';
1976 my ($limit, $label) = $self->get_limit(%arg);
1977 my ($where, undef) = $self->get_param('clients',
1987 if (CGI::param('client_group')) {
1989 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
1990 LEFT JOIN client_group USING (client_group_id)
1995 SELECT Job.JobId AS jobid,
1996 Client.Name AS client,
1997 FileSet.FileSet AS fileset,
1998 Job.Name AS jobname,
2000 StartTime AS starttime,
2002 Pool.Name AS poolname,
2003 JobFiles AS jobfiles,
2004 JobBytes AS jobbytes,
2005 JobStatus AS jobstatus,
2006 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2007 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2010 JobErrors AS joberrors
2013 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2014 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2016 WHERE Client.ClientId=Job.ClientId
2017 AND Job.JobStatus NOT IN ('R', 'C')
2022 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2024 $self->display({ Filter => $label,
2028 sort { $a->{jobid} <=> $b->{jobid} }
2035 # display job informations
2036 sub display_job_zoom
2038 my ($self, $jobid) = @_ ;
2040 $jobid = $self->dbh_quote($jobid);
2043 SELECT DISTINCT Job.JobId AS jobid,
2044 Client.Name AS client,
2045 Job.Name AS jobname,
2046 FileSet.FileSet AS fileset,
2048 Pool.Name AS poolname,
2049 StartTime AS starttime,
2050 JobFiles AS jobfiles,
2051 JobBytes AS jobbytes,
2052 JobStatus AS jobstatus,
2053 JobErrors AS joberrors,
2054 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2055 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2058 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2059 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
2060 WHERE Client.ClientId=Job.ClientId
2061 AND Job.JobId = $jobid
2064 my $row = $self->dbh_selectrow_hashref($query) ;
2066 # display all volumes associate with this job
2068 SELECT Media.VolumeName as volumename
2069 FROM Job,Media,JobMedia
2070 WHERE Job.JobId = $jobid
2071 AND JobMedia.JobId=Job.JobId
2072 AND JobMedia.MediaId=Media.MediaId
2075 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2077 $row->{volumes} = [ values %$all ] ;
2079 $self->display($row, "display_job_zoom.tpl");
2082 sub display_job_group
2084 my ($self, %arg) = @_;
2086 my ($limit, $label) = $self->get_limit(groupby => 'client_group_name', %arg);
2088 my ($where, undef) = $self->get_param('client_groups',
2094 SELECT client_group_name AS client_group_name,
2095 COALESCE(jobok.jobfiles,0) + COALESCE(joberr.jobfiles,0) AS jobfiles,
2096 COALESCE(jobok.jobbytes,0) + COALESCE(joberr.jobbytes,0) AS jobbytes,
2097 COALESCE(jobok.joberrors,0) + COALESCE(joberr.joberrors,0) AS joberrors,
2098 COALESCE(jobok.nbjobs,0) AS nbjobok,
2099 COALESCE(joberr.nbjobs,0) AS nbjoberr,
2100 COALESCE(jobok.duration, '0:0:0') AS duration
2102 FROM client_group LEFT JOIN (
2103 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2104 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2105 SUM(JobErrors) AS joberrors,
2106 SUM($self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
2107 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)))
2110 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2111 JOIN client_group USING (client_group_id)
2113 WHERE JobStatus = 'T'
2116 ) AS jobok USING (client_group_name) LEFT JOIN
2119 SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs,
2120 SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes,
2121 SUM(JobErrors) AS joberrors
2122 FROM Job JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
2123 JOIN client_group USING (client_group_id)
2125 WHERE JobStatus IN ('f','E', 'A')
2128 ) AS joberr USING (client_group_name)
2132 my $all = $self->dbh_selectall_hashref($query, 'client_group_name');
2134 my $rep = { groups => [ values %$all ], age => $arg{age}, filter => $label };
2137 $self->display($rep, "display_job_group.tpl");
2142 my ($self, %arg) = @_ ;
2144 my ($limit, $label) = $self->get_limit(%arg);
2145 my ($where, %elt) = $self->get_param('pools',
2150 my $arg = $self->get_form('jmedias', 'qre_media');
2152 if ($arg->{jmedias}) {
2153 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2155 if ($arg->{qre_media}) {
2156 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
2160 SELECT Media.VolumeName AS volumename,
2161 Media.VolBytes AS volbytes,
2162 Media.VolStatus AS volstatus,
2163 Media.MediaType AS mediatype,
2164 Media.InChanger AS online,
2165 Media.LastWritten AS lastwritten,
2166 Location.Location AS location,
2167 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2168 Pool.Name AS poolname,
2169 $self->{sql}->{FROM_UNIXTIME}(
2170 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2171 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2174 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2175 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2176 Media.MediaType AS MediaType
2178 WHERE Media.VolStatus = 'Full'
2179 GROUP BY Media.MediaType
2180 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2182 WHERE Media.PoolId=Pool.PoolId
2187 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2189 $self->display({ ID => $cur_id++,
2191 Location => $elt{location},
2192 Medias => [ values %$all ]
2194 "display_media.tpl");
2201 my $pool = $self->get_form('db_pools');
2203 foreach my $name (@{ $pool->{db_pools} }) {
2204 CGI::param('pool', $name->{name});
2205 $self->display_media();
2209 sub display_media_zoom
2213 my $medias = $self->get_form('jmedias');
2215 unless ($medias->{jmedias}) {
2216 return $self->error("Can't get media selection");
2220 SELECT InChanger AS online,
2221 VolBytes AS nb_bytes,
2222 VolumeName AS volumename,
2223 VolStatus AS volstatus,
2224 VolMounts AS nb_mounts,
2225 Media.VolUseDuration AS voluseduration,
2226 Media.MaxVolJobs AS maxvoljobs,
2227 Media.MaxVolFiles AS maxvolfiles,
2228 Media.MaxVolBytes AS maxvolbytes,
2229 VolErrors AS nb_errors,
2230 Pool.Name AS poolname,
2231 Location.Location AS location,
2232 Media.Recycle AS recycle,
2233 Media.VolRetention AS volretention,
2234 Media.LastWritten AS lastwritten,
2235 Media.VolReadTime/1000000 AS volreadtime,
2236 Media.VolWriteTime/1000000 AS volwritetime,
2237 Media.RecycleCount AS recyclecount,
2238 Media.Comment AS comment,
2239 $self->{sql}->{FROM_UNIXTIME}(
2240 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2241 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2244 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2245 WHERE Pool.PoolId = Media.PoolId
2246 AND VolumeName IN ($medias->{jmedias})
2249 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2251 foreach my $media (values %$all) {
2252 my $mq = $self->dbh_quote($media->{volumename});
2255 SELECT DISTINCT Job.JobId AS jobid,
2257 Job.StartTime AS starttime,
2260 Job.JobFiles AS files,
2261 Job.JobBytes AS bytes,
2262 Job.jobstatus AS status
2263 FROM Media,JobMedia,Job
2264 WHERE Media.VolumeName=$mq
2265 AND Media.MediaId=JobMedia.MediaId
2266 AND JobMedia.JobId=Job.JobId
2269 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2272 SELECT LocationLog.Date AS date,
2273 Location.Location AS location,
2274 LocationLog.Comment AS comment
2275 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2276 WHERE Media.MediaId = LocationLog.MediaId
2277 AND Media.VolumeName = $mq
2281 my $log = $self->dbh_selectall_arrayref($query) ;
2283 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2286 $self->display({ jobs => [ values %$jobs ],
2287 LocationLog => $logtxt,
2289 "display_media_zoom.tpl");
2297 my $loc = $self->get_form('qlocation');
2298 unless ($loc->{qlocation}) {
2299 return $self->error("Can't get location");
2303 SELECT Location.Location AS location,
2304 Location.Cost AS cost,
2305 Location.Enabled AS enabled
2307 WHERE Location.Location = $loc->{qlocation}
2310 my $row = $self->dbh_selectrow_hashref($query);
2312 $self->display({ ID => $cur_id++,
2313 %$row }, "location_edit.tpl") ;
2321 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2322 unless ($arg->{qlocation}) {
2323 return $self->error("Can't get location");
2325 unless ($arg->{qnewlocation}) {
2326 return $self->error("Can't get new location name");
2328 unless ($arg->{cost}) {
2329 return $self->error("Can't get new cost");
2332 my $enabled = CGI::param('enabled') || '';
2333 $enabled = $enabled?1:0;
2336 UPDATE Location SET Cost = $arg->{cost},
2337 Location = $arg->{qnewlocation},
2339 WHERE Location.Location = $arg->{qlocation}
2342 $self->dbh_do($query);
2344 $self->location_display();
2350 my $arg = $self->get_form(qw/qlocation/) ;
2352 unless ($arg->{qlocation}) {
2353 return $self->error("Can't get location");
2357 SELECT count(Media.MediaId) AS nb
2358 FROM Media INNER JOIN Location USING (LocationID)
2359 WHERE Location = $arg->{qlocation}
2362 my $res = $self->dbh_selectrow_hashref($query);
2365 return $self->error("Sorry, the location must be empty");
2369 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2372 $self->dbh_do($query);
2374 $self->location_display();
2381 my $arg = $self->get_form(qw/qlocation cost/) ;
2383 unless ($arg->{qlocation}) {
2384 $self->display({}, "location_add.tpl");
2387 unless ($arg->{cost}) {
2388 return $self->error("Can't get new cost");
2391 my $enabled = CGI::param('enabled') || '';
2392 $enabled = $enabled?1:0;
2395 INSERT INTO Location (Location, Cost, Enabled)
2396 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2399 $self->dbh_do($query);
2401 $self->location_display();
2404 sub location_display
2409 SELECT Location.Location AS location,
2410 Location.Cost AS cost,
2411 Location.Enabled AS enabled,
2412 (SELECT count(Media.MediaId)
2414 WHERE Media.LocationId = Location.LocationId
2419 my $location = $self->dbh_selectall_hashref($query, 'location');
2421 $self->display({ ID => $cur_id++,
2422 Locations => [ values %$location ] },
2423 "display_location.tpl");
2430 my $medias = $self->get_selected_media_location();
2435 my $arg = $self->get_form('db_locations', 'qnewlocation');
2437 $self->display({ email => $self->{info}->{email_media},
2439 medias => [ values %$medias ],
2441 "update_location.tpl");
2444 ###########################################################
2450 my $grp = $self->get_form(qw/qclient_group db_clients/);
2453 unless ($grp->{qclient_group}) {
2454 return $self->error("Can't get group");
2459 FROM Client JOIN client_group_member using (clientid)
2460 JOIN client_group using (client_group_id)
2461 WHERE client_group_name = $grp->{qclient_group}
2464 my $row = $self->dbh_selectall_hashref($query, "name");
2466 $self->display({ ID => $cur_id++,
2467 client_group => $grp->{qclient_group},
2469 client_group_member => [ values %$row]},
2477 my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2478 unless ($arg->{qclient_group}) {
2479 return $self->error("Can't get groups");
2482 $self->{dbh}->begin_work();
2485 DELETE FROM client_group_member
2486 WHERE client_group_id IN
2487 (SELECT client_group_id
2489 WHERE client_group_name = $arg->{qclient_group})
2491 $self->dbh_do($query);
2494 INSERT INTO client_group_member (clientid, client_group_id)
2496 (SELECT client_group_id
2498 WHERE client_group_name = $arg->{qclient_group})
2499 FROM Client WHERE Name IN ($arg->{jclients})
2502 $self->dbh_do($query);
2504 if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2507 SET client_group_name = $arg->{qnewgroup}
2508 WHERE client_group_name = $arg->{qclient_group}
2511 $self->dbh_do($query);
2514 $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2516 $self->display_groups();
2522 my $arg = $self->get_form(qw/qclient_group/);
2524 unless ($arg->{qclient_group}) {
2525 return $self->error("Can't get groups");
2528 $self->{dbh}->begin_work();
2531 DELETE FROM client_group_member
2532 WHERE client_group_id IN
2533 (SELECT client_group_id
2535 WHERE client_group_name = $arg->{qclient_group});
2537 DELETE FROM client_group
2538 WHERE client_group_name = $arg->{qclient_group};
2540 $self->dbh_do($query);
2542 $self->{dbh}->commit();
2544 $self->display_groups();
2551 my $arg = $self->get_form(qw/qclient_group/) ;
2553 unless ($arg->{qclient_group}) {
2554 $self->display({}, "groups_add.tpl");
2559 INSERT INTO client_group (client_group_name)
2560 VALUES ($arg->{qclient_group})
2563 $self->dbh_do($query);
2565 $self->display_groups();
2572 my $arg = $self->get_form(qw/db_client_groups/) ;
2574 if ($self->{dbh}->errstr) {
2575 return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2580 $self->display({ ID => $cur_id++,
2582 "display_groups.tpl");
2585 ###########################################################
2587 sub get_media_max_size
2589 my ($self, $type) = @_;
2591 "SELECT avg(VolBytes) AS size
2593 WHERE Media.VolStatus = 'Full'
2594 AND Media.MediaType = '$type'
2597 my $res = $self->selectrow_hashref($query);
2600 return $res->{size};
2610 my $media = $self->get_form('qmedia');
2612 unless ($media->{qmedia}) {
2613 return $self->error("Can't get media");
2617 SELECT Media.Slot AS slot,
2618 PoolMedia.Name AS poolname,
2619 Media.VolStatus AS volstatus,
2620 Media.InChanger AS inchanger,
2621 Location.Location AS location,
2622 Media.VolumeName AS volumename,
2623 Media.MaxVolBytes AS maxvolbytes,
2624 Media.MaxVolJobs AS maxvoljobs,
2625 Media.MaxVolFiles AS maxvolfiles,
2626 Media.VolUseDuration AS voluseduration,
2627 Media.VolRetention AS volretention,
2628 Media.Comment AS comment,
2629 PoolRecycle.Name AS poolrecycle
2631 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2632 LEFT JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2633 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2635 WHERE Media.VolumeName = $media->{qmedia}
2638 my $row = $self->dbh_selectrow_hashref($query);
2639 $row->{volretention} = human_sec($row->{volretention});
2640 $row->{voluseduration} = human_sec($row->{voluseduration});
2642 my $elt = $self->get_form(qw/db_pools db_locations/);
2647 }, "update_media.tpl");
2654 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2656 unless ($arg->{jmedias}) {
2657 return $self->error("Can't get selected media");
2660 unless ($arg->{qnewlocation}) {
2661 return $self->error("Can't get new location");
2666 SET LocationId = (SELECT LocationId
2668 WHERE Location = $arg->{qnewlocation})
2669 WHERE Media.VolumeName IN ($arg->{jmedias})
2672 my $nb = $self->dbh_do($query);
2674 print "$nb media updated, you may have to update your autochanger.";
2676 $self->display_media();
2683 my $medias = $self->get_selected_media_location();
2685 return $self->error("Can't get media selection");
2687 my $newloc = CGI::param('newlocation');
2689 my $user = CGI::param('user') || 'unknown';
2690 my $comm = CGI::param('comment') || '';
2691 $comm = $self->dbh_quote("$user: $comm");
2695 foreach my $media (keys %$medias) {
2697 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2699 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2700 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2701 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2704 $self->dbh_do($query);
2705 $self->debug($query);
2709 $q->param('action', 'update_location');
2710 my $url = $q->url(-full => 1, -query=>1);
2712 $self->display({ email => $self->{info}->{email_media},
2714 newlocation => $newloc,
2715 # [ { volumename => 'vol1' }, { volumename => 'vol2'
\81\81 },..]
2716 medias => [ values %$medias ],
2718 "change_location.tpl");
2722 sub display_client_stats
2724 my ($self, %arg) = @_ ;
2726 my $client = $self->dbh_quote($arg{clientname});
2728 my ($limit, $label) = $self->get_limit(%arg);
2732 count(Job.JobId) AS nb_jobs,
2733 sum(Job.JobBytes) AS nb_bytes,
2734 sum(Job.JobErrors) AS nb_err,
2735 sum(Job.JobFiles) AS nb_files,
2736 Client.Name AS clientname
2737 FROM Job JOIN Client USING (ClientId)
2739 Client.Name = $client
2741 GROUP BY Client.Name
2744 my $row = $self->dbh_selectrow_hashref($query);
2746 $row->{ID} = $cur_id++;
2747 $row->{label} = $label;
2748 $row->{grapharg} = "client";
2750 $self->display($row, "display_client_stats.tpl");
2754 sub display_group_stats
2756 my ($self, %arg) = @_ ;
2758 my $carg = $self->get_form(qw/qclient_group/);
2760 unless ($carg->{qclient_group}) {
2761 return $self->error("Can't get group");
2764 my ($limit, $label) = $self->get_limit(%arg);
2768 count(Job.JobId) AS nb_jobs,
2769 sum(Job.JobBytes) AS nb_bytes,
2770 sum(Job.JobErrors) AS nb_err,
2771 sum(Job.JobFiles) AS nb_files,
2772 client_group.client_group_name AS clientname
2773 FROM Job JOIN Client USING (ClientId)
2774 JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
2775 JOIN client_group USING (client_group_id)
2777 client_group.client_group_name = $carg->{qclient_group}
2779 GROUP BY client_group.client_group_name
2782 my $row = $self->dbh_selectrow_hashref($query);
2784 $row->{ID} = $cur_id++;
2785 $row->{label} = $label;
2786 $row->{grapharg} = "client_group";
2788 $self->display($row, "display_client_stats.tpl");
2791 # poolname can be undef
2794 my ($self, $poolname) = @_ ;
2798 my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2799 if ($arg->{jmediatypes}) {
2800 $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2801 $whereA = "AND MediaType IN ($arg->{jmediatypes}) ";
2804 # TODO : afficher les tailles et les dates
2807 SELECT subq.volmax AS volmax,
2808 subq.volnum AS volnum,
2809 subq.voltotal AS voltotal,
2811 Pool.Recycle AS recycle,
2812 Pool.VolRetention AS volretention,
2813 Pool.VolUseDuration AS voluseduration,
2814 Pool.MaxVolJobs AS maxvoljobs,
2815 Pool.MaxVolFiles AS maxvolfiles,
2816 Pool.MaxVolBytes AS maxvolbytes,
2817 subq.PoolId AS PoolId,
2818 subq.MediaType AS mediatype,
2819 $self->{sql}->{CAT_POOL_TYPE} AS uniq
2822 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2823 count(Media.MediaId) AS volnum,
2824 sum(Media.VolBytes) AS voltotal,
2825 Media.PoolId AS PoolId,
2826 Media.MediaType AS MediaType
2828 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2829 Media.MediaType AS MediaType
2831 WHERE Media.VolStatus = 'Full'
2832 GROUP BY Media.MediaType
2833 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2834 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2836 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2840 my $all = $self->dbh_selectall_hashref($query, 'uniq') ;
2843 SELECT Pool.Name AS name,
2844 sum(VolBytes) AS size
2845 FROM Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2846 WHERE Media.VolStatus IN ('Recycled', 'Purged')
2850 my $empty = $self->dbh_selectall_hashref($query, 'name');
2852 foreach my $p (values %$all) {
2853 if ($p->{volmax} > 0) { # mysql returns 0.0000
2854 # we remove Recycled/Purged media from pool usage
2855 if (defined $empty->{$p->{name}}) {
2856 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2858 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2860 $p->{poolusage} = 0;
2864 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2866 WHERE PoolId=$p->{poolid}
2867 AND Media.MediaType = '$p->{mediatype}'
2871 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2872 foreach my $t (values %$content) {
2873 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2878 $self->display({ ID => $cur_id++,
2879 MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2880 Pools => [ values %$all ]},
2881 "display_pool.tpl");
2884 sub display_running_job
2888 my $arg = $self->get_form('client', 'jobid');
2890 if (!$arg->{client} and $arg->{jobid}) {
2893 SELECT Client.Name AS name
2894 FROM Job INNER JOIN Client USING (ClientId)
2895 WHERE Job.JobId = $arg->{jobid}
2898 my $row = $self->dbh_selectrow_hashref($query);
2901 $arg->{client} = $row->{name};
2902 CGI::param('client', $arg->{client});
2906 if ($arg->{client}) {
2907 my $cli = new Bweb::Client(name => $arg->{client});
2908 $cli->display_running_job($self->{info}, $arg->{jobid});
2909 if ($arg->{jobid}) {
2910 $self->get_job_log();
2913 $self->error("Can't get client or jobid");
2917 sub display_running_jobs
2919 my ($self, $display_action) = @_;
2922 SELECT Job.JobId AS jobid,
2923 Job.Name AS jobname,
2925 Job.StartTime AS starttime,
2926 Job.JobFiles AS jobfiles,
2927 Job.JobBytes AS jobbytes,
2928 Job.JobStatus AS jobstatus,
2929 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2930 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2932 Client.Name AS clientname
2933 FROM Job INNER JOIN Client USING (ClientId)
2934 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2936 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2938 $self->display({ ID => $cur_id++,
2939 display_action => $display_action,
2940 Jobs => [ values %$all ]},
2941 "running_job.tpl") ;
2944 # return the autochanger list to update
2949 my $arg = $self->get_form('jmedias');
2951 unless ($arg->{jmedias}) {
2952 return $self->error("Can't get media selection");
2956 SELECT Media.VolumeName AS volumename,
2957 Storage.Name AS storage,
2958 Location.Location AS location,
2960 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2961 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2962 WHERE Media.VolumeName IN ($arg->{jmedias})
2963 AND Media.InChanger = 1
2966 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2968 foreach my $vol (values %$all) {
2969 my $a = $self->ach_get($vol->{location});
2971 $ret{$vol->{location}} = 1;
2973 unless ($a->{have_status}) {
2975 $a->{have_status} = 1;
2978 print "eject $vol->{volumename} from $vol->{storage} : ";
2979 if ($a->send_to_io($vol->{slot})) {
2980 print "<img src='/bweb/T.png' alt='ok'><br/>";
2982 print "<img src='/bweb/E.png' alt='err'><br/>";
2992 my ($to, $subject, $content) = (CGI::param('email'),
2993 CGI::param('subject'),
2994 CGI::param('content'));
2995 $to =~ s/[^\w\d\.\@<>,]//;
2996 $subject =~ s/[^\w\d\.\[\]]/ /;
2998 open(MAIL, "|mail -s '$subject' '$to'") ;
2999 print MAIL $content;
3009 my $arg = $self->get_form('jobid', 'client');
3011 print CGI::header('text/brestore');
3012 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
3013 print "client=$arg->{client}\n" if ($arg->{client});
3014 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
3018 # TODO : move this to Bweb::Autochanger ?
3019 # TODO : make this internal to not eject tape ?
3025 my ($self, $name) = @_;
3028 return $self->error("Can't get your autochanger name ach");
3031 unless ($self->{info}->{ach_list}) {
3032 return $self->error("Could not find any autochanger");
3035 my $a = $self->{info}->{ach_list}->{$name};
3038 $self->error("Can't get your autochanger $name from your ach_list");
3043 $a->{debug} = $self->{debug};
3050 my ($self, $ach) = @_;
3052 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
3054 $self->{info}->save();
3062 my $arg = $self->get_form('ach');
3064 or !$self->{info}->{ach_list}
3065 or !$self->{info}->{ach_list}->{$arg->{ach}})
3067 return $self->error("Can't get autochanger name");
3070 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
3074 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3076 my $b = $self->get_bconsole();
3078 my @storages = $b->list_storage() ;
3080 $ach->{devices} = [ map { { name => $_ } } @storages ];
3082 $self->display($ach, "ach_add.tpl");
3083 delete $ach->{drives};
3084 delete $ach->{devices};
3091 my $arg = $self->get_form('ach');
3094 or !$self->{info}->{ach_list}
3095 or !$self->{info}->{ach_list}->{$arg->{ach}})
3097 return $self->error("Can't get autochanger name");
3100 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3102 $self->{info}->save();
3103 $self->{info}->view();
3109 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3111 my $b = $self->get_bconsole();
3112 my @storages = $b->list_storage() ;
3114 unless ($arg->{ach}) {
3115 $arg->{devices} = [ map { { name => $_ } } @storages ];
3116 return $self->display($arg, "ach_add.tpl");
3120 foreach my $drive (CGI::param('drives'))
3122 unless (grep(/^$drive$/,@storages)) {
3123 return $self->error("Can't find $drive in storage list");
3126 my $index = CGI::param("index_$drive");
3127 unless (defined $index and $index =~ /^(\d+)$/) {
3128 return $self->error("Can't get $drive index");
3131 $drives[$index] = $drive;
3135 return $self->error("Can't get drives from Autochanger");
3138 my $a = new Bweb::Autochanger(name => $arg->{ach},
3139 precmd => $arg->{precmd},
3140 drive_name => \@drives,
3141 device => $arg->{device},
3142 mtxcmd => $arg->{mtxcmd});
3144 $self->ach_register($a) ;
3146 $self->{info}->view();
3152 my $arg = $self->get_form('jobid');
3154 if ($arg->{jobid}) {
3155 my $b = $self->get_bconsole();
3156 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3160 title => "Delete a job ",
3161 name => "delete jobid=$arg->{jobid}",
3170 my $arg = $self->get_form(qw/media volstatus inchanger pool
3171 slot volretention voluseduration
3172 maxvoljobs maxvolfiles maxvolbytes
3173 qcomment poolrecycle
3176 unless ($arg->{media}) {
3177 return $self->error("Can't find media selection");
3180 my $update = "update volume=$arg->{media} ";
3182 if ($arg->{volstatus}) {
3183 $update .= " volstatus=$arg->{volstatus} ";
3186 if ($arg->{inchanger}) {
3187 $update .= " inchanger=yes " ;
3189 $update .= " slot=$arg->{slot} ";
3192 $update .= " slot=0 inchanger=no ";
3196 $update .= " pool=$arg->{pool} " ;
3199 if (defined $arg->{volretention}) {
3200 $update .= " volretention=\"$arg->{volretention}\" " ;
3203 if (defined $arg->{voluseduration}) {
3204 $update .= " voluse=\"$arg->{voluseduration}\" " ;
3207 if (defined $arg->{maxvoljobs}) {
3208 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3211 if (defined $arg->{maxvolfiles}) {
3212 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3215 if (defined $arg->{maxvolbytes}) {
3216 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3219 my $b = $self->get_bconsole();
3222 content => $b->send_cmd($update),
3223 title => "Update a volume ",
3229 my $media = $self->dbh_quote($arg->{media});
3231 my $loc = CGI::param('location') || '';
3233 $loc = $self->dbh_quote($loc); # is checked by db
3234 push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3236 if ($arg->{poolrecycle}) {
3237 push @q, "RecyclePoolId=(SELECT PoolId FROM Pool WHERE Name='$arg->{poolrecycle}')";
3239 if (!$arg->{qcomment}) {
3240 $arg->{qcomment} = "''";
3242 push @q, "Comment=$arg->{qcomment}";
3247 SET " . join (',', @q) . "
3248 WHERE Media.VolumeName = $media
3250 $self->dbh_do($query);
3252 $self->update_media();
3259 my $ach = CGI::param('ach') ;
3260 $ach = $self->ach_get($ach);
3262 return $self->error("Bad autochanger name");
3266 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3267 $b->update_slots($ach->{name});
3275 my $arg = $self->get_form('jobid', 'limit', 'offset');
3276 unless ($arg->{jobid}) {
3277 return $self->error("Can't get jobid");
3280 if ($arg->{limit} == 100) {
3281 $arg->{limit} = 1000;
3284 my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3287 SELECT Job.Name as name, Client.Name as clientname
3288 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3289 WHERE JobId = $arg->{jobid}
3292 my $row = $self->dbh_selectrow_hashref($query);
3295 return $self->error("Can't find $arg->{jobid} in catalog");
3299 SELECT Time AS time, LogText AS log
3301 WHERE Log.JobId = $arg->{jobid}
3302 OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid})
3303 AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3307 OFFSET $arg->{offset}
3310 my $log = $self->dbh_selectall_arrayref($query);
3312 return $self->error("Can't get log for jobid $arg->{jobid}");
3318 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
3320 $logtxt = join("", map { $_->[1] } @$log ) ;
3323 $self->display({ lines=> $logtxt,
3324 jobid => $arg->{jobid},
3325 name => $row->{name},
3326 client => $row->{clientname},
3327 offset => $arg->{offset},
3328 limit => $arg->{limit},
3329 }, 'display_log.tpl');
3337 my $arg = $self->get_form('ach', 'slots', 'drive');
3339 unless ($arg->{ach}) {
3340 return $self->error("Can't find autochanger name");
3343 my $a = $self->ach_get($arg->{ach});
3345 return $self->error("Can't find autochanger name in configuration");
3348 my $storage = $a->get_drive_name($arg->{drive});
3350 return $self->error("Can't get your drive name");
3356 if ($arg->{slots}) {
3357 $slots = join(",", @{ $arg->{slots} });
3358 $slots_sql = " AND Slot IN ($slots) ";
3359 $t += 60*scalar( @{ $arg->{slots} }) ;
3364 SET LocationId = (SELECT LocationId
3366 WHERE Location = '$arg->{ach}'),
3368 RecyclePoolId = (SELECT PoolId
3370 WHERE Name = 'Scratch')
3372 WHERE (LocationId = 0 OR LocationId IS NULL)
3376 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3377 print "<h1>This command can take long time, be patient...</h1>";
3379 $b->label_barcodes(storage => $storage,
3380 drive => $arg->{drive},
3391 my @volume = CGI::param('media');
3394 return $self->error("Can't get media selection");
3397 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3400 content => $b->purge_volume(@volume),
3401 title => "Purge media",
3402 name => "purge volume=" . join(' volume=', @volume),
3411 my @volume = CGI::param('media');
3413 return $self->error("Can't get media selection");
3416 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3419 content => $b->prune_volume(@volume),
3420 title => "Prune media",
3421 name => "prune volume=" . join(' volume=', @volume),
3431 my $arg = $self->get_form('jobid');
3432 unless ($arg->{jobid}) {
3433 return $self->error("Can't get jobid");
3436 my $b = $self->get_bconsole();
3438 content => $b->cancel($arg->{jobid}),
3439 title => "Cancel job",
3440 name => "cancel jobid=$arg->{jobid}",
3446 # Warning, we display current fileset
3449 my $arg = $self->get_form('fileset');
3451 if ($arg->{fileset}) {
3452 my $b = $self->get_bconsole();
3453 my $ret = $b->get_fileset($arg->{fileset});
3454 $self->display({ fileset => $arg->{fileset},
3456 }, "fileset_view.tpl");
3458 $self->error("Can't get fileset name");
3462 sub director_show_sched
3466 my $arg = $self->get_form('days');
3468 my $b = $self->get_bconsole();
3469 my $ret = $b->director_get_sched( $arg->{days} );
3474 }, "scheduled_job.tpl");
3477 sub enable_disable_job
3479 my ($self, $what) = @_ ;
3481 my $name = CGI::param('job') || '';
3482 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3483 return $self->error("Can't find job name");
3486 my $b = $self->get_bconsole();
3496 content => $b->send_cmd("$cmd job=\"$name\""),
3497 title => "$cmd $name",
3498 name => "$cmd job=\"$name\"",
3505 return new Bconsole(pref => $self->{info});
3511 my $b = $self->get_bconsole();
3513 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3515 $self->display({ Jobs => $joblist }, "run_job.tpl");
3520 my ($self, $ouput) = @_;
3523 foreach my $l (split(/\r\n/, $ouput)) {
3524 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3530 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3536 foreach my $k (keys %arg) {
3537 $lowcase{lc($k)} = $arg{$k} ;
3546 my $b = $self->get_bconsole();
3548 my $job = CGI::param('job') || '';
3550 # we take informations from director, and we overwrite with user wish
3551 my $info = $b->send_cmd("show job=\"$job\"");
3552 my $attr = $self->run_parse_job($info);
3554 my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3555 my %job_opt = (%$attr, %$arg);
3557 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3559 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3560 my $clients = [ map { { name => $_ } }$b->list_client()];
3561 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3562 my $storages= [ map { { name => $_ } }$b->list_storage()];
3567 clients => $clients,
3568 filesets => $filesets,
3569 storages => $storages,
3571 }, "run_job_mod.tpl");
3577 my $b = $self->get_bconsole();
3579 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3589 my $b = $self->get_bconsole();
3591 # TODO: check input (don't use pool, level)
3593 my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3594 my $job = CGI::param('job') || '';
3595 my $storage = CGI::param('storage') || '';
3597 my $jobid = $b->run(job => $job,
3598 client => $arg->{client},
3599 priority => $arg->{priority},
3600 level => $arg->{level},
3601 storage => $storage,
3602 pool => $arg->{pool},
3603 fileset => $arg->{fileset},
3604 when => $arg->{when},
3607 print $jobid, $b->{error};
3609 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";