1 ################################################################
6 Copyright (C) 2006 Eric Bollengier
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 Bweb::Gui - Base package for all Bweb object
37 This package define base fonction like new, display, etc..
42 our $template_dir='/usr/share/bweb/tpl';
47 new - creation a of new Bweb object
51 This function take an hash of argument and place them
54 IE : $obj = new Obj(name => 'test', age => '10');
56 $obj->{name} eq 'test' and $obj->{age} eq 10
62 my ($class, %arg) = @_;
67 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
74 my ($self, $what) = @_;
78 print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
80 print "<pre>$what</pre>";
87 error - display an error to the user
91 this function set $self->{error} with arg, display a message with
92 error.tpl and return 0
97 return $self->error("Can't use this file");
104 my ($self, $what) = @_;
105 $self->{error} = $what;
106 $self->display($self, 'error.tpl');
112 display - display an html page with HTML::Template
116 this function is use to render all html codes. it takes an
117 ref hash as arg in which all param are usable in template.
119 it will use global template_dir to search the template file.
121 hash keys are not sensitive. See HTML::Template for more
122 explanations about the hash ref. (it's can be quiet hard to understand)
126 $ref = { name => 'me', age => 26 };
127 $self->display($ref, "people.tpl");
133 my ($self, $hash, $tpl) = @_ ;
135 my $template = HTML::Template->new(filename => $tpl,
136 path =>[$template_dir],
137 die_on_bad_params => 0,
138 case_sensitive => 0);
140 foreach my $var (qw/limit offset/) {
142 unless ($hash->{$var}) {
143 my $value = CGI::param($var) || '';
145 if ($value =~ /^(\d+)$/) {
146 $template->param($var, $1) ;
151 $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
152 $template->param('loginname', CGI::remote_user());
154 $template->param($hash);
155 print $template->output();
159 ################################################################
161 package Bweb::Config;
163 use base q/Bweb::Gui/;
167 Bweb::Config - read, write, display, modify configuration
171 this package is used for manage configuration
175 $conf = new Bweb::Config(config_file => '/path/to/conf');
186 =head1 PACKAGE VARIABLE
188 %k_re - hash of all acceptable option.
192 this variable permit to check all option with a regexp.
196 our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
197 user => qr/^([\w\d\.-]+)$/i,
198 password => qr/^(.*)$/i,
199 template_dir => qr!^([/\w\d\.-]+)$!,
200 debug => qr/^(on)?$/,
201 email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
202 graph_font => qr!^([/\w\d\.-]+.ttf)$!,
203 bconsole => qr!^(.+)?$!,
204 syslog_file => qr!^(.+)?$!,
205 log_dir => qr!^(.+)?$!,
206 ach_list => qr!^(.+)?$!,
211 load - load config_file
215 this function load the specified config_file.
223 unless (open(FP, $self->{config_file}))
225 return $self->error("$self->{config_file} : $!");
228 while (my $line = <FP>)
231 my ($k, $v) = split(/\s*=\s*/, $line, 2);
241 save - save the current configuration to config_file
249 unless (open(FP, ">$self->{config_file}"))
251 return $self->error("$self->{config_file} : $!");
254 foreach my $k (keys %$self)
256 next unless (exists $k_re{$k}) ;
257 print FP "$k = $self->{$k}\n";
266 edit, view, modify - html form ouput
274 $self->display($self, "config_edit.tpl");
281 $self->display($self, "config_view.tpl");
291 foreach my $k (CGI::param())
293 next unless (exists $k_re{$k}) ;
294 my $val = CGI::param($k);
295 if ($val =~ $k_re{$k}) {
298 $self->{error} .= "bad parameter : $k = [$val]";
302 $self->display($self, "config_view.tpl");
304 if ($self->{error}) { # an error as occured
305 $self->display($self, 'error.tpl');
313 ################################################################
315 package Bweb::Client;
317 use base q/Bweb::Gui/;
321 Bweb::Client - Bacula FD
325 this package is use to do all Client operations like, parse status etc...
329 $client = new Bweb::Client(name => 'zog-fd');
330 $client->status(); # do a 'status client=zog-fd'
336 display_running_job - Html display of a running job
340 this function is used to display information about a current job
344 sub display_running_job
346 my ($self, $conf, $jobid) = @_ ;
348 my $status = $self->status($conf);
351 if ($status->{$jobid}) {
352 $self->display($status->{$jobid}, "client_job_status.tpl");
355 for my $id (keys %$status) {
356 $self->display($status->{$id}, "client_job_status.tpl");
363 $client = new Bweb::Client(name => 'plume-fd');
365 $client->status($bweb);
369 dirty hack to parse "status client=xxx-fd"
373 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
374 Backup Job started: 06-jun-06 17:22
375 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
376 Files Examined=10,697
377 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
383 JobName => Full_plume.2006-06-06_17.22.23,
386 Bytes => 194,484,132,
396 my ($self, $conf) = @_ ;
398 if (defined $self->{cur_jobs}) {
399 return $self->{cur_jobs} ;
403 my $b = new Bconsole(pref => $conf);
404 my $ret = $b->send_cmd("st client=$self->{name}");
408 for my $r (split(/\n/, $ret)) {
410 $r =~ s/(^\s+|\s+$)//g;
411 if ($r =~ /JobId (\d+) Job (\S+)/) {
413 $arg->{$jobid} = { @param, JobId => $jobid } ;
417 @param = ( JobName => $2 );
419 } elsif ($r =~ /=.+=/) {
420 push @param, split(/\s+|\s*=\s*/, $r) ;
422 } elsif ($r =~ /=/) { # one per line
423 push @param, split(/\s*=\s*/, $r) ;
425 } elsif ($r =~ /:/) { # one per line
426 push @param, split(/\s*:\s*/, $r, 2) ;
430 if ($jobid and @param) {
431 $arg->{$jobid} = { @param,
433 Client => $self->{name},
437 $self->{cur_jobs} = $arg ;
443 ################################################################
445 package Bweb::Autochanger;
447 use base q/Bweb::Gui/;
451 Bweb::Autochanger - Object to manage Autochanger
455 this package will parse the mtx output and manage drives.
459 $auto = new Bweb::Autochanger(precmd => 'sudo');
461 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
465 $auto->slot_is_full(10);
466 $auto->transfer(10, 11);
470 # TODO : get autochanger definition from config/dump file
475 my ($name, $bweb) = @_;
478 return $bweb->error("Can't get your autochanger name ach");
482 unless (get_defined_ach($bweb)) {
487 my $a = $ach_list->{$name};
490 $bweb->error("Can't get your autochanger $name from your ach_list");
502 if (defined $bweb->{info}->{ach_list}) {
503 if (open(FP, "<$bweb->{info}->{ach_list}")) {
504 my $f=''; my $tmpbuffer;
505 while(read FP,$tmpbuffer,4096)
510 no strict; # I have no idea of the contents of the file
511 eval '$ach_list = ' . $f ;
514 return $bweb->error("Can't open $bweb->{info}->{ach_list} $!");
517 return $bweb->error("Can't find your ach_list file in your configuration");
520 $bweb->debug($ach_list);
527 my ($ach, $bweb) = @_;
530 if (defined $bweb->{info}->{ach_list})
533 get_defined_ach($bweb) ;
536 $ach_list->{$ach->{name}} = $ach;
538 if (open(FP, ">$bweb->{info}->{ach_list}")) {
539 print FP Data::Dumper::Dumper($ach_list);
543 $err .= "\nCan you put this in $bweb->{info}->{ach_list}\n";
544 $err .= Data::Dumper::Dumper($ach_list);
547 $err = "ach_list isn't defined";
551 return $bweb->error("Can't find to your ach_list (see bweb configuration) $err");
559 my ($class, %arg) = @_;
562 name => '', # autochanger name
563 label => {}, # where are volume { label1 => 40, label2 => drive0 }
564 drive => [], # drive use [ 'media1', 'empty', ..]
565 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
566 io => [], # io slot number list [ 41, 42, 43...]
567 info => {slot => 0, # informations (slot, drive, io)
571 mtxcmd => '/usr/sbin/mtx',
573 device => '/dev/changer',
574 precmd => '', # ssh command
575 bweb => undef, # link to bacula web object (use for display)
578 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
585 status - parse the output of mtx status
589 this function will launch mtx status and parse the output. it will
590 give a perlish view of the autochanger content.
592 it uses ssh if the autochanger is on a other host.
599 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
601 # TODO : reset all infos
602 $self->{info}->{drive} = 0;
603 $self->{info}->{slot} = 0;
604 $self->{info}->{io} = 0;
606 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
609 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
610 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
611 #Data Transfer Element 1:Empty
612 # Storage Element 1:Empty
613 # Storage Element 2:Full :VolumeTag=000002
614 # Storage Element 3:Empty
615 # Storage Element 4:Full :VolumeTag=000004
616 # Storage Element 5:Full :VolumeTag=000001
617 # Storage Element 6:Full :VolumeTag=000003
618 # Storage Element 7:Empty
619 # Storage Element 41 IMPORT/EXPORT:Empty
620 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
625 # Storage Element 7:Empty
626 # Storage Element 2:Full :VolumeTag=000002
627 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
630 $self->set_empty_slot($1);
632 $self->set_slot($1, $4);
635 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
638 $self->set_empty_drive($1);
640 $self->set_drive($1, $4, $6);
643 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
646 $self->set_empty_io($1);
648 $self->set_io($1, $4);
651 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
653 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
654 $self->{info}->{drive} = $1;
655 $self->{info}->{slot} = $2;
656 if ($l =~ /(\d+)\s+Import/) {
657 $self->{info}->{io} = $1 ;
659 $self->{info}->{io} = 0;
664 $self->debug($self) ;
669 my ($self, $slot) = @_;
672 if ($self->{slot}->[$slot] eq 'loaded') {
676 my $label = $self->{slot}->[$slot] ;
678 return $self->is_media_loaded($label);
683 my ($self, $drive, $slot) = @_;
685 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
686 return 0 if ($self->slot_is_full($slot)) ;
688 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
691 my $content = $self->get_slot($slot);
692 print "content = $content<br/> $drive => $slot<br/>";
693 $self->set_empty_drive($drive);
694 $self->set_slot($slot, $content);
697 $self->{error} = $out;
702 # TODO: load/unload have to use mtx script from bacula
705 my ($self, $drive, $slot) = @_;
707 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
708 return 0 unless ($self->slot_is_full($slot)) ;
710 print "Loading drive $drive with slot $slot<br/>\n";
711 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
714 my $content = $self->get_slot($slot);
715 print "content = $content<br/> $slot => $drive<br/>";
716 $self->set_drive($drive, $slot, $content);
719 $self->{error} = $out;
727 my ($self, $media) = @_;
729 unless ($self->{label}->{$media}) {
733 if ($self->{label}->{$media} =~ /drive\d+/) {
743 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
748 my ($self, $slot, $tag) = @_;
749 $self->{slot}->[$slot] = $tag || 'full';
750 push @{ $self->{io} }, $slot;
753 $self->{label}->{$tag} = $slot;
759 my ($self, $slot) = @_;
761 push @{ $self->{io} }, $slot;
763 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
764 $self->{slot}->[$slot] = 'empty';
770 my ($self, $slot) = @_;
771 return $self->{slot}->[$slot];
776 my ($self, $slot, $tag) = @_;
777 $self->{slot}->[$slot] = $tag || 'full';
780 $self->{label}->{$tag} = $slot;
786 my ($self, $slot) = @_;
788 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
789 $self->{slot}->[$slot] = 'empty';
795 my ($self, $drive) = @_;
796 $self->{drive}->[$drive] = 'empty';
801 my ($self, $drive, $slot, $tag) = @_;
802 $self->{drive}->[$drive] = $tag || $slot;
804 $self->{slot}->[$slot] = $tag || 'loaded';
807 $self->{label}->{$tag} = "drive$drive";
813 my ($self, $slot) = @_;
815 # slot don't exists => full
816 if (not defined $self->{slot}->[$slot]) {
820 if ($self->{slot}->[$slot] eq 'empty') {
823 return 1; # vol, full, loaded
826 sub slot_get_first_free
829 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
830 return $slot unless ($self->slot_is_full($slot));
834 sub io_get_first_free
838 foreach my $slot (@{ $self->{io} }) {
839 return $slot unless ($self->slot_is_full($slot));
846 my ($self, $media) = @_;
848 return $self->{label}->{$media} ;
853 my ($self, $media) = @_;
855 return defined $self->{label}->{$media} ;
860 my ($self, $slot) = @_;
862 unless ($self->slot_is_full($slot)) {
863 print "Autochanger $self->{name} slot $slot is empty\n";
868 if ($self->is_slot_loaded($slot)) {
871 print "Autochanger $self->{name} $slot is currently in use\n";
875 # autochanger must have I/O
876 unless ($self->have_io()) {
877 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
881 my $dst = $self->io_get_first_free();
884 print "Autochanger $self->{name} you must empty I/O first\n";
887 $self->transfer($slot, $dst);
892 my ($self, $src, $dst) = @_ ;
893 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
894 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
897 my $content = $self->get_slot($src);
898 print "content = $content<br/> $src => $dst<br/>";
899 $self->{slot}->[$src] = 'empty';
900 $self->set_slot($dst, $content);
903 $self->{error} = $out;
910 my ($self, $index) = @_;
911 return $self->{drive_name}->[$index];
914 # TODO : do a tapeinfo request to get informations
924 for my $slot (@{$self->{io}})
926 if ($self->is_slot_loaded($slot)) {
927 print "$slot is currently loaded\n";
931 if ($self->slot_is_full($slot))
933 my $free = $self->slot_get_first_free() ;
934 print "want to move $slot to $free\n";
937 $self->transfer($slot, $free) || print "$self->{error}\n";
940 $self->{error} = "E : Can't find free slot";
946 # TODO : this is with mtx status output,
947 # we can do an other function from bacula view (with StorageId)
951 my $bweb = $self->{bweb};
953 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
954 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
957 SELECT Media.VolumeName AS volumename,
958 Media.VolStatus AS volstatus,
959 Media.LastWritten AS lastwritten,
960 Media.VolBytes AS volbytes,
961 Media.MediaType AS mediatype,
963 Media.InChanger AS inchanger,
965 $bweb->{sql}->{FROM_UNIXTIME}(
966 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
967 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
970 INNER JOIN Pool USING (PoolId)
972 WHERE Media.VolumeName IN ($media_list)
975 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
977 # TODO : verify slot and bacula slot
981 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
983 if ($self->slot_is_full($slot)) {
985 my $vol = $self->{slot}->[$slot];
986 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
988 my $bslot = $all->{$vol}->{slot} ;
989 my $inchanger = $all->{$vol}->{inchanger};
991 # if bacula slot or inchanger flag is bad, we display a message
992 if ($bslot != $slot or !$inchanger) {
993 push @to_update, $slot;
996 $all->{$vol}->{realslot} = $slot;
997 $all->{$vol}->{volbytes} = Bweb::human_size($all->{$vol}->{volbytes}) ;
999 push @{ $param }, $all->{$vol};
1001 } else { # empty or no label
1002 push @{ $param }, {realslot => $slot,
1003 volstatus => 'Unknow',
1004 volumename => $self->{slot}->[$slot]} ;
1007 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
1011 my $i=0; my $drives = [] ;
1012 foreach my $d (@{ $self->{drive} }) {
1013 $drives->[$i] = { index => $i,
1014 load => $self->{drive}->[$i],
1015 name => $self->{drive_name}->[$i],
1020 $bweb->display({ Name => $self->{name},
1021 nb_drive => $self->{info}->{drive},
1022 nb_io => $self->{info}->{io},
1025 Update => scalar(@to_update) },
1033 ################################################################
1037 use base q/Bweb::Gui/;
1041 Bweb - main Bweb package
1045 this package is use to compute and display informations
1050 use POSIX qw/strftime/;
1052 our $bpath="/usr/local/bacula";
1053 our $bconsole="$bpath/sbin/bconsole -c $bpath/etc/bconsole.conf";
1059 %sql_func - hash to make query mysql/postgresql compliant
1065 UNIX_TIMESTAMP => '',
1066 FROM_UNIXTIME => '',
1067 TO_SEC => " interval '1 second' * ",
1068 SEC_TO_INT => "SEC_TO_INT",
1072 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1073 FROM_UNIXTIME => 'FROM_UNIXTIME',
1076 SEC_TO_TIME => 'SEC_TO_TIME',
1080 sub dbh_selectall_arrayref
1082 my ($self, $query) = @_;
1083 $self->connect_db();
1084 $self->debug($query);
1085 return $self->{dbh}->selectall_arrayref($query);
1090 my ($self, @what) = @_;
1091 return join(',', $self->dbh_quote(@what)) ;
1096 my ($self, @what) = @_;
1098 $self->connect_db();
1100 return map { $self->{dbh}->quote($_) } @what;
1102 return $self->{dbh}->quote($what[0]) ;
1108 my ($self, $query) = @_ ;
1109 $self->connect_db();
1110 $self->debug($query);
1111 return $self->{dbh}->do($query);
1114 sub dbh_selectall_hashref
1116 my ($self, $query, $join) = @_;
1118 $self->connect_db();
1119 $self->debug($query);
1120 return $self->{dbh}->selectall_hashref($query, $join) ;
1123 sub dbh_selectrow_hashref
1125 my ($self, $query) = @_;
1127 $self->connect_db();
1128 $self->debug($query);
1129 return $self->{dbh}->selectrow_hashref($query) ;
1135 my @unit = qw(b Kb Mb Gb Tb);
1136 my $val = shift || 0;
1138 my $format = '%i %s';
1139 while ($val / 1024 > 1) {
1143 $format = ($i>0)?'%0.1f %s':'%i %s';
1144 return sprintf($format, $val, $unit[$i]);
1147 # display Day, Hour, Year
1153 $val /= 60; # sec -> min
1155 if ($val / 60 <= 1) {
1159 $val /= 60; # min -> hour
1160 if ($val / 24 <= 1) {
1161 return "$val hours";
1164 $val /= 24; # hour -> day
1165 if ($val / 365 < 2) {
1169 $val /= 365 ; # day -> year
1171 return "$val years";
1174 # get Day, Hour, Year
1180 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1184 my %times = ( m => 60,
1190 my $mult = $times{$2} || 0;
1200 unless ($self->{dbh}) {
1201 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1202 $self->{info}->{user},
1203 $self->{info}->{password});
1205 print "Can't connect to your database, see error log\n"
1206 unless ($self->{dbh});
1208 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1214 my ($class, %arg) = @_;
1216 dbh => undef, # connect_db();
1218 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1224 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1226 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1227 $self->{sql} = $sql_func{$1};
1230 $self->{debug} = $self->{info}->{debug};
1231 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1239 $self->display($self->{info}, "begin.tpl");
1245 $self->display($self->{info}, "end.tpl");
1253 SELECT Name AS name,
1255 AutoPrune AS autoprune,
1256 FileRetention AS fileretention,
1257 JobRetention AS jobretention
1262 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1264 foreach (values %$all) {
1265 $_->{fileretention} = human_sec($_->{fileretention});
1266 $_->{jobretention} = human_sec($_->{jobretention});
1269 my $arg = { ID => $cur_id++,
1270 clients => [ values %$all] };
1272 $self->display($arg, "client_list.tpl") ;
1277 my ($self, %arg) = @_;
1284 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1286 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1288 $self->{sql}->{TO_SEC}($arg{age})
1291 $label = "last " . human_sec($arg{age});
1295 $limit .= " ORDER BY $arg{order} ";
1299 $limit .= " LIMIT $arg{limit} ";
1300 $label .= " limited to $arg{limit}";
1304 $limit .= " OFFSET $arg{offset} ";
1305 $label .= " with $arg{offset} offset ";
1309 $label = 'no filter';
1312 return ($limit, $label);
1317 $bweb->get_form(...) - Get useful stuff
1321 This function get and check parameters against regexp.
1323 If word begin with 'q', the return will be quoted or join quoted
1324 if it's end with 's'.
1329 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1332 qclient => 'plume-fd',
1333 qpools => "'plume-fd', 'test-fd', '...'",
1340 my ($self, @what) = @_;
1341 my %what = map { $_ => 1 } @what;
1358 my %opt_s = ( # default to ''
1368 my %opt_p = ( # option with path
1374 foreach my $i (@what) {
1375 if (exists $opt_i{$i}) {# integer param
1376 my $value = CGI::param($i) || $opt_i{$i} ;
1377 if ($value =~ /^(\d+)$/) {
1380 } elsif ($opt_s{$i}) { # simple string param
1381 my $value = CGI::param($i) || '';
1382 if ($value =~ /^([\w\d\.-]+)$/) {
1385 } elsif ($i =~ /^j(\w+)s$/) { # quote join args
1386 my @value = CGI::param($1) ;
1388 $ret{$i} = $self->dbh_join(@value) ;
1391 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1392 my $value = CGI::param($1) ;
1394 $ret{$i} = $self->dbh_quote($value);
1397 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1398 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1400 } elsif (exists $opt_p{$i}) {
1401 my $value = CGI::param($i) || '';
1402 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1409 foreach my $s (CGI::param('slot')) {
1410 if ($s =~ /^(\d+)$/) {
1411 push @{$ret{slots}}, $s;
1416 if ($what{db_clients}) {
1418 SELECT Client.Name as clientname
1422 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1423 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1427 if ($what{db_mediatypes}) {
1429 SELECT MediaType as mediatype
1433 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1434 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1438 if ($what{db_locations}) {
1440 SELECT Location as location, Cost as cost FROM Location
1442 my $loc = $self->dbh_selectall_hashref($query, 'location');
1443 $ret{db_locations} = [ sort { $a->{location}
1449 if ($what{db_pools}) {
1450 my $query = "SELECT Name as name FROM Pool";
1452 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1453 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1456 if ($what{db_filesets}) {
1458 SELECT FileSet.FileSet AS fileset
1462 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1464 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1465 values %$filesets] ;
1469 if ($what{db_jobnames}) {
1471 SELECT DISTINCT Job.Name AS jobname
1475 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1477 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1478 values %$jobnames] ;
1482 if ($what{db_devices}) {
1484 SELECT Device.Name AS name
1488 my $devices = $self->dbh_selectall_hashref($query, 'name');
1490 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1502 my $fields = $self->get_form(qw/age level status clients filesets
1503 db_clients limit db_filesets width height
1504 qclients qfilesets qjobnames db_jobnames/);
1507 my $url = CGI::url(-full => 0,
1510 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1512 my $type = CGI::param('graph') || '';
1513 if ($type =~ /^(\w+)$/) {
1514 $fields->{graph} = $1;
1517 my $gtype = CGI::param('gtype') || '';
1518 if ($gtype =~ /^(\w+)$/) {
1519 $fields->{gtype} = $1;
1522 # this organisation is to keep user choice between 2 click
1523 # TODO : fileset and client selection doesn't work
1532 sub display_client_job
1534 my ($self, %arg) = @_ ;
1536 $arg{order} = ' Job.JobId DESC ';
1537 my ($limit, $label) = $self->get_limit(%arg);
1539 my $clientname = $self->dbh_quote($arg{clientname});
1542 SELECT DISTINCT Job.JobId AS jobid,
1543 Job.Name AS jobname,
1544 FileSet.FileSet AS fileset,
1546 StartTime AS starttime,
1547 JobFiles AS jobfiles,
1548 JobBytes AS jobbytes,
1549 JobStatus AS jobstatus,
1550 JobErrors AS joberrors
1552 FROM Client,Job,FileSet
1553 WHERE Client.Name=$clientname
1554 AND Client.ClientId=Job.ClientId
1555 AND Job.FileSetId=FileSet.FileSetId
1559 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1561 foreach (values %$all) {
1562 $_->{jobbytes} = human_size($_->{jobbytes}) ;
1565 $self->display({ clientname => $arg{clientname},
1568 Jobs => [ values %$all ],
1570 "display_client_job.tpl") ;
1573 sub get_selected_media_location
1577 my $medias = $self->get_form('jmedias');
1579 unless ($medias->{jmedias}) {
1584 SELECT Media.VolumeName AS volumename, Location.Location AS location
1585 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1586 WHERE Media.VolumeName IN ($medias->{jmedias})
1589 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1591 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1602 my $medias = $self->get_selected_media_location();
1608 my $elt = $self->get_form('db_locations');
1610 $self->display({ ID => $cur_id++,
1611 %$elt, # db_locations
1613 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1623 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1625 $self->display($elt, "help_extern.tpl");
1628 sub help_extern_compute
1632 my $number = CGI::param('limit') || '' ;
1633 unless ($number =~ /^(\d+)$/) {
1634 return $self->error("Bad arg number : $number ");
1637 my ($sql, undef) = $self->get_param('pools',
1638 'locations', 'mediatypes');
1641 SELECT Media.VolumeName AS volumename,
1642 Media.VolStatus AS volstatus,
1643 Media.LastWritten AS lastwritten,
1644 Media.MediaType AS mediatype,
1645 Media.VolMounts AS volmounts,
1647 Media.Recycle AS recycle,
1648 $self->{sql}->{FROM_UNIXTIME}(
1649 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1650 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1653 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1654 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1656 WHERE Media.InChanger = 1
1657 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1659 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1663 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1665 $self->display({ Medias => [ values %$all ] },
1666 "help_extern_compute.tpl");
1673 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1674 $self->display($param, "help_intern.tpl");
1677 sub help_intern_compute
1681 my $number = CGI::param('limit') || '' ;
1682 unless ($number =~ /^(\d+)$/) {
1683 return $self->error("Bad arg number : $number ");
1686 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1688 if (CGI::param('expired')) {
1690 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1691 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1697 SELECT Media.VolumeName AS volumename,
1698 Media.VolStatus AS volstatus,
1699 Media.LastWritten AS lastwritten,
1700 Media.MediaType AS mediatype,
1701 Media.VolMounts AS volmounts,
1703 $self->{sql}->{FROM_UNIXTIME}(
1704 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1705 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1708 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1709 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1711 WHERE Media.InChanger <> 1
1712 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1713 AND Media.Recycle = 1
1715 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1719 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1721 $self->display({ Medias => [ values %$all ] },
1722 "help_intern_compute.tpl");
1728 my ($self, %arg) = @_ ;
1730 my ($limit, $label) = $self->get_limit(%arg);
1734 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1735 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1736 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1737 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1738 (SELECT count(Job.JobId)
1740 WHERE Job.JobStatus IN ('E','e','f','A')
1743 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1746 my $row = $self->dbh_selectrow_hashref($query) ;
1748 $row->{nb_bytes} = human_size($row->{nb_bytes});
1750 $row->{db_size} = '???';
1751 $row->{label} = $label;
1753 $self->display($row, "general.tpl");
1758 my ($self, @what) = @_ ;
1759 my %elt = map { $_ => 1 } @what;
1764 if ($elt{clients}) {
1765 my @clients = CGI::param('client');
1767 $ret{clients} = \@clients;
1768 my $str = $self->dbh_join(@clients);
1769 $limit .= "AND Client.Name IN ($str) ";
1773 if ($elt{filesets}) {
1774 my @filesets = CGI::param('fileset');
1776 $ret{filesets} = \@filesets;
1777 my $str = $self->dbh_join(@filesets);
1778 $limit .= "AND FileSet.FileSet IN ($str) ";
1782 if ($elt{mediatypes}) {
1783 my @medias = CGI::param('mediatype');
1785 $ret{mediatypes} = \@medias;
1786 my $str = $self->dbh_join(@medias);
1787 $limit .= "AND Media.MediaType IN ($str) ";
1792 my $client = CGI::param('client');
1793 $ret{client} = $client;
1794 $client = $self->dbh_join($client);
1795 $limit .= "AND Client.Name = $client ";
1799 my $level = CGI::param('level') || '';
1800 if ($level =~ /^(\w)$/) {
1802 $limit .= "AND Job.Level = '$1' ";
1807 my $jobid = CGI::param('jobid') || '';
1809 if ($jobid =~ /^(\d+)$/) {
1811 $limit .= "AND Job.JobId = '$1' ";
1816 my $status = CGI::param('status') || '';
1817 if ($status =~ /^(\w)$/) {
1819 $limit .= "AND Job.JobStatus = '$1' ";
1823 if ($elt{locations}) {
1824 my @location = CGI::param('location') ;
1826 $ret{locations} = \@location;
1827 my $str = $self->dbh_join(@location);
1828 $limit .= "AND Location.Location IN ($str) ";
1833 my @pool = CGI::param('pool') ;
1835 $ret{pools} = \@pool;
1836 my $str = $self->dbh_join(@pool);
1837 $limit .= "AND Pool.Name IN ($str) ";
1841 if ($elt{location}) {
1842 my $location = CGI::param('location') || '';
1844 $ret{location} = $location;
1845 $location = $self->dbh_quote($location);
1846 $limit .= "AND Location.Location = $location ";
1851 my $pool = CGI::param('pool') || '';
1854 $pool = $self->dbh_quote($pool);
1855 $limit .= "AND Pool.Name = $pool ";
1859 if ($elt{jobtype}) {
1860 my $jobtype = CGI::param('jobtype') || '';
1861 if ($jobtype =~ /^(\w)$/) {
1863 $limit .= "AND Job.Type = '$1' ";
1867 return ($limit, %ret);
1874 SELECT DISTINCT Job.JobId AS jobid,
1875 Client.Name AS client,
1876 FileSet.FileSet AS fileset,
1877 Job.Name AS jobname,
1879 StartTime AS starttime,
1880 JobFiles AS jobfiles,
1881 JobBytes AS jobbytes,
1882 VolumeName AS volumename,
1883 JobStatus AS jobstatus,
1884 JobErrors AS joberrors
1886 FROM Client,Job,JobMedia,Media,FileSet
1887 WHERE Client.ClientId=Job.ClientId
1888 AND Job.FileSetId=FileSet.FileSetId
1889 AND JobMedia.JobId=Job.JobId
1890 AND JobMedia.MediaId=Media.MediaId
1897 my ($self, %arg) = @_ ;
1899 $arg{order} = ' Job.JobId DESC ';
1901 my ($limit, $label) = $self->get_limit(%arg);
1902 my ($where, undef) = $self->get_param('clients',
1910 SELECT Job.JobId AS jobid,
1911 Client.Name AS client,
1912 FileSet.FileSet AS fileset,
1913 Job.Name AS jobname,
1915 StartTime AS starttime,
1916 Pool.Name AS poolname,
1917 JobFiles AS jobfiles,
1918 JobBytes AS jobbytes,
1919 JobStatus AS jobstatus,
1920 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1921 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
1924 JobErrors AS joberrors
1927 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
1928 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
1929 WHERE Client.ClientId=Job.ClientId
1934 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1936 foreach (values %$all) {
1937 $_->{jobbytes} = human_size($_->{jobbytes}) ;
1940 $self->display({ Filter => $label,
1944 sort { $a->{jobid} <=> $b->{jobid} }
1951 # display job informations
1952 sub display_job_zoom
1954 my ($self, $jobid) = @_ ;
1956 $jobid = $self->dbh_quote($jobid);
1959 SELECT DISTINCT Job.JobId AS jobid,
1960 Client.Name AS client,
1961 Job.Name AS jobname,
1962 FileSet.FileSet AS fileset,
1964 Pool.Name AS poolname,
1965 StartTime AS starttime,
1966 JobFiles AS jobfiles,
1967 JobBytes AS jobbytes,
1968 JobStatus AS jobstatus,
1969 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1970 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
1973 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
1974 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
1975 WHERE Client.ClientId=Job.ClientId
1976 AND Job.JobId = $jobid
1979 my $row = $self->dbh_selectrow_hashref($query) ;
1981 $row->{jobbytes} = human_size($row->{jobbytes}) ;
1983 # display all volumes associate with this job
1985 SELECT Media.VolumeName as volumename
1986 FROM Job,Media,JobMedia
1987 WHERE Job.JobId = $jobid
1988 AND JobMedia.JobId=Job.JobId
1989 AND JobMedia.MediaId=Media.MediaId
1992 my $all = $self->dbh_selectall_hashref($query, 'volumename');
1994 $row->{volumes} = [ values %$all ] ;
1996 $self->display($row, "display_job_zoom.tpl");
2003 my ($where, %elt) = $self->get_param('pool',
2006 my $arg = $self->get_form('jmedias');
2008 if ($arg->{jmedias}) {
2009 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
2013 SELECT Media.VolumeName AS volumename,
2014 Media.VolBytes AS volbytes,
2015 Media.VolStatus AS volstatus,
2016 Media.MediaType AS mediatype,
2017 Media.InChanger AS online,
2018 Media.LastWritten AS lastwritten,
2019 Location.Location AS location,
2020 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
2021 Pool.Name AS poolname,
2022 $self->{sql}->{FROM_UNIXTIME}(
2023 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2024 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2027 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2028 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2029 Media.MediaType AS MediaType
2031 WHERE Media.VolStatus = 'Full'
2032 GROUP BY Media.MediaType
2033 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2035 WHERE Media.PoolId=Pool.PoolId
2039 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2040 foreach (values %$all) {
2041 $_->{volbytes} = human_size($_->{volbytes}) ;
2044 $self->display({ ID => $cur_id++,
2046 Location => $elt{location},
2047 Medias => [ values %$all ]
2049 "display_media.tpl");
2056 my $pool = $self->get_form('db_pools');
2058 foreach my $name (@{ $pool->{db_pools} }) {
2059 CGI::param('pool', $name->{name});
2060 $self->display_media();
2064 sub display_media_zoom
2068 my $medias = $self->get_form('jmedias');
2070 unless ($medias->{jmedias}) {
2071 return $self->error("Can't get media selection");
2075 SELECT InChanger AS online,
2076 VolBytes AS nb_bytes,
2077 VolumeName AS volumename,
2078 VolStatus AS volstatus,
2079 VolMounts AS nb_mounts,
2080 Media.VolUseDuration AS voluseduration,
2081 Media.MaxVolJobs AS maxvoljobs,
2082 Media.MaxVolFiles AS maxvolfiles,
2083 Media.MaxVolBytes AS maxvolbytes,
2084 VolErrors AS nb_errors,
2085 Pool.Name AS poolname,
2086 Location.Location AS location,
2087 Media.Recycle AS recycle,
2088 Media.VolRetention AS volretention,
2089 Media.LastWritten AS lastwritten,
2090 Media.VolReadTime/1000000 AS volreadtime,
2091 Media.VolWriteTime/1000000 AS volwritetime,
2092 $self->{sql}->{FROM_UNIXTIME}(
2093 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2094 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2097 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2098 WHERE Pool.PoolId = Media.PoolId
2099 AND VolumeName IN ($medias->{jmedias})
2102 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2104 foreach my $media (values %$all) {
2105 $media->{nb_bytes} = human_size($media->{nb_bytes}) ;
2106 $media->{voluseduration} = human_sec($media->{voluseduration});
2107 $media->{volretention} = human_sec($media->{volretention});
2108 $media->{volreadtime} = human_sec($media->{volreadtime});
2109 $media->{volwritetime} = human_sec($media->{volwritetime});
2110 my $mq = $self->dbh_quote($media->{volumename});
2113 SELECT DISTINCT Job.JobId AS jobid,
2115 Job.StartTime AS starttime,
2118 Job.JobFiles AS files,
2119 Job.JobBytes AS bytes,
2120 Job.jobstatus AS status
2121 FROM Media,JobMedia,Job
2122 WHERE Media.VolumeName=$mq
2123 AND Media.MediaId=JobMedia.MediaId
2124 AND JobMedia.JobId=Job.JobId
2127 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2129 foreach (values %$jobs) {
2130 $_->{bytes} = human_size($_->{bytes}) ;
2133 $self->display({ jobs => [ values %$jobs ],
2135 "display_media_zoom.tpl");
2143 my $loc = $self->get_form('qlocation');
2144 unless ($loc->{qlocation}) {
2145 return $self->error("Can't get location");
2149 SELECT Location.Location AS location,
2150 Location.Cost AS cost,
2151 Location.Enabled AS enabled
2153 WHERE Location.Location = $loc->{qlocation}
2156 my $row = $self->dbh_selectrow_hashref($query);
2158 $self->display({ ID => $cur_id++,
2159 %$row }, "location_edit.tpl") ;
2167 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2168 unless ($arg->{qlocation}) {
2169 return $self->error("Can't get location");
2171 unless ($arg->{qnewlocation}) {
2172 return $self->error("Can't get new location name");
2174 unless ($arg->{cost}) {
2175 return $self->error("Can't get new cost");
2178 my $enabled = CGI::param('enabled') || '';
2179 $enabled = $enabled?1:0;
2182 UPDATE Location SET Cost = $arg->{cost},
2183 Location = $arg->{qnewlocation},
2185 WHERE Location.Location = $arg->{qlocation}
2188 $self->dbh_do($query);
2190 $self->display_location();
2196 my $arg = $self->get_form(qw/qlocation cost/) ;
2198 unless ($arg->{qlocation}) {
2199 $self->display({}, "location_add.tpl");
2202 unless ($arg->{cost}) {
2203 return $self->error("Can't get new cost");
2206 my $enabled = CGI::param('enabled') || '';
2207 $enabled = $enabled?1:0;
2210 INSERT INTO Location (Location, Cost, Enabled)
2211 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2214 $self->dbh_do($query);
2216 $self->display_location();
2219 sub display_location
2224 SELECT Location.Location AS location,
2225 Location.Cost AS cost,
2226 Location.Enabled AS enabled,
2227 (SELECT count(Media.MediaId)
2229 WHERE Media.LocationId = Location.LocationId
2234 my $location = $self->dbh_selectall_hashref($query, 'location');
2236 $self->display({ ID => $cur_id++,
2237 Locations => [ values %$location ] },
2238 "display_location.tpl");
2245 my $medias = $self->get_selected_media_location();
2250 my $arg = $self->get_form('db_locations', 'qnewlocation');
2252 $self->display({ email => $self->{info}->{email_media},
2254 medias => [ values %$medias ],
2256 "update_location.tpl");
2259 sub get_media_max_size
2261 my ($self, $type) = @_;
2263 "SELECT avg(VolBytes) AS size
2265 WHERE Media.VolStatus = 'Full'
2266 AND Media.MediaType = '$type'
2269 my $res = $self->selectrow_hashref($query);
2272 return $res->{size};
2282 my $media = CGI::param('media');
2284 return $self->error("Can't find media selection");
2287 $media = $self->dbh_quote($media);
2291 my $volstatus = CGI::param('volstatus') || '';
2292 $volstatus = $self->dbh_quote($volstatus); # is checked by db
2293 $update .= " VolStatus=$volstatus, ";
2295 my $inchanger = CGI::param('inchanger') || '';
2297 $update .= " InChanger=1, " ;
2298 my $slot = CGI::param('slot') || '';
2299 if ($slot =~ /^(\d+)$/) {
2300 $update .= " Slot=$1, ";
2302 $update .= " Slot=0, ";
2305 $update = " Slot=0, InChanger=0, ";
2308 my $pool = CGI::param('pool') || '';
2309 $pool = $self->dbh_quote($pool); # is checked by db
2310 $update .= " PoolId=(SELECT PoolId FROM Pool WHERE Name=$pool), ";
2312 my $volretention = CGI::param('volretention') || '';
2313 $volretention = from_human_sec($volretention);
2314 unless ($volretention) {
2315 return $self->error("Can't get volume retention");
2318 $update .= " VolRetention = $volretention, ";
2320 my $loc = CGI::param('location') || '';
2321 $loc = $self->dbh_quote($loc); # is checked by db
2322 $update .= " LocationId=(SELECT LocationId FROM Location WHERE Location=$loc), ";
2324 my $usedu = CGI::param('voluseduration') || '0';
2325 $usedu = from_human_sec($usedu);
2326 $update .= " VolUseDuration=$usedu, ";
2328 my $maxj = CGI::param('maxvoljobs') || '0';
2329 unless ($maxj =~ /^(\d+)$/) {
2330 return $self->error("Can't get max jobs");
2332 $update .= " MaxVolJobs=$1, " ;
2334 my $maxf = CGI::param('maxvolfiles') || '0';
2335 unless ($maxj =~ /^(\d+)$/) {
2336 return $self->error("Can't get max files");
2338 $update .= " MaxVolFiles=$1, " ;
2340 my $maxb = CGI::param('maxvolbytes') || '0';
2341 unless ($maxb =~ /^(\d+)$/) {
2342 return $self->error("Can't get max bytes");
2344 $update .= " MaxVolBytes=$1 " ;
2346 my $row=$self->dbh_do("UPDATE Media SET $update WHERE VolumeName=$media");
2349 print "Update Ok\n";
2350 $self->update_media();
2358 my $media = $self->get_form('qmedia');
2360 unless ($media->{qmedia}) {
2361 return $self->error("Can't get media");
2365 SELECT Media.Slot AS slot,
2366 Pool.Name AS poolname,
2367 Media.VolStatus AS volstatus,
2368 Media.InChanger AS inchanger,
2369 Location.Location AS location,
2370 Media.VolumeName AS volumename,
2371 Media.MaxVolBytes AS maxvolbytes,
2372 Media.MaxVolJobs AS maxvoljobs,
2373 Media.MaxVolFiles AS maxvolfiles,
2374 Media.VolUseDuration AS voluseduration,
2375 Media.VolRetention AS volretention
2377 FROM Media INNER JOIN Pool ON (Media.PoolId = Pool.PoolId)
2378 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2380 WHERE Media.VolumeName = $media->{qmedia}
2383 my $row = $self->dbh_selectrow_hashref($query);
2384 $row->{volretention} = human_sec($row->{volretention});
2385 $row->{voluseduration} = human_sec($row->{voluseduration});
2387 my $elt = $self->get_form(qw/db_pools db_locations/);
2393 "update_media.tpl");
2400 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2402 unless ($arg->{jmedias}) {
2403 return $self->error("Can't get selected media");
2406 unless ($arg->{qnewlocation}) {
2407 return $self->error("Can't get new location");
2412 SET LocationId = (SELECT LocationId
2414 WHERE Location = $arg->{qnewlocation})
2415 WHERE Media.VolumeName IN ($arg->{jmedias})
2418 my $nb = $self->dbh_do($query);
2420 print "$nb media updated";
2427 my $medias = $self->get_selected_media_location();
2429 return $self->error("Can't get media selection");
2431 my $newloc = CGI::param('newlocation');
2433 my $user = CGI::param('user') || 'unknow';
2434 my $comm = CGI::param('comment') || '';
2435 $comm = $self->dbh_quote("$user: $comm");
2439 foreach my $media (keys %$medias) {
2441 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2443 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2444 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2445 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2449 $self->debug($query);
2453 $q->param('action', 'update_location');
2454 my $url = $q->url(-full => 1, -query=>1);
2456 $self->display({ email => $self->{info}->{email_media},
2458 newlocation => $newloc,
2459 # [ { volumename => 'vol1' }, { volumename => 'vol2' },..]
2460 medias => [ values %$medias ],
2462 "change_location.tpl");
2466 sub display_client_stats
2468 my ($self, %arg) = @_ ;
2470 my $client = $self->dbh_quote($arg{clientname});
2471 my ($limit, $label) = $self->get_limit(%arg);
2475 count(Job.JobId) AS nb_jobs,
2476 sum(Job.JobBytes) AS nb_bytes,
2477 sum(Job.JobErrors) AS nb_err,
2478 sum(Job.JobFiles) AS nb_files,
2479 Client.Name AS clientname
2480 FROM Job INNER JOIN Client USING (ClientId)
2482 Client.Name = $client
2484 GROUP BY Client.Name
2487 my $row = $self->dbh_selectrow_hashref($query);
2489 $row->{ID} = $cur_id++;
2490 $row->{label} = $label;
2491 $row->{nb_bytes} = human_size($row->{nb_bytes}) ;
2493 $self->display($row, "display_client_stats.tpl");
2496 # poolname can be undef
2499 my ($self, $poolname) = @_ ;
2501 # TODO : afficher les tailles et les dates
2504 SELECT sum(subq.volmax) AS volmax,
2505 sum(subq.volnum) AS volnum,
2506 sum(subq.voltotal) AS voltotal,
2508 Pool.Recycle AS recycle,
2509 Pool.VolRetention AS volretention,
2510 Pool.VolUseDuration AS voluseduration,
2511 Pool.MaxVolJobs AS maxvoljobs,
2512 Pool.MaxVolFiles AS maxvolfiles,
2513 Pool.MaxVolBytes AS maxvolbytes,
2514 subq.PoolId AS PoolId
2517 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2518 count(Media.MediaId) AS volnum,
2519 sum(Media.VolBytes) AS voltotal,
2520 Media.PoolId AS PoolId,
2521 Media.MediaType AS MediaType
2523 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2524 Media.MediaType AS MediaType
2526 WHERE Media.VolStatus = 'Full'
2527 GROUP BY Media.MediaType
2528 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2529 GROUP BY Media.MediaType, Media.PoolId
2531 INNER JOIN Pool ON (Pool.PoolId = subq.PoolId)
2532 GROUP BY subq.PoolId
2535 my $all = $self->dbh_selectall_hashref($query, 'name') ;
2537 foreach my $p (values %$all) {
2538 $p->{maxvolbytes} = human_size($p->{maxvolbytes}) ;
2539 $p->{volretention} = human_sec($p->{volretention}) ;
2540 $p->{voluseduration} = human_sec($p->{voluseduration}) ;
2543 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2545 $p->{poolusage} = 0;
2549 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2551 WHERE PoolId=$p->{poolid}
2554 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2555 foreach my $t (values %$content) {
2556 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2561 $self->display({ ID => $cur_id++,
2562 Pools => [ values %$all ]},
2563 "display_pool.tpl");
2566 sub display_running_job
2570 my $arg = $self->get_form('client', 'jobid');
2572 if (!$arg->{client} and $arg->{jobid}) {
2575 SELECT Client.Name AS name
2576 FROM Job INNER JOIN Client USING (ClientId)
2577 WHERE Job.JobId = $arg->{jobid}
2580 my $row = $self->dbh_selectrow_hashref($query);
2583 $arg->{client} = $row->{name};
2584 CGI::param('client', $arg->{client});
2588 if ($arg->{client}) {
2589 my $cli = new Bweb::Client(name => $arg->{client});
2590 $cli->display_running_job($self->{info}, $arg->{jobid});
2591 if ($arg->{jobid}) {
2592 $self->get_job_log();
2595 $self->error("Can't get client or jobid");
2599 sub display_running_jobs
2601 my ($self, $display_action) = @_;
2604 SELECT Job.JobId AS jobid,
2605 Job.Name AS jobname,
2607 Job.StartTime AS starttime,
2608 Job.JobFiles AS jobfiles,
2609 Job.JobBytes AS jobbytes,
2610 Job.JobStatus AS jobstatus,
2611 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2612 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2614 Client.Name AS clientname
2615 FROM Job INNER JOIN Client USING (ClientId)
2616 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2618 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2620 $self->display({ ID => $cur_id++,
2621 display_action => $display_action,
2622 Jobs => [ values %$all ]},
2623 "running_job.tpl") ;
2629 my $arg = $self->get_form('jmedias', 'slots', 'ach');
2631 unless ($arg->{jmedias}) {
2632 return $self->error("Can't get media selection");
2635 my $a = Bweb::Autochanger::get($arg->{ach}, $self);
2641 SELECT Media.VolumeName AS volumename,
2642 Storage.Name AS storage,
2643 Location.Location AS location,
2645 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2646 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2647 WHERE Media.VolumeName IN ($arg->{jmedias})
2648 AND Media.InChanger = 1
2651 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2655 foreach my $vol (values %$all) {
2656 print "eject $vol->{volumename} from $vol->{storage} : ";
2657 if ($a->send_to_io($vol->{slot})) {
2669 my $arg = $self->get_form('jobid', 'client');
2671 print CGI::header('text/brestore');
2672 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
2673 print "client=$arg->{client}\n" if ($arg->{client});
2674 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
2678 # TODO : move this to Bweb::Autochanger ?
2679 # TODO : make this internal to not eject tape ?
2685 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
2687 my $b = new Bconsole(pref => $self->{info});
2688 my @storages = $b->list_storage() ;
2690 unless ($arg->{ach}) {
2691 $arg->{devices} = [ map { { name => $_ } } @storages ];
2692 return $self->display($arg, "ach_add.tpl");
2696 foreach my $drive (CGI::param('drive'))
2698 unless (grep(/^$drive$/,@storages)) {
2699 return $self->error("Can't find $drive in storage list");
2702 my $index = CGI::param("index_$drive");
2703 unless (defined $index and $index =~ /^(\d+)$/) {
2704 return $self->error("Can't get $drive index");
2707 $drives[$index] = $drive;
2711 return $self->error("Can't get drives from Autochanger");
2714 my $a = new Bweb::Autochanger(name => $arg->{ach},
2715 precmd => $arg->{precmd},
2716 drive_name => \@drives,
2717 device => $arg->{device},
2718 mtxcmd => $arg->{mtxcmd});
2720 return Bweb::Autochanger::register($a, $self) ;
2726 my $arg = $self->get_form('jobid');
2728 my $b = new Bconsole(pref => $self->{info});
2730 if ($arg->{jobid}) {
2731 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
2733 content => $b->send_cmd("delete jobid=\"$arg->{jobid}\""),
2734 title => "Delete a job ",
2735 name => "delete jobid=$arg->{jobid}",
2744 my $ach = CGI::param('ach') ;
2745 unless ($ach =~ /^([\w\d\.-]+)$/) {
2746 return $self->error("Bad autochanger name");
2749 my $b = new Bconsole(pref => $self->{info});
2750 print "<pre>" . $b->update_slots($ach) . "</pre>";
2757 my $arg = $self->get_form('jobid');
2758 unless ($arg->{jobid}) {
2759 return $self->error("Can't get jobid");
2762 my $t = CGI::param('time') || '';
2765 SELECT Job.Name as name, Client.Name as clientname
2766 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
2767 WHERE JobId = $arg->{jobid}
2770 my $row = $self->dbh_selectrow_hashref($query);
2773 return $self->error("Can't find $arg->{jobid} in catalog");
2778 SELECT Time AS time, LogText AS log
2780 WHERE JobId = $arg->{jobid}
2783 my $log = $self->dbh_selectall_arrayref($query);
2785 return $self->error("Can't get log for jobid $arg->{jobid}");
2791 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
2793 $logtxt = join("", map { $_->[1] } @$log ) ;
2796 $self->display({ lines=> $logtxt,
2797 jobid => $arg->{jobid},
2798 name => $row->{name},
2799 client => $row->{clientname},
2800 }, 'display_log.tpl');
2808 my $arg = $self->get_form('ach', 'slots', 'drive');
2810 unless ($arg->{ach}) {
2811 return $self->error("Can't find autochanger name");
2816 if ($arg->{slots}) {
2817 $slots = join(",", @{ $arg->{slots} });
2818 $t += 60*scalar( @{ $arg->{slots} }) ;
2821 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
2822 print "<h1>This command can take long time, be patient...</h1>";
2824 $b->label_barcodes(storage => $arg->{ach},
2825 drive => $arg->{drive},
2835 my @volume = CGI::param('media');
2837 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
2840 content => $b->purge_volume(@volume),
2841 title => "Purge media",
2842 name => "purge volume=" . join(' volume=', @volume),
2850 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
2852 my @volume = CGI::param('media');
2854 content => $b->prune_volume(@volume),
2855 title => "Prune media",
2856 name => "prune volume=" . join(' volume=', @volume),
2864 my $arg = $self->get_form('jobid');
2865 unless ($arg->{jobid}) {
2866 return $self->error('Bad jobid');
2869 my $b = new Bconsole(pref => $self->{info});
2871 content => $b->cancel($arg->{jobid}),
2872 title => "Cancel job",
2873 name => "cancel jobid=$arg->{jobid}",
2877 sub director_show_sched
2881 my $arg = $self->get_form('days');
2883 my $b = new Bconsole(pref => $self->{info}) ;
2885 my $ret = $b->director_get_sched( $arg->{days} );
2890 }, "scheduled_job.tpl");
2893 sub enable_disable_job
2895 my ($self, $what) = @_ ;
2897 my $name = CGI::param('job') || '';
2898 unless ($name =~ /^[\w\d\.\-\s]+$/) {
2899 return $self->error("Can't find job name");
2902 my $b = new Bconsole(pref => $self->{info}) ;
2912 content => $b->send_cmd("$cmd job=\"$name\""),
2913 title => "$cmd $name",
2914 name => "$cmd job=\"$name\"",
2921 $b = new Bconsole(pref => $self->{info});
2923 my $joblist = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".job")) ];
2925 $self->display({ Jobs => $joblist }, "run_job.tpl");
2930 my ($self, $ouput) = @_;
2933 foreach my $l (split(/\r\n/, $ouput)) {
2934 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
2940 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
2946 foreach my $k (keys %arg) {
2947 $lowcase{lc($k)} = $arg{$k} ;
2956 $b = new Bconsole(pref => $self->{info});
2958 my $job = CGI::param('job') || '';
2960 my $info = $b->send_cmd("show job=\"$job\"");
2961 my $attr = $self->run_parse_job($info);
2963 my $jobs = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
2965 my $pools = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".pool")) ];
2966 my $clients = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".client")) ];
2967 my $filesets= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".fileset")) ];
2968 my $storages= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".storage")) ];
2973 clients => $clients,
2974 filesets => $filesets,
2975 storages => $storages,
2977 }, "run_job_mod.tpl");
2983 $b = new Bconsole(pref => $self->{info});
2985 my $jobs = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
2995 $b = new Bconsole(pref => $self->{info});
2997 # TODO: check input (don't use pool, level)
2999 my $arg = $self->get_form('pool', 'level', 'client', 'priority');
3000 my $job = CGI::param('job') || '';
3001 my $storage = CGI::param('storage') || '';
3003 my $jobid = $b->run(job => $job,
3004 client => $arg->{client},
3005 priority => $arg->{priority},
3006 level => $arg->{level},
3007 storage => $storage,
3008 pool => $arg->{pool},
3011 print $jobid, $b->{error};
3013 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";