]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/lib/Bweb.pm
ebl add a check to group_stat
[bacula/bacula] / gui / bweb / lib / Bweb.pm
1 ################################################################
2 use strict;
3
4 =head1 LICENSE
5
6    Bweb - A Bacula web interface
7    Bacula® - The Network Backup Solution
8
9    Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
10
11    The main author of Bweb is Eric Bollengier.
12    The main author of Bacula is Kern Sibbald, with contributions from
13    many others, a complete list can be found in the file AUTHORS.
14
15    This program is Free Software; you can redistribute it and/or
16    modify it under the terms of version two of the GNU General Public
17    License as published by the Free Software Foundation plus additions
18    that are listed in the file LICENSE.
19
20    This program is distributed in the hope that it will be useful, but
21    WITHOUT ANY WARRANTY; without even the implied warranty of
22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23    General Public License for more details.
24
25    You should have received a copy of the GNU General Public License
26    along with this program; if not, write to the Free Software
27    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28    02110-1301, USA.
29
30    Bacula® is a registered trademark of John Walker.
31    The licensor of Bacula is the Free Software Foundation Europe
32    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
33    Switzerland, email:ftf@fsfeurope.org.
34
35 =head1 VERSION
36
37     $Id$
38
39 =cut
40
41 package Bweb::Gui;
42
43 =head1 PACKAGE
44
45     Bweb::Gui - Base package for all Bweb object
46
47 =head2 DESCRIPTION
48
49     This package define base fonction like new, display, etc..
50
51 =cut
52
53 use HTML::Template;
54 our $template_dir='/usr/share/bweb/tpl';
55
56 =head1 FUNCTION
57
58     new - creation a of new Bweb object
59
60 =head2 DESCRIPTION
61
62     This function take an hash of argument and place them
63     on bless ref
64
65     IE : $obj = new Obj(name => 'test', age => '10');
66
67          $obj->{name} eq 'test' and $obj->{age} eq 10
68
69 =cut
70
71 sub new
72 {
73     my ($class, %arg) = @_;
74     my $self = bless {
75         name => undef,
76     }, $class;
77
78     map { $self->{lc($_)} = $arg{$_} } keys %arg ;
79
80     return $self;
81 }
82
83 sub debug
84 {
85     my ($self, $what) = @_;
86
87     if ($self->{debug}) {
88         if (ref $what) {
89             print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
90         } else {
91             print "<pre>$what</pre>";
92         }
93     }
94 }
95
96 =head1 FUNCTION
97
98     error - display an error to the user
99
100 =head2 DESCRIPTION
101
102     this function set $self->{error} with arg, display a message with
103     error.tpl and return 0
104
105 =head2 EXAMPLE
106
107     unless (...) {
108         return $self->error("Can't use this file");
109     }
110
111 =cut
112
113 sub error
114 {
115     my ($self, $what) = @_;
116     $self->{error} = $what;
117     $self->display($self, 'error.tpl');
118     return 0;
119 }
120
121 =head1 FUNCTION
122
123     display - display an html page with HTML::Template
124
125 =head2 DESCRIPTION
126
127     this function is use to render all html codes. it takes an
128     ref hash as arg in which all param are usable in template.
129
130     it will use global template_dir to search the template file.
131
132     hash keys are not sensitive. See HTML::Template for more
133     explanations about the hash ref. (it's can be quiet hard to understand) 
134
135 =head2 EXAMPLE
136
137     $ref = { name => 'me', age => 26 };
138     $self->display($ref, "people.tpl");
139
140 =cut
141
142 sub display
143 {
144     my ($self, $hash, $tpl) = @_ ;
145     
146     my $template = HTML::Template->new(filename => $tpl,
147                                        path =>[$template_dir],
148                                        die_on_bad_params => 0,
149                                        case_sensitive => 0);
150
151     foreach my $var (qw/limit offset/) {
152
153         unless ($hash->{$var}) {
154             my $value = CGI::param($var) || '';
155
156             if ($value =~ /^(\d+)$/) {
157                 $template->param($var, $1) ;
158             }
159         }
160     }
161
162     $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
163     $template->param('loginname', CGI::remote_user());
164
165     $template->param($hash);
166     print $template->output();
167 }
168 1;
169
170 ################################################################
171
172 package Bweb::Config;
173
174 use base q/Bweb::Gui/;
175
176 =head1 PACKAGE
177     
178     Bweb::Config - read, write, display, modify configuration
179
180 =head2 DESCRIPTION
181
182     this package is used for manage configuration
183
184 =head2 USAGE
185
186     $conf = new Bweb::Config(config_file => '/path/to/conf');
187     $conf->load();
188
189     $conf->edit();
190
191     $conf->save();
192
193 =cut
194
195 use CGI;
196
197 =head1 PACKAGE VARIABLE
198
199     %k_re - hash of all acceptable option.
200
201 =head2 DESCRIPTION
202
203     this variable permit to check all option with a regexp.
204
205 =cut
206
207 our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
208               user     => qr/^([\w\d\.-]+)$/i,
209               password => qr/^(.*)$/i,
210               fv_write_path => qr!^([/\w\d\.-]*)$!,
211               template_dir => qr!^([/\w\d\.-]+)$!,
212               debug    => qr/^(on)?$/,
213               email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
214               graph_font  => qr!^([/\w\d\.-]+.ttf)$!,
215               bconsole    => qr!^(.+)?$!,
216               syslog_file => qr!^(.+)?$!,
217               log_dir     => qr!^(.+)?$!,
218               stat_job_table => qr!^(\w*)$!,
219               display_log_time => qr!^(on)?$!,
220               );
221
222 =head1 FUNCTION
223
224     load - load config_file
225
226 =head2 DESCRIPTION
227
228     this function load the specified config_file.
229
230 =cut
231
232 sub load
233 {
234     my ($self) = @_ ;
235
236     unless (open(FP, $self->{config_file}))
237     {
238         return $self->error("can't load config_file $self->{config_file} : $!");
239     }
240     my $f=''; my $tmpbuffer;
241     while(read FP,$tmpbuffer,4096)
242     {
243         $f .= $tmpbuffer;
244     }
245     close(FP);
246
247     my $VAR1;
248
249     no strict; # I have no idea of the contents of the file
250     eval "$f" ;
251     use strict;
252
253     if ($f and $@) {
254         $self->load_old();
255         $self->save();
256         return $self->error("If you update from an old bweb install, your must reload this page and if it's fail again, you have to configure bweb again...") ;
257     }
258
259     foreach my $k (keys %$VAR1) {
260         $self->{$k} = $VAR1->{$k};
261     }
262
263     return 1;
264 }
265
266 =head1 FUNCTION
267
268     load_old - load old configuration format
269
270 =cut
271
272 sub load_old
273 {
274     my ($self) = @_ ;
275
276     unless (open(FP, $self->{config_file}))
277     {
278         return $self->error("$self->{config_file} : $!");
279     }
280
281     while (my $line = <FP>)
282     {
283         chomp($line);
284         my ($k, $v) = split(/\s*=\s*/, $line, 2);
285         if ($k_re{$k}) {
286             $self->{$k} = $v;
287         }
288     }
289
290     close(FP);
291     return 1;
292 }
293
294 =head1 FUNCTION
295
296     save - save the current configuration to config_file
297
298 =cut
299
300 sub save
301 {
302     my ($self) = @_ ;
303
304     if ($self->{ach_list}) {
305         # shortcut for display_begin
306         $self->{achs} = [ map {{ name => $_ }} 
307                           keys %{$self->{ach_list}}
308                         ];
309     }
310
311     unless (open(FP, ">$self->{config_file}"))
312     {
313         return $self->error("$self->{config_file} : $!\n" .
314                             "You must add this to your config file\n" 
315                             . Data::Dumper::Dumper($self));
316     }
317
318     print FP Data::Dumper::Dumper($self);
319     
320     close(FP);       
321     return 1;
322 }
323
324 =head1 FUNCTIONS
325     
326     edit, view, modify - html form ouput
327
328 =cut
329
330 sub edit
331 {
332     my ($self) = @_ ;
333
334     $self->display($self, "config_edit.tpl");
335 }
336
337 sub view
338 {
339     my ($self) = @_ ;
340     $self->display($self, "config_view.tpl");
341 }
342
343 sub modify
344 {
345     my ($self) = @_;
346     
347     $self->{error} = '';
348     $self->{debug} = 0;
349
350     foreach my $k (CGI::param())
351     {
352         next unless (exists $k_re{$k}) ;
353         my $val = CGI::param($k);
354         if ($val =~ $k_re{$k}) {
355             $self->{$k} = $1;
356         } else {
357             $self->{error} .= "bad parameter : $k = [$val]";
358         }
359     }
360
361     $self->view();
362
363     if ($self->{error}) {       # an error as occured
364         $self->display($self, 'error.tpl');
365     } else {
366         $self->save();
367     }
368 }
369
370 1;
371
372 ################################################################
373
374 package Bweb::Client;
375
376 use base q/Bweb::Gui/;
377
378 =head1 PACKAGE
379     
380     Bweb::Client - Bacula FD
381
382 =head2 DESCRIPTION
383
384     this package is use to do all Client operations like, parse status etc...
385
386 =head2 USAGE
387
388     $client = new Bweb::Client(name => 'zog-fd');
389     $client->status();            # do a 'status client=zog-fd'
390
391 =cut
392
393 =head1 FUNCTION
394
395     display_running_job - Html display of a running job
396
397 =head2 DESCRIPTION
398
399     this function is used to display information about a current job
400
401 =cut
402
403 sub display_running_job
404 {
405     my ($self, $conf, $jobid) = @_ ;
406
407     my $status = $self->status($conf);
408
409     if ($jobid) {
410         if ($status->{$jobid}) {
411             $self->display($status->{$jobid}, "client_job_status.tpl");
412         }
413     } else {
414         for my $id (keys %$status) {
415             $self->display($status->{$id}, "client_job_status.tpl");
416         }
417     }
418 }
419
420 =head1 FUNCTION
421
422     $client = new Bweb::Client(name => 'plume-fd');
423                                
424     $client->status($bweb);
425
426 =head2 DESCRIPTION
427
428     dirty hack to parse "status client=xxx-fd"
429
430 =head2 INPUT
431
432    JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
433        Backup Job started: 06-jun-06 17:22
434        Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
435        Files Examined=10,697
436        Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
437        SDReadSeqNo=5 fd=5
438    
439 =head2 OUTPUT
440
441     $VAR1 = { 105 => {
442                 JobName => Full_plume.2006-06-06_17.22.23,
443                 JobId => 105,
444                 Files => 8,971,
445                 Bytes => 194,484,132,
446                 ...
447               },
448               ...
449     };
450
451 =cut
452
453 sub status
454 {
455     my ($self, $conf) = @_ ;
456
457     if (defined $self->{cur_jobs}) {
458         return $self->{cur_jobs} ;
459     }
460
461     my $arg = {};
462     my $b = new Bconsole(pref => $conf);
463     my $ret = $b->send_cmd("st client=$self->{name}");
464     my @param;
465     my $jobid;
466
467     for my $r (split(/\n/, $ret)) {
468         chomp($r);
469         $r =~ s/(^\s+|\s+$)//g;
470         if ($r =~ /JobId (\d+) Job (\S+)/) {
471             if ($jobid) {
472                 $arg->{$jobid} = { @param, JobId => $jobid } ;
473             }
474
475             $jobid = $1;
476             @param = ( JobName => $2 );
477
478         } elsif ($r =~ /=.+=/) {
479             push @param, split(/\s+|\s*=\s*/, $r) ;
480
481         } elsif ($r =~ /=/) {   # one per line
482             push @param, split(/\s*=\s*/, $r) ;
483
484         } elsif ($r =~ /:/) {   # one per line
485             push @param, split(/\s*:\s*/, $r, 2) ;
486         }
487     }
488
489     if ($jobid and @param) {
490         $arg->{$jobid} = { @param,
491                            JobId => $jobid, 
492                            Client => $self->{name},
493                        } ;
494     }
495
496     $self->{cur_jobs} = $arg ;
497
498     return $arg;
499 }
500 1;
501
502 ################################################################
503
504 package Bweb::Autochanger;
505
506 use base q/Bweb::Gui/;
507
508 =head1 PACKAGE
509     
510     Bweb::Autochanger - Object to manage Autochanger
511
512 =head2 DESCRIPTION
513
514     this package will parse the mtx output and manage drives.
515
516 =head2 USAGE
517
518     $auto = new Bweb::Autochanger(precmd => 'sudo');
519     or
520     $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
521                                   
522     $auto->status();
523
524     $auto->slot_is_full(10);
525     $auto->transfer(10, 11);
526
527 =cut
528
529 sub new
530 {
531     my ($class, %arg) = @_;
532
533     my $self = bless {
534         name  => '',    # autochanger name
535         label => {},    # where are volume { label1 => 40, label2 => drive0 }
536         drive => [],    # drive use [ 'media1', 'empty', ..]
537         slot  => [],    # slot use [ undef, 'empty', 'empty', ..] no slot 0
538         io    => [],    # io slot number list [ 41, 42, 43...]
539         info  => {slot => 0,    # informations (slot, drive, io)
540                   io   => 0,
541                   drive=> 0,
542                  },
543         mtxcmd => '/usr/sbin/mtx',
544         debug => 0,
545         device => '/dev/changer',
546         precmd => '',   # ssh command
547         bweb => undef,  # link to bacula web object (use for display) 
548     } ;
549
550     map { $self->{lc($_)} = $arg{$_} } keys %arg ;
551
552     return $self;
553 }
554
555 =head1 FUNCTION
556
557     status - parse the output of mtx status
558
559 =head2 DESCRIPTION
560
561     this function will launch mtx status and parse the output. it will
562     give a perlish view of the autochanger content.
563
564     it uses ssh if the autochanger is on a other host.
565
566 =cut
567
568 sub status
569 {
570     my ($self) = @_;
571     my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
572
573     # TODO : reset all infos
574     $self->{info}->{drive} = 0;
575     $self->{info}->{slot}  = 0;
576     $self->{info}->{io}    = 0;
577
578     #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
579
580 #
581 #  Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
582 #Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
583 #Data Transfer Element 1:Empty
584 #      Storage Element 1:Empty
585 #      Storage Element 2:Full :VolumeTag=000002
586 #      Storage Element 3:Empty
587 #      Storage Element 4:Full :VolumeTag=000004
588 #      Storage Element 5:Full :VolumeTag=000001
589 #      Storage Element 6:Full :VolumeTag=000003
590 #      Storage Element 7:Empty
591 #      Storage Element 41 IMPORT/EXPORT:Empty
592 #      Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
593 #
594
595     for my $l (@out) {
596
597         #          Storage Element 7:Empty
598         #          Storage Element 2:Full :VolumeTag=000002
599         if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
600
601             if ($2 eq 'Empty') {
602                 $self->set_empty_slot($1);
603             } else {
604                 $self->set_slot($1, $4);
605             }
606
607         } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
608
609             if ($2 eq 'Empty') {
610                 $self->set_empty_drive($1);
611             } else {
612                 $self->set_drive($1, $4, $6);
613             }
614
615         } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/) 
616         {
617             if ($2 eq 'Empty') {
618                 $self->set_empty_io($1);
619             } else {
620                 $self->set_io($1, $4);
621             }
622
623 #       Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
624
625         } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
626             $self->{info}->{drive} = $1;
627             $self->{info}->{slot} = $2;
628             if ($l =~ /(\d+)\s+Import/) {
629                 $self->{info}->{io} = $1 ;
630             } else {
631                 $self->{info}->{io} = 0;
632             }
633         } 
634     }
635
636     $self->debug($self) ;
637 }
638
639 sub is_slot_loaded
640 {
641     my ($self, $slot) = @_;
642
643     # no barcodes
644     if ($self->{slot}->[$slot] eq 'loaded') {
645         return 1;
646     } 
647
648     my $label = $self->{slot}->[$slot] ;
649
650     return $self->is_media_loaded($label);
651 }
652
653 sub unload
654 {
655     my ($self, $drive, $slot) = @_;
656
657     return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
658     return 0 if     ($self->slot_is_full($slot)) ;
659
660     my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
661     
662     if ($? == 0) {
663         my $content = $self->get_slot($slot);
664         print "content = $content<br/> $drive => $slot<br/>";
665         $self->set_empty_drive($drive);
666         $self->set_slot($slot, $content);
667         return 1;
668     } else {
669         $self->{error} = $out;
670         return 0;
671     }
672 }
673
674 # TODO: load/unload have to use mtx script from bacula
675 sub load
676 {
677     my ($self, $drive, $slot) = @_;
678
679     return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
680     return 0 unless ($self->slot_is_full($slot)) ;
681
682     print "Loading drive $drive with slot $slot<br/>\n";
683     my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
684     
685     if ($? == 0) {
686         my $content = $self->get_slot($slot);
687         print "content = $content<br/> $slot => $drive<br/>";
688         $self->set_drive($drive, $slot, $content);
689         return 1;
690     } else {
691         $self->{error} = $out;
692         print $out;
693         return 0;
694     }
695 }
696
697 sub is_media_loaded
698 {
699     my ($self, $media) = @_;
700
701     unless ($self->{label}->{$media}) {
702         return 0;
703     }
704
705     if ($self->{label}->{$media} =~ /drive\d+/) {
706         return 1;
707     }
708
709     return 0;
710 }
711
712 sub have_io
713 {
714     my ($self) = @_;
715     return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
716 }
717
718 sub set_io
719 {
720     my ($self, $slot, $tag) = @_;
721     $self->{slot}->[$slot] = $tag || 'full';
722     push @{ $self->{io} }, $slot;
723
724     if ($tag) {
725         $self->{label}->{$tag} = $slot;
726     } 
727 }
728
729 sub set_empty_io
730 {
731     my ($self, $slot) = @_;
732
733     push @{ $self->{io} }, $slot;
734
735     unless ($self->{slot}->[$slot]) {       # can be loaded (parse before) 
736         $self->{slot}->[$slot] = 'empty';
737     }
738 }
739
740 sub get_slot
741 {
742     my ($self, $slot) = @_;
743     return $self->{slot}->[$slot];
744 }
745
746 sub set_slot
747 {
748     my ($self, $slot, $tag) = @_;
749     $self->{slot}->[$slot] = $tag || 'full';
750
751     if ($tag) {
752         $self->{label}->{$tag} = $slot;
753     }
754 }
755
756 sub set_empty_slot
757 {
758     my ($self, $slot) = @_;
759
760     unless ($self->{slot}->[$slot]) {       # can be loaded (parse before) 
761         $self->{slot}->[$slot] = 'empty';
762     }
763 }
764
765 sub set_empty_drive
766 {
767     my ($self, $drive) = @_;
768     $self->{drive}->[$drive] = 'empty';
769 }
770
771 sub set_drive
772 {
773     my ($self, $drive, $slot, $tag) = @_;
774     $self->{drive}->[$drive] = $tag || $slot;
775
776     $self->{slot}->[$slot] = $tag || 'loaded';
777
778     if ($tag) {
779         $self->{label}->{$tag} = "drive$drive";
780     }
781 }
782
783 sub slot_is_full
784 {
785     my ($self, $slot) = @_;
786     
787     # slot don't exists => full
788     if (not defined $self->{slot}->[$slot]) {
789         return 0 ;
790     }
791
792     if ($self->{slot}->[$slot] eq 'empty') {
793         return 0;
794     }
795     return 1;                   # vol, full, loaded
796 }
797
798 sub slot_get_first_free
799 {
800     my ($self) = @_;
801     for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
802         return $slot unless ($self->slot_is_full($slot));
803     }
804 }
805
806 sub io_get_first_free
807 {
808     my ($self) = @_;
809     
810     foreach my $slot (@{ $self->{io} }) {
811         return $slot unless ($self->slot_is_full($slot));       
812     }
813     return 0;
814 }
815
816 sub get_media_slot
817 {
818     my ($self, $media) = @_;
819
820     return $self->{label}->{$media} ;    
821 }
822
823 sub have_media
824 {
825     my ($self, $media) = @_;
826
827     return defined $self->{label}->{$media} ;    
828 }
829
830 sub send_to_io
831 {
832     my ($self, $slot) = @_;
833
834     unless ($self->slot_is_full($slot)) {
835         print "Autochanger $self->{name} slot $slot is empty\n";
836         return 1;               # ok
837     }
838
839     # first, eject it
840     if ($self->is_slot_loaded($slot)) {
841         # bconsole->umount
842         # self->eject
843         print "Autochanger $self->{name} $slot is currently in use\n";
844         return 0;
845     }
846
847     # autochanger must have I/O
848     unless ($self->have_io()) {
849         print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
850         return 0;
851     }
852
853     my $dst = $self->io_get_first_free();
854
855     unless ($dst) {
856         print "Autochanger $self->{name} you must empty I/O first\n";
857     }
858
859     $self->transfer($slot, $dst);
860 }
861
862 sub transfer
863 {
864     my ($self, $src, $dst) = @_ ;
865     if ($self->{debug}) {
866         print "<pre>$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst</pre>\n";
867     }
868     my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
869     
870     if ($? == 0) {
871         my $content = $self->get_slot($src);
872         $self->{slot}->[$src] = 'empty';
873         $self->set_slot($dst, $content);
874         return 1;
875     } else {
876         $self->{error} = $out;
877         return 0;
878     }
879 }
880
881 sub get_drive_name
882 {
883     my ($self, $index) = @_;
884     return $self->{drive_name}->[$index];
885 }
886
887 # TODO : do a tapeinfo request to get informations
888 sub tapeinfo
889 {
890     my ($self) = @_;
891 }
892
893 sub clear_io
894 {
895     my ($self) = @_;
896
897     for my $slot (@{$self->{io}})
898     {
899         if ($self->is_slot_loaded($slot)) {
900             print "$slot is currently loaded\n";
901             next;
902         }
903
904         if ($self->slot_is_full($slot))
905         {
906             my $free = $self->slot_get_first_free() ;
907             print "move $slot to $free :\n";
908
909             if ($free) {
910                 if ($self->transfer($slot, $free)) {
911                     print "<img src='/bweb/T.png' alt='ok'><br/>\n";
912                 } else {
913                     print "<img src='/bweb/E.png' alt='ok' title='$self->{error}'><br/>\n";
914                 }
915                 
916             } else {
917                 $self->{error} = "<img src='/bweb/E.png' alt='ok' title='E : Can t find free slot'><br/>\n";
918             }
919         }
920     }
921 }
922
923 # TODO : this is with mtx status output,
924 # we can do an other function from bacula view (with StorageId)
925 sub display_content
926 {
927     my ($self) = @_;
928     my $bweb = $self->{bweb};
929
930     # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
931     my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
932
933     my $query="
934 SELECT Media.VolumeName  AS volumename,
935        Media.VolStatus   AS volstatus,
936        Media.LastWritten AS lastwritten,
937        Media.VolBytes    AS volbytes,
938        Media.MediaType   AS mediatype,
939        Media.Slot        AS slot,
940        Media.InChanger   AS inchanger,
941        Pool.Name         AS name,
942        $bweb->{sql}->{FROM_UNIXTIME}(
943           $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
944         + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
945        ) AS expire
946 FROM Media 
947  INNER JOIN Pool USING (PoolId) 
948
949 WHERE Media.VolumeName IN ($media_list)
950 ";
951
952     my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
953
954     # TODO : verify slot and bacula slot
955     my $param = [];
956     my @to_update;
957
958     for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
959
960         if ($self->slot_is_full($slot)) {
961
962             my $vol = $self->{slot}->[$slot];
963             if (defined $all->{$vol}) {    # TODO : autochanger without barcodes 
964
965                 my $bslot = $all->{$vol}->{slot} ;
966                 my $inchanger = $all->{$vol}->{inchanger};
967
968                 # if bacula slot or inchanger flag is bad, we display a message
969                 if ($bslot != $slot or !$inchanger) {
970                     push @to_update, $slot;
971                 }
972                 
973                 $all->{$vol}->{realslot} = $slot;
974
975                 push @{ $param }, $all->{$vol};
976
977             } else {            # empty or no label
978                 push @{ $param }, {realslot => $slot,
979                                    volstatus => 'Unknown',
980                                    volumename => $self->{slot}->[$slot]} ;
981             }
982         } else {                # empty
983             push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
984         }
985     }
986
987     my $i=0; my $drives = [] ;
988     foreach my $d (@{ $self->{drive} }) {
989         $drives->[$i] = { index => $i,
990                           load  => $self->{drive}->[$i],
991                           name  => $self->{drive_name}->[$i],
992                       };
993         $i++;
994     }
995
996     $bweb->display({ Name   => $self->{name},
997                      nb_drive => $self->{info}->{drive},
998                      nb_io => $self->{info}->{io},
999                      Drives => $drives,
1000                      Slots  => $param,
1001                      Update => scalar(@to_update) },
1002                    'ach_content.tpl');
1003
1004 }
1005
1006 1;
1007
1008
1009 ################################################################
1010
1011 package Bweb;
1012
1013 use base q/Bweb::Gui/;
1014
1015 =head1 PACKAGE
1016
1017     Bweb - main Bweb package
1018
1019 =head2
1020
1021     this package is use to compute and display informations
1022
1023 =cut
1024
1025 use DBI;
1026 use POSIX qw/strftime/;
1027
1028 our $config_file='/etc/bacula/bweb.conf';
1029
1030 our $cur_id=0;
1031
1032 =head1 VARIABLE
1033
1034     %sql_func - hash to make query mysql/postgresql compliant
1035
1036 =cut
1037
1038 our %sql_func = ( 
1039           Pg => { 
1040               UNIX_TIMESTAMP => '',
1041               FROM_UNIXTIME => '',
1042               TO_SEC => " interval '1 second' * ",
1043               SEC_TO_INT => "SEC_TO_INT",
1044               SEC_TO_TIME => '',
1045               MATCH => " ~ ",
1046               STARTTIME_DAY  => " date_trunc('day', Job.StartTime) ",
1047               STARTTIME_HOUR => " date_trunc('hour', Job.StartTime) ",
1048               STARTTIME_MONTH  => " date_trunc('month', Job.StartTime) ",
1049               STARTTIME_PHOUR=> " date_part('hour', Job.StartTime) ",
1050               STARTTIME_PDAY => " date_part('day', Job.StartTime) ",
1051               STARTTIME_PMONTH => " date_part('month', Job.StartTime) ",
1052               DB_SIZE => " SELECT pg_database_size(current_database()) ",
1053           },
1054           mysql => {
1055               UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
1056               FROM_UNIXTIME => 'FROM_UNIXTIME',
1057               SEC_TO_INT => '',
1058               TO_SEC => '',
1059               SEC_TO_TIME => 'SEC_TO_TIME',
1060               MATCH => " REGEXP ",
1061               STARTTIME_DAY  => " DATE_FORMAT(StartTime, '%Y-%m-%d') ",
1062               STARTTIME_HOUR => " DATE_FORMAT(StartTime, '%Y-%m-%d %H') ",
1063               STARTTIME_MONTH => " DATE_FORMAT(StartTime, '%Y-%m') ",
1064               STARTTIME_PHOUR=> " DATE_FORMAT(StartTime, '%H') ",
1065               STARTTIME_PDAY => " DATE_FORMAT(StartTime, '%d') ",
1066               STARTTIME_PMONTH => " DATE_FORMAT(StartTime, '%m') ",
1067               # with mysql < 5, you have to play with the ugly SHOW command
1068               DB_SIZE => " SELECT 0 ",
1069               # works only with mysql 5
1070               # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
1071           },
1072          );
1073
1074 sub dbh_disconnect
1075 {
1076     my ($self) = @_;
1077     if ($self->{dbh}) {
1078        $self->{dbh}->disconnect();
1079        undef $self->{dbh};
1080     }
1081 }
1082
1083 sub dbh_selectall_arrayref
1084 {
1085     my ($self, $query) = @_;
1086     $self->connect_db();
1087     $self->debug($query);
1088     return $self->{dbh}->selectall_arrayref($query);
1089 }
1090
1091 sub dbh_join
1092 {
1093     my ($self, @what) = @_;
1094     return join(',', $self->dbh_quote(@what)) ;
1095 }
1096
1097 sub dbh_quote
1098 {
1099     my ($self, @what) = @_;
1100
1101     $self->connect_db();
1102     if (wantarray) {
1103         return map { $self->{dbh}->quote($_) } @what;
1104     } else {
1105         return $self->{dbh}->quote($what[0]) ;
1106     }
1107 }
1108
1109 sub dbh_do
1110 {
1111     my ($self, $query) = @_ ; 
1112     $self->connect_db();
1113     $self->debug($query);
1114     return $self->{dbh}->do($query);
1115 }
1116
1117 sub dbh_selectall_hashref
1118 {
1119     my ($self, $query, $join) = @_;
1120     
1121     $self->connect_db();
1122     $self->debug($query);
1123     return $self->{dbh}->selectall_hashref($query, $join) ;
1124 }
1125
1126 sub dbh_selectrow_hashref
1127 {
1128     my ($self, $query) = @_;
1129     
1130     $self->connect_db();
1131     $self->debug($query);
1132     return $self->{dbh}->selectrow_hashref($query) ;
1133 }
1134
1135 # display Mb/Gb/Kb
1136 sub human_size
1137 {
1138     my @unit = qw(b Kb Mb Gb Tb);
1139     my $val = shift || 0;
1140     my $i=0;
1141     my $format = '%i %s';
1142     while ($val / 1024 > 1) {
1143         $i++;
1144         $val /= 1024;
1145     }
1146     $format = ($i>0)?'%0.1f %s':'%i %s';
1147     return sprintf($format, $val, $unit[$i]);
1148 }
1149
1150 # display Day, Hour, Year
1151 sub human_sec
1152 {
1153     use integer;
1154
1155     my $val = shift;
1156     $val /= 60;                 # sec -> min
1157
1158     if ($val / 60 <= 1) {
1159         return "$val mins";
1160     } 
1161
1162     $val /= 60;                 # min -> hour
1163     if ($val / 24 <= 1) {
1164         return "$val hours";
1165     } 
1166
1167     $val /= 24;                 # hour -> day
1168     if ($val / 365 < 2) {
1169         return "$val days";
1170     } 
1171
1172     $val /= 365 ;               # day -> year
1173
1174     return "$val years";   
1175 }
1176
1177 # get Day, Hour, Year
1178 sub from_human_sec
1179 {
1180     use integer;
1181
1182     my $val = shift;
1183     unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
1184         return 0;
1185     }
1186
1187     my %times = ( m   => 60,
1188                   h   => 60*60,
1189                   d   => 60*60*24,
1190                   m   => 60*60*24*31,
1191                   y   => 60*60*24*365,
1192                   );
1193     my $mult = $times{$2} || 0;
1194
1195     return $1 * $mult;   
1196 }
1197
1198
1199 sub connect_db
1200 {
1201     my ($self) = @_;
1202
1203     unless ($self->{dbh}) {
1204         $self->{dbh} = DBI->connect($self->{info}->{dbi}, 
1205                                     $self->{info}->{user},
1206                                     $self->{info}->{password});
1207
1208         $self->error("Can't connect to your database:\n$DBI::errstr\n")
1209             unless ($self->{dbh});
1210
1211         $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
1212
1213         if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
1214             $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
1215         }
1216     }
1217 }
1218
1219 sub new
1220 {
1221     my ($class, %arg) = @_;
1222     my $self = bless { 
1223         dbh => undef,           # connect_db();
1224         info => {
1225             dbi   => '', # DBI:Pg:database=bacula;host=127.0.0.1
1226             user  => 'bacula',
1227             password => 'test', 
1228         },
1229     } ;
1230
1231     map { $self->{lc($_)} = $arg{$_} } keys %arg ;
1232
1233     if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
1234         $self->{sql} = $sql_func{$1};
1235     }
1236
1237     $self->{debug} = $self->{info}->{debug};
1238     $Bweb::Gui::template_dir = $self->{info}->{template_dir};
1239
1240     return $self;
1241 }
1242
1243 sub display_begin
1244 {
1245     my ($self) = @_;
1246     $self->display($self->{info}, "begin.tpl");
1247 }
1248
1249 sub display_end
1250 {
1251     my ($self) = @_;
1252     $self->display($self->{info}, "end.tpl");
1253 }
1254
1255 sub display_clients
1256 {
1257     my ($self) = @_;
1258
1259     my $where='';
1260     my $arg = $self->get_form("client", "qre_client", "jclient_groups");
1261
1262     if ($arg->{qre_client}) {
1263         $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
1264     } elsif ($arg->{client}) {
1265         $where = "WHERE Name = '$arg->{client}' ";
1266     } elsif ($arg->{jclient_groups}) {
1267         $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
1268                   JOIN client_group USING (client_group_id)
1269                   WHERE client_group_name IN ($arg->{jclient_groups})";
1270     }
1271
1272     my $query = "
1273 SELECT Name   AS name,
1274        Uname  AS uname,
1275        AutoPrune AS autoprune,
1276        FileRetention AS fileretention,
1277        JobRetention  AS jobretention
1278 FROM Client
1279 $where
1280 ";
1281
1282     my $all = $self->dbh_selectall_hashref($query, 'name') ;
1283
1284     my $dsp = { ID => $cur_id++,
1285                 clients => [ values %$all] };
1286
1287     $self->display($dsp, "client_list.tpl") ;
1288 }
1289
1290 sub get_limit
1291 {
1292     my ($self, %arg) = @_;
1293
1294     my $limit = '';
1295     my $label = '';
1296
1297     if ($arg{age}) {
1298         $limit = 
1299   "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) 
1300          > 
1301        ( $self->{sql}->{UNIX_TIMESTAMP}(NOW()) 
1302          - 
1303          $self->{sql}->{TO_SEC}($arg{age})
1304        )" ;
1305
1306         $label = "last " . human_sec($arg{age});
1307     }
1308
1309     if ($arg{groupby}) {
1310         $limit .= " GROUP BY $arg{groupby} ";
1311     }
1312
1313     if ($arg{order}) {
1314         $limit .= " ORDER BY $arg{order} ";
1315     }
1316
1317     if ($arg{limit}) {
1318         $limit .= " LIMIT $arg{limit} ";
1319         $label .= " limited to $arg{limit}";
1320     }
1321
1322     if ($arg{offset}) {
1323         $limit .= " OFFSET $arg{offset} ";
1324         $label .= " with $arg{offset} offset ";
1325     }
1326
1327     unless ($label) {
1328         $label = 'no filter';
1329     }
1330
1331     return ($limit, $label);
1332 }
1333
1334 =head1 FUNCTION
1335
1336     $bweb->get_form(...) - Get useful stuff
1337
1338 =head2 DESCRIPTION
1339
1340     This function get and check parameters against regexp.
1341     
1342     If word begin with 'q', the return will be quoted or join quoted
1343     if it's end with 's'.
1344     
1345
1346 =head2 EXAMPLE
1347
1348     $bweb->get_form('jobid', 'qclient', 'qpools') ;
1349
1350     { jobid    => 12,
1351       qclient  => 'plume-fd',
1352       qpools   => "'plume-fd', 'test-fd', '...'",
1353     }
1354
1355 =cut
1356
1357 sub get_form
1358 {
1359     my ($self, @what) = @_;
1360     my %what = map { $_ => 1 } @what;
1361     my %ret;
1362
1363     my %opt_i = (
1364                  limit  => 100,
1365                  cost   =>  10,
1366                  offset =>   0,
1367                  width  => 640,
1368                  height => 480,
1369                  jobid  =>   0,
1370                  slot   =>   0,
1371                  drive  =>   0,
1372                  priority => 10,
1373                  age    => 60*60*24*7,
1374                  days   => 1,
1375                  maxvoljobs  => 0,
1376                  maxvolbytes => 0,
1377                  maxvolfiles => 0,
1378                  );
1379
1380     my %opt_ss =(               # string with space
1381                  job     => 1,
1382                  storage => 1,
1383                  );
1384     my %opt_s = (               # default to ''
1385                  ach    => 1,
1386                  status => 1,
1387                  volstatus => 1,
1388                  inchanger => 1,
1389                  client => 1,
1390                  level  => 1,
1391                  pool   => 1,
1392                  media  => 1,
1393                  ach    => 1,
1394                  jobtype=> 1,
1395                  graph  => 1,
1396                  gtype  => 1,
1397                  type   => 1,
1398                  poolrecycle => 1,
1399                  replace => 1,
1400                  );
1401     my %opt_p = (               # option with path
1402                  fileset=> 1,
1403                  mtxcmd => 1,
1404                  precmd => 1,
1405                  device => 1,
1406                  where  => 1,
1407                  );
1408     my %opt_r = (regexwhere => 1);
1409
1410     my %opt_d = (               # option with date
1411                  voluseduration=> 1,
1412                  volretention => 1,
1413                 );
1414
1415     foreach my $i (@what) {
1416         if (exists $opt_i{$i}) {# integer param
1417             my $value = CGI::param($i) || $opt_i{$i} ;
1418             if ($value =~ /^(\d+)$/) {
1419                 $ret{$i} = $1;
1420             }
1421         } elsif ($opt_s{$i}) {  # simple string param
1422             my $value = CGI::param($i) || '';
1423             if ($value =~ /^([\w\d\.-]+)$/) {
1424                 $ret{$i} = $1;
1425             }
1426         } elsif ($opt_ss{$i}) { # simple string param (with space)
1427             my $value = CGI::param($i) || '';
1428             if ($value =~ /^([\w\d\.\-\s]+)$/) {
1429                 $ret{$i} = $1;
1430             }
1431         } elsif ($i =~ /^j(\w+)s$/) { # quote join args "'arg1', 'arg2'"
1432             my @value = grep { ! /^\s*$/ } CGI::param($1) ;
1433             if (@value) {
1434                 $ret{$i} = $self->dbh_join(@value) ;
1435             }
1436
1437         } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
1438             my $value = CGI::param($1) ;
1439             if ($value) {
1440                 $ret{$i} = $self->dbh_quote($value);
1441             }
1442
1443         } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
1444             $ret{$i} = [ map { { name => $self->dbh_quote($_) } } 
1445                                            grep { ! /^\s*$/ } CGI::param($1) ];
1446         } elsif (exists $opt_p{$i}) {
1447             my $value = CGI::param($i) || '';
1448             if ($value =~ /^([\w\d\.\/\s:\@\-]+)$/) {
1449                 $ret{$i} = $1;
1450             }
1451         } elsif (exists $opt_r{$i}) {
1452             my $value = CGI::param($i) || '';
1453             if ($value =~ /^([^'"']+)$/) {
1454                 $ret{$i} = $1;
1455             }
1456         } elsif (exists $opt_d{$i}) {
1457             my $value = CGI::param($i) || '';
1458             if ($value =~ /^\s*(\d+\s+\w+)$/) {
1459                 $ret{$i} = $1;
1460             }
1461         }
1462     }
1463
1464     if ($what{slots}) {
1465         foreach my $s (CGI::param('slot')) {
1466             if ($s =~ /^(\d+)$/) {
1467                 push @{$ret{slots}}, $s;
1468             }
1469         }
1470     }
1471
1472     if ($what{when}) {
1473         my $when = CGI::param('when') || '';
1474         if ($when =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$/) {
1475             $ret{when} = $1;
1476         }
1477     }
1478
1479     if ($what{db_clients}) {
1480         my $query = "
1481 SELECT Client.Name as clientname
1482   FROM Client
1483 ";
1484
1485         my $clients = $self->dbh_selectall_hashref($query, 'clientname');
1486         $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} } 
1487                               values %$clients] ;
1488     }
1489
1490     if ($what{db_client_groups}) {
1491         my $query = "
1492 SELECT client_group_name AS name 
1493   FROM client_group
1494 ";
1495
1496         my $grps = $self->dbh_selectall_hashref($query, 'name');
1497         $ret{db_client_groups} = [sort {$a->{name} cmp $b->{name} } 
1498                                   values %$grps] ;
1499     }
1500
1501     if ($what{db_mediatypes}) {
1502         my $query = "
1503 SELECT MediaType as mediatype
1504   FROM MediaType
1505 ";
1506
1507         my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
1508         $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} } 
1509                                   values %$medias] ;
1510     }
1511
1512     if ($what{db_locations}) {
1513         my $query = "
1514 SELECT Location as location, Cost as cost 
1515   FROM Location
1516 ";
1517         my $loc = $self->dbh_selectall_hashref($query, 'location');
1518         $ret{db_locations} = [ sort { $a->{location} 
1519                                       cmp 
1520                                       $b->{location} 
1521                                   } values %$loc ];
1522     }
1523
1524     if ($what{db_pools}) {
1525         my $query = "SELECT Name as name FROM Pool";
1526
1527         my $all = $self->dbh_selectall_hashref($query, 'name') ;
1528         $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
1529     }
1530
1531     if ($what{db_filesets}) {
1532         my $query = "
1533 SELECT FileSet.FileSet AS fileset 
1534   FROM FileSet
1535 ";
1536
1537         my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
1538
1539         $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) } 
1540                                values %$filesets] ;
1541     }
1542
1543     if ($what{db_jobnames}) {
1544         my $query = "
1545 SELECT DISTINCT Job.Name AS jobname 
1546   FROM Job
1547 ";
1548
1549         my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
1550
1551         $ret{db_jobnames} = [sort {lc($a->{jobname}) cmp lc($b->{jobname}) } 
1552                                values %$jobnames] ;
1553     }
1554
1555     if ($what{db_devices}) {
1556         my $query = "
1557 SELECT Device.Name AS name
1558   FROM Device
1559 ";
1560
1561         my $devices = $self->dbh_selectall_hashref($query, 'name');
1562
1563         $ret{db_devices} = [sort {lc($a->{name}) cmp lc($b->{name}) } 
1564                                values %$devices] ;
1565     }
1566
1567     return \%ret;
1568 }
1569
1570 sub display_graph
1571 {
1572     my ($self) = @_;
1573
1574     my $fields = $self->get_form(qw/age level status clients filesets 
1575                                     graph gtype type
1576                                     db_clients limit db_filesets width height
1577                                     qclients qfilesets qjobnames db_jobnames/);
1578                                 
1579
1580     my $url = CGI::url(-full => 0,
1581                        -base => 0,
1582                        -query => 1);
1583     $url =~ s/^.+?\?//; # http://path/to/bweb.pl?arg => arg
1584
1585 # this organisation is to keep user choice between 2 click
1586 # TODO : fileset and client selection doesn't work
1587
1588     $self->display({
1589         url => $url,
1590         %$fields,
1591     }, "graph.tpl")
1592
1593 }
1594
1595 sub display_client_job
1596 {
1597     my ($self, %arg) = @_ ;
1598
1599     $arg{order} = ' Job.JobId DESC ';
1600     my ($limit, $label) = $self->get_limit(%arg);
1601
1602     my $clientname = $self->dbh_quote($arg{clientname});
1603
1604     my $query="
1605 SELECT DISTINCT Job.JobId       AS jobid,
1606                 Job.Name        AS jobname,
1607                 FileSet.FileSet AS fileset,
1608                 Level           AS level,
1609                 StartTime       AS starttime,
1610                 JobFiles        AS jobfiles, 
1611                 JobBytes        AS jobbytes,
1612                 JobStatus       AS jobstatus,
1613                 JobErrors       AS joberrors
1614
1615  FROM Client,Job,FileSet
1616  WHERE Client.Name=$clientname
1617  AND Client.ClientId=Job.ClientId
1618  AND Job.FileSetId=FileSet.FileSetId
1619  $limit
1620 ";
1621
1622     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
1623
1624     $self->display({ clientname => $arg{clientname},
1625                      Filter => $label,
1626                      ID => $cur_id++,
1627                      Jobs => [ values %$all ],
1628                    },
1629                    "display_client_job.tpl") ;
1630 }
1631
1632 sub get_selected_media_location
1633 {
1634     my ($self) = @_ ;
1635
1636     my $medias = $self->get_form('jmedias');
1637
1638     unless ($medias->{jmedias}) {
1639         return undef;
1640     }
1641
1642     my $query = "
1643 SELECT Media.VolumeName AS volumename, Location.Location AS location
1644 FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
1645 WHERE Media.VolumeName IN ($medias->{jmedias})
1646 ";
1647
1648     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1649   
1650     # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
1651     #               ..
1652     #             }
1653     # }
1654     return $all;
1655 }
1656
1657 sub move_media
1658 {
1659     my ($self) = @_ ;
1660
1661     my $medias = $self->get_selected_media_location();
1662
1663     unless ($medias) {
1664         return ;
1665     }
1666     
1667     my $elt = $self->get_form('db_locations');
1668
1669     $self->display({ ID => $cur_id++,
1670                      %$elt,     # db_locations
1671                      medias => [ 
1672             sort { $a->{volumename} cmp $b->{volumename} } values %$medias
1673                                ],
1674                      },
1675                    "move_media.tpl");
1676 }
1677
1678 sub help_extern
1679 {
1680     my ($self) = @_ ;
1681
1682     my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
1683     $self->debug($elt);
1684     $self->display($elt, "help_extern.tpl");
1685 }
1686
1687 sub help_extern_compute
1688 {
1689     my ($self) = @_;
1690
1691     my $number = CGI::param('limit') || '' ;
1692     unless ($number =~ /^(\d+)$/) {
1693         return $self->error("Bad arg number : $number ");
1694     }
1695
1696     my ($sql, undef) = $self->get_param('pools', 
1697                                         'locations', 'mediatypes');
1698
1699     my $query = "
1700 SELECT Media.VolumeName  AS volumename,
1701        Media.VolStatus   AS volstatus,
1702        Media.LastWritten AS lastwritten,
1703        Media.MediaType   AS mediatype,
1704        Media.VolMounts   AS volmounts,
1705        Pool.Name         AS name,
1706        Media.Recycle     AS recycle,
1707        $self->{sql}->{FROM_UNIXTIME}(
1708           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
1709         + $self->{sql}->{TO_SEC}(Media.VolRetention)
1710        ) AS expire
1711 FROM Media 
1712  INNER JOIN Pool     ON (Pool.PoolId = Media.PoolId)
1713  LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
1714
1715 WHERE Media.InChanger = 1
1716   AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
1717   $sql
1718 ORDER BY expire DESC, recycle, Media.VolMounts DESC
1719 LIMIT $number
1720 " ;
1721     
1722     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1723
1724     $self->display({ Medias => [ values %$all ] },
1725                    "help_extern_compute.tpl");
1726 }
1727
1728 sub help_intern
1729 {
1730     my ($self) = @_ ;
1731
1732     my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
1733     $self->display($param, "help_intern.tpl");
1734 }
1735
1736 sub help_intern_compute
1737 {
1738     my ($self) = @_;
1739
1740     my $number = CGI::param('limit') || '' ;
1741     unless ($number =~ /^(\d+)$/) {
1742         return $self->error("Bad arg number : $number ");
1743     }
1744
1745     my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
1746
1747     if (CGI::param('expired')) {
1748         $sql = "
1749 AND (    $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
1750        + $self->{sql}->{TO_SEC}(Media.VolRetention)
1751     ) < NOW()
1752  " . $sql ;
1753     }
1754
1755     my $query = "
1756 SELECT Media.VolumeName  AS volumename,
1757        Media.VolStatus   AS volstatus,
1758        Media.LastWritten AS lastwritten,
1759        Media.MediaType   AS mediatype,
1760        Media.VolMounts   AS volmounts,
1761        Pool.Name         AS name,
1762        $self->{sql}->{FROM_UNIXTIME}(
1763           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
1764         + $self->{sql}->{TO_SEC}(Media.VolRetention)
1765        ) AS expire
1766 FROM Media 
1767  INNER JOIN Pool ON (Pool.PoolId = Media.PoolId) 
1768  LEFT  JOIN Location ON (Location.LocationId = Media.LocationId)
1769
1770 WHERE Media.InChanger <> 1
1771   AND Media.VolStatus IN ('Purged', 'Full', 'Append')
1772   AND Media.Recycle = 1
1773   $sql
1774 ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC 
1775 LIMIT $number
1776 " ;
1777     
1778     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
1779
1780     $self->display({ Medias => [ values %$all ] },
1781                    "help_intern_compute.tpl");
1782
1783 }
1784
1785 sub display_general
1786 {
1787     my ($self, %arg) = @_ ;
1788
1789     my ($limit, $label) = $self->get_limit(%arg);
1790
1791     my $query = "
1792 SELECT
1793     (SELECT count(Pool.PoolId)   FROM Pool)   AS nb_pool,
1794     (SELECT count(Media.MediaId) FROM Media)  AS nb_media,
1795     (SELECT count(Job.JobId)     FROM Job)    AS nb_job,
1796     (SELECT sum(VolBytes)        FROM Media)  AS nb_bytes,
1797     ($self->{sql}->{DB_SIZE})                 AS db_size,
1798     (SELECT count(Job.JobId)
1799       FROM Job
1800       WHERE Job.JobStatus IN ('E','e','f','A')
1801       $limit
1802     )                                         AS nb_err,
1803     (SELECT count(Client.ClientId) FROM Client) AS nb_client
1804 ";
1805
1806     my $row = $self->dbh_selectrow_hashref($query) ;
1807
1808     $row->{nb_bytes} = human_size($row->{nb_bytes});
1809
1810     $row->{db_size} = human_size($row->{db_size});
1811     $row->{label} = $label;
1812
1813     $self->display($row, "general.tpl");
1814 }
1815
1816 sub get_param
1817 {
1818     my ($self, @what) = @_ ;
1819     my %elt = map { $_ => 1 } @what;
1820     my %ret;
1821
1822     my $limit = '';
1823
1824     if ($elt{clients}) {
1825         my @clients = grep { ! /^\s*$/ } CGI::param('client');
1826         if (@clients) {
1827             $ret{clients} = \@clients;
1828             my $str = $self->dbh_join(@clients);
1829             $limit .= "AND Client.Name IN ($str) ";
1830         }
1831     }
1832
1833     if ($elt{client_groups}) {
1834         my @clients = grep { ! /^\s*$/ } CGI::param('client_group');
1835         if (@clients) {
1836             $ret{client_groups} = \@clients;
1837             my $str = $self->dbh_join(@clients);
1838             $limit .= "AND client_group_name IN ($str) ";
1839         }
1840     }
1841
1842     if ($elt{filesets}) {
1843         my @filesets = grep { ! /^\s*$/ } CGI::param('fileset');
1844         if (@filesets) {
1845             $ret{filesets} = \@filesets;
1846             my $str = $self->dbh_join(@filesets);
1847             $limit .= "AND FileSet.FileSet IN ($str) ";
1848         }
1849     }
1850
1851     if ($elt{mediatypes}) {
1852         my @medias = grep { ! /^\s*$/ } CGI::param('mediatype');
1853         if (@medias) {
1854             $ret{mediatypes} = \@medias;
1855             my $str = $self->dbh_join(@medias);
1856             $limit .= "AND Media.MediaType IN ($str) ";
1857         }
1858     }
1859
1860     if ($elt{client}) {
1861         my $client = CGI::param('client');
1862         $ret{client} = $client;
1863         $client = $self->dbh_join($client);
1864         $limit .= "AND Client.Name = $client ";
1865     }
1866
1867     if ($elt{level}) {
1868         my $level = CGI::param('level') || '';
1869         if ($level =~ /^(\w)$/) {
1870             $ret{level} = $1;
1871             $limit .= "AND Job.Level = '$1' ";
1872         }
1873     }
1874
1875     if ($elt{jobid}) {
1876         my $jobid = CGI::param('jobid') || '';
1877
1878         if ($jobid =~ /^(\d+)$/) {
1879             $ret{jobid} = $1;
1880             $limit .= "AND Job.JobId = '$1' ";
1881         }
1882     }
1883
1884     if ($elt{status}) {
1885         my $status = CGI::param('status') || '';
1886         if ($status =~ /^(\w)$/) {
1887             $ret{status} = $1;
1888             if ($1 eq 'f') {
1889                 $limit .= "AND Job.JobStatus IN ('f','E') ";            
1890             } elsif ($1 eq 'W') {
1891                 $limit .= "AND Job.JobStatus = 'T' AND Job.JobErrors > 0 ";             
1892             } else {
1893                 $limit .= "AND Job.JobStatus = '$1' ";          
1894             }
1895         }
1896     }
1897
1898     if ($elt{volstatus}) {
1899         my $status = CGI::param('volstatus') || '';
1900         if ($status =~ /^(\w+)$/) {
1901             $ret{status} = $1;
1902             $limit .= "AND Media.VolStatus = '$1' ";            
1903         }
1904     }
1905
1906     if ($elt{locations}) {
1907         my @location = grep { ! /^\s*$/ } CGI::param('location') ;
1908         if (@location) {
1909             $ret{locations} = \@location;           
1910             my $str = $self->dbh_join(@location);
1911             $limit .= "AND Location.Location IN ($str) ";
1912         }
1913     }
1914
1915     if ($elt{pools}) {
1916         my @pool = grep { ! /^\s*$/ } CGI::param('pool') ;
1917         if (@pool) {
1918             $ret{pools} = \@pool; 
1919             my $str = $self->dbh_join(@pool);
1920             $limit .= "AND Pool.Name IN ($str) ";
1921         }
1922     }
1923
1924     if ($elt{location}) {
1925         my $location = CGI::param('location') || '';
1926         if ($location) {
1927             $ret{location} = $location;
1928             $location = $self->dbh_quote($location);
1929             $limit .= "AND Location.Location = $location ";
1930         }
1931     }
1932
1933     if ($elt{pool}) {
1934         my $pool = CGI::param('pool') || '';
1935         if ($pool) {
1936             $ret{pool} = $pool;
1937             $pool = $self->dbh_quote($pool);
1938             $limit .= "AND Pool.Name = $pool ";
1939         }
1940     }
1941
1942     if ($elt{jobtype}) {
1943         my $jobtype = CGI::param('jobtype') || '';
1944         if ($jobtype =~ /^(\w)$/) {
1945             $ret{jobtype} = $1;
1946             $limit .= "AND Job.Type = '$1' ";
1947         }
1948     }
1949
1950     return ($limit, %ret);
1951 }
1952
1953 =head1
1954
1955     get last backup
1956
1957 =cut 
1958
1959 sub display_job
1960 {
1961     my ($self, %arg) = @_ ;
1962
1963     $arg{order} = ' Job.JobId DESC ';
1964
1965     my ($limit, $label) = $self->get_limit(%arg);
1966     my ($where, undef) = $self->get_param('clients',
1967                                           'client_groups',
1968                                           'level',
1969                                           'filesets',
1970                                           'jobtype',
1971                                           'pools',
1972                                           'jobid',
1973                                           'status');
1974
1975     my $cgq = '';
1976     if (CGI::param('client_group')) {
1977         $cgq = "
1978 LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
1979 LEFT JOIN client_group USING (client_group_id)
1980 ";
1981     }
1982
1983     my $query="
1984 SELECT  Job.JobId       AS jobid,
1985         Client.Name     AS client,
1986         FileSet.FileSet AS fileset,
1987         Job.Name        AS jobname,
1988         Level           AS level,
1989         StartTime       AS starttime,
1990         EndTime         AS endtime,
1991         Pool.Name       AS poolname,
1992         JobFiles        AS jobfiles, 
1993         JobBytes        AS jobbytes,
1994         JobStatus       AS jobstatus,
1995      $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
1996                                  - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
1997                         AS duration,
1998
1999         JobErrors       AS joberrors
2000
2001  FROM Client, 
2002       Job LEFT JOIN Pool     ON (Job.PoolId    = Pool.PoolId)
2003           LEFT JOIN FileSet  ON (Job.FileSetId = FileSet.FileSetId)
2004           $cgq
2005  WHERE Client.ClientId=Job.ClientId
2006    AND Job.JobStatus != 'R'
2007  $where
2008  $limit
2009 ";
2010
2011     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2012
2013     $self->display({ Filter => $label,
2014                      ID => $cur_id++,
2015                      Jobs => 
2016                            [ 
2017                              sort { $a->{jobid} <=>  $b->{jobid} } 
2018                                         values %$all 
2019                              ],
2020                    },
2021                    "display_job.tpl");
2022 }
2023
2024 # display job informations
2025 sub display_job_zoom
2026 {
2027     my ($self, $jobid) = @_ ;
2028
2029     $jobid = $self->dbh_quote($jobid);
2030     
2031     my $query="
2032 SELECT DISTINCT Job.JobId       AS jobid,
2033                 Client.Name     AS client,
2034                 Job.Name        AS jobname,
2035                 FileSet.FileSet AS fileset,
2036                 Level           AS level,
2037                 Pool.Name       AS poolname,
2038                 StartTime       AS starttime,
2039                 JobFiles        AS jobfiles, 
2040                 JobBytes        AS jobbytes,
2041                 JobStatus       AS jobstatus,
2042                 JobErrors       AS joberrors,
2043                 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
2044                                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
2045
2046  FROM Client,
2047       Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
2048           LEFT JOIN Pool    ON (Job.PoolId    = Pool.PoolId)
2049  WHERE Client.ClientId=Job.ClientId
2050  AND Job.JobId = $jobid
2051 ";
2052
2053     my $row = $self->dbh_selectrow_hashref($query) ;
2054
2055     # display all volumes associate with this job
2056     $query="
2057 SELECT Media.VolumeName as volumename
2058 FROM Job,Media,JobMedia
2059 WHERE Job.JobId = $jobid
2060  AND JobMedia.JobId=Job.JobId 
2061  AND JobMedia.MediaId=Media.MediaId
2062 ";
2063
2064     my $all = $self->dbh_selectall_hashref($query, 'volumename');
2065
2066     $row->{volumes} = [ values %$all ] ;
2067
2068     $self->display($row, "display_job_zoom.tpl");
2069 }
2070
2071 sub display_media
2072 {
2073     my ($self, %arg) = @_ ;
2074
2075     my ($limit, $label) = $self->get_limit(%arg);    
2076     my ($where, %elt) = $self->get_param('pools',
2077                                          'mediatypes',
2078                                          'volstatus',
2079                                          'locations');
2080
2081     my $arg = $self->get_form('jmedias', 'qre_media');
2082
2083     if ($arg->{jmedias}) {
2084         $where = "AND Media.VolumeName IN ($arg->{jmedias}) $where"; 
2085     }
2086     if ($arg->{qre_media}) {
2087         $where = "AND Media.VolumeName $self->{sql}->{MATCH} $arg->{qre_media} $where"; 
2088     }
2089
2090     my $query="
2091 SELECT Media.VolumeName  AS volumename, 
2092        Media.VolBytes    AS volbytes,
2093        Media.VolStatus   AS volstatus,
2094        Media.MediaType   AS mediatype,
2095        Media.InChanger   AS online,
2096        Media.LastWritten AS lastwritten,
2097        Location.Location AS location,
2098        (volbytes*100/COALESCE(media_avg_size.size,-1))  AS volusage,
2099        Pool.Name         AS poolname,
2100        $self->{sql}->{FROM_UNIXTIME}(
2101           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
2102         + $self->{sql}->{TO_SEC}(Media.VolRetention)
2103        ) AS expire
2104 FROM      Pool, Media 
2105 LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2106 LEFT JOIN (SELECT avg(Media.VolBytes) AS size,
2107                   Media.MediaType     AS MediaType
2108            FROM Media 
2109           WHERE Media.VolStatus = 'Full' 
2110           GROUP BY Media.MediaType
2111            ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2112
2113 WHERE Media.PoolId=Pool.PoolId
2114 $where
2115 $limit
2116 ";
2117
2118     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2119
2120     $self->display({ ID => $cur_id++,
2121                      Pool => $elt{pool},
2122                      Location => $elt{location},
2123                      Medias => [ values %$all ]
2124                    },
2125                    "display_media.tpl");
2126 }
2127
2128 sub display_medias
2129 {
2130     my ($self) = @_ ;
2131
2132     my $pool = $self->get_form('db_pools');
2133     
2134     foreach my $name (@{ $pool->{db_pools} }) {
2135         CGI::param('pool', $name->{name});
2136         $self->display_media();
2137     }
2138 }
2139
2140 sub display_media_zoom
2141 {
2142     my ($self) = @_ ;
2143
2144     my $medias = $self->get_form('jmedias');
2145     
2146     unless ($medias->{jmedias}) {
2147         return $self->error("Can't get media selection");
2148     }
2149     
2150     my $query="
2151 SELECT InChanger     AS online,
2152        VolBytes      AS nb_bytes,
2153        VolumeName    AS volumename,
2154        VolStatus     AS volstatus,
2155        VolMounts     AS nb_mounts,
2156        Media.VolUseDuration   AS voluseduration,
2157        Media.MaxVolJobs AS maxvoljobs,
2158        Media.MaxVolFiles AS maxvolfiles,
2159        Media.MaxVolBytes AS maxvolbytes,
2160        VolErrors     AS nb_errors,
2161        Pool.Name     AS poolname,
2162        Location.Location AS location,
2163        Media.Recycle AS recycle,
2164        Media.VolRetention AS volretention,
2165        Media.LastWritten  AS lastwritten,
2166        Media.VolReadTime/1000000  AS volreadtime,
2167        Media.VolWriteTime/1000000 AS volwritetime,
2168        Media.RecycleCount AS recyclecount,
2169        Media.Comment      AS comment,
2170        $self->{sql}->{FROM_UNIXTIME}(
2171           $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
2172         + $self->{sql}->{TO_SEC}(Media.VolRetention)
2173        ) AS expire
2174  FROM Pool,
2175       Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
2176  WHERE Pool.PoolId = Media.PoolId
2177  AND VolumeName IN ($medias->{jmedias})
2178 ";
2179
2180     my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
2181
2182     foreach my $media (values %$all) {
2183         my $mq = $self->dbh_quote($media->{volumename});
2184
2185         $query = "
2186 SELECT DISTINCT Job.JobId AS jobid,
2187                 Job.Name  AS name,
2188                 Job.StartTime AS starttime,
2189                 Job.Type  AS type,
2190                 Job.Level AS level,
2191                 Job.JobFiles AS files,
2192                 Job.JobBytes AS bytes,
2193                 Job.jobstatus AS status
2194  FROM Media,JobMedia,Job
2195  WHERE Media.VolumeName=$mq
2196  AND Media.MediaId=JobMedia.MediaId              
2197  AND JobMedia.JobId=Job.JobId
2198 ";
2199
2200         my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
2201
2202         $query = "
2203 SELECT LocationLog.Date    AS date,
2204        Location.Location   AS location,
2205        LocationLog.Comment AS comment
2206  FROM Media,LocationLog INNER JOIN Location ON (LocationLog.LocationId = Location.LocationId)
2207  WHERE Media.MediaId = LocationLog.MediaId
2208    AND Media.VolumeName = $mq
2209 ";
2210
2211         my $logtxt = '';
2212         my $log = $self->dbh_selectall_arrayref($query) ;
2213         if ($log) {
2214             $logtxt = join("\n", map { ($_->[0] . ' ' . $_->[1] . ' ' . $_->[2])} @$log ) ;
2215         }
2216
2217         $self->display({ jobs => [ values %$jobs ],
2218                          LocationLog => $logtxt,
2219                          %$media },
2220                        "display_media_zoom.tpl");
2221     }
2222 }
2223
2224 sub location_edit
2225 {
2226     my ($self) = @_ ;
2227
2228     my $loc = $self->get_form('qlocation');
2229     unless ($loc->{qlocation}) {
2230         return $self->error("Can't get location");
2231     }
2232
2233     my $query = "
2234 SELECT Location.Location AS location, 
2235        Location.Cost   AS cost,
2236        Location.Enabled AS enabled
2237 FROM Location
2238 WHERE Location.Location = $loc->{qlocation}
2239 ";
2240
2241     my $row = $self->dbh_selectrow_hashref($query);
2242
2243     $self->display({ ID => $cur_id++,
2244                      %$row }, "location_edit.tpl") ;
2245
2246 }
2247
2248 sub location_save
2249 {
2250     my ($self) = @_ ;
2251
2252     my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
2253     unless ($arg->{qlocation}) {
2254         return $self->error("Can't get location");
2255     }    
2256     unless ($arg->{qnewlocation}) {
2257         return $self->error("Can't get new location name");
2258     }
2259     unless ($arg->{cost}) {
2260         return $self->error("Can't get new cost");
2261     }
2262
2263     my $enabled = CGI::param('enabled') || '';
2264     $enabled = $enabled?1:0;
2265
2266     my $query = "
2267 UPDATE Location SET Cost     = $arg->{cost}, 
2268                     Location = $arg->{qnewlocation},
2269                     Enabled   = $enabled
2270 WHERE Location.Location = $arg->{qlocation}
2271 ";
2272
2273     $self->dbh_do($query);
2274
2275     $self->location_display();
2276 }
2277
2278 sub location_del
2279 {
2280     my ($self) = @_ ;
2281     my $arg = $self->get_form(qw/qlocation/) ;
2282
2283     unless ($arg->{qlocation}) {
2284         return $self->error("Can't get location");
2285     }
2286
2287     my $query = "
2288 SELECT count(Media.MediaId) AS nb 
2289   FROM Media INNER JOIN Location USING (LocationID)
2290 WHERE Location = $arg->{qlocation}
2291 ";
2292
2293     my $res = $self->dbh_selectrow_hashref($query);
2294
2295     if ($res->{nb}) {
2296         return $self->error("Sorry, the location must be empty");
2297     }
2298
2299     $query = "
2300 DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
2301 ";
2302
2303     $self->dbh_do($query);
2304
2305     $self->location_display();
2306 }
2307
2308
2309 sub location_add
2310 {
2311     my ($self) = @_ ;
2312     my $arg = $self->get_form(qw/qlocation cost/) ;
2313
2314     unless ($arg->{qlocation}) {
2315         $self->display({}, "location_add.tpl");
2316         return 1;
2317     }
2318     unless ($arg->{cost}) {
2319         return $self->error("Can't get new cost");
2320     }
2321
2322     my $enabled = CGI::param('enabled') || '';
2323     $enabled = $enabled?1:0;
2324
2325     my $query = "
2326 INSERT INTO Location (Location, Cost, Enabled) 
2327        VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
2328 ";
2329
2330     $self->dbh_do($query);
2331
2332     $self->location_display();
2333 }
2334
2335 sub location_display
2336 {
2337     my ($self) = @_ ;
2338
2339     my $query = "
2340 SELECT Location.Location AS location, 
2341        Location.Cost     AS cost,
2342        Location.Enabled  AS enabled,
2343        (SELECT count(Media.MediaId) 
2344          FROM Media 
2345         WHERE Media.LocationId = Location.LocationId
2346        ) AS volnum
2347 FROM Location
2348 ";
2349
2350     my $location = $self->dbh_selectall_hashref($query, 'location');
2351
2352     $self->display({ ID => $cur_id++,
2353                      Locations => [ values %$location ] },
2354                    "display_location.tpl");
2355 }
2356
2357 sub update_location
2358 {
2359     my ($self) = @_ ;
2360
2361     my $medias = $self->get_selected_media_location();
2362     unless ($medias) {
2363         return ;
2364     }
2365
2366     my $arg = $self->get_form('db_locations', 'qnewlocation');
2367
2368     $self->display({ email  => $self->{info}->{email_media},
2369                      %$arg,
2370                      medias => [ values %$medias ],
2371                    },
2372                    "update_location.tpl");
2373 }
2374
2375 ###########################################################
2376
2377 sub groups_edit
2378 {
2379     my ($self) = @_;
2380
2381     my $grp = $self->get_form(qw/qclient_group db_clients/);
2382     $self->debug($grp);
2383
2384     unless ($grp->{qclient_group}) {
2385         return $self->error("Can't get group");
2386     }
2387
2388     my $query = "
2389 SELECT Name AS name 
2390   FROM Client JOIN client_group_member using (clientid)
2391               JOIN client_group using (client_group_id)
2392 WHERE client_group_name = $grp->{qclient_group}
2393 ";
2394
2395     my $row = $self->dbh_selectall_hashref($query, "name");
2396     $self->debug($row);
2397     $self->display({ ID => $cur_id++,
2398                      client_group => $grp->{qclient_group},
2399                      %$grp,
2400                      client_group_member => [ values %$row]}, 
2401                    "groups_edit.tpl");
2402 }
2403
2404 sub groups_save
2405 {
2406     my ($self) = @_;
2407
2408     my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
2409     unless ($arg->{qclient_group}) {
2410         return $self->error("Can't get groups");
2411     }
2412     
2413     $self->{dbh}->begin_work();
2414
2415     my $query = "
2416 DELETE FROM client_group_member 
2417       WHERE client_group_id IN 
2418            (SELECT client_group_id 
2419               FROM client_group 
2420              WHERE client_group_name = $arg->{qclient_group})
2421 ";
2422     $self->dbh_do($query);
2423
2424     $query = "
2425     INSERT INTO client_group_member (clientid, client_group_id) 
2426        (SELECT  Clientid, 
2427                 (SELECT client_group_id 
2428                    FROM client_group 
2429                   WHERE client_group_name = $arg->{qclient_group})
2430           FROM Client WHERE Name IN ($arg->{jclients})
2431        )
2432 ";
2433     $self->dbh_do($query);
2434
2435     if ($arg->{qclient_group} ne $arg->{qnewgroup}) {
2436         $query = "
2437 UPDATE client_group 
2438    SET client_group_name = $arg->{qnewgroup}
2439  WHERE client_group_name = $arg->{qclient_group}
2440 ";
2441
2442         $self->dbh_do($query);
2443     }
2444
2445     $self->{dbh}->commit() or $self->error($self->{dbh}->errstr);
2446
2447     $self->display_groups();
2448 }
2449
2450 sub groups_del
2451 {
2452     my ($self) = @_;
2453     my $arg = $self->get_form(qw/qclient_group/);
2454
2455     unless ($arg->{qclient_group}) {
2456         return $self->error("Can't get groups");
2457     }
2458
2459     $self->{dbh}->begin_work();
2460
2461     my $query = "
2462 DELETE FROM client_group_member 
2463       WHERE client_group_id IN 
2464            (SELECT client_group_id 
2465               FROM client_group 
2466              WHERE client_group_name = $arg->{qclient_group});
2467
2468 DELETE FROM client_group
2469       WHERE client_group_name = $arg->{qclient_group};
2470 ";
2471     $self->dbh_do($query);
2472
2473     $self->{dbh}->commit();
2474     
2475     $self->display_groups();
2476 }
2477
2478
2479 sub groups_add
2480 {
2481     my ($self) = @_;
2482     my $arg = $self->get_form(qw/qclient_group/) ;
2483
2484     unless ($arg->{qclient_group}) {
2485         $self->display({}, "groups_add.tpl");
2486         return 1;
2487     }
2488
2489     my $query = "
2490 INSERT INTO client_group (client_group_name) 
2491 VALUES ($arg->{qclient_group})
2492 ";
2493
2494     $self->dbh_do($query);
2495
2496     $self->display_groups();
2497 }
2498
2499 sub display_groups
2500 {
2501     my ($self) = @_;
2502
2503     my $arg = $self->get_form(qw/db_client_groups/) ;
2504
2505     if ($self->{dbh}->errstr) {
2506         return $self->error("Can't use groups with bweb, read INSTALL to enable them");
2507     }
2508
2509     $self->debug($arg);
2510
2511     $self->display({ ID => $cur_id++,
2512                      %$arg},
2513                    "display_groups.tpl");
2514 }
2515
2516 ###########################################################
2517
2518 sub get_media_max_size
2519 {
2520     my ($self, $type) = @_;
2521     my $query = 
2522 "SELECT avg(VolBytes) AS size
2523   FROM Media 
2524  WHERE Media.VolStatus = 'Full' 
2525    AND Media.MediaType = '$type'
2526 ";
2527     
2528     my $res = $self->selectrow_hashref($query);
2529
2530     if ($res) {
2531         return $res->{size};
2532     } else {
2533         return 0;
2534     }
2535 }
2536
2537 sub update_media
2538 {
2539     my ($self) = @_ ;
2540
2541     my $media = $self->get_form('qmedia');
2542
2543     unless ($media->{qmedia}) {
2544         return $self->error("Can't get media");
2545     }
2546
2547     my $query = "
2548 SELECT Media.Slot         AS slot,
2549        PoolMedia.Name     AS poolname,
2550        Media.VolStatus    AS volstatus,
2551        Media.InChanger    AS inchanger,
2552        Location.Location  AS location,
2553        Media.VolumeName   AS volumename,
2554        Media.MaxVolBytes  AS maxvolbytes,
2555        Media.MaxVolJobs   AS maxvoljobs,
2556        Media.MaxVolFiles  AS maxvolfiles,
2557        Media.VolUseDuration AS voluseduration,
2558        Media.VolRetention AS volretention,
2559        Media.Comment      AS comment,
2560        PoolRecycle.Name   AS poolrecycle
2561
2562 FROM Media INNER JOIN Pool AS PoolMedia ON (Media.PoolId = PoolMedia.PoolId)
2563            LEFT  JOIN Pool AS PoolRecycle ON (Media.RecyclePoolId = PoolRecycle.PoolId)
2564            LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
2565
2566 WHERE Media.VolumeName = $media->{qmedia}
2567 ";
2568
2569     my $row = $self->dbh_selectrow_hashref($query);
2570     $row->{volretention} = human_sec($row->{volretention});
2571     $row->{voluseduration} = human_sec($row->{voluseduration});
2572
2573     my $elt = $self->get_form(qw/db_pools db_locations/);
2574
2575     $self->display({
2576         %$elt,
2577         %$row,
2578     }, "update_media.tpl");
2579 }
2580
2581 sub save_location
2582 {
2583     my ($self) = @_ ;
2584
2585     my $arg = $self->get_form('jmedias', 'qnewlocation') ;
2586
2587     unless ($arg->{jmedias}) {
2588         return $self->error("Can't get selected media");
2589     }
2590     
2591     unless ($arg->{qnewlocation}) {
2592         return $self->error("Can't get new location");
2593     }
2594
2595     my $query = "
2596  UPDATE Media 
2597      SET LocationId = (SELECT LocationId 
2598                        FROM Location 
2599                        WHERE Location = $arg->{qnewlocation}) 
2600      WHERE Media.VolumeName IN ($arg->{jmedias})
2601 ";
2602
2603     my $nb = $self->dbh_do($query);
2604
2605     print "$nb media updated, you may have to update your autochanger.";
2606
2607     $self->display_media();
2608 }
2609
2610 sub location_change
2611 {
2612     my ($self) = @_ ;
2613
2614     my $medias = $self->get_selected_media_location();
2615     unless ($medias) {
2616         return $self->error("Can't get media selection");
2617     }
2618     my $newloc = CGI::param('newlocation');
2619
2620     my $user = CGI::param('user') || 'unknown';
2621     my $comm = CGI::param('comment') || '';
2622     $comm = $self->dbh_quote("$user: $comm");
2623
2624     my $query;
2625
2626     foreach my $media (keys %$medias) {
2627         $query = "
2628 INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
2629  VALUES(
2630        NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
2631        (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
2632        (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
2633       )
2634 ";
2635         $self->dbh_do($query);
2636         $self->debug($query);
2637     }
2638
2639     my $q = new CGI;
2640     $q->param('action', 'update_location');
2641     my $url = $q->url(-full => 1, -query=>1);
2642
2643     $self->display({ email  => $self->{info}->{email_media},
2644                      url => $url,
2645                      newlocation => $newloc,
2646                      # [ { volumename => 'vol1' }, { volumename => 'vol2'\81\81 },..]
2647                      medias => [ values %$medias ],
2648                    },
2649                    "change_location.tpl");
2650
2651 }
2652
2653 sub display_client_stats
2654 {
2655     my ($self, %arg) = @_ ;
2656
2657     my $client = $self->dbh_quote($arg{clientname});
2658
2659     my ($limit, $label) = $self->get_limit(%arg);
2660
2661     my $query = "
2662 SELECT 
2663     count(Job.JobId)     AS nb_jobs,
2664     sum(Job.JobBytes)    AS nb_bytes,
2665     sum(Job.JobErrors)   AS nb_err,
2666     sum(Job.JobFiles)    AS nb_files,
2667     Client.Name          AS clientname
2668 FROM Job JOIN Client USING (ClientId)
2669 WHERE 
2670     Client.Name = $client
2671     $limit 
2672 GROUP BY Client.Name
2673 ";
2674
2675     my $row = $self->dbh_selectrow_hashref($query);
2676
2677     $row->{ID} = $cur_id++;
2678     $row->{label} = $label;
2679     $row->{grapharg} = "client";
2680
2681     $self->display($row, "display_client_stats.tpl");
2682 }
2683
2684
2685 sub display_group_stats
2686 {
2687     my ($self, %arg) = @_ ;
2688
2689     my $carg = $self->get_form(qw/qclient_group/);
2690
2691     unless ($carg->{qclient_group}) {
2692         return $self->error("Can't get group");
2693     }
2694
2695     my ($limit, $label) = $self->get_limit(%arg);
2696
2697     my $query = "
2698 SELECT 
2699     count(Job.JobId)     AS nb_jobs,
2700     sum(Job.JobBytes)    AS nb_bytes,
2701     sum(Job.JobErrors)   AS nb_err,
2702     sum(Job.JobFiles)    AS nb_files,
2703     client_group.client_group_name  AS clientname
2704 FROM Job JOIN Client USING (ClientId) 
2705          JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
2706          JOIN client_group USING (client_group_id)
2707 WHERE 
2708     client_group.client_group_name = $carg->{qclient_group}
2709     $limit 
2710 GROUP BY client_group.client_group_name
2711 ";
2712
2713     my $row = $self->dbh_selectrow_hashref($query);
2714
2715     $row->{ID} = $cur_id++;
2716     $row->{label} = $label;
2717     $row->{grapharg} = "client_group";
2718
2719     $self->display($row, "display_client_stats.tpl");
2720 }
2721
2722 # poolname can be undef
2723 sub display_pool
2724 {
2725     my ($self, $poolname) = @_ ;
2726     my $whereA = '';
2727     my $whereW = '';
2728
2729     my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2730     if ($arg->{jmediatypes}) { 
2731         $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2732         $whereA = "AND   MediaType IN ($arg->{jmediatypes}) ";
2733     }
2734     
2735 # TODO : afficher les tailles et les dates
2736
2737     my $query = "
2738 SELECT subq.volmax        AS volmax,
2739        subq.volnum        AS volnum,
2740        subq.voltotal      AS voltotal,
2741        Pool.Name          AS name,
2742        Pool.Recycle       AS recycle,
2743        Pool.VolRetention  AS volretention,
2744        Pool.VolUseDuration AS voluseduration,
2745        Pool.MaxVolJobs    AS maxvoljobs,
2746        Pool.MaxVolFiles   AS maxvolfiles,
2747        Pool.MaxVolBytes   AS maxvolbytes,
2748        subq.PoolId        AS PoolId
2749 FROM
2750   (
2751     SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2752            count(Media.MediaId)  AS volnum,
2753            sum(Media.VolBytes)   AS voltotal,
2754            Media.PoolId          AS PoolId,
2755            Media.MediaType       AS MediaType
2756     FROM Media
2757     LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2758                       Media.MediaType     AS MediaType
2759                FROM Media 
2760               WHERE Media.VolStatus = 'Full' 
2761               GROUP BY Media.MediaType
2762                ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2763     GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2764   ) AS subq
2765 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2766 $whereW
2767 ";
2768
2769     my $all = $self->dbh_selectall_hashref($query, 'name') ;
2770
2771     $query = "
2772 SELECT Pool.Name AS name,
2773        sum(VolBytes) AS size
2774 FROM   Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2775 WHERE  Media.VolStatus IN ('Recycled', 'Purged')
2776        $whereA
2777 GROUP BY Pool.Name;
2778 ";
2779     my $empty = $self->dbh_selectall_hashref($query, 'name');
2780
2781     foreach my $p (values %$all) {
2782         if ($p->{volmax} > 0) { # mysql returns 0.0000
2783             # we remove Recycled/Purged media from pool usage
2784             if (defined $empty->{$p->{name}}) {
2785                 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2786             }
2787             $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2788         } else {
2789             $p->{poolusage} = 0;
2790         }
2791
2792         $query = "
2793   SELECT VolStatus AS volstatus, count(MediaId) AS nb
2794     FROM Media 
2795    WHERE PoolId=$p->{poolid} 
2796          $whereA
2797 GROUP BY VolStatus
2798 ";
2799         my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2800         foreach my $t (values %$content) {
2801             $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2802         }
2803     }
2804
2805     $self->debug($all);
2806     $self->display({ ID => $cur_id++,
2807                      MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2808                      Pools => [ values %$all ]},
2809                    "display_pool.tpl");
2810 }
2811
2812 sub display_running_job
2813 {
2814     my ($self) = @_;
2815
2816     my $arg = $self->get_form('client', 'jobid');
2817
2818     if (!$arg->{client} and $arg->{jobid}) {
2819
2820         my $query = "
2821 SELECT Client.Name AS name
2822 FROM Job INNER JOIN Client USING (ClientId)
2823 WHERE Job.JobId = $arg->{jobid}
2824 ";
2825
2826         my $row = $self->dbh_selectrow_hashref($query);
2827
2828         if ($row) {
2829             $arg->{client} = $row->{name};
2830             CGI::param('client', $arg->{client});
2831         }
2832     }
2833
2834     if ($arg->{client}) {
2835         my $cli = new Bweb::Client(name => $arg->{client});
2836         $cli->display_running_job($self->{info}, $arg->{jobid});
2837         if ($arg->{jobid}) {
2838             $self->get_job_log();
2839         }
2840     } else {
2841         $self->error("Can't get client or jobid");
2842     }
2843 }
2844
2845 sub display_running_jobs
2846 {
2847     my ($self, $display_action) = @_;
2848     
2849     my $query = "
2850 SELECT Job.JobId AS jobid, 
2851        Job.Name  AS jobname,
2852        Job.Level     AS level,
2853        Job.StartTime AS starttime,
2854        Job.JobFiles  AS jobfiles,
2855        Job.JobBytes  AS jobbytes,
2856        Job.JobStatus AS jobstatus,
2857 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(NOW())  
2858                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
2859          AS duration,
2860        Client.Name AS clientname
2861 FROM Job INNER JOIN Client USING (ClientId) 
2862 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2863 ";      
2864     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2865     
2866     $self->display({ ID => $cur_id++,
2867                      display_action => $display_action,
2868                      Jobs => [ values %$all ]},
2869                    "running_job.tpl") ;
2870 }
2871
2872 # return the autochanger list to update
2873 sub eject_media
2874 {
2875     my ($self) = @_;
2876     my %ret; 
2877     my $arg = $self->get_form('jmedias');
2878
2879     unless ($arg->{jmedias}) {
2880         return $self->error("Can't get media selection");
2881     }
2882
2883     my $query = "
2884 SELECT Media.VolumeName  AS volumename,
2885        Storage.Name      AS storage,
2886        Location.Location AS location,
2887        Media.Slot        AS slot
2888 FROM Media INNER JOIN Storage  ON (Media.StorageId  = Storage.StorageId)
2889            LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
2890 WHERE Media.VolumeName IN ($arg->{jmedias})
2891   AND Media.InChanger = 1
2892 ";
2893
2894     my $all = $self->dbh_selectall_hashref($query, 'volumename');
2895
2896     foreach my $vol (values %$all) {
2897         my $a = $self->ach_get($vol->{location});
2898         next unless ($a) ;
2899         $ret{$vol->{location}} = 1;
2900
2901         unless ($a->{have_status}) {
2902             $a->status();
2903             $a->{have_status} = 1;
2904         }
2905
2906         print "eject $vol->{volumename} from $vol->{storage} : ";
2907         if ($a->send_to_io($vol->{slot})) {
2908             print "<img src='/bweb/T.png' alt='ok'><br/>";
2909         } else {
2910             print "<img src='/bweb/E.png' alt='err'><br/>";
2911         }
2912     }
2913     return keys %ret;
2914 }
2915
2916 sub move_email
2917 {
2918     my ($self) = @_;
2919
2920     my ($to, $subject, $content) = (CGI::param('email'),
2921                                     CGI::param('subject'),
2922                                     CGI::param('content'));
2923     $to =~ s/[^\w\d\.\@<>,]//;
2924     $subject =~ s/[^\w\d\.\[\]]/ /;    
2925
2926     open(MAIL, "|mail -s '$subject' '$to'") ;
2927     print MAIL $content;
2928     close(MAIL);
2929
2930     print "Mail sent";
2931 }
2932
2933 sub restore
2934 {
2935     my ($self) = @_;
2936     
2937     my $arg = $self->get_form('jobid', 'client');
2938
2939     print CGI::header('text/brestore');
2940     print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
2941     print "client=$arg->{client}\n" if ($arg->{client});
2942     print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
2943     print "\n";
2944 }
2945
2946 # TODO : move this to Bweb::Autochanger ?
2947 # TODO : make this internal to not eject tape ?
2948 use Bconsole;
2949
2950
2951 sub ach_get
2952 {
2953     my ($self, $name) = @_;
2954     
2955     unless ($name) {
2956         return $self->error("Can't get your autochanger name ach");
2957     }
2958
2959     unless ($self->{info}->{ach_list}) {
2960         return $self->error("Could not find any autochanger");
2961     }
2962     
2963     my $a = $self->{info}->{ach_list}->{$name};
2964
2965     unless ($a) {
2966         $self->error("Can't get your autochanger $name from your ach_list");
2967         return undef;
2968     }
2969
2970     $a->{bweb}  = $self;
2971     $a->{debug} = $self->{debug};
2972
2973     return $a;
2974 }
2975
2976 sub ach_register
2977 {
2978     my ($self, $ach) = @_;
2979
2980     $self->{info}->{ach_list}->{$ach->{name}} = $ach;
2981
2982     $self->{info}->save();
2983     
2984     return 1;
2985 }
2986
2987 sub ach_edit
2988 {
2989     my ($self) = @_;
2990     my $arg = $self->get_form('ach');
2991     if (!$arg->{ach} 
2992         or !$self->{info}->{ach_list} 
2993         or !$self->{info}->{ach_list}->{$arg->{ach}}) 
2994     {
2995         return $self->error("Can't get autochanger name");
2996     }
2997
2998     my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
2999
3000     my $i=0;
3001     $ach->{drives} = 
3002         [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
3003
3004     my $b = $self->get_bconsole();
3005
3006     my @storages = $b->list_storage() ;
3007
3008     $ach->{devices} = [ map { { name => $_ } } @storages ];
3009     
3010     $self->display($ach, "ach_add.tpl");
3011     delete $ach->{drives};
3012     delete $ach->{devices};
3013     return 1;
3014 }
3015
3016 sub ach_del
3017 {
3018     my ($self) = @_;
3019     my $arg = $self->get_form('ach');
3020
3021     if (!$arg->{ach} 
3022         or !$self->{info}->{ach_list} 
3023         or !$self->{info}->{ach_list}->{$arg->{ach}}) 
3024     {
3025         return $self->error("Can't get autochanger name");
3026     }
3027    
3028     delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3029    
3030     $self->{info}->save();
3031     $self->{info}->view();
3032 }
3033
3034 sub ach_add
3035 {
3036     my ($self) = @_;
3037     my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3038
3039     my $b = $self->get_bconsole();
3040     my @storages = $b->list_storage() ;
3041
3042     unless ($arg->{ach}) {
3043         $arg->{devices} = [ map { { name => $_ } } @storages ];
3044         return $self->display($arg, "ach_add.tpl");
3045     }
3046
3047     my @drives ;
3048     foreach my $drive (CGI::param('drives'))
3049     {
3050         unless (grep(/^$drive$/,@storages)) {
3051             return $self->error("Can't find $drive in storage list");
3052         }
3053
3054         my $index = CGI::param("index_$drive");
3055         unless (defined $index and $index =~ /^(\d+)$/) {
3056             return $self->error("Can't get $drive index");
3057         }
3058
3059         $drives[$index] = $drive;
3060     }
3061
3062     unless (@drives) {
3063         return $self->error("Can't get drives from Autochanger");
3064     }
3065
3066     my $a = new Bweb::Autochanger(name   => $arg->{ach},
3067                                   precmd => $arg->{precmd},
3068                                   drive_name => \@drives,
3069                                   device => $arg->{device},
3070                                   mtxcmd => $arg->{mtxcmd});
3071
3072     $self->ach_register($a) ;
3073     
3074     $self->{info}->view();
3075 }
3076
3077 sub delete
3078 {
3079     my ($self) = @_;
3080     my $arg = $self->get_form('jobid');
3081
3082     if ($arg->{jobid}) {
3083         my $b = $self->get_bconsole();
3084         my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3085
3086         $self->display({
3087             content => $ret,
3088             title => "Delete a job ",
3089             name => "delete jobid=$arg->{jobid}",
3090         }, "command.tpl");      
3091     }
3092 }
3093
3094 sub do_update_media
3095 {
3096     my ($self) = @_ ;
3097
3098     my $arg = $self->get_form(qw/media volstatus inchanger pool
3099                                  slot volretention voluseduration 
3100                                  maxvoljobs maxvolfiles maxvolbytes
3101                                  qcomment poolrecycle
3102                               /);
3103
3104     unless ($arg->{media}) {
3105         return $self->error("Can't find media selection");
3106     }
3107
3108     my $update = "update volume=$arg->{media} ";
3109
3110     if ($arg->{volstatus}) {
3111         $update .= " volstatus=$arg->{volstatus} ";
3112     }
3113     
3114     if ($arg->{inchanger}) {
3115         $update .= " inchanger=yes " ;
3116         if ($arg->{slot}) {
3117             $update .= " slot=$arg->{slot} ";
3118         }
3119     } else {
3120         $update .= " slot=0 inchanger=no ";
3121     }
3122
3123     if ($arg->{pool}) {
3124         $update .= " pool=$arg->{pool} " ;
3125     }
3126
3127     if (defined $arg->{volretention}) {
3128         $update .= " volretention=\"$arg->{volretention}\" " ;
3129     }
3130
3131     if (defined $arg->{voluseduration}) {
3132         $update .= " voluse=\"$arg->{voluseduration}\" " ;
3133     }
3134
3135     if (defined $arg->{maxvoljobs}) {
3136         $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3137     }
3138     
3139     if (defined $arg->{maxvolfiles}) {
3140         $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3141     }    
3142
3143     if (defined $arg->{maxvolbytes}) {
3144         $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3145     }    
3146
3147     my $b = $self->get_bconsole();
3148
3149     $self->display({
3150         content => $b->send_cmd($update),
3151         title => "Update a volume ",
3152         name => $update,
3153     }, "command.tpl");  
3154
3155
3156     my @q;
3157     my $media = $self->dbh_quote($arg->{media});
3158
3159     my $loc = CGI::param('location') || '';
3160     if ($loc) {
3161         $loc = $self->dbh_quote($loc); # is checked by db
3162         push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3163     }
3164     if ($arg->{poolrecycle}) {
3165         push @q, "RecyclePoolId=(SELECT PoolId FROM Pool WHERE Name='$arg->{poolrecycle}')";
3166     }
3167     if (!$arg->{qcomment}) {
3168         $arg->{qcomment} = "''";
3169     }
3170     push @q, "Comment=$arg->{qcomment}";
3171     
3172
3173     my $query = "
3174 UPDATE Media 
3175    SET " . join (',', @q) . "
3176  WHERE Media.VolumeName = $media
3177 ";
3178     $self->dbh_do($query);
3179
3180     $self->update_media();
3181 }
3182
3183 sub update_slots
3184 {
3185     my ($self) = @_;
3186
3187     my $ach = CGI::param('ach') ;
3188     $ach = $self->ach_get($ach);
3189     unless ($ach) {
3190         return $self->error("Bad autochanger name");
3191     }
3192
3193     print "<pre>";
3194     my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3195     $b->update_slots($ach->{name});
3196     print "</pre>\n" 
3197 }
3198
3199 sub get_job_log
3200 {
3201     my ($self) = @_;
3202
3203     my $arg = $self->get_form('jobid', 'limit', 'offset');
3204     unless ($arg->{jobid}) {
3205         return $self->error("Can't get jobid");
3206     }
3207
3208     if ($arg->{limit} == 100) {
3209         $arg->{limit} = 1000;
3210     }
3211
3212     my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3213
3214     my $query = "
3215 SELECT Job.Name as name, Client.Name as clientname
3216  FROM  Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3217  WHERE JobId = $arg->{jobid}
3218 ";
3219
3220     my $row = $self->dbh_selectrow_hashref($query);
3221
3222     unless ($row) {
3223         return $self->error("Can't find $arg->{jobid} in catalog");
3224     }
3225
3226     $query = "
3227 SELECT Time AS time, LogText AS log 
3228   FROM  Log 
3229  WHERE Log.JobId = $arg->{jobid} 
3230     OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid}) 
3231                       AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3232        )
3233  ORDER BY LogId
3234  LIMIT $arg->{limit}
3235  OFFSET $arg->{offset}
3236 ";
3237
3238     my $log = $self->dbh_selectall_arrayref($query);
3239     unless ($log) {
3240         return $self->error("Can't get log for jobid $arg->{jobid}");
3241     }
3242
3243     my $logtxt;
3244     if ($t) {
3245         # log contains \n
3246         $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ; 
3247     } else {
3248         $logtxt = join("", map { $_->[1] } @$log ) ; 
3249     }
3250     
3251     $self->display({ lines=> $logtxt,
3252                      jobid => $arg->{jobid},
3253                      name  => $row->{name},
3254                      client => $row->{clientname},
3255                      offset => $arg->{offset},
3256                      limit  => $arg->{limit},
3257                  }, 'display_log.tpl');
3258 }
3259
3260
3261 sub label_barcodes
3262 {
3263     my ($self) = @_ ;
3264
3265     my $arg = $self->get_form('ach', 'slots', 'drive');
3266
3267     unless ($arg->{ach}) {
3268         return $self->error("Can't find autochanger name");
3269     }
3270
3271     my $a = $self->ach_get($arg->{ach});
3272     unless ($a) {
3273         return $self->error("Can't find autochanger name in configuration");
3274     } 
3275
3276     my $storage = $a->get_drive_name($arg->{drive});
3277     unless ($storage) {
3278         return $self->error("Can't get your drive name");
3279     }
3280
3281     my $slots = '';
3282     my $t = 300 ;
3283     if ($arg->{slots}) {
3284         $slots = join(",", @{ $arg->{slots} });
3285         $t += 60*scalar( @{ $arg->{slots} }) ;
3286     }
3287
3288     my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3289     print "<h1>This command can take long time, be patient...</h1>";
3290     print "<pre>" ;
3291     $b->label_barcodes(storage => $storage,
3292                        drive => $arg->{drive},
3293                        pool  => 'Scratch',
3294                        slots => $slots) ;
3295     $b->close();
3296     print "</pre>";
3297
3298     $self->dbh_do("
3299   UPDATE Media 
3300        SET LocationId =   (SELECT LocationId 
3301                              FROM Location 
3302                             WHERE Location = '$arg->{ach}'),
3303
3304            RecyclePoolId = PoolId
3305
3306      WHERE Media.PoolId = (SELECT PoolId 
3307                              FROM Pool
3308                             WHERE Name = 'Scratch')
3309        AND (LocationId = 0 OR LocationId IS NULL)
3310 ");
3311
3312 }
3313
3314 sub purge
3315 {
3316     my ($self) = @_;
3317
3318     my @volume = CGI::param('media');
3319
3320     unless (@volume) {
3321         return $self->error("Can't get media selection");
3322     }
3323
3324     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3325
3326     $self->display({
3327         content => $b->purge_volume(@volume),
3328         title => "Purge media",
3329         name => "purge volume=" . join(' volume=', @volume),
3330     }, "command.tpl");  
3331     $b->close();
3332 }
3333
3334 sub prune
3335 {
3336     my ($self) = @_;
3337
3338     my @volume = CGI::param('media');
3339     unless (@volume) {
3340         return $self->error("Can't get media selection");
3341     }
3342
3343     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3344
3345     $self->display({
3346         content => $b->prune_volume(@volume),
3347         title => "Prune media",
3348         name => "prune volume=" . join(' volume=', @volume),
3349     }, "command.tpl");  
3350
3351     $b->close();
3352 }
3353
3354 sub cancel_job
3355 {
3356     my ($self) = @_;
3357
3358     my $arg = $self->get_form('jobid');
3359     unless ($arg->{jobid}) {
3360         return $self->error("Can't get jobid");
3361     }
3362
3363     my $b = $self->get_bconsole();
3364     $self->display({
3365         content => $b->cancel($arg->{jobid}),
3366         title => "Cancel job",
3367         name => "cancel jobid=$arg->{jobid}",
3368     }, "command.tpl");  
3369 }
3370
3371 sub fileset_view
3372 {
3373     # Warning, we display current fileset
3374     my ($self) = @_;
3375
3376     my $arg = $self->get_form('fileset');
3377
3378     if ($arg->{fileset}) {
3379         my $b = $self->get_bconsole();
3380         my $ret = $b->get_fileset($arg->{fileset});
3381         $self->display({ fileset => $arg->{fileset},
3382                          %$ret,
3383                      }, "fileset_view.tpl");
3384     } else {
3385         $self->error("Can't get fileset name");
3386     }
3387 }
3388
3389 sub director_show_sched
3390 {
3391     my ($self) = @_ ;
3392
3393     my $arg = $self->get_form('days');
3394
3395     my $b = $self->get_bconsole();
3396     my $ret = $b->director_get_sched( $arg->{days} );
3397
3398     $self->display({
3399         id => $cur_id++,
3400         list => $ret,
3401     }, "scheduled_job.tpl");
3402 }
3403
3404 sub enable_disable_job
3405 {
3406     my ($self, $what) = @_ ;
3407
3408     my $name = CGI::param('job') || '';
3409     unless ($name =~ /^[\w\d\.\-\s]+$/) {
3410         return $self->error("Can't find job name");
3411     }
3412
3413     my $b = $self->get_bconsole();
3414
3415     my $cmd;
3416     if ($what) {
3417         $cmd = "enable";
3418     } else {
3419         $cmd = "disable";
3420     }
3421
3422     $self->display({
3423         content => $b->send_cmd("$cmd job=\"$name\""),
3424         title => "$cmd $name",
3425         name => "$cmd job=\"$name\"",
3426     }, "command.tpl");  
3427 }
3428
3429 sub get_bconsole
3430 {
3431     my ($self) = @_;
3432     return new Bconsole(pref => $self->{info});
3433 }
3434
3435 sub run_job_select
3436 {
3437     my ($self) = @_;
3438     my $b = $self->get_bconsole();
3439
3440     my $joblist = [ map { { name => $_ } } $b->list_job() ];
3441
3442     $self->display({ Jobs => $joblist }, "run_job.tpl");
3443 }
3444
3445 sub run_parse_job
3446 {
3447     my ($self, $ouput) = @_;
3448
3449     my %arg;
3450     foreach my $l (split(/\r\n/, $ouput)) {
3451         if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3452             $arg{$1} = $2;
3453             $l = $3 
3454                 if ($3) ;
3455         } 
3456
3457         if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3458             %arg = (%arg, @l);
3459         }
3460     }
3461
3462     my %lowcase ;
3463     foreach my $k (keys %arg) {
3464         $lowcase{lc($k)} = $arg{$k} ;
3465     }
3466
3467     return \%lowcase;
3468 }
3469
3470 sub run_job_mod
3471 {
3472     my ($self) = @_;
3473     my $b = $self->get_bconsole();
3474     
3475     my $job = CGI::param('job') || '';
3476
3477     # we take informations from director, and we overwrite with user wish
3478     my $info = $b->send_cmd("show job=\"$job\"");
3479     my $attr = $self->run_parse_job($info);
3480
3481     my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3482     my %job_opt = (%$attr, %$arg);
3483     
3484     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
3485
3486     my $pools  = [ map { { name => $_ } } $b->list_pool() ];
3487     my $clients = [ map { { name => $_ } }$b->list_client()];
3488     my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3489     my $storages= [ map { { name => $_ } }$b->list_storage()];
3490
3491     $self->display({
3492         jobs     => $jobs,
3493         pools    => $pools,
3494         clients  => $clients,
3495         filesets => $filesets,
3496         storages => $storages,
3497         %job_opt,
3498     }, "run_job_mod.tpl");
3499 }
3500
3501 sub run_job
3502 {
3503     my ($self) = @_;
3504     my $b = $self->get_bconsole();
3505     
3506     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
3507
3508     $self->display({
3509         jobs     => $jobs,
3510     }, "run_job.tpl");
3511 }
3512
3513 sub run_job_now
3514 {
3515     my ($self) = @_;
3516     my $b = $self->get_bconsole();
3517     
3518     # TODO: check input (don't use pool, level)
3519
3520     my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3521     my $job = CGI::param('job') || '';
3522     my $storage = CGI::param('storage') || '';
3523
3524     my $jobid = $b->run(job => $job,
3525                         client => $arg->{client},
3526                         priority => $arg->{priority},
3527                         level => $arg->{level},
3528                         storage => $storage,
3529                         pool => $arg->{pool},
3530                         fileset => $arg->{fileset},
3531                         when => $arg->{when},
3532                         );
3533
3534     print $jobid, $b->{error};    
3535
3536     print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";
3537 }
3538
3539 1;