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!^(.+)?$!,
210 load - load config_file
214 this function load the specified config_file.
222 unless (open(FP, $self->{config_file}))
224 return $self->error("$self->{config_file} : $!");
226 my $f=''; my $tmpbuffer;
227 while(read FP,$tmpbuffer,4096)
235 no strict; # I have no idea of the contents of the file
242 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...") ;
245 foreach my $k (keys %$VAR1) {
246 $self->{$k} = $VAR1->{$k};
254 load_old - load old configuration format
262 unless (open(FP, $self->{config_file}))
264 return $self->error("$self->{config_file} : $!");
267 while (my $line = <FP>)
270 my ($k, $v) = split(/\s*=\s*/, $line, 2);
282 save - save the current configuration to config_file
290 if ($self->{ach_list}) {
291 # shortcut for display_begin
292 $self->{achs} = [ map {{ name => $_ }}
293 keys %{$self->{ach_list}}
297 unless (open(FP, ">$self->{config_file}"))
299 return $self->error("$self->{config_file} : $!\n" .
300 "You must add this to your config file\n"
301 . Data::Dumper::Dumper($self));
304 print FP Data::Dumper::Dumper($self);
312 edit, view, modify - html form ouput
320 $self->display($self, "config_edit.tpl");
326 $self->display($self, "config_view.tpl");
336 foreach my $k (CGI::param())
338 next unless (exists $k_re{$k}) ;
339 my $val = CGI::param($k);
340 if ($val =~ $k_re{$k}) {
343 $self->{error} .= "bad parameter : $k = [$val]";
349 if ($self->{error}) { # an error as occured
350 $self->display($self, 'error.tpl');
358 ################################################################
360 package Bweb::Client;
362 use base q/Bweb::Gui/;
366 Bweb::Client - Bacula FD
370 this package is use to do all Client operations like, parse status etc...
374 $client = new Bweb::Client(name => 'zog-fd');
375 $client->status(); # do a 'status client=zog-fd'
381 display_running_job - Html display of a running job
385 this function is used to display information about a current job
389 sub display_running_job
391 my ($self, $conf, $jobid) = @_ ;
393 my $status = $self->status($conf);
396 if ($status->{$jobid}) {
397 $self->display($status->{$jobid}, "client_job_status.tpl");
400 for my $id (keys %$status) {
401 $self->display($status->{$id}, "client_job_status.tpl");
408 $client = new Bweb::Client(name => 'plume-fd');
410 $client->status($bweb);
414 dirty hack to parse "status client=xxx-fd"
418 JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
419 Backup Job started: 06-jun-06 17:22
420 Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
421 Files Examined=10,697
422 Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
428 JobName => Full_plume.2006-06-06_17.22.23,
431 Bytes => 194,484,132,
441 my ($self, $conf) = @_ ;
443 if (defined $self->{cur_jobs}) {
444 return $self->{cur_jobs} ;
448 my $b = new Bconsole(pref => $conf);
449 my $ret = $b->send_cmd("st client=$self->{name}");
453 for my $r (split(/\n/, $ret)) {
455 $r =~ s/(^\s+|\s+$)//g;
456 if ($r =~ /JobId (\d+) Job (\S+)/) {
458 $arg->{$jobid} = { @param, JobId => $jobid } ;
462 @param = ( JobName => $2 );
464 } elsif ($r =~ /=.+=/) {
465 push @param, split(/\s+|\s*=\s*/, $r) ;
467 } elsif ($r =~ /=/) { # one per line
468 push @param, split(/\s*=\s*/, $r) ;
470 } elsif ($r =~ /:/) { # one per line
471 push @param, split(/\s*:\s*/, $r, 2) ;
475 if ($jobid and @param) {
476 $arg->{$jobid} = { @param,
478 Client => $self->{name},
482 $self->{cur_jobs} = $arg ;
488 ################################################################
490 package Bweb::Autochanger;
492 use base q/Bweb::Gui/;
496 Bweb::Autochanger - Object to manage Autochanger
500 this package will parse the mtx output and manage drives.
504 $auto = new Bweb::Autochanger(precmd => 'sudo');
506 $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
510 $auto->slot_is_full(10);
511 $auto->transfer(10, 11);
517 my ($class, %arg) = @_;
520 name => '', # autochanger name
521 label => {}, # where are volume { label1 => 40, label2 => drive0 }
522 drive => [], # drive use [ 'media1', 'empty', ..]
523 slot => [], # slot use [ undef, 'empty', 'empty', ..] no slot 0
524 io => [], # io slot number list [ 41, 42, 43...]
525 info => {slot => 0, # informations (slot, drive, io)
529 mtxcmd => '/usr/sbin/mtx',
531 device => '/dev/changer',
532 precmd => '', # ssh command
533 bweb => undef, # link to bacula web object (use for display)
536 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
543 status - parse the output of mtx status
547 this function will launch mtx status and parse the output. it will
548 give a perlish view of the autochanger content.
550 it uses ssh if the autochanger is on a other host.
557 my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
559 # TODO : reset all infos
560 $self->{info}->{drive} = 0;
561 $self->{info}->{slot} = 0;
562 $self->{info}->{io} = 0;
564 #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
567 # Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
568 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
569 #Data Transfer Element 1:Empty
570 # Storage Element 1:Empty
571 # Storage Element 2:Full :VolumeTag=000002
572 # Storage Element 3:Empty
573 # Storage Element 4:Full :VolumeTag=000004
574 # Storage Element 5:Full :VolumeTag=000001
575 # Storage Element 6:Full :VolumeTag=000003
576 # Storage Element 7:Empty
577 # Storage Element 41 IMPORT/EXPORT:Empty
578 # Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
583 # Storage Element 7:Empty
584 # Storage Element 2:Full :VolumeTag=000002
585 if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
588 $self->set_empty_slot($1);
590 $self->set_slot($1, $4);
593 } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
596 $self->set_empty_drive($1);
598 $self->set_drive($1, $4, $6);
601 } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/)
604 $self->set_empty_io($1);
606 $self->set_io($1, $4);
609 # Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
611 } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
612 $self->{info}->{drive} = $1;
613 $self->{info}->{slot} = $2;
614 if ($l =~ /(\d+)\s+Import/) {
615 $self->{info}->{io} = $1 ;
617 $self->{info}->{io} = 0;
622 $self->debug($self) ;
627 my ($self, $slot) = @_;
630 if ($self->{slot}->[$slot] eq 'loaded') {
634 my $label = $self->{slot}->[$slot] ;
636 return $self->is_media_loaded($label);
641 my ($self, $drive, $slot) = @_;
643 return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
644 return 0 if ($self->slot_is_full($slot)) ;
646 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
649 my $content = $self->get_slot($slot);
650 print "content = $content<br/> $drive => $slot<br/>";
651 $self->set_empty_drive($drive);
652 $self->set_slot($slot, $content);
655 $self->{error} = $out;
660 # TODO: load/unload have to use mtx script from bacula
663 my ($self, $drive, $slot) = @_;
665 return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
666 return 0 unless ($self->slot_is_full($slot)) ;
668 print "Loading drive $drive with slot $slot<br/>\n";
669 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
672 my $content = $self->get_slot($slot);
673 print "content = $content<br/> $slot => $drive<br/>";
674 $self->set_drive($drive, $slot, $content);
677 $self->{error} = $out;
685 my ($self, $media) = @_;
687 unless ($self->{label}->{$media}) {
691 if ($self->{label}->{$media} =~ /drive\d+/) {
701 return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
706 my ($self, $slot, $tag) = @_;
707 $self->{slot}->[$slot] = $tag || 'full';
708 push @{ $self->{io} }, $slot;
711 $self->{label}->{$tag} = $slot;
717 my ($self, $slot) = @_;
719 push @{ $self->{io} }, $slot;
721 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
722 $self->{slot}->[$slot] = 'empty';
728 my ($self, $slot) = @_;
729 return $self->{slot}->[$slot];
734 my ($self, $slot, $tag) = @_;
735 $self->{slot}->[$slot] = $tag || 'full';
738 $self->{label}->{$tag} = $slot;
744 my ($self, $slot) = @_;
746 unless ($self->{slot}->[$slot]) { # can be loaded (parse before)
747 $self->{slot}->[$slot] = 'empty';
753 my ($self, $drive) = @_;
754 $self->{drive}->[$drive] = 'empty';
759 my ($self, $drive, $slot, $tag) = @_;
760 $self->{drive}->[$drive] = $tag || $slot;
762 $self->{slot}->[$slot] = $tag || 'loaded';
765 $self->{label}->{$tag} = "drive$drive";
771 my ($self, $slot) = @_;
773 # slot don't exists => full
774 if (not defined $self->{slot}->[$slot]) {
778 if ($self->{slot}->[$slot] eq 'empty') {
781 return 1; # vol, full, loaded
784 sub slot_get_first_free
787 for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
788 return $slot unless ($self->slot_is_full($slot));
792 sub io_get_first_free
796 foreach my $slot (@{ $self->{io} }) {
797 return $slot unless ($self->slot_is_full($slot));
804 my ($self, $media) = @_;
806 return $self->{label}->{$media} ;
811 my ($self, $media) = @_;
813 return defined $self->{label}->{$media} ;
818 my ($self, $slot) = @_;
820 unless ($self->slot_is_full($slot)) {
821 print "Autochanger $self->{name} slot $slot is empty\n";
826 if ($self->is_slot_loaded($slot)) {
829 print "Autochanger $self->{name} $slot is currently in use\n";
833 # autochanger must have I/O
834 unless ($self->have_io()) {
835 print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
839 my $dst = $self->io_get_first_free();
842 print "Autochanger $self->{name} you must empty I/O first\n";
845 $self->transfer($slot, $dst);
850 my ($self, $src, $dst) = @_ ;
851 print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
852 my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
855 my $content = $self->get_slot($src);
856 print "$content ($src) => $dst<br/>";
857 $self->{slot}->[$src] = 'empty';
858 $self->set_slot($dst, $content);
861 $self->{error} = $out;
868 my ($self, $index) = @_;
869 return $self->{drive_name}->[$index];
872 # TODO : do a tapeinfo request to get informations
882 for my $slot (@{$self->{io}})
884 if ($self->is_slot_loaded($slot)) {
885 print "$slot is currently loaded\n";
889 if ($self->slot_is_full($slot))
891 my $free = $self->slot_get_first_free() ;
892 print "want to move $slot to $free\n";
895 $self->transfer($slot, $free) || print "$self->{error}\n";
898 $self->{error} = "E : Can't find free slot";
904 # TODO : this is with mtx status output,
905 # we can do an other function from bacula view (with StorageId)
909 my $bweb = $self->{bweb};
911 # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
912 my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
915 SELECT Media.VolumeName AS volumename,
916 Media.VolStatus AS volstatus,
917 Media.LastWritten AS lastwritten,
918 Media.VolBytes AS volbytes,
919 Media.MediaType AS mediatype,
921 Media.InChanger AS inchanger,
923 $bweb->{sql}->{FROM_UNIXTIME}(
924 $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
925 + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
928 INNER JOIN Pool USING (PoolId)
930 WHERE Media.VolumeName IN ($media_list)
933 my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
935 # TODO : verify slot and bacula slot
939 for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
941 if ($self->slot_is_full($slot)) {
943 my $vol = $self->{slot}->[$slot];
944 if (defined $all->{$vol}) { # TODO : autochanger without barcodes
946 my $bslot = $all->{$vol}->{slot} ;
947 my $inchanger = $all->{$vol}->{inchanger};
949 # if bacula slot or inchanger flag is bad, we display a message
950 if ($bslot != $slot or !$inchanger) {
951 push @to_update, $slot;
954 $all->{$vol}->{realslot} = $slot;
956 push @{ $param }, $all->{$vol};
958 } else { # empty or no label
959 push @{ $param }, {realslot => $slot,
960 volstatus => 'Unknow',
961 volumename => $self->{slot}->[$slot]} ;
964 push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
968 my $i=0; my $drives = [] ;
969 foreach my $d (@{ $self->{drive} }) {
970 $drives->[$i] = { index => $i,
971 load => $self->{drive}->[$i],
972 name => $self->{drive_name}->[$i],
977 $bweb->display({ Name => $self->{name},
978 nb_drive => $self->{info}->{drive},
979 nb_io => $self->{info}->{io},
982 Update => scalar(@to_update) },
990 ################################################################
994 use base q/Bweb::Gui/;
998 Bweb - main Bweb package
1002 this package is use to compute and display informations
1007 use POSIX qw/strftime/;
1013 %sql_func - hash to make query mysql/postgresql compliant
1019 UNIX_TIMESTAMP => '',
1020 FROM_UNIXTIME => '',
1021 TO_SEC => " interval '1 second' * ",
1022 SEC_TO_INT => "SEC_TO_INT",
1025 STARTTIME_DAY => " date_trunc('day', Job.StartTime) ",
1026 STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1027 STARTTIME_MONTH => " date_trunc('month', Job.StartTime) ",
1028 STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1029 STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1030 STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1033 UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1034 FROM_UNIXTIME => 'FROM_UNIXTIME',
1037 SEC_TO_TIME => 'SEC_TO_TIME',
1038 MATCH => " REGEXP ",
1039 STARTTIME_DAY => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1040 STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1041 STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1042 STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1043 STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1044 STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1048 sub dbh_selectall_arrayref
1050 my ($self, $query) = @_;
1051 $self->connect_db();
1052 $self->debug($query);
1053 return $self->{dbh}->selectall_arrayref($query);
1058 my ($self, @what) = @_;
1059 return join(',', $self->dbh_quote(@what)) ;
1064 my ($self, @what) = @_;
1066 $self->connect_db();
1068 return map { $self->{dbh}->quote($_) } @what;
1070 return $self->{dbh}->quote($what[0]) ;
1076 my ($self, $query) = @_ ;
1077 $self->connect_db();
1078 $self->debug($query);
1079 return $self->{dbh}->do($query);
1082 sub dbh_selectall_hashref
1084 my ($self, $query, $join) = @_;
1086 $self->connect_db();
1087 $self->debug($query);
1088 return $self->{dbh}->selectall_hashref($query, $join) ;
1091 sub dbh_selectrow_hashref
1093 my ($self, $query) = @_;
1095 $self->connect_db();
1096 $self->debug($query);
1097 return $self->{dbh}->selectrow_hashref($query) ;
1103 my @unit = qw(b Kb Mb Gb Tb);
1104 my $val = shift || 0;
1106 my $format = '%i %s';
1107 while ($val / 1024 > 1) {
1111 $format = ($i>0)?'%0.1f %s':'%i %s';
1112 return sprintf($format, $val, $unit[$i]);
1115 # display Day, Hour, Year
1121 $val /= 60; # sec -> min
1123 if ($val / 60 <= 1) {
1127 $val /= 60; # min -> hour
1128 if ($val / 24 <= 1) {
1129 return "$val hours";
1132 $val /= 24; # hour -> day
1133 if ($val / 365 < 2) {
1137 $val /= 365 ; # day -> year
1139 return "$val years";
1142 # get Day, Hour, Year
1148 unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1152 my %times = ( m => 60,
1158 my $mult = $times{$2} || 0;
1168 unless ($self->{dbh}) {
1169 $self->{dbh} = DBI->connect($self->{info}->{dbi},
1170 $self->{info}->{user},
1171 $self->{info}->{password});
1173 print "Can't connect to your database, see error log\n"
1174 unless ($self->{dbh});
1176 $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1182 my ($class, %arg) = @_;
1184 dbh => undef, # connect_db();
1186 dbi => '', # DBI:Pg:database=bacula;host=127.0.0.1
1192 map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1194 if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1195 $self->{sql} = $sql_func{$1};
1198 $self->{debug} = $self->{info}->{debug};
1199 $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1207 $self->display($self->{info}, "begin.tpl");
1213 $self->display($self->{info}, "end.tpl");
1221 my $arg = $self->get_form("client", "qre_client");
1223 if ($arg->{qre_client}) {
1224 $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1225 } elsif ($arg->{client}) {
1226 $where = "WHERE Name = '$arg->{client}' ";
1230 SELECT Name AS name,
1232 AutoPrune AS autoprune,
1233 FileRetention AS fileretention,
1234 JobRetention AS jobretention
1239 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1241 my $dsp = { ID => $cur_id++,
1242 clients => [ values %$all] };
1244 $self->display($dsp, "client_list.tpl") ;
1249 my ($self, %arg) = @_;
1256 "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1258 ( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
1260 $self->{sql}->{TO_SEC}($arg{age})
1263 $label = "last " . human_sec($arg{age});
1266 if ($arg{groupby}) {
1267 $limit .= " GROUP BY $arg{groupby} ";
1271 $limit .= " ORDER BY $arg{order} ";
1275 $limit .= " LIMIT $arg{limit} ";
1276 $label .= " limited to $arg{limit}";
1280 $limit .= " OFFSET $arg{offset} ";
1281 $label .= " with $arg{offset} offset ";
1285 $label = 'no filter';
1288 return ($limit, $label);
1293 $bweb->get_form(...) - Get useful stuff
1297 This function get and check parameters against regexp.
1299 If word begin with 'q', the return will be quoted or join quoted
1300 if it's end with 's'.
1305 $bweb->get_form('jobid', 'qclient', 'qpools') ;
1308 qclient => 'plume-fd',
1309 qpools => "'plume-fd', 'test-fd', '...'",
1316 my ($self, @what) = @_;
1317 my %what = map { $_ => 1 } @what;
1337 my %opt_s = ( # default to ''
1352 my %opt_p = ( # option with path
1359 my %opt_d = ( # option with date
1364 foreach my $i (@what) {
1365 if (exists $opt_i{$i}) {# integer param
1366 my $value = CGI::param($i) || $opt_i{$i} ;
1367 if ($value =~ /^(\d+)$/) {
1370 } elsif ($opt_s{$i}) { # simple string param
1371 my $value = CGI::param($i) || '';
1372 if ($value =~ /^([\w\d\.-]+)$/) {
1376 } elsif ($i =~ /^j(\w+)s$/) { # quote join args
1377 my @value = CGI::param($1) ;
1379 $ret{$i} = $self->dbh_join(@value) ;
1382 } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1383 my $value = CGI::param($1) ;
1385 $ret{$i} = $self->dbh_quote($value);
1388 } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1389 $ret{$i} = [ map { { name => $self->dbh_quote($_) } }
1391 } elsif (exists $opt_p{$i}) {
1392 my $value = CGI::param($i) || '';
1393 if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1396 } elsif (exists $opt_d{$i}) {
1397 my $value = CGI::param($i) || '';
1398 if ($value =~ /^\s*(\d+\s+\w+)$/) {
1405 foreach my $s (CGI::param('slot')) {
1406 if ($s =~ /^(\d+)$/) {
1407 push @{$ret{slots}}, $s;
1412 if ($what{db_clients}) {
1414 SELECT Client.Name as clientname
1418 my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1419 $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} }
1423 if ($what{db_mediatypes}) {
1425 SELECT MediaType as mediatype
1429 my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1430 $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} }
1434 if ($what{db_locations}) {
1436 SELECT Location as location, Cost as cost FROM Location
1438 my $loc = $self->dbh_selectall_hashref($query, 'location');
1439 $ret{db_locations} = [ sort { $a->{location}
1445 if ($what{db_pools}) {
1446 my $query = "SELECT Name as name FROM Pool";
1448 my $all = $self->dbh_selectall_hashref($query, 'name') ;
1449 $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1452 if ($what{db_filesets}) {
1454 SELECT FileSet.FileSet AS fileset
1458 my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1460 $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) }
1461 values %$filesets] ;
1464 if ($what{db_jobnames}) {
1466 SELECT DISTINCT Job.Name AS jobname
1470 my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1472 $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) }
1473 values %$jobnames] ;
1476 if ($what{db_devices}) {
1478 SELECT Device.Name AS name
1482 my $devices = $self->dbh_selectall_hashref($query, 'name');
1484 $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) }
1495 my $fields = $self->get_form(qw/age level status clients filesets
1497 db_clients limit db_filesets width height
1498 qclients qfilesets qjobnames db_jobnames/);
1501 my $url = CGI::url(-full => 0,
1504 $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1506 # this organisation is to keep user choice between 2 click
1507 # TODO : fileset and client selection doesn't work
1516 sub display_client_job
1518 my ($self, %arg) = @_ ;
1520 $arg{order} = ' Job.JobId DESC ';
1521 my ($limit, $label) = $self->get_limit(%arg);
1523 my $clientname = $self->dbh_quote($arg{clientname});
1526 SELECT DISTINCT Job.JobId AS jobid,
1527 Job.Name AS jobname,
1528 FileSet.FileSet AS fileset,
1530 StartTime AS starttime,
1531 JobFiles AS jobfiles,
1532 JobBytes AS jobbytes,
1533 JobStatus AS jobstatus,
1534 JobErrors AS joberrors
1536 FROM Client,Job,FileSet
1537 WHERE Client.Name=$clientname
1538 AND Client.ClientId=Job.ClientId
1539 AND Job.FileSetId=FileSet.FileSetId
1543 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1545 $self->display({ clientname => $arg{clientname},
1548 Jobs => [ values %$all ],
1550 "display_client_job.tpl") ;
1553 sub get_selected_media_location
1557 my $medias = $self->get_form('jmedias');
1559 unless ($medias->{jmedias}) {
1564 SELECT Media.VolumeName AS volumename, Location.Location AS location
1565 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1566 WHERE Media.VolumeName IN ($medias->{jmedias})
1569 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1571 # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1582 my $medias = $self->get_selected_media_location();
1588 my $elt = $self->get_form('db_locations');
1590 $self->display({ ID => $cur_id++,
1591 %$elt, # db_locations
1593 sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1603 my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1605 $self->display($elt, "help_extern.tpl");
1608 sub help_extern_compute
1612 my $number = CGI::param('limit') || '' ;
1613 unless ($number =~ /^(\d+)$/) {
1614 return $self->error("Bad arg number : $number ");
1617 my ($sql, undef) = $self->get_param('pools',
1618 'locations', 'mediatypes');
1621 SELECT Media.VolumeName AS volumename,
1622 Media.VolStatus AS volstatus,
1623 Media.LastWritten AS lastwritten,
1624 Media.MediaType AS mediatype,
1625 Media.VolMounts AS volmounts,
1627 Media.Recycle AS recycle,
1628 $self->{sql}->{FROM_UNIXTIME}(
1629 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1630 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1633 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1634 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1636 WHERE Media.InChanger = 1
1637 AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1639 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1643 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1645 $self->display({ Medias => [ values %$all ] },
1646 "help_extern_compute.tpl");
1653 my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1654 $self->display($param, "help_intern.tpl");
1657 sub help_intern_compute
1661 my $number = CGI::param('limit') || '' ;
1662 unless ($number =~ /^(\d+)$/) {
1663 return $self->error("Bad arg number : $number ");
1666 my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1668 if (CGI::param('expired')) {
1670 AND ( $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1671 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1677 SELECT Media.VolumeName AS volumename,
1678 Media.VolStatus AS volstatus,
1679 Media.LastWritten AS lastwritten,
1680 Media.MediaType AS mediatype,
1681 Media.VolMounts AS volmounts,
1683 $self->{sql}->{FROM_UNIXTIME}(
1684 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1685 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1688 INNER JOIN Pool ON (Pool.PoolId = Media.PoolId)
1689 LEFT JOIN Location ON (Location.LocationId = Media.LocationId)
1691 WHERE Media.InChanger <> 1
1692 AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1693 AND Media.Recycle = 1
1695 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC
1699 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1701 $self->display({ Medias => [ values %$all ] },
1702 "help_intern_compute.tpl");
1708 my ($self, %arg) = @_ ;
1710 my ($limit, $label) = $self->get_limit(%arg);
1714 (SELECT count(Pool.PoolId) FROM Pool) AS nb_pool,
1715 (SELECT count(Media.MediaId) FROM Media) AS nb_media,
1716 (SELECT count(Job.JobId) FROM Job) AS nb_job,
1717 (SELECT sum(VolBytes) FROM Media) AS nb_bytes,
1718 (SELECT count(Job.JobId)
1720 WHERE Job.JobStatus IN ('E','e','f','A')
1723 (SELECT count(Client.ClientId) FROM Client) AS nb_client
1726 my $row = $self->dbh_selectrow_hashref($query) ;
1728 $row->{nb_bytes} = human_size($row->{nb_bytes});
1730 $row->{db_size} = '???';
1731 $row->{label} = $label;
1733 $self->display($row, "general.tpl");
1738 my ($self, @what) = @_ ;
1739 my %elt = map { $_ => 1 } @what;
1744 if ($elt{clients}) {
1745 my @clients = CGI::param('client');
1747 $ret{clients} = \@clients;
1748 my $str = $self->dbh_join(@clients);
1749 $limit .= "AND Client.Name IN ($str) ";
1753 if ($elt{filesets}) {
1754 my @filesets = CGI::param('fileset');
1756 $ret{filesets} = \@filesets;
1757 my $str = $self->dbh_join(@filesets);
1758 $limit .= "AND FileSet.FileSet IN ($str) ";
1762 if ($elt{mediatypes}) {
1763 my @medias = CGI::param('mediatype');
1765 $ret{mediatypes} = \@medias;
1766 my $str = $self->dbh_join(@medias);
1767 $limit .= "AND Media.MediaType IN ($str) ";
1772 my $client = CGI::param('client');
1773 $ret{client} = $client;
1774 $client = $self->dbh_join($client);
1775 $limit .= "AND Client.Name = $client ";
1779 my $level = CGI::param('level') || '';
1780 if ($level =~ /^(\w)$/) {
1782 $limit .= "AND Job.Level = '$1' ";
1787 my $jobid = CGI::param('jobid') || '';
1789 if ($jobid =~ /^(\d+)$/) {
1791 $limit .= "AND Job.JobId = '$1' ";
1796 my $status = CGI::param('status') || '';
1797 if ($status =~ /^(\w)$/) {
1800 $limit .= "AND Job.JobStatus IN ('f','E') ";
1802 $limit .= "AND Job.JobStatus = '$1' ";
1807 if ($elt{locations}) {
1808 my @location = CGI::param('location') ;
1810 $ret{locations} = \@location;
1811 my $str = $self->dbh_join(@location);
1812 $limit .= "AND Location.Location IN ($str) ";
1817 my @pool = CGI::param('pool') ;
1819 $ret{pools} = \@pool;
1820 my $str = $self->dbh_join(@pool);
1821 $limit .= "AND Pool.Name IN ($str) ";
1825 if ($elt{location}) {
1826 my $location = CGI::param('location') || '';
1828 $ret{location} = $location;
1829 $location = $self->dbh_quote($location);
1830 $limit .= "AND Location.Location = $location ";
1835 my $pool = CGI::param('pool') || '';
1838 $pool = $self->dbh_quote($pool);
1839 $limit .= "AND Pool.Name = $pool ";
1843 if ($elt{jobtype}) {
1844 my $jobtype = CGI::param('jobtype') || '';
1845 if ($jobtype =~ /^(\w)$/) {
1847 $limit .= "AND Job.Type = '$1' ";
1851 return ($limit, %ret);
1862 my ($self, %arg) = @_ ;
1864 $arg{order} = ' Job.JobId DESC ';
1866 my ($limit, $label) = $self->get_limit(%arg);
1867 my ($where, undef) = $self->get_param('clients',
1875 SELECT Job.JobId AS jobid,
1876 Client.Name AS client,
1877 FileSet.FileSet AS fileset,
1878 Job.Name AS jobname,
1880 StartTime AS starttime,
1881 Pool.Name AS poolname,
1882 JobFiles AS jobfiles,
1883 JobBytes AS jobbytes,
1884 JobStatus AS jobstatus,
1885 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1886 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
1889 JobErrors AS joberrors
1892 Job LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
1893 LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
1894 WHERE Client.ClientId=Job.ClientId
1895 AND Job.JobStatus != 'R'
1900 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1902 $self->display({ Filter => $label,
1906 sort { $a->{jobid} <=> $b->{jobid} }
1913 # display job informations
1914 sub display_job_zoom
1916 my ($self, $jobid) = @_ ;
1918 $jobid = $self->dbh_quote($jobid);
1921 SELECT DISTINCT Job.JobId AS jobid,
1922 Client.Name AS client,
1923 Job.Name AS jobname,
1924 FileSet.FileSet AS fileset,
1926 Pool.Name AS poolname,
1927 StartTime AS starttime,
1928 JobFiles AS jobfiles,
1929 JobBytes AS jobbytes,
1930 JobStatus AS jobstatus,
1931 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(EndTime)
1932 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
1935 Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
1936 LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId)
1937 WHERE Client.ClientId=Job.ClientId
1938 AND Job.JobId = $jobid
1941 my $row = $self->dbh_selectrow_hashref($query) ;
1943 # display all volumes associate with this job
1945 SELECT Media.VolumeName as volumename
1946 FROM Job,Media,JobMedia
1947 WHERE Job.JobId = $jobid
1948 AND JobMedia.JobId=Job.JobId
1949 AND JobMedia.MediaId=Media.MediaId
1952 my $all = $self->dbh_selectall_hashref($query, 'volumename');
1954 $row->{volumes} = [ values %$all ] ;
1956 $self->display($row, "display_job_zoom.tpl");
1963 my ($where, %elt) = $self->get_param('pool',
1966 my $arg = $self->get_form('jmedias', 'qre_media');
1968 if ($arg->{jmedias}) {
1969 $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where";
1971 if ($arg->{qre_media}) {
1972 $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where";
1976 SELECT Media.VolumeName AS volumename,
1977 Media.VolBytes AS volbytes,
1978 Media.VolStatus AS volstatus,
1979 Media.MediaType AS mediatype,
1980 Media.InChanger AS online,
1981 Media.LastWritten AS lastwritten,
1982 Location.Location AS location,
1983 (volbytes*100/COALESCE(media_avg_size.size,-1)) AS volusage,
1984 Pool.Name AS poolname,
1985 $self->{sql}->{FROM_UNIXTIME}(
1986 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
1987 + $self->{sql}->{TO_SEC}(Media.VolRetention)
1990 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1991 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
1992 Media.MediaType AS MediaType
1994 WHERE Media.VolStatus = 'Full'
1995 GROUP BY Media.MediaType
1996 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
1998 WHERE Media.PoolId=Pool.PoolId
2002 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2004 $self->display({ ID => $cur_id++,
2006 Location => $elt{location},
2007 Medias => [ values %$all ]
2009 "display_media.tpl");
2016 my $pool = $self->get_form('db_pools');
2018 foreach my $name (@{ $pool->{db_pools} }) {
2019 CGI::param('pool', $name->{name});
2020 $self->display_media();
2024 sub display_media_zoom
2028 my $medias = $self->get_form('jmedias');
2030 unless ($medias->{jmedias}) {
2031 return $self->error("Can't get media selection");
2035 SELECT InChanger AS online,
2036 VolBytes AS nb_bytes,
2037 VolumeName AS volumename,
2038 VolStatus AS volstatus,
2039 VolMounts AS nb_mounts,
2040 Media.VolUseDuration AS voluseduration,
2041 Media.MaxVolJobs AS maxvoljobs,
2042 Media.MaxVolFiles AS maxvolfiles,
2043 Media.MaxVolBytes AS maxvolbytes,
2044 VolErrors AS nb_errors,
2045 Pool.Name AS poolname,
2046 Location.Location AS location,
2047 Media.Recycle AS recycle,
2048 Media.VolRetention AS volretention,
2049 Media.LastWritten AS lastwritten,
2050 Media.VolReadTime/1000000 AS volreadtime,
2051 Media.VolWriteTime/1000000 AS volwritetime,
2052 $self->{sql}->{FROM_UNIXTIME}(
2053 $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten)
2054 + $self->{sql}->{TO_SEC}(Media.VolRetention)
2057 Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2058 WHERE Pool.PoolId = Media.PoolId
2059 AND VolumeName IN ($medias->{jmedias})
2062 my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2064 foreach my $media (values %$all) {
2065 my $mq = $self->dbh_quote($media->{volumename});
2068 SELECT DISTINCT Job.JobId AS jobid,
2070 Job.StartTime AS starttime,
2073 Job.JobFiles AS files,
2074 Job.JobBytes AS bytes,
2075 Job.jobstatus AS status
2076 FROM Media,JobMedia,Job
2077 WHERE Media.VolumeName=$mq
2078 AND Media.MediaId=JobMedia.MediaId
2079 AND JobMedia.JobId=Job.JobId
2082 my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2085 SELECT LocationLog.Date AS date,
2086 Location.Location AS location,
2087 LocationLog.Comment AS comment
2088 FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2089 WHERE Media.MediaId = LocationLog.MediaId
2090 AND Media.VolumeName = $mq
2094 my $log = $self->dbh_selectall_arrayref($query) ;
2096 $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2099 $self->display({ jobs => [ values %$jobs ],
2100 LocationLog => $logtxt,
2102 "display_media_zoom.tpl");
2110 my $loc = $self->get_form('qlocation');
2111 unless ($loc->{qlocation}) {
2112 return $self->error("Can't get location");
2116 SELECT Location.Location AS location,
2117 Location.Cost AS cost,
2118 Location.Enabled AS enabled
2120 WHERE Location.Location = $loc->{qlocation}
2123 my $row = $self->dbh_selectrow_hashref($query);
2125 $self->display({ ID => $cur_id++,
2126 %$row }, "location_edit.tpl") ;
2134 my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2135 unless ($arg->{qlocation}) {
2136 return $self->error("Can't get location");
2138 unless ($arg->{qnewlocation}) {
2139 return $self->error("Can't get new location name");
2141 unless ($arg->{cost}) {
2142 return $self->error("Can't get new cost");
2145 my $enabled = CGI::param('enabled') || '';
2146 $enabled = $enabled?1:0;
2149 UPDATE Location SET Cost = $arg->{cost},
2150 Location = $arg->{qnewlocation},
2152 WHERE Location.Location = $arg->{qlocation}
2155 $self->dbh_do($query);
2157 $self->display_location();
2163 my $arg = $self->get_form(qw/qlocation/) ;
2165 unless ($arg->{qlocation}) {
2166 return $self->error("Can't get location");
2170 SELECT count(Media.MediaId) AS nb
2171 FROM Media INNER JOIN Location USING (LocationID)
2172 WHERE Location = $arg->{qlocation}
2175 my $res = $self->dbh_selectrow_hashref($query);
2178 return $self->error("Sorry, the location must be empty");
2182 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2185 $self->dbh_do($query);
2187 $self->display_location();
2194 my $arg = $self->get_form(qw/qlocation cost/) ;
2196 unless ($arg->{qlocation}) {
2197 $self->display({}, "location_add.tpl");
2200 unless ($arg->{cost}) {
2201 return $self->error("Can't get new cost");
2204 my $enabled = CGI::param('enabled') || '';
2205 $enabled = $enabled?1:0;
2208 INSERT INTO Location (Location, Cost, Enabled)
2209 VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2212 $self->dbh_do($query);
2214 $self->display_location();
2217 sub display_location
2222 SELECT Location.Location AS location,
2223 Location.Cost AS cost,
2224 Location.Enabled AS enabled,
2225 (SELECT count(Media.MediaId)
2227 WHERE Media.LocationId = Location.LocationId
2232 my $location = $self->dbh_selectall_hashref($query, 'location');
2234 $self->display({ ID => $cur_id++,
2235 Locations => [ values %$location ] },
2236 "display_location.tpl");
2243 my $medias = $self->get_selected_media_location();
2248 my $arg = $self->get_form('db_locations', 'qnewlocation');
2250 $self->display({ email => $self->{info}->{email_media},
2252 medias => [ values %$medias ],
2254 "update_location.tpl");
2257 sub get_media_max_size
2259 my ($self, $type) = @_;
2261 "SELECT avg(VolBytes) AS size
2263 WHERE Media.VolStatus = 'Full'
2264 AND Media.MediaType = '$type'
2267 my $res = $self->selectrow_hashref($query);
2270 return $res->{size};
2280 my $media = $self->get_form('qmedia');
2282 unless ($media->{qmedia}) {
2283 return $self->error("Can't get media");
2287 SELECT Media.Slot AS slot,
2288 Pool.Name AS poolname,
2289 Media.VolStatus AS volstatus,
2290 Media.InChanger AS inchanger,
2291 Location.Location AS location,
2292 Media.VolumeName AS volumename,
2293 Media.MaxVolBytes AS maxvolbytes,
2294 Media.MaxVolJobs AS maxvoljobs,
2295 Media.MaxVolFiles AS maxvolfiles,
2296 Media.VolUseDuration AS voluseduration,
2297 Media.VolRetention AS volretention
2299 FROM Media INNER JOIN Pool ON (Media.PoolId = Pool.PoolId)
2300 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2302 WHERE Media.VolumeName = $media->{qmedia}
2305 my $row = $self->dbh_selectrow_hashref($query);
2306 $row->{volretention} = human_sec($row->{volretention});
2307 $row->{voluseduration} = human_sec($row->{voluseduration});
2309 my $elt = $self->get_form(qw/db_pools db_locations/);
2314 }, "update_media.tpl");
2321 my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2323 unless ($arg->{jmedias}) {
2324 return $self->error("Can't get selected media");
2327 unless ($arg->{qnewlocation}) {
2328 return $self->error("Can't get new location");
2333 SET LocationId = (SELECT LocationId
2335 WHERE Location = $arg->{qnewlocation})
2336 WHERE Media.VolumeName IN ($arg->{jmedias})
2339 my $nb = $self->dbh_do($query);
2341 print "$nb media updated, you may have to update your autochanger.";
2343 $self->display_media();
2350 my $medias = $self->get_selected_media_location();
2352 return $self->error("Can't get media selection");
2354 my $newloc = CGI::param('newlocation');
2356 my $user = CGI::param('user') || 'unknow';
2357 my $comm = CGI::param('comment') || '';
2358 $comm = $self->dbh_quote("$user: $comm");
2362 foreach my $media (keys %$medias) {
2364 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2366 NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2367 (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2368 (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2371 $self->dbh_do($query);
2372 $self->debug($query);
2376 $q->param('action', 'update_location');
2377 my $url = $q->url(-full => 1, -query=>1);
2379 $self->display({ email => $self->{info}->{email_media},
2381 newlocation => $newloc,
2382 # [ { volumename => 'vol1' }, { volumename => 'vol2' },..]
2383 medias => [ values %$medias ],
2385 "change_location.tpl");
2389 sub display_client_stats
2391 my ($self, %arg) = @_ ;
2393 my $client = $self->dbh_quote($arg{clientname});
2394 my ($limit, $label) = $self->get_limit(%arg);
2398 count(Job.JobId) AS nb_jobs,
2399 sum(Job.JobBytes) AS nb_bytes,
2400 sum(Job.JobErrors) AS nb_err,
2401 sum(Job.JobFiles) AS nb_files,
2402 Client.Name AS clientname
2403 FROM Job INNER JOIN Client USING (ClientId)
2405 Client.Name = $client
2407 GROUP BY Client.Name
2410 my $row = $self->dbh_selectrow_hashref($query);
2412 $row->{ID} = $cur_id++;
2413 $row->{label} = $label;
2415 $self->display($row, "display_client_stats.tpl");
2418 # poolname can be undef
2421 my ($self, $poolname) = @_ ;
2423 # TODO : afficher les tailles et les dates
2426 SELECT subq.volmax AS volmax,
2427 subq.volnum AS volnum,
2428 subq.voltotal AS voltotal,
2430 Pool.Recycle AS recycle,
2431 Pool.VolRetention AS volretention,
2432 Pool.VolUseDuration AS voluseduration,
2433 Pool.MaxVolJobs AS maxvoljobs,
2434 Pool.MaxVolFiles AS maxvolfiles,
2435 Pool.MaxVolBytes AS maxvolbytes,
2436 subq.PoolId AS PoolId
2439 SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2440 count(Media.MediaId) AS volnum,
2441 sum(Media.VolBytes) AS voltotal,
2442 Media.PoolId AS PoolId,
2443 Media.MediaType AS MediaType
2445 LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2446 Media.MediaType AS MediaType
2448 WHERE Media.VolStatus = 'Full'
2449 GROUP BY Media.MediaType
2450 ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2451 GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2453 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2456 my $all = $self->dbh_selectall_hashref($query, 'name') ;
2458 foreach my $p (values %$all) {
2460 $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2462 $p->{poolusage} = 0;
2466 SELECT VolStatus AS volstatus, count(MediaId) AS nb
2468 WHERE PoolId=$p->{poolid}
2471 my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2472 foreach my $t (values %$content) {
2473 $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2478 $self->display({ ID => $cur_id++,
2479 Pools => [ values %$all ]},
2480 "display_pool.tpl");
2483 sub display_running_job
2487 my $arg = $self->get_form('client', 'jobid');
2489 if (!$arg->{client} and $arg->{jobid}) {
2492 SELECT Client.Name AS name
2493 FROM Job INNER JOIN Client USING (ClientId)
2494 WHERE Job.JobId = $arg->{jobid}
2497 my $row = $self->dbh_selectrow_hashref($query);
2500 $arg->{client} = $row->{name};
2501 CGI::param('client', $arg->{client});
2505 if ($arg->{client}) {
2506 my $cli = new Bweb::Client(name => $arg->{client});
2507 $cli->display_running_job($self->{info}, $arg->{jobid});
2508 if ($arg->{jobid}) {
2509 $self->get_job_log();
2512 $self->error("Can't get client or jobid");
2516 sub display_running_jobs
2518 my ($self, $display_action) = @_;
2521 SELECT Job.JobId AS jobid,
2522 Job.Name AS jobname,
2524 Job.StartTime AS starttime,
2525 Job.JobFiles AS jobfiles,
2526 Job.JobBytes AS jobbytes,
2527 Job.JobStatus AS jobstatus,
2528 $self->{sql}->{SEC_TO_TIME}( $self->{sql}->{UNIX_TIMESTAMP}(NOW())
2529 - $self->{sql}->{UNIX_TIMESTAMP}(StartTime))
2531 Client.Name AS clientname
2532 FROM Job INNER JOIN Client USING (ClientId)
2533 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2535 my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2537 $self->display({ ID => $cur_id++,
2538 display_action => $display_action,
2539 Jobs => [ values %$all ]},
2540 "running_job.tpl") ;
2546 my $arg = $self->get_form('jmedias');
2548 unless ($arg->{jmedias}) {
2549 return $self->error("Can't get media selection");
2553 SELECT Media.VolumeName AS volumename,
2554 Storage.Name AS storage,
2555 Location.Location AS location,
2557 FROM Media INNER JOIN Storage ON (Media.StorageId = Storage.StorageId)
2558 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2559 WHERE Media.VolumeName IN ($arg->{jmedias})
2560 AND Media.InChanger = 1
2563 my $all = $self->dbh_selectall_hashref($query, 'volumename');
2565 foreach my $vol (values %$all) {
2566 my $a = $self->ach_get($vol->{location});
2569 unless ($a->{have_status}) {
2571 $a->{have_status} = 1;
2574 print "eject $vol->{volumename} from $vol->{storage} : ";
2575 if ($a->send_to_io($vol->{slot})) {
2587 my ($to, $subject, $content) = (CGI::param('email'),
2588 CGI::param('subject'),
2589 CGI::param('content'));
2590 $to =~ s/[^\w\d\.\@<>,]//;
2591 $subject =~ s/[^\w\d\.\[\]]/ /;
2593 open(MAIL, "|mail -s '$subject' '$to'") ;
2594 print MAIL $content;
2604 my $arg = $self->get_form('jobid', 'client');
2606 print CGI::header('text/brestore');
2607 print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
2608 print "client=$arg->{client}\n" if ($arg->{client});
2609 print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
2613 # TODO : move this to Bweb::Autochanger ?
2614 # TODO : make this internal to not eject tape ?
2620 my ($self, $name) = @_;
2623 return $self->error("Can't get your autochanger name ach");
2626 unless ($self->{info}->{ach_list}) {
2627 return $self->error("Could not find any autochanger");
2630 my $a = $self->{info}->{ach_list}->{$name};
2633 $self->error("Can't get your autochanger $name from your ach_list");
2644 my ($self, $ach) = @_;
2646 $self->{info}->{ach_list}->{$ach->{name}} = $ach;
2648 $self->{info}->save();
2656 my $arg = $self->get_form('ach');
2658 or !$self->{info}->{ach_list}
2659 or !$self->{info}->{ach_list}->{$arg->{ach}})
2661 return $self->error("Can't get autochanger name");
2664 my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
2668 [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
2670 my $b = $self->get_bconsole();
2672 my @storages = $b->list_storage() ;
2674 $ach->{devices} = [ map { { name => $_ } } @storages ];
2676 $self->display($ach, "ach_add.tpl");
2677 delete $ach->{drives};
2678 delete $ach->{devices};
2685 my $arg = $self->get_form('ach');
2688 or !$self->{info}->{ach_list}
2689 or !$self->{info}->{ach_list}->{$arg->{ach}})
2691 return $self->error("Can't get autochanger name");
2694 delete $self->{info}->{ach_list}->{$arg->{ach}} ;
2696 $self->{info}->save();
2697 $self->{info}->view();
2703 my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
2705 my $b = $self->get_bconsole();
2706 my @storages = $b->list_storage() ;
2708 unless ($arg->{ach}) {
2709 $arg->{devices} = [ map { { name => $_ } } @storages ];
2710 return $self->display($arg, "ach_add.tpl");
2714 foreach my $drive (CGI::param('drives'))
2716 unless (grep(/^$drive$/,@storages)) {
2717 return $self->error("Can't find $drive in storage list");
2720 my $index = CGI::param("index_$drive");
2721 unless (defined $index and $index =~ /^(\d+)$/) {
2722 return $self->error("Can't get $drive index");
2725 $drives[$index] = $drive;
2729 return $self->error("Can't get drives from Autochanger");
2732 my $a = new Bweb::Autochanger(name => $arg->{ach},
2733 precmd => $arg->{precmd},
2734 drive_name => \@drives,
2735 device => $arg->{device},
2736 mtxcmd => $arg->{mtxcmd});
2738 $self->ach_register($a) ;
2740 $self->{info}->view();
2746 my $arg = $self->get_form('jobid');
2748 if ($arg->{jobid}) {
2749 my $b = $self->get_bconsole();
2750 my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
2754 title => "Delete a job ",
2755 name => "delete jobid=$arg->{jobid}",
2764 my $arg = $self->get_form(qw/media volstatus inchanger pool
2765 slot volretention voluseduration
2766 maxvoljobs maxvolfiles maxvolbytes
2769 unless ($arg->{media}) {
2770 return $self->error("Can't find media selection");
2773 my $update = "update volume=$arg->{media} ";
2775 if ($arg->{volstatus}) {
2776 $update .= " volstatus=$arg->{volstatus} ";
2779 if ($arg->{inchanger}) {
2780 $update .= " inchanger=yes " ;
2782 $update .= " slot=$arg->{slot} ";
2785 $update .= " slot=0 inchanger=no ";
2789 $update .= " pool=$arg->{pool} " ;
2792 $arg->{volretention} ||= 0 ;
2793 if ($arg->{volretention}) {
2794 $update .= " volretention=\"$arg->{volretention}\" " ;
2797 $arg->{voluseduration} ||= 0 ;
2798 if ($arg->{voluseduration}) {
2799 $update .= " voluse=\"$arg->{voluseduration}\" " ;
2802 $arg->{maxvoljobs} ||= 0;
2803 if ($arg->{maxvoljobs}) {
2804 $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
2807 $arg->{maxvolfiles} ||= 0;
2808 if ($arg->{maxvolfiles}) {
2809 $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
2812 $arg->{maxvolbytes} ||= 0;
2813 if ($arg->{maxvolbytes}) {
2814 $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
2817 my $b = $self->get_bconsole();
2820 content => $b->send_cmd($update),
2821 title => "Update a volume ",
2826 my $loc = CGI::param('location') || '';
2828 my $media = $self->dbh_quote($arg->{media});
2829 $loc = $self->dbh_quote($loc); # is checked by db
2832 SET LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)
2833 WHERE Media.VolumeName = $media
2835 $self->dbh_do($query);
2838 $self->update_media();
2845 my $ach = CGI::param('ach') ;
2846 unless ($ach =~ /^([\w\d\.-]+)$/) {
2847 return $self->error("Bad autochanger name");
2851 my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
2852 $b->update_slots($ach);
2860 my $arg = $self->get_form('jobid');
2861 unless ($arg->{jobid}) {
2862 return $self->error("Can't get jobid");
2865 my $t = CGI::param('time') || '';
2868 SELECT Job.Name as name, Client.Name as clientname
2869 FROM Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
2870 WHERE JobId = $arg->{jobid}
2873 my $row = $self->dbh_selectrow_hashref($query);
2876 return $self->error("Can't find $arg->{jobid} in catalog");
2880 SELECT Time AS time, LogText AS log
2882 WHERE JobId = $arg->{jobid}
2885 my $log = $self->dbh_selectall_arrayref($query);
2887 return $self->error("Can't get log for jobid $arg->{jobid}");
2893 $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ;
2895 $logtxt = join("", map { $_->[1] } @$log ) ;
2898 $self->display({ lines=> $logtxt,
2899 jobid => $arg->{jobid},
2900 name => $row->{name},
2901 client => $row->{clientname},
2902 }, 'display_log.tpl');
2910 my $arg = $self->get_form('ach', 'slots', 'drive');
2912 unless ($arg->{ach}) {
2913 return $self->error("Can't find autochanger name");
2918 if ($arg->{slots}) {
2919 $slots = join(",", @{ $arg->{slots} });
2920 $t += 60*scalar( @{ $arg->{slots} }) ;
2923 my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
2924 print "<h1>This command can take long time, be patient...</h1>";
2926 $b->label_barcodes(storage => $arg->{ach},
2927 drive => $arg->{drive},
2938 my @volume = CGI::param('media');
2941 return $self->error("Can't get media selection");
2944 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
2947 content => $b->purge_volume(@volume),
2948 title => "Purge media",
2949 name => "purge volume=" . join(' volume=', @volume),
2958 my @volume = CGI::param('media');
2960 return $self->error("Can't get media selection");
2963 my $b = new Bconsole(pref => $self->{info}, timeout => 60);
2966 content => $b->prune_volume(@volume),
2967 title => "Prune media",
2968 name => "prune volume=" . join(' volume=', @volume),
2978 my $arg = $self->get_form('jobid');
2979 unless ($arg->{jobid}) {
2980 return $self->error("Can't get jobid");
2983 my $b = $self->get_bconsole();
2985 content => $b->cancel($arg->{jobid}),
2986 title => "Cancel job",
2987 name => "cancel jobid=$arg->{jobid}",
2993 # Warning, we display current fileset
2996 my $arg = $self->get_form('fileset');
2998 if ($arg->{fileset}) {
2999 my $b = $self->get_bconsole();
3000 my $ret = $b->get_fileset($arg->{fileset});
3001 $self->display({ fileset => $arg->{fileset},
3003 }, "fileset_view.tpl");
3005 $self->error("Can't get fileset name");
3009 sub director_show_sched
3013 my $arg = $self->get_form('days');
3015 my $b = $self->get_bconsole();
3016 my $ret = $b->director_get_sched( $arg->{days} );
3021 }, "scheduled_job.tpl");
3024 sub enable_disable_job
3026 my ($self, $what) = @_ ;
3028 my $name = CGI::param('job') || '';
3029 unless ($name =~ /^[\w\d\.\-\s]+$/) {
3030 return $self->error("Can't find job name");
3033 my $b = $self->get_bconsole();
3043 content => $b->send_cmd("$cmd job=\"$name\""),
3044 title => "$cmd $name",
3045 name => "$cmd job=\"$name\"",
3052 return new Bconsole(pref => $self->{info});
3058 my $b = $self->get_bconsole();
3060 my $joblist = [ map { { name => $_ } } $b->list_job() ];
3062 $self->display({ Jobs => $joblist }, "run_job.tpl");
3067 my ($self, $ouput) = @_;
3070 foreach my $l (split(/\r\n/, $ouput)) {
3071 if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3077 if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3083 foreach my $k (keys %arg) {
3084 $lowcase{lc($k)} = $arg{$k} ;
3093 my $b = $self->get_bconsole();
3095 my $job = CGI::param('job') || '';
3097 my $info = $b->send_cmd("show job=\"$job\"");
3098 my $attr = $self->run_parse_job($info);
3100 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3102 my $pools = [ map { { name => $_ } } $b->list_pool() ];
3103 my $clients = [ map { { name => $_ } }$b->list_client()];
3104 my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3105 my $storages= [ map { { name => $_ } }$b->list_storage()];
3110 clients => $clients,
3111 filesets => $filesets,
3112 storages => $storages,
3114 }, "run_job_mod.tpl");
3120 my $b = $self->get_bconsole();
3122 my $jobs = [ map {{ name => $_ }} $b->list_job() ];
3132 my $b = $self->get_bconsole();
3134 # TODO: check input (don't use pool, level)
3136 my $arg = $self->get_form('pool', 'level', 'client', 'priority');
3137 my $job = CGI::param('job') || '';
3138 my $storage = CGI::param('storage') || '';
3140 my $jobid = $b->run(job => $job,
3141 client => $arg->{client},
3142 priority => $arg->{priority},
3143 level => $arg->{level},
3144 storage => $storage,
3145 pool => $arg->{pool},
3148 print $jobid, $b->{error};
3150 print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";