]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/lib/Bweb.pm
ebl enable groups
[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     my ($limit, $label) = $self->get_limit(%arg);
2692
2693     my $query = "
2694 SELECT 
2695     count(Job.JobId)     AS nb_jobs,
2696     sum(Job.JobBytes)    AS nb_bytes,
2697     sum(Job.JobErrors)   AS nb_err,
2698     sum(Job.JobFiles)    AS nb_files,
2699     client_group.client_group_name  AS clientname
2700 FROM Job JOIN Client USING (ClientId) 
2701          JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
2702          JOIN client_group USING (client_group_id)
2703 WHERE 
2704     client_group.client_group_name = $carg->{qclient_group}
2705     $limit 
2706 GROUP BY client_group.client_group_name
2707 ";
2708
2709     my $row = $self->dbh_selectrow_hashref($query);
2710
2711     $row->{ID} = $cur_id++;
2712     $row->{label} = $label;
2713     $row->{grapharg} = "client_group";
2714
2715     $self->display($row, "display_client_stats.tpl");
2716 }
2717
2718 # poolname can be undef
2719 sub display_pool
2720 {
2721     my ($self, $poolname) = @_ ;
2722     my $whereA = '';
2723     my $whereW = '';
2724
2725     my $arg = $self->get_form('jmediatypes', 'qmediatypes');
2726     if ($arg->{jmediatypes}) { 
2727         $whereW = "WHERE MediaType IN ($arg->{jmediatypes}) ";
2728         $whereA = "AND   MediaType IN ($arg->{jmediatypes}) ";
2729     }
2730     
2731 # TODO : afficher les tailles et les dates
2732
2733     my $query = "
2734 SELECT subq.volmax        AS volmax,
2735        subq.volnum        AS volnum,
2736        subq.voltotal      AS voltotal,
2737        Pool.Name          AS name,
2738        Pool.Recycle       AS recycle,
2739        Pool.VolRetention  AS volretention,
2740        Pool.VolUseDuration AS voluseduration,
2741        Pool.MaxVolJobs    AS maxvoljobs,
2742        Pool.MaxVolFiles   AS maxvolfiles,
2743        Pool.MaxVolBytes   AS maxvolbytes,
2744        subq.PoolId        AS PoolId
2745 FROM
2746   (
2747     SELECT COALESCE(media_avg_size.volavg,0) * count(Media.MediaId) AS volmax,
2748            count(Media.MediaId)  AS volnum,
2749            sum(Media.VolBytes)   AS voltotal,
2750            Media.PoolId          AS PoolId,
2751            Media.MediaType       AS MediaType
2752     FROM Media
2753     LEFT JOIN (SELECT avg(Media.VolBytes) AS volavg,
2754                       Media.MediaType     AS MediaType
2755                FROM Media 
2756               WHERE Media.VolStatus = 'Full' 
2757               GROUP BY Media.MediaType
2758                ) AS media_avg_size ON (Media.MediaType = media_avg_size.MediaType)
2759     GROUP BY Media.MediaType, Media.PoolId, media_avg_size.volavg
2760   ) AS subq
2761 LEFT JOIN Pool ON (Pool.PoolId = subq.PoolId)
2762 $whereW
2763 ";
2764
2765     my $all = $self->dbh_selectall_hashref($query, 'name') ;
2766
2767     $query = "
2768 SELECT Pool.Name AS name,
2769        sum(VolBytes) AS size
2770 FROM   Media JOIN Pool ON (Media.PoolId = Pool.PoolId)
2771 WHERE  Media.VolStatus IN ('Recycled', 'Purged')
2772        $whereA
2773 GROUP BY Pool.Name;
2774 ";
2775     my $empty = $self->dbh_selectall_hashref($query, 'name');
2776
2777     foreach my $p (values %$all) {
2778         if ($p->{volmax} > 0) { # mysql returns 0.0000
2779             # we remove Recycled/Purged media from pool usage
2780             if (defined $empty->{$p->{name}}) {
2781                 $p->{voltotal} -= $empty->{$p->{name}}->{size};
2782             }
2783             $p->{poolusage} = sprintf('%.2f', $p->{voltotal} * 100/ $p->{volmax}) ;
2784         } else {
2785             $p->{poolusage} = 0;
2786         }
2787
2788         $query = "
2789   SELECT VolStatus AS volstatus, count(MediaId) AS nb
2790     FROM Media 
2791    WHERE PoolId=$p->{poolid} 
2792          $whereA
2793 GROUP BY VolStatus
2794 ";
2795         my $content = $self->dbh_selectall_hashref($query, 'volstatus');
2796         foreach my $t (values %$content) {
2797             $p->{"nb_" . $t->{volstatus}} = $t->{nb} ;
2798         }
2799     }
2800
2801     $self->debug($all);
2802     $self->display({ ID => $cur_id++,
2803                      MediaType => $arg->{qmediatypes}, # [ { name => type1 } , { name => type2 } ]
2804                      Pools => [ values %$all ]},
2805                    "display_pool.tpl");
2806 }
2807
2808 sub display_running_job
2809 {
2810     my ($self) = @_;
2811
2812     my $arg = $self->get_form('client', 'jobid');
2813
2814     if (!$arg->{client} and $arg->{jobid}) {
2815
2816         my $query = "
2817 SELECT Client.Name AS name
2818 FROM Job INNER JOIN Client USING (ClientId)
2819 WHERE Job.JobId = $arg->{jobid}
2820 ";
2821
2822         my $row = $self->dbh_selectrow_hashref($query);
2823
2824         if ($row) {
2825             $arg->{client} = $row->{name};
2826             CGI::param('client', $arg->{client});
2827         }
2828     }
2829
2830     if ($arg->{client}) {
2831         my $cli = new Bweb::Client(name => $arg->{client});
2832         $cli->display_running_job($self->{info}, $arg->{jobid});
2833         if ($arg->{jobid}) {
2834             $self->get_job_log();
2835         }
2836     } else {
2837         $self->error("Can't get client or jobid");
2838     }
2839 }
2840
2841 sub display_running_jobs
2842 {
2843     my ($self, $display_action) = @_;
2844     
2845     my $query = "
2846 SELECT Job.JobId AS jobid, 
2847        Job.Name  AS jobname,
2848        Job.Level     AS level,
2849        Job.StartTime AS starttime,
2850        Job.JobFiles  AS jobfiles,
2851        Job.JobBytes  AS jobbytes,
2852        Job.JobStatus AS jobstatus,
2853 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(NOW())  
2854                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
2855          AS duration,
2856        Client.Name AS clientname
2857 FROM Job INNER JOIN Client USING (ClientId) 
2858 WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
2859 ";      
2860     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
2861     
2862     $self->display({ ID => $cur_id++,
2863                      display_action => $display_action,
2864                      Jobs => [ values %$all ]},
2865                    "running_job.tpl") ;
2866 }
2867
2868 # return the autochanger list to update
2869 sub eject_media
2870 {
2871     my ($self) = @_;
2872     my %ret; 
2873     my $arg = $self->get_form('jmedias');
2874
2875     unless ($arg->{jmedias}) {
2876         return $self->error("Can't get media selection");
2877     }
2878
2879     my $query = "
2880 SELECT Media.VolumeName  AS volumename,
2881        Storage.Name      AS storage,
2882        Location.Location AS location,
2883        Media.Slot        AS slot
2884 FROM Media INNER JOIN Storage  ON (Media.StorageId  = Storage.StorageId)
2885            LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
2886 WHERE Media.VolumeName IN ($arg->{jmedias})
2887   AND Media.InChanger = 1
2888 ";
2889
2890     my $all = $self->dbh_selectall_hashref($query, 'volumename');
2891
2892     foreach my $vol (values %$all) {
2893         my $a = $self->ach_get($vol->{location});
2894         next unless ($a) ;
2895         $ret{$vol->{location}} = 1;
2896
2897         unless ($a->{have_status}) {
2898             $a->status();
2899             $a->{have_status} = 1;
2900         }
2901
2902         print "eject $vol->{volumename} from $vol->{storage} : ";
2903         if ($a->send_to_io($vol->{slot})) {
2904             print "<img src='/bweb/T.png' alt='ok'><br/>";
2905         } else {
2906             print "<img src='/bweb/E.png' alt='err'><br/>";
2907         }
2908     }
2909     return keys %ret;
2910 }
2911
2912 sub move_email
2913 {
2914     my ($self) = @_;
2915
2916     my ($to, $subject, $content) = (CGI::param('email'),
2917                                     CGI::param('subject'),
2918                                     CGI::param('content'));
2919     $to =~ s/[^\w\d\.\@<>,]//;
2920     $subject =~ s/[^\w\d\.\[\]]/ /;    
2921
2922     open(MAIL, "|mail -s '$subject' '$to'") ;
2923     print MAIL $content;
2924     close(MAIL);
2925
2926     print "Mail sent";
2927 }
2928
2929 sub restore
2930 {
2931     my ($self) = @_;
2932     
2933     my $arg = $self->get_form('jobid', 'client');
2934
2935     print CGI::header('text/brestore');
2936     print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
2937     print "client=$arg->{client}\n" if ($arg->{client});
2938     print "\n\nYou have to assign this mime type with /usr/bin/brestore.pl\n";
2939     print "\n";
2940 }
2941
2942 # TODO : move this to Bweb::Autochanger ?
2943 # TODO : make this internal to not eject tape ?
2944 use Bconsole;
2945
2946
2947 sub ach_get
2948 {
2949     my ($self, $name) = @_;
2950     
2951     unless ($name) {
2952         return $self->error("Can't get your autochanger name ach");
2953     }
2954
2955     unless ($self->{info}->{ach_list}) {
2956         return $self->error("Could not find any autochanger");
2957     }
2958     
2959     my $a = $self->{info}->{ach_list}->{$name};
2960
2961     unless ($a) {
2962         $self->error("Can't get your autochanger $name from your ach_list");
2963         return undef;
2964     }
2965
2966     $a->{bweb}  = $self;
2967     $a->{debug} = $self->{debug};
2968
2969     return $a;
2970 }
2971
2972 sub ach_register
2973 {
2974     my ($self, $ach) = @_;
2975
2976     $self->{info}->{ach_list}->{$ach->{name}} = $ach;
2977
2978     $self->{info}->save();
2979     
2980     return 1;
2981 }
2982
2983 sub ach_edit
2984 {
2985     my ($self) = @_;
2986     my $arg = $self->get_form('ach');
2987     if (!$arg->{ach} 
2988         or !$self->{info}->{ach_list} 
2989         or !$self->{info}->{ach_list}->{$arg->{ach}}) 
2990     {
2991         return $self->error("Can't get autochanger name");
2992     }
2993
2994     my $ach = $self->{info}->{ach_list}->{$arg->{ach}};
2995
2996     my $i=0;
2997     $ach->{drives} = 
2998         [ map { { name => $_, index => $i++ } } @{$ach->{drive_name}} ] ;
2999
3000     my $b = $self->get_bconsole();
3001
3002     my @storages = $b->list_storage() ;
3003
3004     $ach->{devices} = [ map { { name => $_ } } @storages ];
3005     
3006     $self->display($ach, "ach_add.tpl");
3007     delete $ach->{drives};
3008     delete $ach->{devices};
3009     return 1;
3010 }
3011
3012 sub ach_del
3013 {
3014     my ($self) = @_;
3015     my $arg = $self->get_form('ach');
3016
3017     if (!$arg->{ach} 
3018         or !$self->{info}->{ach_list} 
3019         or !$self->{info}->{ach_list}->{$arg->{ach}}) 
3020     {
3021         return $self->error("Can't get autochanger name");
3022     }
3023    
3024     delete $self->{info}->{ach_list}->{$arg->{ach}} ;
3025    
3026     $self->{info}->save();
3027     $self->{info}->view();
3028 }
3029
3030 sub ach_add
3031 {
3032     my ($self) = @_;
3033     my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
3034
3035     my $b = $self->get_bconsole();
3036     my @storages = $b->list_storage() ;
3037
3038     unless ($arg->{ach}) {
3039         $arg->{devices} = [ map { { name => $_ } } @storages ];
3040         return $self->display($arg, "ach_add.tpl");
3041     }
3042
3043     my @drives ;
3044     foreach my $drive (CGI::param('drives'))
3045     {
3046         unless (grep(/^$drive$/,@storages)) {
3047             return $self->error("Can't find $drive in storage list");
3048         }
3049
3050         my $index = CGI::param("index_$drive");
3051         unless (defined $index and $index =~ /^(\d+)$/) {
3052             return $self->error("Can't get $drive index");
3053         }
3054
3055         $drives[$index] = $drive;
3056     }
3057
3058     unless (@drives) {
3059         return $self->error("Can't get drives from Autochanger");
3060     }
3061
3062     my $a = new Bweb::Autochanger(name   => $arg->{ach},
3063                                   precmd => $arg->{precmd},
3064                                   drive_name => \@drives,
3065                                   device => $arg->{device},
3066                                   mtxcmd => $arg->{mtxcmd});
3067
3068     $self->ach_register($a) ;
3069     
3070     $self->{info}->view();
3071 }
3072
3073 sub delete
3074 {
3075     my ($self) = @_;
3076     my $arg = $self->get_form('jobid');
3077
3078     if ($arg->{jobid}) {
3079         my $b = $self->get_bconsole();
3080         my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
3081
3082         $self->display({
3083             content => $ret,
3084             title => "Delete a job ",
3085             name => "delete jobid=$arg->{jobid}",
3086         }, "command.tpl");      
3087     }
3088 }
3089
3090 sub do_update_media
3091 {
3092     my ($self) = @_ ;
3093
3094     my $arg = $self->get_form(qw/media volstatus inchanger pool
3095                                  slot volretention voluseduration 
3096                                  maxvoljobs maxvolfiles maxvolbytes
3097                                  qcomment poolrecycle
3098                               /);
3099
3100     unless ($arg->{media}) {
3101         return $self->error("Can't find media selection");
3102     }
3103
3104     my $update = "update volume=$arg->{media} ";
3105
3106     if ($arg->{volstatus}) {
3107         $update .= " volstatus=$arg->{volstatus} ";
3108     }
3109     
3110     if ($arg->{inchanger}) {
3111         $update .= " inchanger=yes " ;
3112         if ($arg->{slot}) {
3113             $update .= " slot=$arg->{slot} ";
3114         }
3115     } else {
3116         $update .= " slot=0 inchanger=no ";
3117     }
3118
3119     if ($arg->{pool}) {
3120         $update .= " pool=$arg->{pool} " ;
3121     }
3122
3123     if (defined $arg->{volretention}) {
3124         $update .= " volretention=\"$arg->{volretention}\" " ;
3125     }
3126
3127     if (defined $arg->{voluseduration}) {
3128         $update .= " voluse=\"$arg->{voluseduration}\" " ;
3129     }
3130
3131     if (defined $arg->{maxvoljobs}) {
3132         $update .= " maxvoljobs=$arg->{maxvoljobs} " ;
3133     }
3134     
3135     if (defined $arg->{maxvolfiles}) {
3136         $update .= " maxvolfiles=$arg->{maxvolfiles} " ;
3137     }    
3138
3139     if (defined $arg->{maxvolbytes}) {
3140         $update .= " maxvolbytes=$arg->{maxvolbytes} " ;
3141     }    
3142
3143     my $b = $self->get_bconsole();
3144
3145     $self->display({
3146         content => $b->send_cmd($update),
3147         title => "Update a volume ",
3148         name => $update,
3149     }, "command.tpl");  
3150
3151
3152     my @q;
3153     my $media = $self->dbh_quote($arg->{media});
3154
3155     my $loc = CGI::param('location') || '';
3156     if ($loc) {
3157         $loc = $self->dbh_quote($loc); # is checked by db
3158         push @q, "LocationId=(SELECT LocationId FROM Location WHERE Location=$loc)";
3159     }
3160     if ($arg->{poolrecycle}) {
3161         push @q, "RecyclePoolId=(SELECT PoolId FROM Pool WHERE Name='$arg->{poolrecycle}')";
3162     }
3163     if (!$arg->{qcomment}) {
3164         $arg->{qcomment} = "''";
3165     }
3166     push @q, "Comment=$arg->{qcomment}";
3167     
3168
3169     my $query = "
3170 UPDATE Media 
3171    SET " . join (',', @q) . "
3172  WHERE Media.VolumeName = $media
3173 ";
3174     $self->dbh_do($query);
3175
3176     $self->update_media();
3177 }
3178
3179 sub update_slots
3180 {
3181     my ($self) = @_;
3182
3183     my $ach = CGI::param('ach') ;
3184     $ach = $self->ach_get($ach);
3185     unless ($ach) {
3186         return $self->error("Bad autochanger name");
3187     }
3188
3189     print "<pre>";
3190     my $b = new Bconsole(pref => $self->{info},timeout => 60,log_stdout => 1);
3191     $b->update_slots($ach->{name});
3192     print "</pre>\n" 
3193 }
3194
3195 sub get_job_log
3196 {
3197     my ($self) = @_;
3198
3199     my $arg = $self->get_form('jobid', 'limit', 'offset');
3200     unless ($arg->{jobid}) {
3201         return $self->error("Can't get jobid");
3202     }
3203
3204     if ($arg->{limit} == 100) {
3205         $arg->{limit} = 1000;
3206     }
3207
3208     my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
3209
3210     my $query = "
3211 SELECT Job.Name as name, Client.Name as clientname
3212  FROM  Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
3213  WHERE JobId = $arg->{jobid}
3214 ";
3215
3216     my $row = $self->dbh_selectrow_hashref($query);
3217
3218     unless ($row) {
3219         return $self->error("Can't find $arg->{jobid} in catalog");
3220     }
3221
3222     $query = "
3223 SELECT Time AS time, LogText AS log 
3224   FROM  Log 
3225  WHERE Log.JobId = $arg->{jobid} 
3226     OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid}) 
3227                       AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
3228        )
3229  ORDER BY LogId
3230  LIMIT $arg->{limit}
3231  OFFSET $arg->{offset}
3232 ";
3233
3234     my $log = $self->dbh_selectall_arrayref($query);
3235     unless ($log) {
3236         return $self->error("Can't get log for jobid $arg->{jobid}");
3237     }
3238
3239     my $logtxt;
3240     if ($t) {
3241         # log contains \n
3242         $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ; 
3243     } else {
3244         $logtxt = join("", map { $_->[1] } @$log ) ; 
3245     }
3246     
3247     $self->display({ lines=> $logtxt,
3248                      jobid => $arg->{jobid},
3249                      name  => $row->{name},
3250                      client => $row->{clientname},
3251                      offset => $arg->{offset},
3252                      limit  => $arg->{limit},
3253                  }, 'display_log.tpl');
3254 }
3255
3256
3257 sub label_barcodes
3258 {
3259     my ($self) = @_ ;
3260
3261     my $arg = $self->get_form('ach', 'slots', 'drive');
3262
3263     unless ($arg->{ach}) {
3264         return $self->error("Can't find autochanger name");
3265     }
3266
3267     my $a = $self->ach_get($arg->{ach});
3268     unless ($a) {
3269         return $self->error("Can't find autochanger name in configuration");
3270     } 
3271
3272     my $storage = $a->get_drive_name($arg->{drive});
3273     unless ($storage) {
3274         return $self->error("Can't get your drive name");
3275     }
3276
3277     my $slots = '';
3278     my $t = 300 ;
3279     if ($arg->{slots}) {
3280         $slots = join(",", @{ $arg->{slots} });
3281         $t += 60*scalar( @{ $arg->{slots} }) ;
3282     }
3283
3284     my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
3285     print "<h1>This command can take long time, be patient...</h1>";
3286     print "<pre>" ;
3287     $b->label_barcodes(storage => $storage,
3288                        drive => $arg->{drive},
3289                        pool  => 'Scratch',
3290                        slots => $slots) ;
3291     $b->close();
3292     print "</pre>";
3293
3294     $self->dbh_do("
3295   UPDATE Media 
3296        SET LocationId =   (SELECT LocationId 
3297                              FROM Location 
3298                             WHERE Location = '$arg->{ach}'),
3299
3300            RecyclePoolId = PoolId
3301
3302      WHERE Media.PoolId = (SELECT PoolId 
3303                              FROM Pool
3304                             WHERE Name = 'Scratch')
3305        AND (LocationId = 0 OR LocationId IS NULL)
3306 ");
3307
3308 }
3309
3310 sub purge
3311 {
3312     my ($self) = @_;
3313
3314     my @volume = CGI::param('media');
3315
3316     unless (@volume) {
3317         return $self->error("Can't get media selection");
3318     }
3319
3320     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3321
3322     $self->display({
3323         content => $b->purge_volume(@volume),
3324         title => "Purge media",
3325         name => "purge volume=" . join(' volume=', @volume),
3326     }, "command.tpl");  
3327     $b->close();
3328 }
3329
3330 sub prune
3331 {
3332     my ($self) = @_;
3333
3334     my @volume = CGI::param('media');
3335     unless (@volume) {
3336         return $self->error("Can't get media selection");
3337     }
3338
3339     my $b = new Bconsole(pref => $self->{info}, timeout => 60);
3340
3341     $self->display({
3342         content => $b->prune_volume(@volume),
3343         title => "Prune media",
3344         name => "prune volume=" . join(' volume=', @volume),
3345     }, "command.tpl");  
3346
3347     $b->close();
3348 }
3349
3350 sub cancel_job
3351 {
3352     my ($self) = @_;
3353
3354     my $arg = $self->get_form('jobid');
3355     unless ($arg->{jobid}) {
3356         return $self->error("Can't get jobid");
3357     }
3358
3359     my $b = $self->get_bconsole();
3360     $self->display({
3361         content => $b->cancel($arg->{jobid}),
3362         title => "Cancel job",
3363         name => "cancel jobid=$arg->{jobid}",
3364     }, "command.tpl");  
3365 }
3366
3367 sub fileset_view
3368 {
3369     # Warning, we display current fileset
3370     my ($self) = @_;
3371
3372     my $arg = $self->get_form('fileset');
3373
3374     if ($arg->{fileset}) {
3375         my $b = $self->get_bconsole();
3376         my $ret = $b->get_fileset($arg->{fileset});
3377         $self->display({ fileset => $arg->{fileset},
3378                          %$ret,
3379                      }, "fileset_view.tpl");
3380     } else {
3381         $self->error("Can't get fileset name");
3382     }
3383 }
3384
3385 sub director_show_sched
3386 {
3387     my ($self) = @_ ;
3388
3389     my $arg = $self->get_form('days');
3390
3391     my $b = $self->get_bconsole();
3392     my $ret = $b->director_get_sched( $arg->{days} );
3393
3394     $self->display({
3395         id => $cur_id++,
3396         list => $ret,
3397     }, "scheduled_job.tpl");
3398 }
3399
3400 sub enable_disable_job
3401 {
3402     my ($self, $what) = @_ ;
3403
3404     my $name = CGI::param('job') || '';
3405     unless ($name =~ /^[\w\d\.\-\s]+$/) {
3406         return $self->error("Can't find job name");
3407     }
3408
3409     my $b = $self->get_bconsole();
3410
3411     my $cmd;
3412     if ($what) {
3413         $cmd = "enable";
3414     } else {
3415         $cmd = "disable";
3416     }
3417
3418     $self->display({
3419         content => $b->send_cmd("$cmd job=\"$name\""),
3420         title => "$cmd $name",
3421         name => "$cmd job=\"$name\"",
3422     }, "command.tpl");  
3423 }
3424
3425 sub get_bconsole
3426 {
3427     my ($self) = @_;
3428     return new Bconsole(pref => $self->{info});
3429 }
3430
3431 sub run_job_select
3432 {
3433     my ($self) = @_;
3434     my $b = $self->get_bconsole();
3435
3436     my $joblist = [ map { { name => $_ } } $b->list_job() ];
3437
3438     $self->display({ Jobs => $joblist }, "run_job.tpl");
3439 }
3440
3441 sub run_parse_job
3442 {
3443     my ($self, $ouput) = @_;
3444
3445     my %arg;
3446     foreach my $l (split(/\r\n/, $ouput)) {
3447         if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
3448             $arg{$1} = $2;
3449             $l = $3 
3450                 if ($3) ;
3451         } 
3452
3453         if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
3454             %arg = (%arg, @l);
3455         }
3456     }
3457
3458     my %lowcase ;
3459     foreach my $k (keys %arg) {
3460         $lowcase{lc($k)} = $arg{$k} ;
3461     }
3462
3463     return \%lowcase;
3464 }
3465
3466 sub run_job_mod
3467 {
3468     my ($self) = @_;
3469     my $b = $self->get_bconsole();
3470     
3471     my $job = CGI::param('job') || '';
3472
3473     # we take informations from director, and we overwrite with user wish
3474     my $info = $b->send_cmd("show job=\"$job\"");
3475     my $attr = $self->run_parse_job($info);
3476
3477     my $arg = $self->get_form('pool', 'level', 'client', 'fileset', 'storage');
3478     my %job_opt = (%$attr, %$arg);
3479     
3480     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
3481
3482     my $pools  = [ map { { name => $_ } } $b->list_pool() ];
3483     my $clients = [ map { { name => $_ } }$b->list_client()];
3484     my $filesets= [ map { { name => $_ } }$b->list_fileset() ];
3485     my $storages= [ map { { name => $_ } }$b->list_storage()];
3486
3487     $self->display({
3488         jobs     => $jobs,
3489         pools    => $pools,
3490         clients  => $clients,
3491         filesets => $filesets,
3492         storages => $storages,
3493         %job_opt,
3494     }, "run_job_mod.tpl");
3495 }
3496
3497 sub run_job
3498 {
3499     my ($self) = @_;
3500     my $b = $self->get_bconsole();
3501     
3502     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
3503
3504     $self->display({
3505         jobs     => $jobs,
3506     }, "run_job.tpl");
3507 }
3508
3509 sub run_job_now
3510 {
3511     my ($self) = @_;
3512     my $b = $self->get_bconsole();
3513     
3514     # TODO: check input (don't use pool, level)
3515
3516     my $arg = $self->get_form('pool', 'level', 'client', 'priority', 'when', 'fileset');
3517     my $job = CGI::param('job') || '';
3518     my $storage = CGI::param('storage') || '';
3519
3520     my $jobid = $b->run(job => $job,
3521                         client => $arg->{client},
3522                         priority => $arg->{priority},
3523                         level => $arg->{level},
3524                         storage => $storage,
3525                         pool => $arg->{pool},
3526                         fileset => $arg->{fileset},
3527                         when => $arg->{when},
3528                         );
3529
3530     print $jobid, $b->{error};    
3531
3532     print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";
3533 }
3534
3535 1;