]> git.sur5r.net Git - bacula/bacula/commitdiff
ebl add to cvs
authorEric Bollengier <eric@eb.homelinux.org>
Tue, 8 Aug 2006 21:45:32 +0000 (21:45 +0000)
committerEric Bollengier <eric@eb.homelinux.org>
Tue, 8 Aug 2006 21:45:32 +0000 (21:45 +0000)
git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@3272 91ce42f0-d328-0410-95d8-f526ca767f89

90 files changed:
gui/bweb/INSTALL [new file with mode: 0644]
gui/bweb/cgi/bgraph.pl [new file with mode: 0755]
gui/bweb/cgi/bweb.pl [new file with mode: 0755]
gui/bweb/html/A.png [new file with mode: 0644]
gui/bweb/html/E.png [new file with mode: 0644]
gui/bweb/html/R.png [new file with mode: 0644]
gui/bweb/html/T.png [new file with mode: 0644]
gui/bweb/html/add.png [new file with mode: 0644]
gui/bweb/html/apply.png [new file with mode: 0644]
gui/bweb/html/bweb.css [new file with mode: 0644]
gui/bweb/html/bweb.js [new file with mode: 0644]
gui/bweb/html/cancel.png [new file with mode: 0644]
gui/bweb/html/chart.png [new file with mode: 0644]
gui/bweb/html/down.gif [new file with mode: 0755]
gui/bweb/html/edit.png [new file with mode: 0644]
gui/bweb/html/extern.png [new file with mode: 0644]
gui/bweb/html/f.png [new file with mode: 0644]
gui/bweb/html/filename.png [new file with mode: 0644]
gui/bweb/html/first.gif [new file with mode: 0755]
gui/bweb/html/first.png [new file with mode: 0644]
gui/bweb/html/inflag0.png [new file with mode: 0644]
gui/bweb/html/inflag1.png [new file with mode: 0644]
gui/bweb/html/intern.png [new file with mode: 0644]
gui/bweb/html/kaiska.css [new file with mode: 0644]
gui/bweb/html/label.png [new file with mode: 0644]
gui/bweb/html/last.gif [new file with mode: 0755]
gui/bweb/html/last.png [new file with mode: 0644]
gui/bweb/html/lcorner.png [new file with mode: 0644]
gui/bweb/html/left.gif [new file with mode: 0755]
gui/bweb/html/left.png [new file with mode: 0644]
gui/bweb/html/load.png [new file with mode: 0644]
gui/bweb/html/lock.png [new file with mode: 0644]
gui/bweb/html/natcompare.js [new file with mode: 0755]
gui/bweb/html/next.png [new file with mode: 0644]
gui/bweb/html/nrs_table.js [new file with mode: 0755]
gui/bweb/html/prev.png [new file with mode: 0644]
gui/bweb/html/prune.png [new file with mode: 0644]
gui/bweb/html/purge.png [new file with mode: 0644]
gui/bweb/html/rcorner.png [new file with mode: 0644]
gui/bweb/html/remove.png [new file with mode: 0644]
gui/bweb/html/right.gif [new file with mode: 0755]
gui/bweb/html/right.png [new file with mode: 0644]
gui/bweb/html/save.png [new file with mode: 0644]
gui/bweb/html/style.css [new file with mode: 0644]
gui/bweb/html/unload.png [new file with mode: 0644]
gui/bweb/html/up.gif [new file with mode: 0755]
gui/bweb/html/update.png [new file with mode: 0644]
gui/bweb/html/zoom.png [new file with mode: 0644]
gui/bweb/lib/Bconsole.pm [new file with mode: 0644]
gui/bweb/lib/Bweb.pm [new file with mode: 0644]
gui/bweb/script/bweb-postgresql.sql [new file with mode: 0644]
gui/bweb/tpl/about.tpl [new file with mode: 0644]
gui/bweb/tpl/ach_content.tpl [new file with mode: 0644]
gui/bweb/tpl/begin.tpl [new file with mode: 0644]
gui/bweb/tpl/change_location.tpl [new file with mode: 0644]
gui/bweb/tpl/client_job_status.tpl [new file with mode: 0644]
gui/bweb/tpl/client_list.tpl [new file with mode: 0644]
gui/bweb/tpl/client_status.tpl [new file with mode: 0644]
gui/bweb/tpl/command.tpl [new file with mode: 0644]
gui/bweb/tpl/config_edit.tpl [new file with mode: 0644]
gui/bweb/tpl/config_view.tpl [new file with mode: 0644]
gui/bweb/tpl/display_client_job.tpl [new file with mode: 0644]
gui/bweb/tpl/display_client_stats.tpl [new file with mode: 0644]
gui/bweb/tpl/display_form_job.tpl [new file with mode: 0644]
gui/bweb/tpl/display_job.tpl [new file with mode: 0644]
gui/bweb/tpl/display_job_zoom.tpl [new file with mode: 0644]
gui/bweb/tpl/display_location.tpl [new file with mode: 0644]
gui/bweb/tpl/display_log.tpl [new file with mode: 0644]
gui/bweb/tpl/display_media.tpl [new file with mode: 0644]
gui/bweb/tpl/display_media_zoom.tpl [new file with mode: 0644]
gui/bweb/tpl/display_pool.tpl [new file with mode: 0644]
gui/bweb/tpl/end.tpl [new file with mode: 0644]
gui/bweb/tpl/error.tpl [new file with mode: 0644]
gui/bweb/tpl/general.tpl [new file with mode: 0644]
gui/bweb/tpl/graph.tpl [new file with mode: 0644]
gui/bweb/tpl/help_extern.tpl [new file with mode: 0644]
gui/bweb/tpl/help_extern_compute.tpl [new file with mode: 0644]
gui/bweb/tpl/help_intern.tpl [new file with mode: 0644]
gui/bweb/tpl/help_intern_compute.tpl [new file with mode: 0644]
gui/bweb/tpl/install.tpl [new file with mode: 0644]
gui/bweb/tpl/job_select.tpl [new file with mode: 0644]
gui/bweb/tpl/location_add.tpl [new file with mode: 0644]
gui/bweb/tpl/location_edit.tpl [new file with mode: 0644]
gui/bweb/tpl/move_media.tpl [new file with mode: 0644]
gui/bweb/tpl/run_job.tpl [new file with mode: 0644]
gui/bweb/tpl/run_job_mod.tpl [new file with mode: 0644]
gui/bweb/tpl/running_job.tpl [new file with mode: 0644]
gui/bweb/tpl/scheduled_job.tpl [new file with mode: 0644]
gui/bweb/tpl/update_location.tpl [new file with mode: 0644]
gui/bweb/tpl/update_media.tpl [new file with mode: 0644]

diff --git a/gui/bweb/INSTALL b/gui/bweb/INSTALL
new file mode 100644 (file)
index 0000000..9c5317e
--- /dev/null
@@ -0,0 +1,164 @@
+################################################################
+#               INSTALL NOTES                                  #
+################################################################
+
+Bweb works well with 1.39 release.
+
+1) install perl lib
+2) copy your files
+3) intialise your configuration file
+4) do some sql stuff (for postgresql user)
+5) get a bconsole that work with Expect
+6) get bacula log more useful
+7) bweb limitation
+8) using sudo with autochanger
+
+################ FILE COPY #####################################
+
+ # first, copy bweb perl librarie in your PERL5 INC path
+ install -m 644 -o root -g root  bweb/lib/*.pm /usr/share/perl5
+
+ # copy bweb perl program to you cgi location
+ mkdir -m 755 /usr/lib/cgi-bin/bweb
+ install -m 755 -o root -g root  bweb/cgi/*.pl /usr/lib/cgi-bin/bweb
+
+ # get a config file
+ mkdir -m 750 /etc/bweb
+ chown root:www-data /etc/bweb
+ echo "template_dir = /usr/share/bweb/tpl" > /etc/bweb/config
+ chown www-data /etc/bweb/config
+
+ # copy bweb template file
+ mkdir -p /usr/share/bweb/tpl
+ install -m 644 -o root -g root  bweb/tpl/*.tpl /usr/share/bweb/tpl
+
+ # copy bweb graphics elements (bweb elements must reside on /bweb)
+ mkdir /var/www/bweb
+ install -m 644 -o root -g root  bweb/html/*.{js,png,css,gif} /var/www/bweb
+
+ # done !
+
+################ INSTALL PERL LIBRARY ##########################
+
+ - perl modules
+    - DBI (with mysql or postgresql support DBD::Pg and DBD::mysql)
+    - Gd::Graph
+    - HTML::Template
+    - CGI
+    - Expect
+
+ You can install perl modules with CPAN
+ perl -e shell -MCPAN
+  > install Expect
+
+ Or use your distribution
+ apt-get install libgd-graph-perl libhtml-template-perl libexpect-perl
+ apt-get install libdbd-mysql-perl libdbd-pg-perl libdbi-perl
+
+################ APACHE CONFIGURATION ##########################
+
+It could be a good idea to protect your bweb installation.
+
+Put this in you httpd.conf, and add user with htpasswd
+
+<Directory /usr/lib/cgi-bin/bweb>
+        Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
+        AuthType Basic
+        AuthName MyPrivateFile
+        AuthUserFile /etc/apache/htpasswd
+        AllowOverride None
+        Require valid-user
+</Directory>
+
+
+################ CONFIGURATION #################################
+
+/etc/bweb/config look like : (you can edit it inside bweb)
+
+dbi = DBI:Pg:database=bacula;host=192.168.1.2
+user = bacula
+password = test
+template_dir = /usr/share/bweb/tpl
+graph_font = /usr/share/fonts/truetype/msttcorefonts/Arial.ttf
+email_media = eric@localhost
+bconsole = /usr/local/bacula/sbin/bconsole -c /usr/local/bacula/etc/bconsole.conf
+
+################ BRESTORE ######################################
+
+If you want to use brestore with bweb, you must associate mime type
+text/brestore with your brestore.pl.
+
+################ POSTGRESQL NOTES ##############################
+
+If you think that Mysql is not a great database (or just a toy), you must add
+function to the Postgresql bacula database to get Bweb works.
+
+psql -u bacula bacula < script/bweb-postgresql.sql
+
+################ BCONSOLE NOTES ################################
+
+You must use bconsole without conio/readline support ! You can have 2 bconsole
+binary at the same time.
+
+./configure <your-other-options> --disable-conio
+cd src/lib
+make
+cd ..
+cd console
+make
+cp bconsole <your_destination>
+
+################ BACULA LOG ####################################
+
+To use Bweb log engine, you must apply this little patch and have the
+new Log table in your database.
+
+After, you can fill your database with :
+tail -f /tmp/log.sql | bacula -u bacula bacula
+
+cd bacula-src
+patch < message.patch
+--- cvs/src/lib/message.c       2006-07-27 21:06:20.000000000 +0200
++++ cvs/src/lib/message.c.director      2006-07-28 13:46:49.171083494 +0200
+@@ -716,6 +716,18 @@
+                 }
+                 fputs(dt, d->fd);
+                 fputs(msg, d->fd);
++               void db_escape_string(char *snew, char *old, int len);
++               if (jcr) {
++                       char ed1[50];
++                       POOL_MEM cmd(PM_MESSAGE);
++                       int len = strlen(msg);
++                       char *p = (char *)malloc(len * 2 + 1);
++                       db_escape_string(p, msg, len);
++                       FILE *fp = fopen("/tmp/log.sql", "a");
++                       fprintf(fp, "INSERT INTO Log (Time, JobId, LogText) VALUES (NOW(),%s, '%s');\n", edit_int64(jcr->JobId, ed1), p);
++                       fclose(fp);
++               }
++
+                 break;
+              case MD_DIRECTOR:
+                 Dmsg1(850, "DIRECTOR for following msg: %s", msg);
+
+
+
+
+################ BWEB LIMITATION ###############################
+
+To get bweb working, you must follow these rules
+ - Media, Storage and Pool must have [A-Za-z_0-9\.-]+ (no space)
+ - AutoChanger name must be same as Storage name device in bacula
+
+################ SUDO CONFIGURATION ############################
+
+*** At this time, autochanger module works only at home :)
+
+If you use sudo, put this on you /etc/sudoers
+
+www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer transfer *
+www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer status
+www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer load *
+www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer unload *
+
+
+Enjoy !
diff --git a/gui/bweb/cgi/bgraph.pl b/gui/bweb/cgi/bgraph.pl
new file mode 100755 (executable)
index 0000000..c691e26
--- /dev/null
@@ -0,0 +1,298 @@
+#!/usr/bin/perl -w
+use strict;
+
+=head1 LICENSE
+
+    Copyright (C) 2006 Eric Bollengier
+        All rights reserved.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=head1 VERSION
+
+    $Id$
+
+=cut
+
+use Bweb;
+
+use Data::Dumper;
+use CGI;
+
+use POSIX qw/strftime/;
+
+my $conf = new Bweb::Config(config_file => '/etc/bweb/config');
+$conf->load();
+
+my $bweb = new Bweb(info => $conf);
+$bweb->connect_db();
+my $dbh = $bweb->{dbh};
+my $debug = $bweb->{debug};
+
+my $graph = CGI::param('graph') || 'begin';
+
+my $arg = $bweb->get_form(qw/width height limit offset age
+                            jfilesets level status jjobnames jclients/);
+
+my ($limitq, $label) = $bweb->get_limit(age   => $arg->{age},
+                                       limit => $arg->{limit},
+                                       offset=> $arg->{offset},
+                                       order => 'Job.StartTime ASC',
+                                       );
+
+my $statusq='';
+if ($arg->{status} and $arg->{status} ne 'Any') {
+    $statusq = " AND Job.JobStatus = '$arg->{status}' ";
+}
+    
+my $levelq='';
+if ($arg->{level} and $arg->{level} ne 'Any') {
+    $levelq = " AND Job.Level = '$arg->{level}' ";
+} 
+
+my $filesetq='';
+if ($arg->{jfilesets}) {
+    $filesetq = " AND FileSet.FileSet IN ($arg->{qfilesets}) ";
+} 
+
+my $jobnameq='';
+if ($arg->{jjobnames}) {
+    $jobnameq = " AND Job.Name IN ($arg->{jjobnames}) ";
+} else {
+    $arg->{jjobnames} = 'all'; # skip warning
+} 
+
+my $clientq='';
+if ($arg->{jclients}) {
+    $clientq = " AND Client.Name IN ($arg->{jclients}) ";
+} else {
+    $arg->{jclients} = 'all';  # skip warning
+}
+
+my $gtype = CGI::param('gtype') || 'bars';
+
+print CGI::header('image/png');
+
+sub get_graph
+{
+    my (@options) = @_;
+    my $graph;
+    if ($gtype eq 'lines') {
+       use GD::Graph::lines;
+       $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
+
+    } elsif ($gtype eq 'bars') {
+       use GD::Graph::bars;
+       $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
+
+    } elsif ($gtype eq 'linespoints') {
+       use GD::Graph::linespoints;
+       $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
+
+    } elsif ($gtype eq 'bars3d') {
+       use GD::Graph::bars3d;
+       $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
+
+    } else {
+       return undef;
+    }
+
+    $graph->set('x_label' => 'Time',
+               'x_number_format' => sub { strftime('%D', localtime($_[0])) },
+               'x_tick_number' => 1,
+               @options,
+               );
+
+    return $graph;
+}
+
+sub make_tab
+{
+    my ($all_row) = @_;
+
+    my $i=0;
+    my $last_date=0;
+
+    my $ret = {};
+    
+    foreach my $row (@$all_row) {
+       my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
+
+       $ret->{date}->[$i]   = $row->[0];       
+       $ret->{$label}->[$i] = $row->[3];
+       $i++;
+       $last_date = $row->[0];
+    }
+
+    # insert a fake element
+    foreach my $elt ( keys %{$ret}) {
+       $ret->{$elt}->[$i] =  undef;
+    }
+
+    $ret->{date}->[$i] = $last_date + 1;
+
+    my $date = $ret->{date} ;
+    delete $ret->{date};
+
+    return ($date, $ret);
+}
+
+if ($graph eq 'job_size') {
+
+    my $query = "
+SELECT 
+       UNIX_TIMESTAMP(Job.StartTime)    AS starttime,
+       Client.Name                      AS clientname,
+       Job.Name                         AS jobname,
+       Job.JobBytes                     AS jobbytes
+FROM Job, Client, FileSet
+WHERE Job.ClientId = Client.ClientId
+  AND Job.FileSetId = FileSet.FileSetId
+  AND Job.Type = 'B'
+  $clientq
+  $statusq
+  $filesetq
+  $levelq
+  $jobnameq
+$limitq
+";
+
+    print STDERR $query if ($debug);
+
+    my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
+                       'y_label' => 'Size',
+                       'y_min_value' => 0,
+                       'y_number_format' => \&Bweb::human_size,
+                       );
+
+    my $all = $dbh->selectall_arrayref($query) ;
+
+    my ($d, $ret) = make_tab($all);
+    $obj->set_legend(keys %$ret);
+    print $obj->plot([$d, values %$ret])->png;
+}
+
+if ($graph eq 'job_file') {
+
+    my $query = "
+SELECT 
+       UNIX_TIMESTAMP(Job.StartTime)    AS starttime,
+       Client.Name                      AS clientname,
+       Job.Name                         AS jobname,
+       Job.JobFiles                     AS jobfiles
+FROM Job, Client, FileSet
+WHERE Job.ClientId = Client.ClientId
+  AND Job.FileSetId = FileSet.FileSetId
+  AND Job.Type = 'B'
+  $clientq
+  $statusq
+  $filesetq
+  $levelq
+  $jobnameq
+$limitq
+";
+
+    print STDERR $query if ($debug);
+
+    my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
+                       'y_label' => 'Number Files',
+                       'y_min_value' => 0,
+                       );
+
+    my $all = $dbh->selectall_arrayref($query) ;
+
+    my ($d, $ret) = make_tab($all);
+    $obj->set_legend(keys %$ret);
+    print $obj->plot([$d, values %$ret])->png;
+}
+
+elsif ($graph eq 'job_rate') {
+
+    my $query = "
+SELECT 
+       UNIX_TIMESTAMP(Job.StartTime)                          AS starttime,
+       Client.Name                      AS clientname,
+       Job.Name                         AS jobname,
+       Job.JobBytes /
+       ($bweb->{sql}->{SEC_TO_INT}(
+                          $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)  
+                        - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01) 
+         AS rate
+
+FROM Job, Client, FileSet
+WHERE Job.ClientId = Client.ClientId
+  AND Job.FileSetId = FileSet.FileSetId
+  AND Job.Type = 'B'
+  $clientq
+  $statusq
+  $filesetq
+  $levelq
+  $jobnameq
+$limitq
+";
+
+    print STDERR $query if ($debug);
+
+    my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
+                       'y_label' => 'Rate b/s',
+                       'y_min_value' => 0,
+                       'y_number_format' => \&Bweb::human_size,
+                       );
+
+    my $all = $dbh->selectall_arrayref($query) ;
+
+    my ($d, $ret) = make_tab($all);    
+    $obj->set_legend(keys %$ret);
+    print $obj->plot([$d, values %$ret])->png;
+}
+
+
+
+elsif ($graph eq 'job_duration') {
+
+    my $query = "
+SELECT 
+       UNIX_TIMESTAMP(Job.StartTime)                           AS starttime,
+       Client.Name                                             AS clientname,
+       Job.Name                                                AS jobname,
+  $bweb->{sql}->{SEC_TO_INT}(  $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)  
+                             - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
+         AS duration
+FROM Job, Client, FileSet
+WHERE Job.ClientId = Client.ClientId
+  AND Job.FileSetId = FileSet.FileSetId
+  AND Job.Type = 'B'
+  $clientq
+  $statusq
+  $filesetq
+  $levelq
+  $jobnameq
+$limitq
+";
+
+    print STDERR $query if ($debug);
+
+    my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
+                       'y_label' => 'Duration',
+                       'y_min_value' => 0,
+                       'y_number_format' => \&Bweb::human_sec,
+                       );
+    my $all = $dbh->selectall_arrayref($query) ;
+
+    my ($d, $ret) = make_tab($all);
+    $obj->set_legend(keys %$ret);
+    print $obj->plot([$d, values %$ret])->png;
+}
+
diff --git a/gui/bweb/cgi/bweb.pl b/gui/bweb/cgi/bweb.pl
new file mode 100755 (executable)
index 0000000..f802b2d
--- /dev/null
@@ -0,0 +1,356 @@
+#!/usr/bin/perl -w
+use strict ;
+
+=head1 LICENSE
+
+    Copyright (C) 2006 Eric Bollengier
+        All rights reserved.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=head1 VERSION
+
+    $Id$
+
+=cut
+
+use Data::Dumper;
+use Bweb;
+use CGI;
+
+my $client_re = qr/^([\w\d\.-]+)$/;
+my $pool_re   = $client_re; 
+
+my $action = CGI::param('action') || 'begin';
+
+if ($action eq 'restore') {
+    print CGI::header('text/brestore');        # specialy to run brestore.pl
+
+} else {
+    print CGI::header('text/html');
+}
+
+# loading config file
+my $conf = new Bweb::Config(config_file => '/etc/bweb/config');
+$conf->load();
+
+my $bweb = new Bweb(info => $conf);
+
+# just send data with text/brestore content
+if ($action eq 'restore') {
+    $bweb->restore();
+    exit 0;
+}
+
+my $arg = $bweb->get_form('jobid', 'limit', 'offset', 'age');
+
+$bweb->display_begin();
+
+if ($action eq 'begin') {              # main display
+    print "<table border='0'><td valign='top' width='100%'>\n";
+    $bweb->display_general(age => $arg->{age});
+    $bweb->display_running_jobs(0);
+    print "</td><td valign='top'>";
+    print "
+<div class='titlediv'>
+  <h1 class='newstitle'> Statistics (last 48 hours)</h1>
+</div>
+<div class='bodydiv'>
+<a href='?action=job;age=172800;jobtype=B'>
+<img src='bgraph.pl?age=172800;width=450;height=250;graph=job_size;limit=100;action=graph' alt='Nothing to display'>
+</a>
+</div>";
+    print "</td></table>";
+    $bweb->display_job(limit => 10); 
+
+} elsif ($action eq 'view_conf') {
+    $conf->view()
+
+} elsif ($action eq 'edit_conf') {
+    $conf->edit();
+
+} elsif ($action eq 'apply_conf') {
+    $conf->modify();
+
+} elsif ($action eq 'client') {        
+    $bweb->display_clients();
+
+} elsif ($action eq 'pool') {
+    $bweb->display_pool();
+
+} elsif ($action eq 'location_edit') {
+    $bweb->location_edit();
+
+} elsif ($action eq 'location_save') {
+    $bweb->location_save();
+
+} elsif ($action eq 'location_add') {
+    $bweb->location_add();
+
+#} elsif ($action eq 'del_location') {
+#    $bweb->del_location();
+#
+} elsif ($action eq 'media') {
+    $bweb->display_media();
+
+} elsif ($action eq 'medias') {
+    $bweb->display_medias();
+
+} elsif ($action eq 'eject') {
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $bweb);
+
+    $a->status();
+    foreach my $slot (CGI::param('slot')) {
+       print $a->{error} unless $a->send_to_io($slot);
+    }
+
+    foreach my $media (CGI::param('media')) {
+       my $slot = $a->get_media_slot($media);
+       print $a->{error} unless $a->send_to_io($slot);
+    }
+
+    $a->display_content();
+
+} elsif ($action eq 'eject_media') {
+    $bweb->eject_media();
+
+} elsif ($action eq 'clear_io') {
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $bweb);
+    $a->status();
+    $a->clear_io();
+    $a->display_content();
+   
+} elsif ($action eq 'ach_view') {
+    # TODO : get autochanger name and create it
+    $bweb->connect_db();
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $bweb);
+    $a->status();
+    $a->display_content();
+
+} elsif ($action eq 'ach_load') {
+    my $arg = $bweb->get_form('drive', 'slot');
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $bweb);
+    $a->status();
+    $a->load($arg->{drive}, $arg->{slot}) ;
+
+} elsif ($action eq 'ach_unload') {
+    my $arg = $bweb->get_form('drive', 'slot');
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $bweb);
+    $a->status();
+    $a->unload($arg->{drive}, $arg->{slot}) ;
+   
+} elsif ($action eq 'intern_media') {
+    $bweb->help_intern();
+
+} elsif ($action eq 'compute_intern_media') {
+    $bweb->help_intern_compute();
+
+} elsif ($action eq 'extern_media') {
+    $bweb->help_extern();
+
+} elsif ($action eq 'compute_extern_media') {
+    $bweb->help_extern_compute();
+
+} elsif ($action eq 'extern') {
+    print "TODO : Eject ", join(",", CGI::param('media'));
+    $bweb->move_media();
+
+} elsif ($action eq 'change_location') {
+    $bweb->change_location();
+
+} elsif ($action eq 'location') {
+    $bweb->display_location();
+
+} elsif ($action eq 'about') {
+    $bweb->display($bweb, 'about.tpl');
+
+} elsif ($action eq 'intern') {
+    $bweb->move_media(); # TODO : remove that
+
+} elsif ($action eq 'move_media') {
+    $bweb->move_media(); 
+
+} elsif ($action eq 'save_location') {
+    $bweb->save_location();
+
+} elsif ($action eq 'update_location') {
+    $bweb->update_location();
+
+} elsif ($action eq 'update_media') {
+    $bweb->update_media();
+
+} elsif ($action eq 'do_update_media') {
+    $bweb->do_update_media();
+
+} elsif ($action eq 'update_slots') {
+    $bweb->update_slots();
+
+} elsif ($action eq 'graph') {
+    $bweb->display_graph();
+
+} elsif ($action eq 'next_job') {
+    $bweb->director_show_sched();
+
+} elsif ($action eq 'enable_job') {
+    $bweb->enable_disable_job(1);
+
+} elsif ($action eq 'disable_job') {
+    $bweb->enable_disable_job(0);
+
+} elsif ($action eq 'job') {
+
+    print "<table border='0'><td valign='top'>\n";
+    my $fields = $bweb->get_form(qw/status level db_clients db_filesets
+                                   limit age offset qclients qfilesets
+                                   jobtype/);
+    $bweb->display($fields, "display_form_job.tpl");
+
+    print "</td><td valign='top'>";
+    $bweb->display_job(age => $arg->{age},  # last 7 days
+                      offset => $arg->{offset},
+                      limit => $arg->{limit});
+    print "</td></table>";
+} elsif ($action eq 'client_stats') {
+
+    foreach my $client (CGI::param('client')) {
+       if ($client =~ m/$client_re/) {
+           $bweb->display_client_stats(clientname => $1,
+                                       age => $arg->{age});
+       }
+    }
+
+  
+
+} elsif ($action eq 'running') {
+    $bweb->display_running_jobs(1);
+
+} elsif ($action eq 'dsp_cur_job') {
+    $bweb->display_running_job();
+
+} elsif ($action eq 'update_from_pool') {
+    my $elt = $bweb->get_form(qw/media pool/);
+    unless ($elt->{media} || $elt->{pool}) {
+       $bweb->error("Can't get media or pool param");
+    } else {
+       my $b = new Bconsole(pref => $conf) ;
+
+       $bweb->display({
+ content => $b->send_cmd("update volume=$elt->{media} fromPool=$elt->{pool}"),
+ title => "Update pool",
+ name => "update volume=$elt->{media} fromPool=$elt->{pool}",
+       }, "command.tpl");      
+    }
+    
+    $bweb->update_media();
+
+} elsif ($action eq 'client_status') {
+    my $b;
+    foreach my $client (CGI::param('client')) {
+       if ($client =~ m/$client_re/) {
+           $client = $1;
+           $b = new Bconsole(pref => $conf) 
+               unless ($b) ;
+
+           $bweb->display({
+               content => $b->send_cmd("st client=$client"),
+               title => "Client status",
+               name => $client,
+           }, "command.tpl");
+           
+       } else {
+           $bweb->error("Can't get client selection");
+       }
+    }
+
+} elsif ($action eq 'cancel_job') {
+    $bweb->cancel_job();
+
+} elsif  ($action eq 'media_zoom') {
+    $bweb->display_media_zoom();
+
+} elsif  ($action eq 'job_zoom') {
+    if ($arg->{jobid}) {
+       $bweb->display_job_zoom($arg->{jobid});
+       $bweb->get_job_log();
+    } 
+} elsif ($action eq 'job_log') {
+    $bweb->get_job_log();
+
+} elsif ($action eq 'prune') {
+    $bweb->prune();
+
+} elsif ($action eq 'purge') {
+    $bweb->purge();
+
+} elsif ($action eq 'run_job') {
+    $bweb->run_job();
+
+} elsif ($action eq 'run_job_mod') {
+    $bweb->run_job_mod();
+
+} elsif ($action eq 'run_job_now') {
+    $bweb->run_job_now();
+
+} elsif ($action eq 'label_barcodes') {
+    $bweb->label_barcodes();
+
+} elsif ($action eq 'delete') {
+    $bweb->delete();
+
+} else {
+    $bweb->error("Sorry, this action don't exist");
+}
+
+$bweb->display_end();
+
+
+__END__
+
+TODO :
+
+ o Affichage des job en cours, termines
+ o Affichage du detail d'un job (status client)
+ o Acces aux log d'une sauvegarde
+ o Cancel d'un job
+ o Lancement d'un job
+
+ o Affichage des medias (pool, cf bacweb)
+ o Affichage de la liste des cartouches
+ o Affichage d'un autochangeur
+ o Mise a jour des slots
+ o Label barcodes
+ o Affichage des medias qui ont besoin d'etre change
+
+ o Affichage des stats sur les dernieres sauvegardes (cf bacula-web)
+ o Affichage des stats sur un type de job
+ o Affichage des infos de query.sql
+
+ - Affichage des du TapeAlert sur le site
+ - Recuperation des erreurs SCSI de /var/log/kern.log
+
+ o update d'un volume
+ o update d'un pool
+
+ - Configuration des autochanger a la main dans un hash dumper
+
+ {
+   L10 => {
+     name => 'L10',
+     drive_name => ['SDLT-1', 'STLD-2'],
+     login => 'bacula',
+     host  => 'storehost',
+     device => '/dev/changer',
+   },
+ }
diff --git a/gui/bweb/html/A.png b/gui/bweb/html/A.png
new file mode 100644 (file)
index 0000000..89c2124
Binary files /dev/null and b/gui/bweb/html/A.png differ
diff --git a/gui/bweb/html/E.png b/gui/bweb/html/E.png
new file mode 100644 (file)
index 0000000..89c2124
Binary files /dev/null and b/gui/bweb/html/E.png differ
diff --git a/gui/bweb/html/R.png b/gui/bweb/html/R.png
new file mode 100644 (file)
index 0000000..166c220
Binary files /dev/null and b/gui/bweb/html/R.png differ
diff --git a/gui/bweb/html/T.png b/gui/bweb/html/T.png
new file mode 100644 (file)
index 0000000..d92d88d
Binary files /dev/null and b/gui/bweb/html/T.png differ
diff --git a/gui/bweb/html/add.png b/gui/bweb/html/add.png
new file mode 100644 (file)
index 0000000..062d005
Binary files /dev/null and b/gui/bweb/html/add.png differ
diff --git a/gui/bweb/html/apply.png b/gui/bweb/html/apply.png
new file mode 100644 (file)
index 0000000..58a64cf
Binary files /dev/null and b/gui/bweb/html/apply.png differ
diff --git a/gui/bweb/html/bweb.css b/gui/bweb/html/bweb.css
new file mode 100644 (file)
index 0000000..bb6e678
--- /dev/null
@@ -0,0 +1,11 @@
+body { background-color: #ffffff; font-family: verdana,arial,helvetica; font-size: 8pt;}
+a { text-decoration: none;}
+.code_display   {       background-color: #b9b9ac;
+                        border: 1px solid #9d9d94;
+}
+
+abutton.formulaire { class: formulaire; font-size: 9; height: 48px; width: 80px; background-color: transparent; }
+
+button.formulaire { class: formulaire; border: 0px; font-size: 9;  background-color: transparent; }
+
+td.joberr { background-color: red; font-color: white;}
diff --git a/gui/bweb/html/bweb.js b/gui/bweb/html/bweb.js
new file mode 100644 (file)
index 0000000..69fc247
--- /dev/null
@@ -0,0 +1,55 @@
+ var even_cell_color = "#FFFFFF";
+ var odd_cell_color  = "#EEEEEE";
+ var header_color    = "#E1E0DA";
+ var rows_per_page   = 20;
+ var up_icon         = "/bweb/up.gif";
+ var down_icon       = "/bweb/down.gif";
+ var prev_icon       = "/bweb/left.gif";
+ var next_icon       = "/bweb/right.gif";
+ var rew_icon        = "/bweb/first.gif";
+ var fwd_icon        = "/bweb/last.gif";
+
+ var jobstatus = {
+ 'C': 'created but not yet running',
+ 'R': 'running',
+ 'B': 'blocked',
+ 'T': 'terminated normally',
+ 'E': 'Job terminated in error',
+ 'e': 'Non-fatal error',
+ 'f': 'Fatal error',
+ 'D': 'Verify differences',
+ 'A': 'canceled by user',
+ 'F': 'waiting on File daemon',
+ 'S': 'waiting on the Storage daemon',
+ 'm': 'waiting for new media',
+ 'M': 'waiting for Mount',
+ 's': 'Waiting for storage resource',
+ 'j': 'Waiting for job resource',
+ 'c': 'Waiting for Client resource',
+ 'd': 'Waiting for maximum jobs',
+ 't': 'Waiting for start time',
+ 'p': 'Waiting for higher priority jobs to finish'
+};
+
+var joblevel = {
+ 'F': 'Full backup',
+ 'I': 'Incr (since last backup)',
+ 'D': 'Diff (since last full backup)',
+ 'C': 'verify from catalog',
+ 'V': 'verify save (init DB)',
+ 'O': 'verify Volume to catalog entries',
+ 'd': 'verify Disk attributes to catalog',
+ 'A': 'verify data on volume',
+ 'B': 'Base level job'
+};
+
+
+var refresh_time = 60000;
+
+function bweb_refresh() {
+  location.reload(true)
+}
+function bweb_add_refresh(){
+       window.setInterval("bweb_refresh()",refresh_time);
+}
+
diff --git a/gui/bweb/html/cancel.png b/gui/bweb/html/cancel.png
new file mode 100644 (file)
index 0000000..89c2124
Binary files /dev/null and b/gui/bweb/html/cancel.png differ
diff --git a/gui/bweb/html/chart.png b/gui/bweb/html/chart.png
new file mode 100644 (file)
index 0000000..333769d
Binary files /dev/null and b/gui/bweb/html/chart.png differ
diff --git a/gui/bweb/html/down.gif b/gui/bweb/html/down.gif
new file mode 100755 (executable)
index 0000000..ce294fd
Binary files /dev/null and b/gui/bweb/html/down.gif differ
diff --git a/gui/bweb/html/edit.png b/gui/bweb/html/edit.png
new file mode 100644 (file)
index 0000000..3677539
Binary files /dev/null and b/gui/bweb/html/edit.png differ
diff --git a/gui/bweb/html/extern.png b/gui/bweb/html/extern.png
new file mode 100644 (file)
index 0000000..cfc3b35
Binary files /dev/null and b/gui/bweb/html/extern.png differ
diff --git a/gui/bweb/html/f.png b/gui/bweb/html/f.png
new file mode 100644 (file)
index 0000000..89c2124
Binary files /dev/null and b/gui/bweb/html/f.png differ
diff --git a/gui/bweb/html/filename.png b/gui/bweb/html/filename.png
new file mode 100644 (file)
index 0000000..7e5776a
Binary files /dev/null and b/gui/bweb/html/filename.png differ
diff --git a/gui/bweb/html/first.gif b/gui/bweb/html/first.gif
new file mode 100755 (executable)
index 0000000..10a4e7e
Binary files /dev/null and b/gui/bweb/html/first.gif differ
diff --git a/gui/bweb/html/first.png b/gui/bweb/html/first.png
new file mode 100644 (file)
index 0000000..5e8aa7a
Binary files /dev/null and b/gui/bweb/html/first.png differ
diff --git a/gui/bweb/html/inflag0.png b/gui/bweb/html/inflag0.png
new file mode 100644 (file)
index 0000000..6478554
Binary files /dev/null and b/gui/bweb/html/inflag0.png differ
diff --git a/gui/bweb/html/inflag1.png b/gui/bweb/html/inflag1.png
new file mode 100644 (file)
index 0000000..e061e7f
Binary files /dev/null and b/gui/bweb/html/inflag1.png differ
diff --git a/gui/bweb/html/intern.png b/gui/bweb/html/intern.png
new file mode 100644 (file)
index 0000000..64b8300
Binary files /dev/null and b/gui/bweb/html/intern.png differ
diff --git a/gui/bweb/html/kaiska.css b/gui/bweb/html/kaiska.css
new file mode 100644 (file)
index 0000000..00d51a0
--- /dev/null
@@ -0,0 +1,970 @@
+/*
+* ----------------------------------------------------------------------------
+* "THE BEER-WARE LICENSE" (Revision 42):
+* Willy Morin (kaiska@ifrance.com) wrote this file. As long as you retain this
+* notice you can do whatever you want with this stuff. If we meet some day,
+* and you think this stuff is worth it, you can buy me a beer in return
+*
+* Willy Morin
+* http://kaiska.flinkserver.net
+*
+* Addendum Ã‰tienne Bersac ( commentsbrowser ), http://bersace03.free.fr
+* ----------------------------------------------------------------------------
+*/ 
+
+* { font-family: sans-serif, monospace; }
+
+body { background: #f1f1f1; }
+
+a:link {    
+    color: #36598E;
+    text-decoration: none;
+}    
+    
+a:visited {    
+    color: #B64545;
+    text-decoration: none;
+}
+
+h1.rubrique_info {
+    color: #552 !important;  
+    margin: 0px 0px 0px 0px;
+    padding: 0em; 
+    border: 0px;
+    font-size: 12px;
+}
+h1.connexe {
+    font-size: 12px;
+    padding: 0em;
+    margin: 0px 0px 0px 0px;
+    color: #900;
+}
+
+a.rubrique_infolink { text-decoration: none; }
+ul.rubrique_infoul {
+    display: inline;
+    list-style-type: square;
+}
+ul.rubrique_infoul * { width: 100%; }
+li.rubrique_infoul { margin-left: 15px; }
+
+.leftbox ul.rubrique_infoul {
+       display:block;
+       padding: 0 10px 0 10px;
+       margin-right: -10px;
+}
+
+h1 { color: #990033; }
+
+div.main {  
+               margin: 15px 10px 0px 10px;
+    border: 2px #ddd solid;
+    text-align: left;
+    font-size: 0.8em;
+}
+
+div.lsfnbanner {
+       position: absolute;
+       top: 0;
+       left: 4px;
+       width: 99%;
+       text-align: center;
+       font-size: .8em;
+       padding: 0;
+       margin: 0;
+       border: 0;
+}
+
+div.footer {
+       padding-top: 5px;
+       padding-bottom: 3px;
+       border-top: 1px #ccc solid;
+       border-left: 1px #ccc solid;
+       border-right: 1px #ccc solid;
+       text-align: left;
+       font-size: 9px;
+       background: #eeeae6;
+       margin-top: 40px;
+       margin-left: 20px;
+       margin-right: 20px;
+}
+div.footer p {
+       margin-left: 10px;
+       margin-top: 2px;
+       margin-bottom: 2px;
+}
+div.menubartop {
+    margin: 0;
+               padding:0;
+               background:#ddd;
+}
+
+div.smallmenubar {
+    padding: 4px;
+               padding-left: 10px;
+               padding-right: 10em;
+               margin-top: -4px;
+               font-weight: normal;
+    font-size: 0.8em;
+    text-align: left;
+               background: #e0ddd8;
+}
+
+div.smallmenubar a:hover{
+       margin-bottom:4px;
+}
+div.menuevent {
+    float: left;
+    width: 350px;
+    font-size: 11px;
+    text-align: left;
+    padding-top: 0px;
+    padding-bottom: 0px;
+    padding-left: 10px;
+    font-weight: bold;
+    margin: 0px;
+}
+div.menubar {
+    background: #eeeae6;
+    border-top: 1px #999 solid;
+    border-bottom: 1px #999 solid;
+    padding: 2px 10px 3px 10px;
+    font-weight:bold;
+    font-size: 1em;
+}
+div.menubar a {
+       text-decoration: none;
+}
+div.menubar p {
+       padding: 0px;
+       margin: 0px;
+}
+
+.menudate { visibility:hidden;height:0;margin:0;padding:0;}
+
+div.menusearch {
+       float: none;
+       position: absolute;
+       top: 19px;
+       right:22px;
+       margin:0;
+  text-align: left;
+       padding: 0;
+       width: auto;
+       background: transparent url('/images/search.png') 2px no-repeat;
+       padding-left:25px;
+}
+div.menusearch p {
+       margin: 0;
+}
+div.newsletter {
+       float: right;
+       text-align: right;
+       margin: 0px;
+       padding: 0px;
+       font-size: 8px;
+       font-weight: normal;
+}
+div.newsletter p { margin: 0px 0px 0px 0px; }
+.searchinput {
+       border: solid 1px #ccc;
+       padding-left: 5px;
+       padding-right: 5px;
+       height:1.1em;
+}
+.searchinput:focus{
+       border: solid 1px #777;
+       background: #f5f5e9;
+       padding-left: 5px;
+       padding-right: 5px;
+}
+
+.newsletterinput {
+    border: solid 1px black;
+    font-weight: normal;
+}
+a#menulinkselect { color: #ed7e00; }
+
+div.leftbox {
+    width: 200px; 
+    float: left; 
+    padding-right: 5px;
+    padding-bottom: 5px;
+    border-right: 0px solid #ccc;
+    border-bottom: 0px solid #ccc;
+    background: white;
+    margin-bottom: 10px;
+    margin-right: 20px;
+    overflow: hidden;
+}
+div.leftbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 10px;
+       margin: 0px;
+}
+div.leftbox h2 {
+       font-weight: bold;
+       font-size: 12px;
+       margin: 0px;
+}
+div.leftbox ul {
+  list-style-type: square;
+       color: #552;
+       margin-left: 5px;
+       margin-right: 5px;
+       padding-left: 10px;
+       border: 1px solid #bbb;
+       background: #eeeae6;
+       -moz-border-radius: 10px;
+}
+div.leftbox li {
+       margin-left: 10px;
+       margin-top: 3px;
+       padding-bottom: 3px;
+       border-bottom: 1px solid #ddd;
+       font-size:0.8em;
+}
+
+div.leftbox p {
+       color: #552;
+       text-align:center;
+}
+
+div.rightbox {
+    width: 140px; 
+    float: right; 
+    padding-top: 10px;
+    padding-left: 5px;
+               padding-right: 15px;
+    padding-bottom: 5px;
+    border: 1px solid #ccc;
+               margin: 10px;
+               -moz-border-radius: 10px;
+               text-align: left;
+               background: #eeeae6;
+}
+div.rightbox h1{
+       color: #552;
+}
+
+div.rightbox ul {
+  list-style-type: square;
+       color: #552;
+       padding:0;
+}
+div.rightbox li {
+       margin-left: 10px;
+       margin-top: 3px;
+       padding-bottom: 3px;
+       border-bottom: 1px solid #ddd;
+       font-size:0.8em;
+}
+
+div.journaldiv {
+       margin-left: 0px;
+       margin-right: 0px;
+       margin-top: 20px;
+       margin-bottom: 20px;
+       border: 1px solid #ccc;
+       padding-top: 5px;
+       padding-bottom: 5px;
+       padding-right: 10px;
+       padding-left: 10px;
+       background-color: #eeeae6;
+       -moz-border-radius: 10px;
+}
+div.journaldiv p {
+       margin-top: 10px;
+       margin-bottom: 0px;
+}
+div.journaldiv h1 {
+  color: #552;
+       margin: 0;
+       border-bottom: 1px dotted #bbb;
+}
+div.journaldiv h2 {
+       font-size: 10px;
+       margin: 0;
+}
+div.tipdiv {
+       margin-right: 50px;
+       padding-top: 5px;
+       padding-right: 10px;
+       padding-left: 10px;
+       text-align: justify;
+       background-color: #eee;
+       border: black solid 1px;
+       margin-left: 10px;
+}
+div.tipdiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       color: black;
+  margin-top: 0;
+  margin-bottom: 20px;
+}
+div.tipdiv h2 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 12px;
+       margin: 0;
+}
+
+div.newsdiv {
+    margin-left: 220px;
+    margin-right: 180px;
+    margin-top: 10px;
+    margin-bottom: 20px;
+       text-align: justify;
+}
+div.newsdiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       margin: 0px;
+}
+div.newsdiv h2 {
+       font-weight: normal;
+       font-size: 12px;
+       margin: 0px;
+}
+div.newsdiv h3 {
+       font-weight: normal;
+       font-size: 12px;
+       margin-bottom: 20px;
+}
+div.objdiv {
+    margin-left: 220px;
+    margin-right: 20px;
+    margin-top: 10px;
+    margin-bottom: 20px;
+}
+
+h1.newstitle {
+    text-align: left;
+    font-size: 14px;
+    margin: 0px 0px 0px 0px;
+    color: #552;
+}
+div.titlediv {
+    border-top: solid #cac2a8 0px;
+    margin-top: 20px;
+               padding-top: 5px;
+    background-color: #eeeae6;
+    padding-left: 10px;
+    font-size: 11px;
+               color:#666;
+               -moz-border-radius: 10px 10px 0 0;
+}
+div.bodydiv {
+       border-top: solid #ccc 0px;
+       border-bottom: solid #ccc 1px;
+       border-left: solid #ccc 1px;
+       border-right: solid #ccc 1px;
+       padding-left: 10px;
+  padding-right: 10px;
+       padding-top: 10px;
+       padding-bottom: 5px;
+  margin-top: 0;
+  text-align: justify;
+       background: #f5f5f5;
+       -moz-border-radius: 0 0 10px 10px;
+}
+
+div.comments {
+    padding: 10px;
+    border-top: solid 2px #552;
+    border-bottom: solid 2px #552;
+    border-left: 1px solid #ccc;
+               border-right: 1px solid #ccc;
+               margin-top: 20px;
+    margin-bottom: 10px;
+    background: #dad6d1;
+    font-size: 12px;
+    line-height: 1.3em;
+                       -moz-border-radius: 10px;
+}
+
+.comment-vote { font-style: italic; color:#666; font-size:0.9em;}
+
+p.commentsbody {
+  border-left: 1px solid #aaa;
+  padding-left: 10px;
+       text-align: justify;
+       margin-right: 20px;
+}
+div.commentsreply {
+       text-align: center;
+       margin-top: 10px;
+       font-size:0.9em;
+       color: #666;
+}
+
+div.commentsreply .searchinput{ height:auto; }
+
+ul.commentsul {
+  list-style-type: none;
+       margin-bottom: 10px;
+       margin-left: 1.25em;
+       padding-left: 0;
+}
+ul.commentsli { margin: 10px; }
+div.comments li {
+       margin-top: 20px;
+       margin-left: 2px;
+}
+
+div.comments h1 {
+    font-size: 12px;
+    color: black;
+    margin: 0px 20px 3px 0px;
+               background:#ebe7e2;
+    padding-left: 1px;
+    font-weight: normal;
+               -moz-border-radius: 10px;
+}
+
+div.articlediv {
+    padding-left: 20px;
+    padding-right: 20px;
+    padding-top: 10px;
+    padding-bottom: 20px;
+    margin-right: 180px;
+    margin-left: 220px;
+    margin-top: 10px;
+    text-align: justify;
+    background: #dad6d1;
+               -moz-border-radius: 10px;
+               border: 1px solid #ccc;
+}
+img { border: 0; }
+div.sectionimg {
+    float: left;
+    margin-right: 10px;
+    margin-top: 5px;
+}
+p.hautpage {
+       margin-top: 20px;
+       margin-bottom: 20px;
+       margin-left: 10px;
+}
+div.leftcol {
+       width: 202px;
+       float: left;
+       padding: 0px;
+}
+div.logodiv {
+       border-right: 0px black solid;
+       border-bottom: 0px black solid;
+       padding: 0px;
+       line-height: 0px
+}
+div.loginbox {
+       margin-left: 4px;
+       border: solid #bbb 1px;
+       margin-top: 5px;
+       padding: 5px;
+       background-color: #E0DDD8;
+       font-size: 0.8em;
+       -moz-border-radius: 10px;
+}
+div.loginbox p {
+       margin: 0px;
+       padding: 0px;
+}
+div.loginbox ul {
+       margin-left: 2px;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding: 0px;
+  list-style-type: none;
+       border-left: 1px solid #aaa;
+       padding-left: 8px;
+}
+div.loginbox h1 {
+       text-transform: none;
+       font-weight: bold;
+       color: #552;
+       font-size: 1.2em;
+       margin: 0px;
+       margin-top:10px;
+       border-bottom:1px solid #ccc;
+}
+div.loginbox h2 {
+       font-weight: bold;
+       font-size: 1.1em;
+       margin: 0px;
+}
+div.loginbox h3 {
+       margin-top: 0px;
+       margin-bottom: 5px;
+       font-size: 12px;
+}
+#poll div.otherbox ul {
+       margin-left: 0px;
+       margin-top: 0px;
+       margin-bottom: 10px;
+       padding: 0px;
+       list-style-type: none;
+}
+div.otherboxtitle {
+       margin-bottom: 2px;
+       background-color: #e0ddd8;
+       margin-left: 4px;
+       margin-top: 5px;
+       margin-bottom: 0px;
+       padding-left: 5px;
+       padding-top: 2px;
+       font-size: 11px;
+       border-top: 1px solid #ccc;
+       border-bottom: 0px solid #ccc;
+       font-size: 1em;
+       font-weight: bold;
+       -moz-border-radius: 10px 10px 0px 0px;
+       text-transform: none;
+}
+div.otherbox {
+       margin-left: 4px;
+       border: 1px #ccc solid;
+       border-top:0px;
+       margin-top: 0px;
+       padding: 5px;
+       text-align: left;
+       background: #e0ddd8;
+       font-size: 10px;
+       -moz-border-radius: 0px 0px 10px 10px;
+}
+div.otherbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #552;
+       font-size: 1.2em;
+       margin: 0px;
+       text-transform: none;
+       border-bottom: 1px dotted #ccc;
+}
+div.otherbox h2 {
+       font-weight: bold;
+       font-size: 11px;
+       margin: 0px;
+}
+div.otherbox p {
+       margin-top: 5px;
+       margin-bottom: 10px;
+}
+div.rightlogo { 
+    width: 90px; 
+    float: right;
+    margin-right: 0px;
+    padding-top: 0px;
+    padding-left: 0px;
+    padding-bottom: 0px;
+}   
+div.centraldiv {
+  margin-left: 220px;
+  margin-right: 10px;
+  margin-bottom: 20px;
+       margin-top: 10px;
+}
+div.centraldiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       margin: 0px;
+}
+div.centraldiv h2 {
+       font-weight: normal;
+       font-size: 12px;
+       margin: 0px;
+}
+div.centraldiv h3 {
+       font-weight: normal;
+       font-size: 12px;
+       margin-bottom: 20px;
+}
+div.lefttopbox {
+       padding: 5px;
+       border: 2px #ddd solid;
+       text-align: justify;
+       background: #f9f9f9;
+       font-size: 0.8em;
+       float: left;
+       -moz-border-radius: 10px;
+}
+div.lefttopbox p {     margin-top: 5px; margin-bottom: 10px; }
+div.lefttopbox h1 {
+       font-size: 0px;
+       font-weight: bold;
+       margin: 0px;
+       text-align: right;
+}
+div.lefttopbox h2 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 13px;
+       margin: 0px;
+}
+div.lefttopbox h3 {
+       font-weight: bold;
+       font-size: 1.1em;
+       text-align:left;
+       margin: 0px;
+}
+div.righttopbox {
+       border: 1px #ccc solid;
+       background: white;
+       padding: 5px;
+       text-align: justify;
+       font-size: 0.8em;
+       width: 34%;
+       float: right;
+       -moz-border-radius: 10px;
+}
+div.righttopbox h1 {
+       text-transform:none;
+       font-weight: bold;
+       font-size: 10px;
+       margin: 0px;
+       color: #552;
+       border-bottom: 1px solid #ccc;
+}
+div.righttopbox h2 {
+       font-weight: bold;
+       font-size: 1.1em;
+       color: #666;
+       margin: 0px;
+}
+div.boardindex {
+       text-align: justify;
+       font-size: 11px;
+       padding: 10px;
+       margin-left: 20px;
+       margin-right: 20px;
+}
+a.boardindex:link,a.boardindex:visited,a.boardindex:active {
+       text-decoration:none;
+       color: red;
+}
+div.boardleftmsg {
+       float: left;
+       margin-top: 3px;
+}
+div.boardrightmsg {
+       margin-left: 130px;
+       margin-top: 3px;
+       padding-left: 5px;
+}
+div.journalbody {
+       margin-left: 40px;
+       margin-top: 40px;
+}
+div.journalbody h1 {
+       font-size: 15px;
+       font-weight: bold;
+}
+div.journalbody p {
+       margin-bottom: 20px;
+}
+.formulaire {
+    border: solid 1px black;
+font-size: 12px;
+background-color: #fffbf7;
+color: #000000;
+}
+
+.newcomments {
+       color: red;
+       font-weight: bold;
+}
+div.commentsreplythanks {
+       margin-left: 100px;
+       margin-top: 50px;
+       margin-right: 100px;
+       background-color: #eee;
+       border: black solid 1px;
+       padding: 10px;
+}
+div.archivediv { margin-right: 20px; }
+.archivedate { color:#f30; }
+.archivelink {
+       font-size: 14px;
+       font-weight: bold;
+       text-decoration: underline;
+}
+div.forumgroup {
+       text-align: left;
+       border: solid black 1px;
+       padding: 10px 20px 20px 10px;
+       background-color: #eeeae6;
+       border: 1px solid #ccc;
+       -moz-border-radius: 10px;
+}
+
+div.forumgroup b{
+       color: #552;
+       background: #e0ddd8;
+       font-size: 1.1em;
+       display:block;
+       -moz-border-radius: 10px;
+       padding-left: 10px;
+       padding-right: 10px;
+       margin-left:-15px;
+       margin-right:-15px;
+       margin-top:-3px;
+}
+
+div.forumgroup td{
+       -moz-border-radius: 10px;
+}
+
+div.adminall {
+       font-size: 12px;
+       padding: 10px;
+}
+div.floatwin {
+    position:absolute;
+    top: 150px;
+    left: 450px;
+    width:200px;
+    visibility:hidden;
+    background: #dcdff4;
+    padding: 5px;
+    border: solid black 1px;
+}
+
+div.poll-result-bar {
+       background: #eeeae6;
+       color: black;
+       border: solid 1px #ccc;
+       font-size: 0.7em;
+       -moz-border-radius: 10px;
+}
+div.funbanner {
+       text-align: right;
+       border-top: #aaa solid 1px;
+       border-bottom: #aaa solid 1px;
+       padding-right: 20px;
+       padding-top: 2px;
+       padding-bottom: 2px;
+       font-size: 10px;
+       background-color: #fff2e8;
+       font-weight: bold;
+       margin-top: 10px;
+}
+
+div.main
+{
+       margin-bottom: 3em !important;
+}
+
+.replie
+{
+       display: none;  
+}
+
+.deplie
+{
+       display: block;
+}
+
+#commentsnav
+{
+       display: none;
+       text-align: right;
+}
+
+#commentsbrowser
+{
+  border: 1px solid #baa !important;
+  background: #dfd6d1 !important;
+  color: #333333;
+  position: fixed;
+  bottom: 0.5ex;
+  max-height: 0.8ex;
+  padding: 2px 0 0;
+  overflow: hidden;
+  left: 19px !important;
+  right: 19px !important;
+  font-size: 10px;
+}
+
+#commentsbrowser:hover
+{ 
+  max-height: none;
+  padding: 0.5ex;
+  overflow: hidden;
+}
+
+div.comments h1.nouveau
+{
+       border: 1px solid rgb(211, 117, 55);
+       /*border: 1px solid #D58E64;*/
+       background-color: rgb(225, 220, 220);
+       
+       color: #111111;
+}
+
+#pallierdiv, #csspersodiv
+{
+       position: fixed;
+       bottom: 5em;
+       left: 3%;
+       max-width: 17em;
+       border: 1px solid #333333;
+       background: #EEEEEE;
+       color: #333333;
+       padding: 0.6em;
+       display: none;
+}
+
+#csspersodiv
+{
+       left: 37%;
+       max-width: 35em;
+}
+
+#pallierdiv input, #csspersodiv input
+{
+       margin-left: 0.2em;
+}
+
+form
+{
+       margin: 0;
+}
+
+#cssurl
+{
+       width: 12em;
+}
+
+#pallierdiv h3, #csspersodiv h3
+{
+    font-size: 12px;
+    padding: 0;
+    margin: 0 0 0.5em 0;
+    color: #990033;
+}
+
+tt 
+{
+       font-size: 1em;
+       background:#fffbec;
+       color: #330;
+}
+               
+pre, code, blockquote,quote
+{
+       font-size: 1em;
+       display:block;
+       background:#fffbec;
+       margin: 5px 15px 5px 15px;
+       padding: 5px;
+       border-left: 1px solid #552;
+       color: #330;
+}
+
+tt, pre, code
+{
+       font-family: "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
+}
+
+blockquote {   font-style:italic; }
+
+div.titledivmini {
+    border-top: solid #cac2a8 0px;
+               padding-top: 5px;
+    background-color: #eeeae6;
+    padding-left: 10px;
+               color:#666;
+               -moz-border-radius: 10px 10px 0 0;
+}
+h1.newstitlemini {
+    text-align: left;
+    margin: 0px 0px 0px 0px;
+    color: #552;
+               font-size: 12px !important;
+}
+div.subtitlemini {
+    border-top: solid #cac2a8 0px;
+    margin-top: 0px;
+               padding-top: 5px;
+    background-color: #eeeae6;
+    padding-left: 1px;
+    font-size: 11px;
+               color:#666;
+}
+div.bodydivmini {
+       border-top: solid #ccc 0px;
+       border-bottom: solid #ccc 1px;
+       border-left: solid #ccc 1px;
+       border-right: solid #ccc 1px;
+       padding-left: 2px;
+  padding-right: 2px;
+       padding-top: 1px;
+       padding-bottom: 1px;
+  margin-top: 0;
+  margin-bottom: 2px;
+  text-align: justify;
+       background: #f5f5f5;
+       font-size: 11px;
+       -moz-border-radius: 0 0 10px 10px;
+}
+
+blockquote:before {
+       content: "\00AB";
+       font-size:1.3em;
+       font-weight: bold;
+       color: #552;
+}
+
+blockquote:after {
+       content: "\00BB";
+       font-size:1.3em;
+       font-weight: bold;
+       color: #552;
+}
+
+em{ font-style: italic; }
+
+#palliercontainer {
+      text-align: left;
+      margin-left: 20px;
+}
+
+#csscourantecontainer {
+      text-align: center;
+      padding-left: 20%;
+}
+
+#newcommentsnav {
+      float: right;
+      text-align: right;
+}
+
+.centraldiv table {
+border: 0;
+}
+.centraldiv table td {
+border: thin solid #BBBBBB;
+background:#E0DDD8;
+}
+
+.centraldiv table th {
+padding:1em;
+background:#CCCCCC;
+border:0;
+border-top:1em solid white;
+}
+
+div.extrait {
+visibility:hidden;
+position:fixed;
+bottom:1em;
+right:1em;
+left:1em;
+background:#E0DDD8;
+border:thin solid #BBBBBB;
+}
diff --git a/gui/bweb/html/label.png b/gui/bweb/html/label.png
new file mode 100644 (file)
index 0000000..23a5f7c
Binary files /dev/null and b/gui/bweb/html/label.png differ
diff --git a/gui/bweb/html/last.gif b/gui/bweb/html/last.gif
new file mode 100755 (executable)
index 0000000..986868c
Binary files /dev/null and b/gui/bweb/html/last.gif differ
diff --git a/gui/bweb/html/last.png b/gui/bweb/html/last.png
new file mode 100644 (file)
index 0000000..81303ad
Binary files /dev/null and b/gui/bweb/html/last.png differ
diff --git a/gui/bweb/html/lcorner.png b/gui/bweb/html/lcorner.png
new file mode 100644 (file)
index 0000000..068aed6
Binary files /dev/null and b/gui/bweb/html/lcorner.png differ
diff --git a/gui/bweb/html/left.gif b/gui/bweb/html/left.gif
new file mode 100755 (executable)
index 0000000..c9d91e3
Binary files /dev/null and b/gui/bweb/html/left.gif differ
diff --git a/gui/bweb/html/left.png b/gui/bweb/html/left.png
new file mode 100644 (file)
index 0000000..4fa61ea
Binary files /dev/null and b/gui/bweb/html/left.png differ
diff --git a/gui/bweb/html/load.png b/gui/bweb/html/load.png
new file mode 100644 (file)
index 0000000..493e149
Binary files /dev/null and b/gui/bweb/html/load.png differ
diff --git a/gui/bweb/html/lock.png b/gui/bweb/html/lock.png
new file mode 100644 (file)
index 0000000..9c46dbb
Binary files /dev/null and b/gui/bweb/html/lock.png differ
diff --git a/gui/bweb/html/natcompare.js b/gui/bweb/html/natcompare.js
new file mode 100755 (executable)
index 0000000..2f99489
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+natcompare.js -- Perform 'natural order' comparisons of strings in JavaScript.
+Copyright (C) 2005 by SCK-CEN (Belgian Nucleair Research Centre)
+Written by Kristof Coomans <kristof[dot]coomans[at]sckcen[dot]be>
+
+Based on the Java version by Pierre-Luc Paour, of which this is more or less a straight conversion.
+Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
+
+The Java version was based on the C version by Martin Pool.
+Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+
+function isWhitespaceChar(a)
+{
+    var charCode;
+    charCode = a.charCodeAt(0);
+
+    if ( charCode <= 32 )
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+function isDigitChar(a)
+{
+    var charCode;
+    charCode = a.charCodeAt(0);
+
+    if ( charCode >= 48  && charCode <= 57 )
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+function compareRight(a,b)
+{
+    var bias = 0;
+    var ia = 0;
+    var ib = 0;
+
+    var ca;
+    var cb;
+
+    // The longest run of digits wins.  That aside, the greatest
+    // value wins, but we can't know that it will until we've scanned
+    // both numbers to know that they have the same magnitude, so we
+    // remember it in BIAS.
+    for (;; ia++, ib++) {
+        ca = a.charAt(ia);
+        cb = b.charAt(ib);
+
+        if (!isDigitChar(ca)
+                && !isDigitChar(cb)) {
+            return bias;
+        } else if (!isDigitChar(ca)) {
+            return -1;
+        } else if (!isDigitChar(cb)) {
+            return +1;
+        } else if (ca < cb) {
+            if (bias == 0) {
+                bias = -1;
+            }
+        } else if (ca > cb) {
+            if (bias == 0)
+                bias = +1;
+        } else if (ca == 0 && cb == 0) {
+            return bias;
+        }
+    }
+}
+
+function natcompare(a,b) {
+
+    var ia = 0, ib = 0;
+       var nza = 0, nzb = 0;
+       var ca, cb;
+       var result;
+
+    while (true)
+    {
+        // only count the number of zeroes leading the last number compared
+        nza = nzb = 0;
+
+        ca = a.charAt(ia);
+        cb = b.charAt(ib);
+
+        // skip over leading spaces or zeros
+        while ( isWhitespaceChar( ca ) || ca =='0' ) {
+            if (ca == '0') {
+                nza++;
+            } else {
+                // only count consecutive zeroes
+                nza = 0;
+            }
+
+            ca = a.charAt(++ia);
+        }
+
+        while ( isWhitespaceChar( cb ) || cb == '0') {
+            if (cb == '0') {
+                nzb++;
+            } else {
+                // only count consecutive zeroes
+                nzb = 0;
+            }
+
+            cb = b.charAt(++ib);
+        }
+
+        // process run of digits
+        if (isDigitChar(ca) && isDigitChar(cb)) {
+            if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
+                return result;
+            }
+        }
+
+        if (ca == 0 && cb == 0) {
+            // The strings compare the same.  Perhaps the caller
+            // will want to call strcmp to break the tie.
+            return nza - nzb;
+        }
+
+        if (ca < cb) {
+            return -1;
+        } else if (ca > cb) {
+            return +1;
+        }
+
+        ++ia; ++ib;
+    }
+}
+
diff --git a/gui/bweb/html/next.png b/gui/bweb/html/next.png
new file mode 100644 (file)
index 0000000..1a21626
Binary files /dev/null and b/gui/bweb/html/next.png differ
diff --git a/gui/bweb/html/nrs_table.js b/gui/bweb/html/nrs_table.js
new file mode 100755 (executable)
index 0000000..e84daa8
--- /dev/null
@@ -0,0 +1,1071 @@
+/**
+ * Copyright 2005 New Roads School
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * \class nrsTable
+ * This describes the nrsTable, which is a table created in JavaScript that is
+ * able to be sorted and displayed in different ways based on teh configuration
+ * parameters passed to it. 
+ * to create a new table one only needs to call the setup function like so:
+ * <pre>
+ * nrsTable.setup(
+ * {
+ *     table_name: "table_container",
+ *     table_data: d,
+ *     table_header: h
+ * }
+ * );
+ * </pre>
+ * Where table_name is the name of the table to build.  THis must be defined in
+ * your HTML by putting a table declaration, such as <table id=table_name>
+ * </table>.  This will declare where your table will be shown.
+ * All sorts of parameters can be customized here.  For details look at the
+ * function setup.
+ * \see setup
+ */
+
+
+/**
+ * Debug function.  Set debug to tru to view messages.
+ * \param msg Message to display in an alert.
+ */
+debug = false;
+function DEBUG(msg)
+{
+       if(debug)
+               alert(msg);
+}
+
+/**
+ * There is a memory leak problem that I can't seem to fix.  I'm attching 
+ * something that I found from Aaron Boodman, which will clean up all the
+ * memory leaks in the page (this can be found at http://youngpup.net/2005/0221010713
+ * ). This is a little clunky, but it will do till I track this problem.
+ * Oh, and this problem only occurrs is IE.
+ */
+if (window.attachEvent) {
+    var clearElementProps = [
+               'data',
+        'onmouseover',
+        'onmouseout',
+        'onmousedown',
+        'onmouseup',
+        'ondblclick',
+        'onclick',
+        'onselectstart',
+        'oncontextmenu'
+    ];
+
+    window.attachEvent("onunload", function() {
+        var el;
+        for(var d = document.all.length;d--;){
+            el = document.all[d];
+            for(var c = clearElementProps.length;c--;){
+                el[clearElementProps[c]] = null;
+            }
+        }
+    });
+}
+
+
+/**
+ * This is the constructor.
+ * It only needs the name of the table.  This should never be called directly, 
+ * instead use setup function.
+ * \param table The name of the table to create.
+ * \see setup
+ */
+function nrsTable(table)
+{
+       this.my_table = table;
+       this.field_to_sort = 0;
+       this.field_asc = true;
+}
+
+new nrsTable('');
+/**
+ * This function is responsible for setting up an nrsTable.  All the parameters
+ * can be configured directly from this function.  The params array of this 
+ * function is a class (or a associative array, depending on how you want to
+ * look at it) with the following possible parameters:
+ *     - table_name: required.  The id of the table tag.
+ *     - table_header: required.  An array containing the header names.
+ *     - table_data: optional.  A 2D array of strings containing the cell contents.
+ *     - caption: optional.  A caption to include on the table.
+ *     - row_links: optional.  An array with hyperlinks per row.  Must be a javascript function.
+ *     - cell_links: optional.  A 2D array with links on every cell.  Must be a javascript function
+ *     - up_icon: optional.  A path to the ascending sort arrow.
+ *     - down_icon: optional.  A path to the descending sort arrow.
+ *     - prev_icon: optional.  A path to the previous page icon in the navigation.
+ *     - next_icon: optional.  A path to the next page icon in the navigation.
+ *     - rew_icon: optional.  A path to the first page icon in the navigation.
+ *     - fwd_icon: optional.  A path to the last page icon in the navigation.
+ *     - rows_per_page: optional.  The number of rows per page to display at any one time.
+ *     - display_nav: optional.  Displays navigation (prev, next, first, last)
+ *     - foot_headers: optional.  Whether to display th eheaders at the foot of the table.
+ *     - header_color: optional.  The color of the header cells.  Will default to whatever is defined in CSS.
+ *     - even_cell_color: optional.  The color of the even data cells.  Will default to whatever is defined in CSS.
+ *     - odd_cell_color: optional.  The color of the odd data cells.  Will default to whatever is defined in CSS.
+ *     - footer_color: optional.  The color of the footer cells.  Will default to whatever is defined in CSS.
+ *     - hover_color: optional.  The color tha a row should turn when the mouse is over it.
+ *     - padding: optional.  Individual cell padding, in pixels.
+ *     - natural_compare: optional.  Uses the natural compare algorithm (separate from this program) to sort.
+ *     - disable_sorting: optional.  An array specifying the columns top disable sorting on (0 is the first column).
+ *
+ * \params params An array as described above.
+ */
+nrsTable.setup = function(params)
+{
+       //here we assign all the veriables that we are passed, or the defaults if 
+       //they are not defined.
+       //Note that the only requirements are a table name and a header.
+       if(typeof params['table_name'] == "undefined")
+       {
+               alert("Error! You must supply a table name!");
+               return null;
+       }
+       if(typeof params['table_header'] == "undefined")
+       {
+               alert("Error! You must supply a table header!");
+               return null;
+       }
+       
+       //check if the global array exists, else create it.
+       if(typeof(nrsTables) == "undefined")
+       {
+               eval("nrsTables = new Array();");
+       }
+       nrsTables[params['table_name']] = new nrsTable(params['table_name']);
+       nrsTables[params['table_name']].heading = params['table_header'].concat();
+       
+       //now the non-required elements.  Data elements first
+       nrsTables[params['table_name']].data = (typeof params['table_data'] == "undefined" || !params['table_data'])? null: params['table_data'].concat();
+       nrsTables[params['table_name']].caption = (typeof params['caption'] == "undefined")? null: params['caption'];
+       nrsTables[params['table_name']].row_links = (typeof params['row_links'] == "undefined" || !params['row_links'])? null: params['row_links'].concat();
+       nrsTables[params['table_name']].cell_links = (typeof params['cell_links'] == "undefined" || !params['row_links'])? null: params['cell_links'].concat();
+       
+       //these are the icons.
+       nrsTables[params['table_name']].up_icon = (typeof params['up_icon'] == "undefined")? "up.gif": params['up_icon'];
+       nrsTables[params['table_name']].down_icon = (typeof params['down_icon'] == "undefined")? "down.gif": params['down_icon'];
+       nrsTables[params['table_name']].prev_icon = (typeof params['prev_icon'] == "undefined")? "left.gif": params['prev_icon'];
+       nrsTables[params['table_name']].next_icon = (typeof params['next_icon'] == "undefined")? "right.gif": params['next_icon'];
+       nrsTables[params['table_name']].rew_icon = (typeof params['rew_icon'] == "undefined")? "first.gif": params['rew_icon'];
+       nrsTables[params['table_name']].fwd_icon = (typeof params['fwd_icon'] == "undefined")? "last.gif": params['fwd_icon'];
+       
+       //now the look and feel options.
+       nrsTables[params['table_name']].rows_per_page = (typeof params['rows_per_page'] == "undefined")? -1: params['rows_per_page'];
+       nrsTables[params['table_name']].page_nav = (typeof params['page_nav'] == "undefined")? false: params['page_nav'];
+       nrsTables[params['table_name']].foot_headers = (typeof params['foot_headers'] == "undefined")? false: params['foot_headers'];
+       nrsTables[params['table_name']].header_color = (typeof params['header_color'] == "undefined")? null: params['header_color'];
+       nrsTables[params['table_name']].even_cell_color = (typeof params['even_cell_color'] == "undefined")? null: params['even_cell_color'];
+       nrsTables[params['table_name']].odd_cell_color = (typeof params['odd_cell_color'] == "undefined")? null: params['odd_cell_color'];
+       nrsTables[params['table_name']].footer_color = (typeof params['footer_color'] == "undefined")? null: params['footer_color'];
+       nrsTables[params['table_name']].hover_color = (typeof params['hover_color'] == "undefined")? null: params['hover_color'];
+       nrsTables[params['table_name']].padding = (typeof params['padding'] == "undefined")? null: params['padding'];
+       nrsTables[params['table_name']].natural_compare = (typeof params['natural_compare'] == "undefined")? false: true;
+       nrsTables[params['table_name']].disable_sorting = 
+               (typeof params['disable_sorting'] == "undefined")? false: "." + params['disable_sorting'].join(".") + ".";
+       //finally, build the table
+       nrsTables[params['table_name']].buildTable();
+};
+
+
+/**
+ * This is the Javascript quicksort implementation.  This will sort the 
+ * this.data and the this.data_nodes based on the this.field_to_sort parameter.
+ * \param left The left index of the array.
+ * \param right The right index of the array
+ */
+nrsTable.prototype.quickSort = function(left, right)
+{
+       if(!this.data || this.data.length == 0)
+               return;
+//     alert("left = " + left + " right = " + right);
+       var i = left;
+       var j = right;
+       var k = this.data[Math.round((left + right) / 2)][this.field_to_sort];
+        if (isNaN(k)) {
+          k = k.toLowerCase();
+        } else {
+         k = parseInt(k, 10);
+       }
+        
+       while(j > i)
+       {
+               if(this.field_asc)
+               {
+                       while(this.data[i][this.field_to_sort].toLowerCase() < k)
+                               i++;
+                       while(this.data[j][this.field_to_sort].toLowerCase() > k)
+                               j--;
+               }
+               else
+               {
+                       while(this.data[i][this.field_to_sort].toLowerCase() > k)
+                               i++;
+                       while(this.data[j][this.field_to_sort].toLowerCase() < k)
+                               j--;
+               }
+               if(i <= j )
+               {
+                       //swap both values
+                       //sort data
+                       var temp = this.data[i];
+                       this.data[i] = this.data[j];
+                       this.data[j] = temp;
+                       
+                       //sort contents
+                       var temp = this.data_nodes[i];
+                       this.data_nodes[i] = this.data_nodes[j];
+                       this.data_nodes[j] = temp;
+                       i++;
+                       j--;
+               }
+       }
+       if(left < j)
+               this.quickSort(left, j);
+       if(right > i)
+               this.quickSort(i, right);
+}
+
+/**
+ * This is the Javascript natural sort function.  Because of some obscure JavaScript
+ * quirck, we could not do quicsort while calling natcompare to compare, so this
+ * function will so a simple bubble sort using the natural compare algorithm.
+ */
+nrsTable.prototype.natSort = function()
+{
+       if(!this.data || this.data.length == 0)
+               return;
+       var swap;
+       for(i = 0; i < this.data.length - 1; i++)
+       {
+               for(j = i; j < this.data.length; j++)
+               {
+                       if(!this.field_asc)
+                       {
+                               if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
+                                       this.data[j][this.field_to_sort].toLowerCase()) == -1)
+                                       swap = true;
+                               else
+                                       swap = false;
+                       }
+                       else
+                       {
+                               if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
+                                       this.data[j][this.field_to_sort].toLowerCase()) == 1)
+                                       swap = true;
+                               else
+                                       swap = false;
+                       }
+                       if(swap)
+                       {
+                               //swap both values
+                               //sort data
+                               var temp = this.data[i];
+                               this.data[i] = this.data[j];
+                               this.data[j] = temp;
+                               
+                               //sort contents
+                               var temp = this.data_nodes[i];
+                               this.data_nodes[i] = this.data_nodes[j];
+                               this.data_nodes[j] = temp;
+                       }
+               }
+       }
+}
+
+/**
+ * This function will recolor all the the nodes to conform to the alternating 
+ * row colors.
+ */
+nrsTable.prototype.recolorRows = function()
+{
+       if(this.even_cell_color || this.odd_cell_color)
+       {
+               DEBUG("Recoloring Rows. length = " + this.data_nodes.length);
+               for(var i = 0; i < this.data_nodes.length; i++)
+               {
+                       if(i % 2 == 0)
+                       {
+                               if(this.even_cell_color)
+                                       this.data_nodes[i].style.backgroundColor = this.even_cell_color;
+                               this.data_nodes[i].setAttribute("id", "even_row");
+                       }
+                       else
+                       {
+                               if(this.odd_cell_color)
+                                       this.data_nodes[i].style.backgroundColor = this.odd_cell_color;
+                               this.data_nodes[i].setAttribute("id", "odd_row");
+                       }
+               }
+       }
+}
+
+/**
+ * This function will create the Data Nodes, which are a reference to the table
+ * rows in the HTML.
+ */
+nrsTable.prototype.createDataNodes = function()
+{
+       if(this.data_nodes)
+               delete this.data_nodes;
+       this.data_nodes = new Array();
+       if(!this.data)
+               return;
+       for(var i = 0; i < this.data.length; i++)
+       {
+               var curr_row = document.createElement("TR");
+               
+               for(var j = 0; j < this.data[i].length; j++)
+               {
+                       var curr_cell = document.createElement("TD");
+                       //do we need to create links on every cell?
+                       if(this.cell_links)
+                       {
+                               var fn = new Function("", this.cell_links[i][j]);
+                               curr_cell.onclick = fn;
+                               curr_cell.style.cursor = 'pointer';
+                       }
+                       //workaround for IE
+                       curr_cell.setAttribute("className", "dataTD" + j);
+                       //assign the padding
+                       if(this.padding)
+                       {
+                               curr_cell.style.paddingLeft = this.padding + "px";
+                               curr_cell.style.paddingRight = this.padding + "px";
+                       }
+                       
+                       if (typeof this.data[i][j] == "object") {
+                               curr_cell.appendChild(this.data[i][j]);
+                       } else {
+                               curr_cell.appendChild(document.createTextNode(this.data[i][j]));
+                       }
+
+                       curr_row.appendChild(curr_cell);
+               }
+               //do we need to create links on every row?
+               if(!this.cell_links && this.row_links)
+               {
+                       var fn = new Function("", this.row_links[i]);
+                       curr_row.onclick = fn;
+                       curr_row.style.cursor = 'pointer';
+               }
+               //sets the id for odd and even rows.
+               if(i % 2 == 0)
+               {
+                       curr_row.setAttribute("id", "even_row");
+                       if(this.even_cell_color)
+                               curr_row.style.backgroundColor = this.even_cell_color;
+               }
+               else
+               {
+                       curr_row.setAttribute("id", "odd_row");
+                       if(this.odd_cell_color)
+                               curr_row.style.backgroundColor = this.odd_cell_color;
+               }
+               if(this.hover_color)
+               {
+                       curr_row.onmouseover = new Function("", "this.style.backgroundColor='" + this.hover_color + "';");
+                       curr_row.onmouseout = new Function("", "this.style.backgroundColor=(this.id=='even_row')?'" + 
+                                                               this.even_cell_color + "':'" + this.odd_cell_color + "';");
+               }
+               this.data_nodes[i] = curr_row;
+       }
+}
+
+/**
+ * This function will update the nav page display.
+ */
+nrsTable.prototype.updateNav = function()
+{
+       if(this.page_nav)
+       {
+               var p = 0;
+               if(this.foot_headers)
+                       p++;
+               var t = document.getElementById(this.my_table);
+               var nav = t.tFoot.childNodes[p];
+               if(nav)
+               {
+                       var caption = t.tFoot.childNodes[p].childNodes[0].childNodes[2];
+                       caption.innerHTML = "Page " + (this.current_page + 1) + " of " + this.num_pages;
+               }
+               else
+               {
+                       if(this.num_pages > 1)
+                       {
+                               this.insertNav();
+                               nav = t.tFoot.childNodes[p];
+                       }
+               }
+               if(nav)
+               {
+                       if(this.current_page == 0)
+                               this.hideLeftArrows();
+                       else
+                               this.showLeftArrows();
+                       
+                       if(this.current_page + 1 == this.num_pages)
+                               this.hideRightArrows();
+                       else
+                               this.showRightArrows();
+               }
+       }
+}
+
+/**
+ * This function will flip the sort arrow in place.  If a heading is used in the
+ * footer, then it will flip that one too.
+ */
+nrsTable.prototype.flipSortArrow = function()
+{
+       this.field_asc = !this.field_asc;
+       //flip the arrow on the heading.
+       var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
+       if(this.field_asc)
+               heading.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
+       else
+               heading.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
+       //is there a heading in the footer?
+       if(this.foot_headers)
+       {
+               //yes, so flip that arrow too.
+               var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
+               if(this.field_asc)
+                       footer.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
+               else
+                       footer.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
+       }
+}
+
+/**
+ * This function will move the sorting arrow from the place specified in 
+ * this.field_to_sort to the passed parameter.  It will also set 
+ * this.field_to_sort to the new value.  It will also do it in the footers, 
+ * if they exists.
+ * \param field The new field to move it to.
+ */
+nrsTable.prototype.moveSortArrow = function(field)
+{
+       var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
+       var img = heading.removeChild(heading.getElementsByTagName("IMG")[0]);
+       heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[field];
+       heading.appendChild(img);
+       //are there headers in the footers.
+       if(this.foot_headers)
+       {
+               //yes, so switch them too.
+               var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
+               var img = footer.removeChild(footer.getElementsByTagName("IMG")[0]);
+               footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[field];
+               footer.appendChild(img);
+       }
+       //finally, set the field to sort by.
+       this.field_to_sort = field;
+}
+
+/**
+ * This function completely destroys a table.  Should be used only when building
+ * a brand new table (ie, new headers).  Else you should use a function like
+ * buildNewData which only deletes the TBody section.
+ */
+nrsTable.prototype.emptyTable = function()
+{
+       var t = document.getElementById(this.my_table);
+       while(t.childNodes.length != 0)
+               t.removeChild(t.childNodes[0]);
+};
+
+/**
+ * This function builds a brand new table from scratch.  This function should
+ * only be called when a brand new table (with headers, footers, etc) needs
+ * to be created.  NOT when refreshing data or changing data.
+ */
+nrsTable.prototype.buildTable = function()
+{
+       //reset the sorting information.
+       this.field_to_sort = 0;
+       this.field_asc = true;
+       
+       //remove the nodes links.
+       delete this.data_nodes;
+       
+       //do we have to calculate the number of pages?
+       if(this.data && this.rows_per_page != -1)
+       {
+               //we do.
+               this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
+               this.current_page = 0;
+       }
+       
+       //blank out the table.
+       this.emptyTable();
+       
+       //this is the table that we will be using.
+       var table = document.getElementById(this.my_table);
+       
+       //is there a caption?
+       if(this.caption)
+       {
+               var caption = document.createElement("CAPTION");
+               caption.setAttribute("align", "top");
+               caption.appendChild(document.createTextNode(this.caption));
+               table.appendChild(caption);
+       }
+       
+       //do the heading first
+       var table_header = document.createElement("THEAD");
+       var table_heading = document.createElement("TR");
+       //since this is a new table the first field is what's being sorted.
+       var curr_cell = document.createElement("TH");
+       var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + 0 + ");");
+       if(!this.disable_sorting || this.disable_sorting.indexOf(".0.") == -1)
+               curr_cell.onclick = fn;
+       if(this.header_color)
+               curr_cell.style.backgroundColor = this.header_color;
+       curr_cell.style.cursor = 'pointer';
+       var img = document.createElement("IMG");
+       img.setAttribute("src", this.up_icon);
+       img.setAttribute("border", "0");
+       img.setAttribute("height", "8");
+       img.setAttribute("width", "8");
+       curr_cell.appendChild(document.createTextNode(this.heading[0]));
+       curr_cell.appendChild(img);
+       table_heading.appendChild(curr_cell);
+       //now do the rest of the heading.
+       for(var i = 1; i < this.heading.length; i++)
+       {
+               curr_cell = document.createElement("TH");
+               var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + i + ");");
+               if(!this.disable_sorting || this.disable_sorting.indexOf("." + i + ".") == -1)
+                       curr_cell.onclick = fn;
+               if(this.header_color)
+                       curr_cell.style.backgroundColor = this.header_color;
+               curr_cell.style.cursor = 'pointer';
+               //build the sorter
+               curr_cell.appendChild(document.createTextNode(this.heading[i]));
+               table_heading.appendChild(curr_cell);
+       }
+       table_header.appendChild(table_heading);
+       
+       //now the content
+       var table_body = document.createElement("TBODY");
+       this.createDataNodes();
+       if(this.data)
+       {
+               if(this.natural_compare)
+                       this.natSort(0, this.data.length - 1);
+               else
+                       this.quickSort(0, this.data.length - 1);
+               this.recolorRows();
+       }
+
+       //finally, the footer
+       var table_footer = document.createElement("TFOOT");
+       if(this.foot_headers)
+       {
+               table_footer.appendChild(table_heading.cloneNode(true));
+       }
+       
+       if(this.page_nav && this.num_pages > 1)
+       {
+               //print out the page navigation
+               //first and previous page
+               var nav = document.createElement("TR");
+               var nav_cell = document.createElement("TH");
+               nav_cell.colSpan = this.heading.length;
+               
+               var left = document.createElement("DIV");
+               if(document.attachEvent)
+                       left.style.styleFloat = "left";
+               else
+                       left.style.cssFloat = "left";
+               var img = document.createElement("IMG");
+               img.setAttribute("src", this.rew_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
+               img.style.cursor = 'pointer';
+               left.appendChild(img);
+               //hack to space the arrows, cause IE is absolute crap
+               left.appendChild(document.createTextNode(" "));
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.prev_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
+               img.style.cursor = 'pointer';
+               left.appendChild(img);
+               //apend it to the cell
+               nav_cell.appendChild(left);
+               
+               //next and last pages
+               var right = document.createElement("DIV");
+               if(document.attachEvent)
+                       right.style.styleFloat = "right";
+               else
+                       right.style.cssFloat = "right";
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.next_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
+               img.style.cursor = 'pointer';
+               right.appendChild(img);
+               //hack to space the arrows, cause IE is absolute crap
+               right.appendChild(document.createTextNode(" "));
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.fwd_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
+               img.style.cursor = 'pointer';
+               right.appendChild(img);
+               //apend it to the cell
+               nav_cell.appendChild(right);
+               
+               //page position
+               var pos = document.createElement("SPAN");
+               pos.setAttribute("id", "nav_pos");
+               pos.appendChild(document.createTextNode("Page " + 
+                                               (this.current_page + 1) + " of " + this.num_pages));
+               //append it to the cell.
+               nav_cell.appendChild(pos);
+               
+               nav.appendChild(nav_cell);
+               //append it to the footer
+               table_footer.appendChild(nav);
+       }
+       
+       if(this.footer_color)
+       {
+               for(var i = 0; i < table_footer.childNodes.length; i++)
+                       table_footer.childNodes[i].style.backgroundColor = this.footer_color;
+       }
+       
+       //append the data
+       table.appendChild(table_header);
+       table.appendChild(table_body);
+       table.appendChild(table_footer);
+       if(this.data)
+       {
+               if(this.natural_compare)
+                       this.natSort(0, this.data.length - 1);
+               else
+                       this.quickSort(0, this.data.length - 1);
+       }
+       this.refreshTable();
+};
+
+/**
+ * This function will remove the elements in teh TBody section of the table and
+ * return an array of references of those elements.  This array can then be 
+ * sorted and re-inserted into the table.
+ * \return An array to references of the TBody contents.
+ */
+nrsTable.prototype.extractElements = function()
+{
+       var tbody = document.getElementById(this.my_table).tBodies[0];
+       var nodes = new Array();
+       var i = 0;
+       while(tbody.childNodes.length > 0)
+       {
+               nodes[i] = tbody.removeChild(tbody.childNodes[0]);
+               i++;
+       }
+       return nodes;
+}
+
+/**
+ * This function will re-insert an array of elements into the TBody of a table.
+ * Note that the array elements are stored in the this.data_nodes reference.
+ */
+nrsTable.prototype.insertElements = function()
+{
+       var tbody = document.getElementById(this.my_table).tBodies[0];
+       var start = 0;
+       var num_elements = this.data_nodes.length;
+       if(this.rows_per_page != -1)
+       {
+               start = this.current_page * this.rows_per_page;
+               num_elements = (this.data_nodes.length - start) > this.rows_per_page?
+                                                       this.rows_per_page + start:
+                                                       this.data_nodes.length;
+       }
+       DEBUG("start is " + start + " and num_elements is " + num_elements);
+       for(var i = start; i < num_elements; i++)
+       {
+               tbody.appendChild(this.data_nodes[i]);
+       }
+}
+
+/**
+ * This function will sort the table's data by a specific field.  The field 
+ * parameter referes to which field index should be sorted.
+ * \param field The field index which to sort on.
+ */
+nrsTable.prototype.fieldSort = function(field)
+{
+       if(this.field_to_sort == field)
+       {
+               //only need to reverse the array.
+               if(this.data)
+               {
+                       this.data.reverse();
+                       this.data_nodes.reverse();
+               }
+               //flip the arrow on the heading.
+               this.flipSortArrow();
+       }
+       else
+       {
+               //In this case, we need to sort the array.  We'll sort it last, first 
+               //make sure that the arrow images are set correctly.
+               this.moveSortArrow(field);
+               //finally, set the field to sort by.
+               this.field_to_sort = field;
+               if(this.data)
+               {
+                       //we'll be using our implementation of quicksort
+                       if(this.natural_compare)
+                               this.natSort(0, this.data.length - 1);
+                       else
+                               this.quickSort(0, this.data.length - 1);
+               }
+       }
+       //finally, we refresh the table.
+       this.refreshTable();
+};
+
+/**
+ * This function will refresh the data in the table.  This function should be
+ * used whenever the nodes have changed, or when chanign pages.  Note that 
+ * this will NOT re-sort.
+ */
+nrsTable.prototype.refreshTable = function()
+{
+       this.extractElements();
+       this.recolorRows();
+       this.insertElements();
+       //finally, if there is a nav, upate it.
+       this.updateNav();
+}
+
+/**
+ * This function will advance a page.  If we are already at the last page, then 
+ * it will remain there.
+ */
+nrsTable.prototype.nextPage = function()
+{
+       DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
+       if(this.current_page + 1 != this.num_pages)
+       {
+               this.current_page++;
+               this.refreshTable();
+       }
+       DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
+}
+
+/**
+ * This function will go back a page.  If we are already at the first page, then 
+ * it will remain there.
+ */
+nrsTable.prototype.prevPage = function()
+{
+       DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
+       if(this.current_page != 0)
+       {
+               this.current_page--;
+               this.refreshTable();
+       }
+       DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
+}
+
+/**
+ * This function will go to the first page.
+ */
+nrsTable.prototype.firstPage = function()
+{
+       if(this.current_page != 0)
+       {
+               this.current_page = 0;
+               this.refreshTable();
+       }
+}
+
+/**
+ * This function will go to the last page.
+ */
+nrsTable.prototype.lastPage = function()
+{
+       DEBUG("lastPage(), current_page: " + this.current_page + " and num_pages: " + this.num_pages);
+       if(this.current_page != (this.num_pages - 1))
+       {
+               this.current_page = this.num_pages - 1;
+               this.refreshTable();
+       }
+}
+
+/**
+ * This function will go to a specific page.  valid values are pages 1 to 
+ * however many number of pages there are.
+ * \param page The page number to go to.
+ */
+nrsTable.prototype.gotoPage = function(page)
+{
+       page--;
+       if(page >=0 && page < this.num_pages)
+       {
+               this.current_page = page;
+               this.refreshTable();
+       }
+}
+
+/**
+ * This function can be used to change the number of entries per row displayed
+ * on the fly.
+ * \param entries The number of entries per page.
+ */
+nrsTable.prototype.changeNumRows = function(entries)
+{
+       if(entries > 0)
+       {
+               this.rows_per_page = entries;
+               //we do.
+               this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
+               this.refreshTable();
+       }
+}
+
+/**
+ * This function will take in a new data array and , optionally, a new cell_link
+ * array OR a new row_link array.  Only one will be used, with the cell_link
+ * array taking precedence.  It will then re-build the table with the new data
+ * array.
+ * \param new_data This is the new data array.  This is required.
+ * \param cell_links This is the new cell links array, a 2D array for each cell.
+ * \param row_links This is the new row links array, a 1D array for each row.
+ */
+nrsTable.prototype.newData = function(new_data, cell_links, row_links)
+{
+       //extract the elements from teh table to clear the table.
+       this.extractElements();
+       //now delete all the data related to this table.  I do this so that 
+       //(hopefully) the memory will be freed.  This is realy needed for IE, whose
+       //memory handling is almost non-existant
+       delete this.data;
+       delete this.data_nodes;
+       delete this.cell_links;
+       delete this.row_links
+       //now re-assign.
+       this.data = new_data;
+       this.cell_links = cell_links;
+       this.row_links = row_links;
+       if(this.rows_per_page != -1)
+       {
+               //we do.
+               this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
+               if(this.num_pages <= 1 && this.page_nav)
+                       this.removeNav();
+               else if(this.page_nav)
+                       this.insertNav();
+               this.current_page = 0;
+       }
+       this.createDataNodes();
+       if(this.field_to_sort != 0)
+               this.moveSortArrow(0);
+       if(!this.field_asc)
+               this.flipSortArrow();
+       this.insertElements();
+       this.updateNav();
+}
+
+/**
+ * This function will remove the NAV bar (if one exists) from the table.
+ */
+nrsTable.prototype.removeNav = function()
+{
+       if(this.page_nav)
+       {
+               //in this case, remove the nav from the existing structure.
+               var table = document.getElementById(this.my_table);
+               var p = 0;
+               if(this.foot_headers)
+                       p++;
+               var nav = table.tFoot.childNodes[p];
+               if(nav)
+               {
+                       table.tFoot.removeChild(nav);
+                       delete nav;
+               }
+       }
+}
+
+/**
+ * This function wil re-insert the nav into the table.
+ */
+nrsTable.prototype.insertNav = function()
+{
+       table = document.getElementById(this.my_table);
+       var p = 0;
+       if(this.foot_headers)
+               p++;
+       if(this.page_nav && !table.tFoot.childNodes[p])
+       {
+               //this means there should be a nav and there isn't one.
+               //print out the page navigation
+               //first and previous page
+               var nav = document.createElement("TR");
+               var nav_cell = document.createElement("TH");
+               nav_cell.colSpan = this.heading.length;
+               
+               var left = document.createElement("DIV");
+               if(document.attachEvent)
+                       left.style.styleFloat = "left";
+               else
+                       left.style.cssFloat = "left";
+               var img = document.createElement("IMG");
+               img.setAttribute("src", this.rew_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
+               img.style.cursor = 'pointer';
+               left.appendChild(img);
+               //hack to space the arrows, cause IE is absolute crap
+               left.appendChild(document.createTextNode(" "));
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.prev_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
+               img.style.cursor = 'pointer';
+               left.appendChild(img);
+               //apend it to the cell
+               nav_cell.appendChild(left);
+               
+               //next and last pages
+               var right = document.createElement("DIV");
+               if(document.attachEvent)
+                       right.style.styleFloat = "right";
+               else
+                       right.style.cssFloat = "right";
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.next_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
+               img.style.cursor = 'pointer';
+               right.appendChild(img);
+               //hack to space the arrows, cause IE is absolute crap
+               right.appendChild(document.createTextNode(" "));
+               img = document.createElement("IMG");
+               img.setAttribute("src", this.fwd_icon);
+               img.setAttribute("border", "0");
+               img.setAttribute("height", "10");
+               img.setAttribute("width", "10");
+               img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
+               img.style.cursor = 'pointer';
+               right.appendChild(img);
+               //apend it to the cell
+               nav_cell.appendChild(right);
+               
+               //page position
+               var pos = document.createElement("SPAN");
+               pos.setAttribute("id", "nav_pos");
+               pos.appendChild(document.createTextNode("Page " + 
+                                               (this.current_page + 1) + " of " + this.num_pages));
+               //append it to the cell.
+               nav_cell.appendChild(pos);
+               
+               nav.appendChild(nav_cell);
+               //append it to the footer
+               table.tFoot.appendChild(nav);
+       }
+}
+
+/**
+ * This function will hide the previous arrow and the rewind arrows from the
+ * nav field.
+ */
+nrsTable.prototype.hideLeftArrows = function()
+{
+       if(!this.page_nav)
+               return;
+       var myTable = document.getElementById(this.my_table);
+       var p = 0;
+       if(this.foot_headers)
+               p++;
+       var nav = myTable.tFoot.childNodes[p];
+       nav.childNodes[0].childNodes[0].style.display = "none";
+}
+
+/**
+ * This function will show the previous arrow and the rewind arrows from the
+ * nav field.
+ */
+nrsTable.prototype.showLeftArrows = function()
+{
+       if(!this.page_nav)
+               return;
+       table = document.getElementById(this.my_table);
+       var p = 0;
+       if(this.foot_headers)
+               p++;
+       var nav = table.tFoot.childNodes[p];
+       nav.childNodes[0].childNodes[0].style.display = "block";
+}
+
+/**
+ * This function will hide the next arrow and the fast foward arrows from the
+ * nav field.
+ */
+nrsTable.prototype.hideRightArrows = function()
+{
+       if(!this.page_nav)
+               return;
+       table = document.getElementById(this.my_table);
+       var p = 0;
+       if(this.foot_headers)
+               p++;
+       var nav = table.tFoot.childNodes[p];
+       nav.childNodes[0].childNodes[1].style.display = "none";
+}
+
+/**
+ * This function will show the next arrow and the fast foward arrows from the
+ * nav field.
+ */
+nrsTable.prototype.showRightArrows = function()
+{
+       if(!this.page_nav)
+               return;
+       table = document.getElementById(this.my_table);
+       var p = 0;
+       if(this.foot_headers)
+               p++;
+       var nav = table.tFoot.childNodes[p];
+       nav.childNodes[0].childNodes[1].style.display = "block";
+}
diff --git a/gui/bweb/html/prev.png b/gui/bweb/html/prev.png
new file mode 100644 (file)
index 0000000..c60eb07
Binary files /dev/null and b/gui/bweb/html/prev.png differ
diff --git a/gui/bweb/html/prune.png b/gui/bweb/html/prune.png
new file mode 100644 (file)
index 0000000..96b6d57
Binary files /dev/null and b/gui/bweb/html/prune.png differ
diff --git a/gui/bweb/html/purge.png b/gui/bweb/html/purge.png
new file mode 100644 (file)
index 0000000..9ba7c53
Binary files /dev/null and b/gui/bweb/html/purge.png differ
diff --git a/gui/bweb/html/rcorner.png b/gui/bweb/html/rcorner.png
new file mode 100644 (file)
index 0000000..1099733
Binary files /dev/null and b/gui/bweb/html/rcorner.png differ
diff --git a/gui/bweb/html/remove.png b/gui/bweb/html/remove.png
new file mode 100644 (file)
index 0000000..19eaef3
Binary files /dev/null and b/gui/bweb/html/remove.png differ
diff --git a/gui/bweb/html/right.gif b/gui/bweb/html/right.gif
new file mode 100755 (executable)
index 0000000..9ab5a5f
Binary files /dev/null and b/gui/bweb/html/right.gif differ
diff --git a/gui/bweb/html/right.png b/gui/bweb/html/right.png
new file mode 100644 (file)
index 0000000..ac0c51a
Binary files /dev/null and b/gui/bweb/html/right.png differ
diff --git a/gui/bweb/html/save.png b/gui/bweb/html/save.png
new file mode 100644 (file)
index 0000000..b52d1d7
Binary files /dev/null and b/gui/bweb/html/save.png differ
diff --git a/gui/bweb/html/style.css b/gui/bweb/html/style.css
new file mode 100644 (file)
index 0000000..2831248
--- /dev/null
@@ -0,0 +1,720 @@
+* {
+    font-family: Verdana, Arial, Helvetica, sans-serif, monospace;
+}
+
+body {  
+    background: #606060;
+    color: #000000;  
+}  
+
+a:link {    
+    color: #0000FF;    
+    background: transparent;
+    text-decoration: none;
+}    
+    
+a:visited {    
+    color: #990099;    
+    background: transparent;
+    text-decoration: none;
+}    
+    
+a:active {    
+    color: #000000;    
+    background: #ADD8E6;
+    text-decoration: none;
+}    
+    
+h1.rubrique_info {  
+    color: #990033;  
+    margin: 0px 0px 0px 0px;
+    padding: 0em; 
+    border: 0px;
+    font-size: 12px;
+}
+h1.connexe {
+    font-size: 12px;
+    padding: 0em;
+    margin: 0px 0px 0px 0px;
+    color: #990033;
+}
+
+a.rubrique_infolink {
+    text-decoration: none;
+}
+ul.rubrique_infoul {
+    display: inline;
+    list-style-type: square;
+}
+ul.rubrique_infoul * {
+    width: 100%;
+}
+li.rubrique_infoul {
+    margin-left: 15px;
+}
+h1 {
+    color: #990033;   
+}
+
+div.main {  
+    background: white;  
+    color: #000000;  
+    margin-left: 5px;
+    margin-right: 5px;
+    /*padding-left: 10px;
+    padding-right: 10px;*/
+    border: 1px black solid;
+    text-align: left;
+    font-size: 12px;
+}
+div.lsfnbanner {
+       margin-left: 150px;
+       margin-right: 170px;
+       border-top: none;
+    padding-left: 10px;
+    padding-right: 10px;
+    border-bottom: 1px black solid;
+    border-right: 1px black solid;
+    border-left: 1px black solid;
+    text-align: left;
+    font-size: 11px;
+       padding-top: 2px;
+       background-color: #eeeae6;
+}
+div.footer {
+    padding-top: 5px;
+    padding-bottom: 3px;
+    border-top: 1px black solid;
+       border-left: 1px black solid;
+       border-right: 1px black solid;
+    text-align: left;
+    font-size: 9px;
+    background: #dcdff4;
+       margin-top: 40px;
+       margin-left: 20px;
+       margin-right: 20px;
+}
+div.footer p {
+       margin-left: 10px;
+       margin-top: 2px;
+       margin-bottom: 2px;
+}
+a.lsfnlink:link,a.lsfnlink:visited,a.lsfnlink:active {
+    text-decoration: none;
+    color: #333333;
+    font-size: 10px;
+}
+a.lsfnlink:hover {
+    text-decoration: underline;
+    color: black;
+}
+div.menubartop {
+    margin-bottom: 10px;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 0px;
+    font-size: 13px;
+}
+div.smallmenubar {
+    background: white;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 0px;
+    padding-bottom: 0px;
+    font-weight:bold;
+    font-size: 10px;
+    text-align: right;
+}
+div.menuevent {
+    float: left;
+    width: 350px;
+    font-size: 11px;
+    text-align: left;
+    padding-top: 0px;
+    padding-bottom: 0px;
+    padding-left: 10px;
+    font-weight: bold;
+    margin: 0px;
+}
+div.menubar {
+    background: #cac2a8;
+    border-top: 1px black solid;
+    border-bottom: 1px black solid;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 3px;
+       padding-bottom: 2px;
+    font-weight:bold;
+    font-size: 13px;
+}
+div.menubar a {
+       text-decoration: none;
+}
+div.menubar p {
+       padding: 0px;
+       margin: 0px;
+}
+div.menudate {
+    float: left;
+    width: 130px;
+       padding-top: 5px;
+}
+div.menuaccroche {
+       margin-left: 30px;
+       float: left;
+    text-decoration: underline;
+       font-size: 14px;
+}
+div.menusearch {
+       float: right;
+    text-align: right;
+       padding-top: 5px;
+       width: 170px;
+}
+div.menusearch p {
+       margin: 0px 0px 0px 0px;
+}
+div.newsletter {
+       float: right;
+       text-align: right;
+       margin: 0px;
+       padding: 0px;
+       font-size: 8px;
+       font-weight: normal;
+}
+div.newsletter p {
+       margin: 0px 0px 0px 0px;
+}
+.searchinput {
+    border: solid 1px black;
+}
+.newsletterinput {
+    border: solid 1px black;
+    font-weight: normal;
+}
+a#menulinkselect {
+       color: #ed7e00;
+}
+
+div.leftbox {
+    width: 200px; 
+    float: left; 
+    padding-left: 5px;
+    padding-right: 5px;
+    padding-bottom: 5px;
+    border-right: 1px black solid;
+    border-bottom: 1px black solid;
+    background: white;
+    margin-bottom: 10px;
+    margin-right: 20px;
+    overflow: hidden;
+}
+div.leftbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 10px;
+       margin: 0px;
+}
+div.leftbox h2 {
+       font-weight: bold;
+       font-size: 12px;
+       margin: 0px;
+}
+div.leftbox ul {
+    list-style-type: square;
+       margin-bottom: 10px;
+       margin-left: 0em;
+       padding-left: 0em;
+}
+div.leftbox li {
+       margin-left: 10px;
+       margin-top: 10px;
+}
+
+div.rightbox {
+    width: 150px; 
+    float: right; 
+    padding-top: 10px;
+    padding-left: 5px;
+       padding-right: 15px;
+    padding-bottom: 5px;
+    border-left: 1px black solid;
+    border-bottom: 1px black solid;
+       text-align: left;
+}
+div.tipdiv {
+       margin-right: 50px;
+       padding-top: 5px;
+       padding-right: 10px;
+       padding-left: 10px;
+       text-align: justify;
+       background-color: #eee;
+       border: black solid 1px;
+       margin-left: 10px;
+}
+div.tipdiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       color: black;
+    margin-top: 0px;
+    margin-bottom: 20px;
+}
+div.tipdiv h2 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 12px;
+       margin: 0px;
+}
+
+div.newsdiv {
+    margin-left: 220px;
+    margin-right: 180px;
+    margin-top: 10px;
+    margin-bottom: 20px;
+       text-align: justify;
+}
+div.newsdiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       margin: 0px;
+}
+div.newsdiv h2 {
+       font-weight: normal;
+       font-size: 12px;
+       margin: 0px;
+}
+div.newsdiv h3 {
+       font-weight: normal;
+       font-size: 12px;
+       margin-bottom: 20px;
+}
+div.objdiv {
+    margin-left: 220px;
+    margin-right: 20px;
+    margin-top: 10px;
+    margin-bottom: 20px;
+}
+
+h1.newstitle {
+    text-align: left;
+    font-size: 14px;
+    margin: 0px 0px 0px 0px;
+    color: black;
+}
+div.titlediv {
+    border-top: solid #cac2a8 2px;
+    margin-top: 20px;
+    background-color: #eeeae6;
+    padding-left: 10px;
+    font-size: 11px;
+}
+div.bodydiv {
+    border: solid #9e9784 1px;
+    padding-left: 10px;
+    padding-right: 10px;
+       padding-top: 10px;
+       padding-bottom: 5px;
+    margin-top: 2px;
+    text-align: justify;
+}
+div.bodydiv ul {
+    list-style-type: square;
+}
+
+div.comments {
+    padding: 10px;
+    border-top: solid 2px #d37537;
+    border-bottom: solid 2px #d37537;
+    margin-top: 20px;
+    margin-bottom: 10px;
+    background-color: #cacaca;
+    font-size: 12px;
+    line-height: 1.3em;
+}
+
+.comment-vote {
+    font-style: italic;
+}
+
+p.commentsbody {
+    border-left-style: solid;
+    border-width: 1px;
+    border-color: rgb(0, 0, 0);
+    padding-left: 10px;
+       text-align: justify;
+       margin-right: 20px;
+}
+div.commentsreply {
+       /*margin-left: 220px;*/
+       text-align: center;
+       margin-top: 50px;
+}
+
+ul.commentsul {
+    list-style-type: none;
+       margin-bottom: 10px;
+       margin-left: 1.25em;
+       padding-left: 0em;
+       /*border-left: 1px solid black;*/
+}
+ul.commentsli {
+       margin: 10px;
+}
+div.comments li {
+       margin-top: 20px;
+       margin-left: 2px;
+}
+
+div.comments h1 {
+    font-size: 12px;
+    color: black;
+    margin: 0px 20px 3px 0px;
+    background-color: rgb(226, 226, 226);
+    padding-left: 1px;
+    font-weight: normal;
+}
+
+div.articlediv {
+    padding-left: 20px;
+    padding-right: 20px;
+    padding-top: 10px;
+    padding-bottom: 20px;
+    margin-right: 180px;
+    margin-left: 220px;
+    border: solid 1px black;
+    margin-top: 10px;
+    text-align: justify;
+    background-color: #E2E2E2;
+}
+img {
+    border: 0px;
+}
+div.sectionimg {
+    float: left;
+    margin-right: 10px;
+    margin-top: 5px;
+}
+p.hautpage {
+       margin-top: 20px;
+       margin-bottom: 20px;
+       margin-left: 10px;
+}
+div.leftcol {
+       /*width: 202px;*/ 
+       width: 202px;
+       float: left;
+       padding: 0px;
+    /*overflow: hidden;*/
+}
+div.logodiv {
+       border-right: 1px black solid;
+       border-bottom: 1px black solid;
+       padding: 0px;
+       line-height: 0px
+}
+div.loginbox {
+       margin-left: 4px;
+       border: solid #a59f8b 1px;
+       margin-top: 2px;
+       padding: 5px;
+       background-color: #fff2e8;
+       font-size: 10px;
+}
+div.loginbox p {
+       margin: 0px;
+       padding: 0px;
+}
+div.loginbox ul {
+       margin-left: 10px;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding: 0px;
+       list-style-type: none;
+}
+div.loginbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 10px;
+       margin: 0px;
+}
+div.loginbox h2 {
+       font-weight: bold;
+       font-size: 12px;
+       margin: 0px;
+}
+div.loginbox h3 {
+       margin-top: 0px;
+       margin-bottom: 5px;
+       font-size: 12px;
+}
+div.polldivtitle {
+       margin-bottom: 1px;
+       background-color: #cac2a8;
+       margin-left: 4px;
+       margin-top: 15px;
+       padding-left: 5px;
+       font-size: 10px;
+       border-top: solid #a59f8b 1px;
+       border-bottom: solid #a59f8b 1px;
+       text-transform: uppercase;
+}
+div.polldiv {
+       margin-left: 4px;
+       border: 1px #a59f8b solid;
+       margin-top: 0px;
+       padding: 5px;
+       background: #fff2e8;
+}
+div.polldiv p {
+margin: 5px; padding: 0px;
+}
+div.polldiv ul {
+       margin-left: 5px;
+       margin-top: 0px;
+       margin-bottom: 10px;
+       padding: 0px;
+    list-style-type: none;
+}
+div.otherboxtitle {
+       margin-bottom: 2px;
+       background-color: #e3dabc;
+       margin-left: 4px;
+       margin-top: 10px;
+       padding-left: 5px;
+       padding-top: 2px;
+       font-size: 11px;
+       border-top: solid #777364 1px;
+       border-bottom: solid #777364 1px;
+       /*text-transform: uppercase;*/
+       /*font-weight: bold;*/
+}
+div.otherbox {
+       margin-left: 4px;
+       margin-right: 1px;
+       border: 1px #a59f8b solid;
+       margin-top: 0px;
+       padding: 5px;
+       text-align: justify;
+       background: #fff2e8;
+       font-size: 10px;
+}
+div.otherbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 10px;
+       margin: 0px;
+}
+div.otherbox h2 {
+       font-weight: bold;
+       font-size: 11px;
+       margin: 0px;
+}
+div.otherbox p {
+       margin-top: 5px;
+       margin-bottom: 10px;
+}
+div.rightlogo { 
+    width: 90px; 
+    float: right;
+    margin-right: 0px;
+    padding-top: 0px;
+    padding-left: 0px;
+    padding-bottom: 0px;
+}   
+div.centraldiv {
+    margin-left: 220px;
+    margin-right: 10px;
+    margin-bottom: 20px;
+       margin-top: 10px;
+}
+div.centraldiv h1 {
+       font-weight: bold;
+       font-size: 14px;
+       margin: 0px;
+}
+div.centraldiv h2 {
+       font-weight: normal;
+       font-size: 12px;
+       margin: 0px;
+}
+div.centraldiv h3 {
+       font-weight: normal;
+       font-size: 12px;
+       margin-bottom: 20px;
+}
+div.centralinfo {
+       margin-bottom: 20px;
+}
+div.lefttopbox {
+       padding-left: 5px;
+       padding-right: 5px;
+       padding-top: 5px;
+       text-align: justify;
+       border: 2px #a59f8b solid;
+       background: #ffffbb;
+       /*
+       background: #c8ff9b;
+       */
+       font-size: 13px;
+       width: 60%;
+       float: left;
+}
+div.lefttopbox p {
+       margin-top: 5px; margin-bottom: 10px;
+}
+div.lefttopbox h1 {
+       font-size: 13px;
+       font-weight: bold;
+       margin: 0px;
+       text-align: right;
+}
+div.lefttopbox h2 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 13px;
+       margin: 0px;
+}
+div.lefttopbox h3 {
+       font-weight: bold;
+       font-size: 14px;
+       margin: 0px;
+}
+div.righttopbox {
+       border: 1px #a59f8b solid;
+       background: white;
+       padding: 5px;
+       text-align: justify;
+       font-size: 12px;
+       width: 34%;
+       float: right;
+}
+div.righttopbox h1 {
+       text-transform: uppercase;
+       font-weight: bold;
+       color: #ed7e00;
+       font-size: 10px;
+       margin: 0px;
+}
+div.righttopbox h2 {
+       font-weight: bold;
+       font-size: 12px;
+       margin: 0px;
+}
+div.boardindex {
+       text-align: justify;
+       font-size: 11px;
+       padding: 10px;
+       margin-left: 20px;
+       margin-right: 20px;
+}
+a.boardindex:link,a.boardindex:visited,a.boardindex:active {
+       text-decoration:none;
+       color: red;
+}
+div.boardleftmsg {
+       float: left;
+       margin-top: 3px;
+}
+div.boardrightmsg {
+       margin-left: 130px;
+       margin-top: 3px;
+       padding-left: 5px;
+}
+div.journalbody {
+       margin-left: 40px;
+       margin-top: 40px;
+}
+div.journalbody h1 {
+       font-size: 15px;
+       font-weight: bold;
+}
+div.journalbody p {
+       margin-bottom: 20px;
+}
+.formulaire {
+       border: solid 1px black;
+       font-size: 12px;
+       background-color: #fffbf7;
+       color: #000000;
+}
+.formulaire:focus {
+       background-color: #eeeae6;
+       border: 1px solid #777;
+       color: #000000;
+}
+.newcomments {
+       color: red;
+       font-weight: bold;
+}
+.misspelled {
+       color: red;
+       font-weight: bold;
+}
+div.commentsreplythanks {
+       margin-left: 100px;
+       margin-top: 50px;
+       margin-right: 100px;
+       background-color: #eee;
+       border: black solid 1px;
+       padding: 10px;
+}
+div.archivediv {
+       margin-right: 20px;
+}
+.archivedate {
+       color:#f30;
+}
+.archivelink {
+       font-size: 14px;
+       font-weight: bold;
+       text-decoration: underline;
+}
+div.forumgroup {
+       text-align: left;
+       border: solid black 1px;
+       padding: 10px 20px 20px 10px;
+       background-color: #eee;
+}
+
+div.adminall {
+       font-size: 12px;
+       padding: 10px;
+}
+div.floatwin {
+    position:absolute;
+    top: 150px;
+    left: 450px;
+    width:200px;
+    visibility:hidden;
+    background: #dcdff4;
+    padding: 5px;
+    border: solid black 1px;
+}
+
+div.poll-result-bar {
+       background-color: #bbb;
+       color: black;
+       border: solid 1px black;
+}
+div.funbanner {
+       text-align: right;
+       border-top: #aaa solid 1px;
+       border-bottom: #aaa solid 1px;
+       padding-right: 20px;
+       padding-top: 2px;
+       padding-bottom: 2px;
+       font-size: 10px;
+       background-color: #fff2e8;
+       font-weight: bold;
+       margin-top: 10px;
+}
+
+div.replie
+{
+       display: none;  
+}
diff --git a/gui/bweb/html/unload.png b/gui/bweb/html/unload.png
new file mode 100644 (file)
index 0000000..ce290ba
Binary files /dev/null and b/gui/bweb/html/unload.png differ
diff --git a/gui/bweb/html/up.gif b/gui/bweb/html/up.gif
new file mode 100755 (executable)
index 0000000..4824985
Binary files /dev/null and b/gui/bweb/html/up.gif differ
diff --git a/gui/bweb/html/update.png b/gui/bweb/html/update.png
new file mode 100644 (file)
index 0000000..c7e691b
Binary files /dev/null and b/gui/bweb/html/update.png differ
diff --git a/gui/bweb/html/zoom.png b/gui/bweb/html/zoom.png
new file mode 100644 (file)
index 0000000..1ac4864
Binary files /dev/null and b/gui/bweb/html/zoom.png differ
diff --git a/gui/bweb/lib/Bconsole.pm b/gui/bweb/lib/Bconsole.pm
new file mode 100644 (file)
index 0000000..9bac755
--- /dev/null
@@ -0,0 +1,408 @@
+use strict;
+
+=head1 LICENSE
+
+    Copyright (C) 2006 Eric Bollengier
+        All rights reserved.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=head1 VERSION
+
+    $Id$
+
+=cut
+
+
+################################################################
+# Manage with Expect the bconsole tool
+package Bconsole;
+use Expect;
+use POSIX qw/_exit/;
+
+# my $pref = new Pref(config_file => 'brestore.conf');
+# my $bconsole = new Bconsole(pref => $pref);
+sub new
+{
+    my ($class, %arg) = @_;
+
+    my $self = bless {
+       pref => $arg{pref},     # Pref object
+       bconsole => undef,      # Expect object
+       log_stdout => $arg{log_stdout} || 0,
+       timeout => $arg{debug} || 10,
+       debug   => $arg{debug} || 0,
+    };
+
+    return $self;
+}
+
+sub run
+{
+    my ($self, %arg) = @_;
+
+    my $cmd = 'run ';
+    for my $key (keys %arg) {
+       if ($arg{$key}) {
+           $arg{$key} =~ tr/""/  /;
+           $cmd .= "$key=\"$arg{$key}\" ";
+       }
+    }
+
+    unless ($self->connect()) {
+       return 0;
+    }
+
+    print "=> $cmd yes\n";
+
+    $self->{bconsole}->clear_accum();
+    $self->send("$cmd yes\n");
+    $self->expect_it('-re',qr/^[*]/);
+    my $ret = $self->before();
+    if ($ret =~ /jobid=(\d+)/is) {
+       return $1;
+    } else {
+       return 0;
+    }
+}
+
+sub send
+{
+    my ($self, $what) = @_;
+    $self->{bconsole}->send($what);
+}
+
+sub expect_it
+{
+    my ($self, @what) = @_;
+    unless ($self->{bconsole}->expect($self->{timeout}, @what)) {
+       $self->{error} = $!;
+       return 0;
+    }
+    return 1;
+}
+
+sub connect
+{
+    my ($self) = @_;
+
+    if ($self->{error}) {
+       return 0 ;
+    }
+
+    unless ($self->{bconsole}) {
+       my @cmd = split(/\s+/, $self->{pref}->{bconsole}) ;
+       unless (@cmd) {
+           $self->{error} = "bconsole string not found";
+           return 0;
+       }
+       $self->{bconsole} = new Expect;
+       $self->{bconsole}->raw_pty(0);
+       $self->{bconsole}->debug($self->{debug});
+       $self->{bconsole}->log_stdout($self->{debug} || $self->{log_stdout});
+
+       # WARNING : die is trapped by gtk_main_loop()
+       # and exit() closes DBI connection 
+       my $ret;
+       { 
+           my $sav = $SIG{__DIE__};
+           $SIG{__DIE__} = sub {  _exit 1 ;};
+           $ret = $self->{bconsole}->spawn(@cmd) ;
+           $SIG{__DIE__} = $sav;
+       }
+
+       unless ($ret) {
+           $self->{error} = $!;
+           return 0;
+       }
+       
+       # TODO : we must verify that expect return the good value
+
+       $self->expect_it('*');
+       $self->send_cmd('gui on');
+    }
+    return 1 ;
+}
+
+sub cancel
+{
+    my ($self, $jobid) = @_;
+    return $self->send_cmd("cancel jobid=$jobid");
+}
+
+# get text between to expect
+sub before
+{
+    my ($self) = @_;
+    return $self->{bconsole}->before();
+}
+
+sub send_cmd
+{
+    my ($self, $cmd) = @_;
+    unless ($self->connect()) {
+       return '';
+    }
+    $self->send("$cmd\n");
+    $self->expect_it($cmd);
+    $self->{bconsole}->clear_accum();
+    $self->expect_it('-re',qr/^[*]/);
+    return $self->before();
+}
+
+sub send_cmd_yes
+{
+    my ($self, $cmd) = @_;
+    unless ($self->connect()) {
+       return '';
+    }
+    $self->send("$cmd\n");
+    $self->expect_it('-re', '[?].+:');
+
+    $self->send("yes\n");
+    $self->expect_it("yes");
+    $self->{bconsole}->clear_accum();
+    $self->expect_it('-re',qr/^[*]/);
+    return $self->before();
+}
+
+sub send_cmd_with_drive
+{
+    my ($self, $cmd, $drive) = @_;
+    $drive = $drive || '0';
+
+    unless ($self->connect()) {
+       return '';
+    }
+    $self->send("$cmd\n");
+    $self->expect_it('-re', '\[0\]\s*:');
+
+    $self->send("$drive\n");
+    $self->expect_it('-re', '[0-9]');
+    $self->{bconsole}->clear_accum();
+    $self->expect_it('-re',qr/^[*]/);
+    return $self->before();
+}
+
+sub label_barcodes
+{
+    my ($self, %arg) = @_;
+
+    unless ($arg{storage}) {
+       return '';
+    }
+
+    unless ($self->connect()) {
+       return '';
+    }
+
+    $arg{drive} = $arg{drive} || '0' ;
+    $arg{pool} = $arg{pool} || 'Scratch';
+
+    my $cmd = "label barcodes pool=\"$arg{pool}\" storage=\"$arg{storage}\"";
+
+    if ($arg{slots}) {
+       $cmd .= " slots=$arg{slots}";
+    }
+
+    $self->send("$cmd\n");
+    $self->expect_it('-re', '\[0\]\s*:');
+    $self->send("$arg{drive}\n");
+    $self->expect_it('-re', '[?].+\)\s*:');
+    my $res = $self->before();
+    $self->send("yes\n");
+    $self->expect_it("yes");
+    $res .= $self->before();
+    $self->expect_it('-re',qr/^[*]/);
+    $res .= $self->before();
+    return $res;
+}
+
+#
+# return [ { name => 'test1', vol => '00001', ... },
+#          { name => 'test2', vol => '00002', ... }... ] 
+#
+sub director_get_sched
+{
+    my ($self, $days) = @_ ;
+
+    $days = $days || 1;
+
+    unless ($self->connect()) {
+       return '';
+    }
+   
+    my $status = $self->send_cmd("st director days=$days") ;
+
+    my @ret;
+    foreach my $l (split(/\r?\n/, $status)) {
+       #Level          Type     Pri  Scheduled        Name       Volume
+       #Incremental    Backup    11  03-ao-06 23:05  TEST_DATA  000001
+       if ($l =~ /^(I|F|Di)\w+\s+\w+\s+\d+/i) {
+           my ($level, $type, $pri, $d, $h, @name_vol) = split(/\s+/, $l);
+
+           my $vol = pop @name_vol; # last element
+           my $name = join(" ", @name_vol); # can contains space
+
+           push @ret, {
+               level => $level,
+               type  => $type,
+               priority => $pri,
+               date  => "$d $h",
+               name  => $name,
+               volume => $vol,
+           };
+       }
+
+    }
+    return \@ret;
+}
+
+sub update_slots
+{
+    my ($self, $storage, $drive) = @_;
+    
+    return $self->send_cmd_with_drive("update slots storage=$storage", $drive);
+}
+
+sub list_job
+{
+    my ($self) = @_;
+    return split(/\r\n/, $self->send_cmd(".jobs"));
+}
+
+sub list_fileset
+{
+    my ($self) = @_;
+    return split(/\r\n/, $self->send_cmd(".filesets"));
+}
+
+sub list_storage
+{
+    my ($self) = @_;
+    return split(/\r\n/, $self->send_cmd(".storage"));
+}
+
+sub list_client
+{
+    my ($self) = @_;
+    return split(/\r\n/, $self->send_cmd(".clients"));
+}
+
+use Time::ParseDate qw/parsedate/;
+use POSIX qw/strftime/;
+use Data::Dumper;
+
+sub _get_volume
+{
+    my ($self, @volume) = @_;
+    return '' unless (@volume);
+
+    my $sel='';
+    foreach my $vol (@volume) {
+       if ($vol =~ /^([\w\d\.-]+)$/) {
+           $sel .= " volume=$1";
+
+       } else {
+           $self->{error} = "Sorry media is bad";
+           return '';
+       }
+    }
+
+    return $sel;
+}
+
+sub purge_volume
+{
+    my ($self, @volume) = @_;
+
+    my $sel = $self->_get_volume(@volume);
+    my $ret;
+    if ($sel) {
+       $ret = $self->send_cmd("purge $sel");
+    } else {
+       $ret = $self->{error};
+    }
+    return $ret;
+}
+
+sub prune_volume
+{
+    my ($self, @volume) = @_;
+
+    my $sel = $self->_get_volume(@volume);
+    my $ret;
+    if ($sel) {
+       $ret = $self->send_cmd_yes("prune $sel");
+    } else {
+       $ret = $self->{error};
+    }
+    return $ret;
+}
+
+sub purge_job
+{
+    my ($self, @jobid) = @_;
+
+    return 0 unless (@jobid);
+
+    my $sel='';
+    foreach my $job (@jobid) {
+       if ($job =~ /^(\d+)$/) {
+           $sel .= " jobid=$1";
+
+       } else {
+           $self->{error} = "Sorry jobid is bad";
+           return 0;
+       }
+    }
+
+    $self->send_cmd("purge $sel");
+}
+
+sub close
+{
+    my ($self) = @_;
+    $self->send("quit\n");
+    $self->{bconsole}->soft_close();
+    $self->{bconsole} = undef;
+}
+
+1;
+
+__END__
+
+# to use this
+# grep -v __END__ Bconsole.pm | perl
+
+package main;
+
+print "test sans conio\n";
+
+my $c = new Bconsole(pref => {
+    bconsole => '/usr/local/bacula/sbin/bconsole -c /usr/local/bacula/etc/bconsole.conf',
+},
+                    debug => 1);
+
+print "fileset : ", join(',', $c->list_fileset()), "\n";
+print "job : ",     join(',', $c->list_job()), "\n";
+print "storage : ", join(',', $c->list_storage()), "\n";
+#print "prune : " . $c->prune_volume('000001'), "\n";
+#print "update : " . $c->send_cmd_with_drive('update slots storage=SDLT-1-2'), "\n";
+print "label : ", join(',', $c->label_barcodes(storage => 'SDLT-1-2',
+                                              slots => 6,
+                                              drive => 0)), "\n";
+
+
diff --git a/gui/bweb/lib/Bweb.pm b/gui/bweb/lib/Bweb.pm
new file mode 100644 (file)
index 0000000..7badb6a
--- /dev/null
@@ -0,0 +1,2775 @@
+################################################################
+use strict;
+
+=head1 LICENSE
+
+    Copyright (C) 2006 Eric Bollengier
+        All rights reserved.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=head1 VERSION
+
+    $Id$
+
+=cut
+
+package Bweb::Gui;
+
+=head1 PACKAGE
+
+    Bweb::Gui - Base package for all Bweb object
+
+=head2 DESCRIPTION
+
+    This package define base fonction like new, display, etc..
+
+=cut
+
+use HTML::Template;
+our $template_dir='/usr/share/bweb/tpl';
+
+
+=head1 FUNCTION
+
+    new - creation a of new Bweb object
+
+=head2 DESCRIPTION
+
+    This function take an hash of argument and place them
+    on bless ref
+
+    IE : $obj = new Obj(name => 'test', age => '10');
+
+         $obj->{name} eq 'test' and $obj->{age} eq 10
+
+=cut
+
+sub new
+{
+    my ($class, %arg) = @_;
+    my $self = bless {
+       name => undef,
+    }, $class;
+
+    map { $self->{lc($_)} = $arg{$_} } keys %arg ;
+
+    return $self;
+}
+
+sub debug
+{
+    my ($self, $what) = @_;
+
+    if ($self->{debug}) {
+       if (ref $what) {
+           print "<pre>" . Data::Dumper::Dumper($what) . "</pre>";
+       } else {
+           print "<pre>$what</pre>";
+       }
+    }
+}
+
+=head1 FUNCTION
+
+    error - display an error to the user
+
+=head2 DESCRIPTION
+
+    this function set $self->{error} with arg, display a message with
+    error.tpl and return 0
+
+=head2 EXAMPLE
+
+    unless (...) {
+        return $self->error("Can't use this file");
+    }
+
+=cut
+
+sub error
+{
+    my ($self, $what) = @_;
+    $self->{error} = $what;
+    $self->display($self, 'error.tpl');
+    return 0;
+}
+
+=head1 FUNCTION
+
+    display - display an html page with HTML::Template
+
+=head2 DESCRIPTION
+
+    this function is use to render all html codes. it takes an
+    ref hash as arg in which all param are usable in template.
+
+    it will use global template_dir to search the template file.
+
+    hash keys are not sensitive. See HTML::Template for more
+    explanations about the hash ref. (it's can be quiet hard to understand) 
+
+=head2 EXAMPLE
+
+    $ref = { name => 'me', age => 26 };
+    $self->display($ref, "people.tpl");
+
+=cut
+
+sub display
+{
+    my ($self, $hash, $tpl) = @_ ;
+    
+    my $template = HTML::Template->new(filename => $tpl,
+                                      path =>[$template_dir],
+                                      die_on_bad_params => 0,
+                                      case_sensitive => 0);
+
+    foreach my $var (qw/limit offset/) {
+
+       unless ($hash->{$var}) {
+           my $value = CGI::param($var) || '';
+
+           if ($value =~ /^(\d+)$/) {
+               $template->param($var, $1) ;
+           }
+       }
+    }
+
+    $template->param('thisurl', CGI::url(-relative => 1, -query=>1));
+    $template->param('loginname', CGI::remote_user());
+
+    $template->param($hash);
+    print $template->output();
+}
+1;
+
+################################################################
+
+package Bweb::Config;
+
+use base q/Bweb::Gui/;
+
+=head1 PACKAGE
+    
+    Bweb::Config - read, write, display, modify configuration
+
+=head2 DESCRIPTION
+
+    this package is used for manage configuration
+
+=head2 USAGE
+
+    $conf = new Bweb::Config(config_file => '/path/to/conf');
+    $conf->load();
+
+    $conf->edit();
+
+    $conf->save();
+
+=cut
+
+use CGI;
+
+=head1 PACKAGE VARIABLE
+
+    %k_re - hash of all acceptable option.
+
+=head2 DESCRIPTION
+
+    this variable permit to check all option with a regexp.
+
+=cut
+
+our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
+             user     => qr/^([\w\d\.-]+)$/i,
+             password => qr/^(.*)$/i,
+             template_dir => qr!^([/\w\d\.-]+)$!,
+             debug    => qr/^(on)?$/,
+             email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
+             graph_font  => qr!^([/\w\d\.-]+.ttf)$!,
+             bconsole    => qr!^(.+)?$!,
+             syslog_file => qr!^(.+)?$!,
+             log_dir     => qr!^(.+)?$!,
+             );
+
+=head1 FUNCTION
+
+    load - load config_file
+
+=head2 DESCRIPTION
+
+    this function load the specified config_file.
+
+=cut
+
+sub load
+{
+    my ($self) = @_ ;
+
+    unless (open(FP, $self->{config_file}))
+    {
+       return $self->error("$self->{config_file} : $!");
+    }
+
+    while (my $line = <FP>) 
+    {
+       chomp($line);
+       my ($k, $v) = split(/\s*=\s*/, $line, 2);
+       $self->{$k} = $v;
+    }
+
+    close(FP);
+    return 1;
+}
+
+=head1 FUNCTION
+
+    save - save the current configuration to config_file
+
+=cut
+
+sub save
+{
+    my ($self) = @_ ;
+
+    unless (open(FP, ">$self->{config_file}"))
+    {
+       return $self->error("$self->{config_file} : $!");
+    }
+    
+    foreach my $k (keys %$self)
+    {
+       next unless (exists $k_re{$k}) ;
+       print FP "$k = $self->{$k}\n";
+    }
+
+    close(FP);       
+    return 1;
+}
+
+=head1 FUNCTIONS
+    
+    edit, view, modify - html form ouput
+
+=cut
+
+sub edit
+{
+    my ($self) = @_ ;
+
+    $self->display($self, "config_edit.tpl");
+}
+
+sub view
+{
+    my ($self) = @_ ;
+
+    $self->display($self, "config_view.tpl");    
+}
+
+sub modify
+{
+    my ($self) = @_;
+    
+    $self->{error} = '';
+    $self->{debug} = 0;
+
+    foreach my $k (CGI::param())
+    {
+       next unless (exists $k_re{$k}) ;
+       my $val = CGI::param($k);
+       if ($val =~ $k_re{$k}) {
+           $self->{$k} = $1;
+       } else {
+           $self->{error} .= "bad parameter : $k = [$val]";
+       }
+    }
+
+    $self->display($self, "config_view.tpl");
+
+    if ($self->{error}) {      # an error as occured
+       $self->display($self, 'error.tpl');
+    } else {
+       $self->save();
+    }
+}
+
+1;
+
+################################################################
+
+package Bweb::Client;
+
+use base q/Bweb::Gui/;
+
+=head1 PACKAGE
+    
+    Bweb::Client - Bacula FD
+
+=head2 DESCRIPTION
+
+    this package is use to do all Client operations like, parse status etc...
+
+=head2 USAGE
+
+    $client = new Bweb::Client(name => 'zog-fd');
+    $client->status();            # do a 'status client=zog-fd'
+
+=cut
+
+=head1 FUNCTION
+
+    display_running_job - Html display of a running job
+
+=head2 DESCRIPTION
+
+    this function is used to display information about a current job
+
+=cut
+
+sub display_running_job
+{
+    my ($self, $conf, $jobid) = @_ ;
+
+    my $status = $self->status($conf);
+
+    if ($jobid) {
+       if ($status->{$jobid}) {
+           $self->display($status->{$jobid}, "client_job_status.tpl");
+       }
+    } else {
+       for my $id (keys %$status) {
+           $self->display($status->{$id}, "client_job_status.tpl");
+       }
+    }
+}
+
+=head1 FUNCTION
+
+    $client = new Bweb::Client(name => 'plume-fd');
+                               
+    $client->status($bweb);
+
+=head2 DESCRIPTION
+
+    dirty hack to parse "status client=xxx-fd"
+
+=head2 INPUT
+
+   JobId 105 Job Full_plume.2006-06-06_17.22.23 is running.
+       Backup Job started: 06-jun-06 17:22
+       Files=8,971 Bytes=194,484,132 Bytes/sec=7,480,158
+       Files Examined=10,697
+       Processing file: /home/eric/.openoffice.org2/user/config/standard.sod
+       SDReadSeqNo=5 fd=5
+   
+=head2 OUTPUT
+
+    $VAR1 = { 105 => {
+               JobName => Full_plume.2006-06-06_17.22.23,
+               JobId => 105,
+               Files => 8,971,
+               Bytes => 194,484,132,
+               ...
+              },
+             ...
+    };
+
+=cut
+
+sub status
+{
+    my ($self, $conf) = @_ ;
+
+    if (defined $self->{cur_jobs}) {
+       return $self->{cur_jobs} ;
+    }
+
+    my $arg = {};
+    my $b = new Bconsole(pref => $conf);
+    my $ret = $b->send_cmd("st client=$self->{name}");
+    my @param;
+    my $jobid;
+
+    for my $r (split(/\n/, $ret)) {
+       chomp($r);
+       $r =~ s/(^\s+|\s+$)//g;
+       if ($r =~ /JobId (\d+) Job (\S+)/) {
+           if ($jobid) {
+               $arg->{$jobid} = { @param, JobId => $jobid } ;
+           }
+
+           $jobid = $1;
+           @param = ( JobName => $2 );
+
+       } elsif ($r =~ /=.+=/) {
+           push @param, split(/\s+|\s*=\s*/, $r) ;
+
+       } elsif ($r =~ /=/) {   # one per line
+           push @param, split(/\s*=\s*/, $r) ;
+
+       } elsif ($r =~ /:/) {   # one per line
+           push @param, split(/\s*:\s*/, $r, 2) ;
+       }
+    }
+
+    if ($jobid and @param) {
+       $arg->{$jobid} = { @param,
+                          JobId => $jobid, 
+                          Client => $self->{name},
+                      } ;
+    }
+
+    $self->{cur_jobs} = $arg ;
+
+    return $arg;
+}
+1;
+
+################################################################
+
+package Bweb::Autochanger;
+
+use base q/Bweb::Gui/;
+
+=head1 PACKAGE
+    
+    Bweb::Autochanger - Object to manage Autochanger
+
+=head2 DESCRIPTION
+
+    this package will parse the mtx output and manage drives.
+
+=head2 USAGE
+
+    $auto = new Bweb::Autochanger(precmd => 'sudo');
+    or
+    $auto = new Bweb::Autochanger(precmd => 'ssh root@robot');
+                                  
+    $auto->status();
+
+    $auto->slot_is_full(10);
+    $auto->transfer(10, 11);
+
+=cut
+
+# TODO : get autochanger definition from config/dump file
+my %ach_list ;
+
+sub get
+{
+    my ($name, $bweb) = @_;
+    my $a = new Bweb::Autochanger(debug => $bweb->{debug}, 
+                                 bweb => $bweb,
+                                 name => 'SDLT-1-2',
+                                 precmd => 'sudo',
+                                 drive_name => ['SDLT-1', 'SDLT-2'],
+                                 );
+    return $a;
+}
+
+sub new
+{
+    my ($class, %arg) = @_;
+
+    my $self = bless {
+       name  => '',    # autochanger name
+       label => {},    # where are volume { label1 => 40, label2 => drive0 }
+       drive => [],    # drive use [ 'media1', 'empty', ..]
+       slot  => [],    # slot use [ undef, 'empty', 'empty', ..] no slot 0
+       io    => [],    # io slot number list [ 41, 42, 43...]
+       info  => {slot => 0,    # informations (slot, drive, io)
+                 io   => 0,
+                 drive=> 0,
+                },
+       mtxcmd => '/usr/sbin/mtx',
+       debug => 0,
+       device => '/dev/changer',
+       precmd => '',   # ssh command
+       bweb => undef,  # link to bacula web object (use for display) 
+    } ;
+
+    map { $self->{lc($_)} = $arg{$_} } keys %arg ;
+
+    return $self;
+}
+
+=head1 FUNCTION
+
+    status - parse the output of mtx status
+
+=head2 DESCRIPTION
+
+    this function will launch mtx status and parse the output. it will
+    give a perlish view of the autochanger content.
+
+    it uses ssh if the autochanger is on a other host.
+
+=cut
+
+sub status
+{
+    my ($self) = @_;
+    my @out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} status` ;
+
+    # TODO : reset all infos
+    $self->{info}->{drive} = 0;
+    $self->{info}->{slot}  = 0;
+    $self->{info}->{io}    = 0;
+
+    #my @out = `cat /home/eric/travail/brestore/plume/mtx` ;
+
+#
+#  Storage Changer /dev/changer:2 Drives, 45 Slots ( 5 Import/Export )
+#Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = 000000
+#Data Transfer Element 1:Empty
+#      Storage Element 1:Empty
+#      Storage Element 2:Full :VolumeTag=000002
+#      Storage Element 3:Empty
+#      Storage Element 4:Full :VolumeTag=000004
+#      Storage Element 5:Full :VolumeTag=000001
+#      Storage Element 6:Full :VolumeTag=000003
+#      Storage Element 7:Empty
+#      Storage Element 41 IMPORT/EXPORT:Empty
+#      Storage Element 41 IMPORT/EXPORT:Full :VolumeTag=000002
+#
+
+    for my $l (@out) {
+
+        #          Storage Element 7:Empty
+        #          Storage Element 2:Full :VolumeTag=000002
+       if ($l =~ /Storage Element (\d+):(Empty|Full)(\s+:VolumeTag=([\w\d]+))?/){
+
+           if ($2 eq 'Empty') {
+               $self->set_empty_slot($1);
+           } else {
+               $self->set_slot($1, $4);
+           }
+
+       } elsif ($l =~ /Data Transfer.+(\d+):(Full|Empty)(\s+.Storage Element (\d+) Loaded.(:VolumeTag = ([\w\d]+))?)?/) {
+
+           if ($2 eq 'Empty') {
+               $self->set_empty_drive($1);
+           } else {
+               $self->set_drive($1, $4, $6);
+           }
+
+       } elsif ($l =~ /Storage Element (\d+).+IMPORT\/EXPORT:(Empty|Full)( :VolumeTag=([\d\w]+))?/) 
+       {
+           if ($2 eq 'Empty') {
+               $self->set_empty_io($1);
+           } else {
+               $self->set_io($1, $4);
+           }
+
+#       Storage Changer /dev/changer:2 Drives, 30 Slots ( 1 Import/Export )
+
+       } elsif ($l =~ /Storage Changer .+:(\d+) Drives, (\d+) Slots/) {
+           $self->{info}->{drive} = $1;
+           $self->{info}->{slot} = $2;
+           if ($l =~ /(\d+)\s+Import/) {
+               $self->{info}->{io} = $1 ;
+           } else {
+               $self->{info}->{io} = 0;
+           }
+       } 
+    }
+
+    $self->debug($self) ;
+}
+
+sub is_slot_loaded
+{
+    my ($self, $slot) = @_;
+
+    # no barcodes
+    if ($self->{slot}->[$slot] eq 'loaded') {
+       return 1;
+    } 
+
+    my $label = $self->{slot}->[$slot] ;
+
+    return $self->is_media_loaded($label);
+}
+
+sub unload
+{
+    my ($self, $drive, $slot) = @_;
+
+    return 0 if (not defined $drive or $self->{drive}->[$drive] eq 'empty') ;
+    return 0 if     ($self->slot_is_full($slot)) ;
+
+    my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} unload $slot $drive 2>&1`;
+    
+    if ($? == 0) {
+       my $content = $self->get_slot($slot);
+       print "content = $content<br/> $drive => $slot<br/>";
+       $self->set_empty_drive($drive);
+       $self->set_slot($slot, $content);
+       return 1;
+    } else {
+       $self->{error} = $out;
+       return 0;
+    }
+}
+
+# TODO: load/unload have to use mtx script from bacula
+sub load
+{
+    my ($self, $drive, $slot) = @_;
+
+    return 0 if (not defined $drive or $self->{drive}->[$drive] ne 'empty') ;
+    return 0 unless ($self->slot_is_full($slot)) ;
+
+    print "Loading drive $drive with slot $slot<br/>\n";
+    my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} load $slot $drive 2>&1`;
+    
+    if ($? == 0) {
+       my $content = $self->get_slot($slot);
+       print "content = $content<br/> $slot => $drive<br/>";
+       $self->set_drive($drive, $slot, $content);
+       return 1;
+    } else {
+       $self->{error} = $out;
+       print $out;
+       return 0;
+    }
+}
+
+sub is_media_loaded
+{
+    my ($self, $media) = @_;
+
+    unless ($self->{label}->{$media}) {
+       return 0;
+    }
+
+    if ($self->{label}->{$media} =~ /drive\d+/) {
+       return 1;
+    }
+
+    return 0;
+}
+
+sub have_io
+{
+    my ($self) = @_;
+    return (defined $self->{info}->{io} and $self->{info}->{io} > 0);
+}
+
+sub set_io
+{
+    my ($self, $slot, $tag) = @_;
+    $self->{slot}->[$slot] = $tag || 'full';
+    push @{ $self->{io} }, $slot;
+
+    if ($tag) {
+       $self->{label}->{$tag} = $slot;
+    } 
+}
+
+sub set_empty_io
+{
+    my ($self, $slot) = @_;
+
+    push @{ $self->{io} }, $slot;
+
+    unless ($self->{slot}->[$slot]) {       # can be loaded (parse before) 
+       $self->{slot}->[$slot] = 'empty';
+    }
+}
+
+sub get_slot
+{
+    my ($self, $slot) = @_;
+    return $self->{slot}->[$slot];
+}
+
+sub set_slot
+{
+    my ($self, $slot, $tag) = @_;
+    $self->{slot}->[$slot] = $tag || 'full';
+
+    if ($tag) {
+       $self->{label}->{$tag} = $slot;
+    }
+}
+
+sub set_empty_slot
+{
+    my ($self, $slot) = @_;
+
+    unless ($self->{slot}->[$slot]) {       # can be loaded (parse before) 
+       $self->{slot}->[$slot] = 'empty';
+    }
+}
+
+sub set_empty_drive
+{
+    my ($self, $drive) = @_;
+    $self->{drive}->[$drive] = 'empty';
+}
+
+sub set_drive
+{
+    my ($self, $drive, $slot, $tag) = @_;
+    $self->{drive}->[$drive] = $tag || $slot;
+
+    $self->{slot}->[$slot] = $tag || 'loaded';
+
+    if ($tag) {
+       $self->{label}->{$tag} = "drive$drive";
+    }
+}
+
+sub slot_is_full
+{
+    my ($self, $slot) = @_;
+    
+    # slot don't exists => full
+    if (not defined $self->{slot}->[$slot]) {
+       return 0 ;
+    }
+
+    if ($self->{slot}->[$slot] eq 'empty') {
+       return 0;
+    }
+    return 1;                  # vol, full, loaded
+}
+
+sub slot_get_first_free
+{
+    my ($self) = @_;
+    for (my $slot=1; $slot < $self->{info}->{slot}; $slot++) {
+       return $slot unless ($self->slot_is_full($slot));
+    }
+}
+
+sub io_get_first_free
+{
+    my ($self) = @_;
+    
+    foreach my $slot (@{ $self->{io} }) {
+       return $slot unless ($self->slot_is_full($slot));       
+    }
+    return 0;
+}
+
+sub get_media_slot
+{
+    my ($self, $media) = @_;
+
+    return $self->{label}->{$media} ;    
+}
+
+sub have_media
+{
+    my ($self, $media) = @_;
+
+    return defined $self->{label}->{$media} ;    
+}
+
+sub send_to_io
+{
+    my ($self, $slot) = @_;
+
+    unless ($self->slot_is_full($slot)) {
+       print "Autochanger $self->{name} slot $slot is empty\n";
+       return 1;               # ok
+    }
+
+    # first, eject it
+    if ($self->is_slot_loaded($slot)) {
+       # bconsole->umount
+       # self->eject
+       print "Autochanger $self->{name} $slot is currently in use\n";
+       return 0;
+    }
+
+    # autochanger must have I/O
+    unless ($self->have_io()) {
+       print "Autochanger $self->{name} don't have I/O, you can take media yourself\n";
+       return 0;
+    }
+
+    my $dst = $self->io_get_first_free();
+
+    unless ($dst) {
+       print "Autochanger $self->{name} you must empty I/O first\n";
+    }
+
+    $self->transfer($slot, $dst);
+}
+
+sub transfer
+{
+    my ($self, $src, $dst) = @_ ;
+    print "$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst\n";
+    my $out = `$self->{precmd} $self->{mtxcmd} -f $self->{device} transfer $src $dst 2>&1`;
+    
+    if ($? == 0) {
+       my $content = $self->get_slot($src);
+       print "content = $content<br/> $src => $dst<br/>";
+       $self->{slot}->[$src] = 'empty';
+       $self->set_slot($dst, $content);
+       return 1;
+    } else {
+       $self->{error} = $out;
+       return 0;
+    }
+}
+
+# TODO : do a tapeinfo request to get informations
+sub tapeinfo
+{
+    my ($self) = @_;
+}
+
+sub clear_io
+{
+    my ($self) = @_;
+
+    for my $slot (@{$self->{io}})
+    {
+       if ($self->is_slot_loaded($slot)) {
+           print "$slot is currently loaded\n";
+           next;
+       }
+
+       if ($self->slot_is_full($slot))
+       {
+           my $free = $self->slot_get_first_free() ;
+           print "want to move $slot to $free\n";
+
+           if ($free) {
+               $self->transfer($slot, $free) || print "$self->{error}\n";
+               
+           } else {
+               $self->{error} = "E : Can't find free slot";
+           }
+       }
+    }
+}
+
+# TODO : this is with mtx status output,
+# we can do an other function from bacula view (with StorageId)
+sub display_content
+{
+    my ($self) = @_;
+    my $bweb = $self->{bweb};
+
+    # $self->{label} => ('vol1', 'vol2', 'vol3', ..);
+    my $media_list = $bweb->dbh_join( keys %{ $self->{label} });
+
+    my $query="
+SELECT Media.VolumeName  AS volumename,
+       Media.VolStatus   AS volstatus,
+       Media.LastWritten AS lastwritten,
+       Media.VolBytes    AS volbytes,
+       Media.MediaType   AS mediatype,
+       Media.Slot        AS slot,
+       Media.InChanger   AS inchanger,
+       Pool.Name         AS name,
+       $bweb->{sql}->{FROM_UNIXTIME}(
+          $bweb->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+        + $bweb->{sql}->{TO_SEC}(Media.VolRetention)
+       ) AS expire
+FROM Media 
+ INNER JOIN Pool USING (PoolId) 
+
+WHERE Media.VolumeName IN ($media_list)
+";
+
+    my $all = $bweb->dbh_selectall_hashref($query, 'volumename') ;
+
+    # TODO : verify slot and bacula slot
+    my $param = [];
+    my @to_update;
+
+    for (my $slot=1; $slot <= $self->{info}->{slot} ; $slot++) {
+
+       if ($self->slot_is_full($slot)) {
+
+           my $vol = $self->{slot}->[$slot];
+           if (defined $all->{$vol}) {    # TODO : autochanger without barcodes 
+
+               my $bslot = $all->{$vol}->{slot} ;
+               my $inchanger = $all->{$vol}->{inchanger};
+
+               # if bacula slot or inchanger flag is bad, we display a message
+               if ($bslot != $slot or !$inchanger) {
+                   push @to_update, $slot;
+               }
+               
+               $all->{$vol}->{realslot} = $slot;
+               $all->{$vol}->{volbytes} = Bweb::human_size($all->{$vol}->{volbytes}) ;
+               
+               push @{ $param }, $all->{$vol};
+
+           } else {            # empty or no label
+               push @{ $param }, {realslot => $slot,
+                                  volstatus => 'Unknow',
+                                  volumename => $self->{slot}->[$slot]} ;
+           }
+       } else {                # empty
+           push @{ $param }, {realslot => $slot, volumename => 'empty'} ;
+       }
+    }
+
+    my $i=0; my $drives = [] ;
+    foreach my $d (@{ $self->{drive} }) {
+       $drives->[$i] = { index => $i,
+                         load  => $self->{drive}->[$i],
+                         name  => $self->{drive_name}->[$i],
+                     };
+       $i++;
+    }
+
+    $bweb->display({ Name   => $self->{name},
+                    nb_drive => $self->{info}->{drive},
+                    nb_io => $self->{info}->{io},
+                    Drives => $drives,
+                    Slots  => $param,
+                    Update => scalar(@to_update) },
+                  'ach_content.tpl');
+
+}
+
+1;
+
+
+################################################################
+
+package Bweb;
+
+use base q/Bweb::Gui/;
+
+=head1 PACKAGE
+
+    Bweb - main Bweb package
+
+=head2
+
+    this package is use to compute and display informations
+
+=cut
+
+use DBI;
+use POSIX qw/strftime/;
+
+our $bpath="/usr/local/bacula";
+our $bconsole="$bpath/sbin/bconsole -c $bpath/etc/bconsole.conf";
+
+our $cur_id=0;
+
+=head1 VARIABLE
+
+    %sql_func - hash to make query mysql/postgresql compliant
+
+=cut
+
+our %sql_func = ( 
+                 Pg => { 
+                     UNIX_TIMESTAMP => '',
+                     FROM_UNIXTIME => '',
+                     TO_SEC => " interval '1 second' * ",
+                     SEC_TO_INT => "SEC_TO_INT",
+                     SEC_TO_TIME => '',
+                 },
+                 mysql => {
+                     UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
+                     FROM_UNIXTIME => 'FROM_UNIXTIME',
+                     SEC_TO_INT => '',
+                     TO_SEC => '',
+                     SEC_TO_TIME => 'SEC_TO_TIME',
+                 },
+                );
+
+sub dbh_selectall_arrayref
+{
+    my ($self, $query) = @_;
+    $self->connect_db();
+    $self->debug($query);
+    return $self->{dbh}->selectall_arrayref($query);
+}
+
+sub dbh_join
+{
+    my ($self, @what) = @_;
+    return join(',', $self->dbh_quote(@what)) ;
+}
+
+sub dbh_quote
+{
+    my ($self, @what) = @_;
+
+    $self->connect_db();
+    if (wantarray) {
+       return map { $self->{dbh}->quote($_) } @what;
+    } else {
+       return $self->{dbh}->quote($what[0]) ;
+    }
+}
+
+sub dbh_do
+{
+    my ($self, $query) = @_ ; 
+    $self->connect_db();
+    $self->debug($query);
+    return $self->{dbh}->do($query);
+}
+
+sub dbh_selectall_hashref
+{
+    my ($self, $query, $join) = @_;
+    
+    $self->connect_db();
+    $self->debug($query);
+    return $self->{dbh}->selectall_hashref($query, $join) ;
+}
+
+sub dbh_selectrow_hashref
+{
+    my ($self, $query) = @_;
+    
+    $self->connect_db();
+    $self->debug($query);
+    return $self->{dbh}->selectrow_hashref($query) ;
+}
+
+# display Mb/Gb/Kb
+sub human_size
+{
+    my @unit = qw(b Kb Mb Gb Tb);
+    my $val = shift || 0;
+    my $i=0;
+    my $format = '%i %s';
+    while ($val / 1024 > 1) {
+       $i++;
+       $val /= 1024;
+    }
+    $format = ($i>0)?'%0.1f %s':'%i %s';
+    return sprintf($format, $val, $unit[$i]);
+}
+
+# display Day, Hour, Year
+sub human_sec
+{
+    use integer;
+
+    my $val = shift;
+    $val /= 60;                        # sec -> min
+
+    if ($val / 60 <= 1) {
+       return "$val mins";
+    } 
+
+    $val /= 60;                        # min -> hour
+    if ($val / 24 <= 1) {
+       return "$val hours";
+    } 
+
+    $val /= 24;                        # hour -> day
+    if ($val / 365 < 2) {
+       return "$val days";
+    } 
+
+    $val /= 365 ;              # day -> year
+
+    return "$val years";   
+}
+
+# get Day, Hour, Year
+sub from_human_sec
+{
+    use integer;
+
+    my $val = shift;
+    unless ($val =~ /^\s*(\d+)\s*(\w)\w*\s*$/) {
+       return 0;
+    }
+
+    my %times = ( m   => 60,
+                 h   => 60*60,
+                 d   => 60*60*24,
+                 m   => 60*60*24*31,
+                 y   => 60*60*24*365,
+                 );
+    my $mult = $times{$2} || 0;
+
+    return $1 * $mult;   
+}
+
+
+sub connect_db
+{
+    my ($self) = @_;
+
+    unless ($self->{dbh}) {
+       $self->{dbh} = DBI->connect($self->{info}->{dbi}, 
+                                   $self->{info}->{user},
+                                   $self->{info}->{password});
+
+       print "Can't connect to your database, see error log\n"
+           unless ($self->{dbh});
+
+       $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
+    }
+}
+
+sub new
+{
+    my ($class, %arg) = @_;
+    my $self = bless { 
+       dbh => undef,           # connect_db();
+       info => {
+           dbi   => 'DBI:Pg:database=bacula;host=127.0.0.1',
+           user  => 'bacula',
+           password => 'test', 
+       },
+    } ;
+
+    map { $self->{lc($_)} = $arg{$_} } keys %arg ;
+
+    if ($self->{info}->{dbi} =~ /DBI:(\w+):/i) {
+       $self->{sql} = $sql_func{$1};
+    }
+
+    $self->{debug} = $self->{info}->{debug};
+    $Bweb::Gui::template_dir = $self->{info}->{template_dir};
+
+    return $self;
+}
+
+sub display_begin
+{
+    my ($self) = @_;
+    $self->display($self->{info}, "begin.tpl");
+}
+
+sub display_end
+{
+    my ($self) = @_;
+    $self->display($self->{info}, "end.tpl");
+}
+
+sub display_clients
+{
+    my ($self) = @_;
+
+    my $query = "
+SELECT Name   AS name,
+       Uname  AS uname,
+       AutoPrune AS autoprune,
+       FileRetention AS fileretention,
+       JobRetention  AS jobretention
+
+FROM Client
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'name') ;
+
+    foreach (values %$all) {
+       $_->{fileretention} = human_sec($_->{fileretention});
+       $_->{jobretention} = human_sec($_->{jobretention});
+    }
+
+    my $arg = { ID => $cur_id++,
+               clients => [ values %$all] };
+
+    $self->display($arg, "client_list.tpl") ;
+}
+
+sub get_limit
+{
+    my ($self, %arg) = @_;
+
+    my $limit = '';
+    my $label = '';
+
+    if ($arg{age}) {
+       $limit = 
+  "AND $self->{sql}->{UNIX_TIMESTAMP}(EndTime) 
+         > 
+       ( $self->{sql}->{UNIX_TIMESTAMP}(NOW()) 
+         - 
+         $self->{sql}->{TO_SEC}($arg{age})
+       )" ;
+
+       $label = "last " . human_sec($arg{age});
+    }
+
+    if ($arg{order}) {
+       $limit .= " ORDER BY $arg{order} ";
+    }
+
+    if ($arg{limit}) {
+       $limit .= " LIMIT $arg{limit} ";
+       $label .= " limited to $arg{limit}";
+    }
+
+    if ($arg{offset}) {
+       $limit .= " OFFSET $arg{offset} ";
+       $label .= " with $arg{offset} offset ";
+    }
+
+    unless ($label) {
+       $label = 'no filter';
+    }
+
+    return ($limit, $label);
+}
+
+=head1 FUNCTION
+
+    $bweb->get_form(...) - Get useful stuff
+
+=head2 DESCRIPTION
+
+    This function get and check parameters against regexp.
+    
+    If word begin with 'q', the return will be quoted or join quoted
+    if it's end with 's'.
+    
+
+=head2 EXAMPLE
+
+    $bweb->get_form('jobid', 'qclient', 'qpools') ;
+
+    { jobid    => 12,
+      qclient  => 'plume-fd',
+      qpools   => "'plume-fd', 'test-fd', '...'",
+    }
+
+=cut
+
+sub get_form
+{
+    my ($self, @what) = @_;
+    my %what = map { $_ => 1 } @what;
+    my %ret;
+
+    my %opt_i = (
+                limit  => 100,
+                cost   =>  10,
+                offset =>   0,
+                width  => 640,
+                height => 480,
+                jobid  =>   0,
+                slot   =>   0,
+                drive  =>   undef,
+                priority => 10,
+                age    => 60*60*24*7,
+                days   => 1,
+                );
+
+    my %opt_s = (              # default to ''
+                ach    => 1,
+                status => 1,
+                 client => 1,
+                level  => 1,
+                pool   => 1,
+                media  => 1,
+                 ach    => 1,
+                 jobtype=> 1,
+                );
+
+    foreach my $i (@what) {
+       if (exists $opt_i{$i}) {# integer param
+           my $value = CGI::param($i) || $opt_i{$i} ;
+           if ($value =~ /^(\d+)$/) {
+               $ret{$i} = $1;
+           }
+       } elsif ($opt_s{$i}) {  # simple string param
+           my $value = CGI::param($i) || '';
+           if ($value =~ /^([\w\d\.-]+)$/) {
+               $ret{$i} = $1;
+           }
+       } elsif ($i =~ /^j(\w+)s$/) { # quote join args
+           my @value = CGI::param($1) ;
+           if (@value) {
+               $ret{$i} = $self->dbh_join(@value) ;
+           }
+
+       } elsif ($i =~ /^q(\w+[^s])$/) { # 'arg1'
+           my $value = CGI::param($1) ;
+           if ($value) {
+               $ret{$i} = $self->dbh_quote($value);
+           }
+
+       } elsif ($i =~ /^q(\w+)s$/) { #[ 'arg1', 'arg2']
+           $ret{$i} = [ map { { name => $self->dbh_quote($_) } } 
+                                 CGI::param($1) ];
+       }
+    }
+
+    if ($what{slots}) {
+       foreach my $s (CGI::param('slot')) {
+           if ($s =~ /^(\d+)$/) {
+               push @{$ret{slots}}, $s;
+           }
+       }
+    }
+
+    if ($what{db_clients}) {
+       my $query = "
+SELECT Client.Name as clientname
+FROM Client
+";
+
+       my $clients = $self->dbh_selectall_hashref($query, 'clientname');
+       $ret{db_clients} = [sort {$a->{clientname} cmp $b->{clientname} } 
+                             values %$clients] ;
+    }
+
+    if ($what{db_mediatypes}) {
+       my $query = "
+SELECT MediaType as mediatype
+FROM MediaType
+";
+
+       my $medias = $self->dbh_selectall_hashref($query, 'mediatype');
+       $ret{db_mediatypes} = [sort {$a->{mediatype} cmp $b->{mediatype} } 
+                                 values %$medias] ;
+    }
+
+    if ($what{db_locations}) {
+       my $query = "
+SELECT Location as location, Cost as cost FROM Location
+";
+       my $loc = $self->dbh_selectall_hashref($query, 'location');
+       $ret{db_locations} = [ sort { $a->{location} 
+                                     cmp 
+                                     $b->{location} 
+                                 } values %$loc ];
+    }
+
+    if ($what{db_pools}) {
+       my $query = "SELECT Name as name FROM Pool";
+
+       my $all = $self->dbh_selectall_hashref($query, 'name') ;
+       $ret{db_pools} = [ sort { $a->{name} cmp $b->{name} } values %$all ];
+    }
+
+    if ($what{db_filesets}) {
+       my $query = "
+SELECT FileSet.FileSet AS fileset 
+FROM FileSet
+";
+
+       my $filesets = $self->dbh_selectall_hashref($query, 'fileset');
+
+       $ret{db_filesets} = [sort {lc($a->{fileset}) cmp lc($b->{fileset}) } 
+                              values %$filesets] ;
+
+    }
+
+    return \%ret;
+}
+
+sub display_graph
+{
+    my ($self) = @_;
+
+    my $fields = $self->get_form(qw/age level status clients filesets 
+                                  db_clients limit db_filesets width height
+                                  qclients qfilesets/);
+                               
+
+    my $url = CGI::url(-full => 0,
+                      -base => 0,
+                      -query => 1);
+    $url =~ s/^.+?\?//;        # http://path/to/bweb.pl?arg => arg
+
+    my $type = CGI::param('graph') || '';
+    if ($type =~ /^(\w+)$/) {
+       $fields->{graph} = $1;
+    }
+
+    my $gtype = CGI::param('gtype') || '';
+    if ($gtype =~ /^(\w+)$/) {
+       $fields->{gtype} = $1;
+    } 
+
+# this organisation is to keep user choice between 2 click
+# TODO : fileset and client selection doesn't work
+
+    $self->display({
+       url => $url,
+       %$fields,
+    }, "graph.tpl")
+
+}
+
+sub display_client_job
+{
+    my ($self, %arg) = @_ ;
+
+    $arg{order} = ' Job.JobId DESC ';
+    my ($limit, $label) = $self->get_limit(%arg);
+
+    my $clientname = $self->dbh_quote($arg{clientname});
+
+    my $query="
+SELECT DISTINCT Job.JobId       AS jobid,
+               Job.Name        AS jobname,
+                FileSet.FileSet AS fileset,
+                Level           AS level,
+                StartTime       AS starttime,
+                JobFiles        AS jobfiles, 
+                JobBytes        AS jobbytes,
+                JobStatus       AS jobstatus,
+               JobErrors       AS joberrors
+
+ FROM Client,Job,FileSet
+ WHERE Client.Name=$clientname
+ AND Client.ClientId=Job.ClientId
+ AND Job.FileSetId=FileSet.FileSetId
+ $limit
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
+
+    foreach (values %$all) {
+       $_->{jobbytes} = human_size($_->{jobbytes}) ;
+    }
+
+    $self->display({ clientname => $arg{clientname},
+                    Filter => $label,
+                    ID => $cur_id++,
+                    Jobs => [ values %$all ],
+                  },
+                  "display_client_job.tpl") ;
+}
+
+sub get_selected_media_location
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_form('jmedias');
+
+    unless ($medias->{jmedias}) {
+       return undef;
+    }
+
+    my $query = "
+SELECT Media.VolumeName AS volumename, Location.Location AS location
+FROM Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
+WHERE Media.VolumeName IN ($medias->{jmedias})
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
+  
+    # { 'vol1' => { [volumename => 'vol1', location => 'ici'],
+    #               ..
+    #             }
+    # }
+    return $all;
+}
+
+sub move_media
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_selected_media_location();
+
+    unless ($medias) {
+       return ;
+    }
+    
+    my $elt = $self->get_form('db_locations');
+
+    $self->display({ ID => $cur_id++,
+                    %$elt,     # db_locations
+                    medias => [ 
+            sort { $a->{volumename} cmp $b->{volumename} } values %$medias
+                              ],
+                    },
+                  "move_media.tpl");
+}
+
+sub help_extern
+{
+    my ($self) = @_ ;
+
+    my $elt = $self->get_form(qw/db_pools db_mediatypes db_locations/) ;
+    $self->debug($elt);
+    $self->display($elt, "help_extern.tpl");
+}
+
+sub help_extern_compute
+{
+    my ($self) = @_;
+
+    my $number = CGI::param('limit') || '' ;
+    unless ($number =~ /^(\d+)$/) {
+       return $self->error("Bad arg number : $number ");
+    }
+
+    my ($sql, undef) = $self->get_param('pools', 
+                                       'locations', 'mediatypes');
+
+    my $query = "
+SELECT Media.VolumeName  AS volumename,
+       Media.VolStatus   AS volstatus,
+       Media.LastWritten AS lastwritten,
+       Media.MediaType   AS mediatype,
+       Media.VolMounts   AS volmounts,
+       Pool.Name         AS name,
+       Media.Recycle     AS recycle,
+       $self->{sql}->{FROM_UNIXTIME}(
+          $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+        + $self->{sql}->{TO_SEC}(Media.VolRetention)
+       ) AS expire
+FROM Media 
+ INNER JOIN Pool     ON (Pool.PoolId = Media.PoolId)
+ LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
+
+WHERE Media.InChanger = 1
+  AND Media.VolStatus IN ('Disabled', 'Error', 'Full')
+  $sql
+ORDER BY expire DESC, recycle, Media.VolMounts DESC
+LIMIT $number
+" ;
+    
+    my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
+
+    $self->display({ Medias => [ values %$all ] },
+                  "help_extern_compute.tpl");
+}
+
+sub help_intern
+{
+    my ($self) = @_ ;
+
+    my $param = $self->get_form(qw/db_locations db_pools db_mediatypes/) ;
+    $self->display($param, "help_intern.tpl");
+}
+
+sub help_intern_compute
+{
+    my ($self) = @_;
+
+    my $number = CGI::param('limit') || '' ;
+    unless ($number =~ /^(\d+)$/) {
+       return $self->error("Bad arg number : $number ");
+    }
+
+    my ($sql, undef) = $self->get_param('pools', 'locations', 'mediatypes');
+
+    if (CGI::param('expired')) {
+       $sql = "
+AND (    $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+       + $self->{sql}->{TO_SEC}(Media.VolRetention)
+    ) < NOW()
+ " . $sql ;
+    }
+
+    my $query = "
+SELECT Media.VolumeName  AS volumename,
+       Media.VolStatus   AS volstatus,
+       Media.LastWritten AS lastwritten,
+       Media.MediaType   AS mediatype,
+       Media.VolMounts   AS volmounts,
+       Pool.Name         AS name,
+       $self->{sql}->{FROM_UNIXTIME}(
+          $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+        + $self->{sql}->{TO_SEC}(Media.VolRetention)
+       ) AS expire
+FROM Media 
+ INNER JOIN Pool ON (Pool.PoolId = Media.PoolId) 
+ LEFT  JOIN Location ON (Location.LocationId = Media.LocationId)
+
+WHERE Media.InChanger <> 1
+  AND Media.VolStatus IN ('Purged', 'Full', 'Append')
+  AND Media.Recycle = 1
+  $sql
+ORDER BY Media.VolUseDuration DESC, Media.VolMounts ASC, expire ASC 
+LIMIT $number
+" ;
+    
+    my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
+
+    $self->display({ Medias => [ values %$all ] },
+                  "help_intern_compute.tpl");
+
+}
+
+sub display_general
+{
+    my ($self, %arg) = @_ ;
+
+    my ($limit, $label) = $self->get_limit(%arg);
+
+    my $query = "
+SELECT 
+    (SELECT count(Pool.PoolId)   FROM Pool)   AS nb_pool, 
+    (SELECT count(Media.MediaId) FROM Media)  AS nb_media, 
+    (SELECT count(Job.JobId)     FROM Job)    AS nb_job,
+    (SELECT sum(VolBytes)        FROM Media)  AS nb_bytes,
+    (SELECT count(Job.JobId)     
+      FROM Job
+      WHERE Job.JobStatus IN ('E','e','f','A')
+      $limit
+    )                                        AS nb_err,
+    (SELECT count(Client.ClientId) FROM Client) AS nb_client
+";
+
+    my $row = $self->dbh_selectrow_hashref($query) ;
+
+    $row->{nb_bytes} = human_size($row->{nb_bytes});
+
+    $row->{db_size} = '???';
+    $row->{label} = $label;
+
+    $self->display($row, "general.tpl");
+}
+
+sub get_param
+{
+    my ($self, @what) = @_ ;
+    my %elt = map { $_ => 1 } @what;
+    my %ret;
+
+    my $limit = '';
+
+    if ($elt{clients}) {
+       my @clients = CGI::param('client');
+       if (@clients) {
+           $ret{clients} = \@clients;
+           my $str = $self->dbh_join(@clients);
+           $limit .= "AND Client.Name IN ($str) ";
+       }
+    }
+
+    if ($elt{filesets}) {
+       my @filesets = CGI::param('fileset');
+       if (@filesets) {
+           $ret{filesets} = \@filesets;
+           my $str = $self->dbh_join(@filesets);
+           $limit .= "AND FileSet.FileSet IN ($str) ";
+       }
+    }
+
+    if ($elt{mediatypes}) {
+       my @medias = CGI::param('mediatype');
+       if (@medias) {
+           $ret{mediatypes} = \@medias;
+           my $str = $self->dbh_join(@medias);
+           $limit .= "AND Media.MediaType IN ($str) ";
+       }
+    }
+
+    if ($elt{client}) {
+       my $client = CGI::param('client');
+       $ret{client} = $client;
+       $client = $self->dbh_join($client);
+       $limit .= "AND Client.Name = $client ";
+    }
+
+    if ($elt{level}) {
+       my $level = CGI::param('level') || '';
+       if ($level =~ /^(\w)$/) {
+           $ret{level} = $1;
+           $limit .= "AND Job.Level = '$1' ";
+       }
+    }
+
+    if ($elt{jobid}) {
+       my $jobid = CGI::param('jobid') || '';
+
+       if ($jobid =~ /^(\d+)$/) {
+           $ret{jobid} = $1;
+           $limit .= "AND Job.JobId = '$1' ";
+       }
+    }
+
+    if ($elt{status}) {
+       my $status = CGI::param('status') || '';
+       if ($status =~ /^(\w)$/) {
+           $ret{status} = $1;
+           $limit .= "AND Job.JobStatus = '$1' ";
+       }
+    }
+
+    if ($elt{locations}) {
+       my @location = CGI::param('location') ;
+       if (@location) {
+           $ret{locations} = \@location;           
+           my $str = $self->dbh_join(@location);
+           $limit .= "AND Location.Location IN ($str) ";
+       }
+    }
+
+    if ($elt{pools}) {
+       my @pool = CGI::param('pool') ;
+       if (@pool) {
+           $ret{pools} = \@pool; 
+           my $str = $self->dbh_join(@pool);
+           $limit .= "AND Pool.Name IN ($str) ";
+       }
+    }
+
+    if ($elt{location}) {
+       my $location = CGI::param('location') || '';
+       if ($location) {
+           $ret{location} = $location;
+           $location = $self->dbh_quote($location);
+           $limit .= "AND Location.Location = $location ";
+       }
+    }
+
+    if ($elt{pool}) {
+       my $pool = CGI::param('pool') || '';
+       if ($pool) {
+           $ret{pool} = $pool;
+           $pool = $self->dbh_quote($pool);
+           $limit .= "AND Pool.Name = $pool ";
+       }
+    }
+
+    if ($elt{jobtype}) {
+       my $jobtype = CGI::param('jobtype') || '';
+       if ($jobtype =~ /^(\w)$/) {
+           $ret{jobtype} = $1;
+           $limit .= "AND Job.Type = '$1' ";
+       }
+    }
+
+    return ($limit, %ret);
+}
+
+=head1
+
+    get last backup
+
+SELECT DISTINCT Job.JobId       AS jobid,
+                Client.Name     AS client,
+                FileSet.FileSet AS fileset,
+               Job.Name        AS jobname,
+                Level           AS level,
+                StartTime       AS starttime,
+                JobFiles        AS jobfiles, 
+                JobBytes        AS jobbytes,
+                VolumeName      AS volumename,
+               JobStatus       AS jobstatus,
+                JobErrors      AS joberrors
+
+ FROM Client,Job,JobMedia,Media,FileSet
+ WHERE Client.ClientId=Job.ClientId
+   AND Job.FileSetId=FileSet.FileSetId
+   AND JobMedia.JobId=Job.JobId 
+   AND JobMedia.MediaId=Media.MediaId
+ $limit
+
+=cut 
+
+sub display_job
+{
+    my ($self, %arg) = @_ ;
+
+    $arg{order} = ' Job.JobId DESC ';
+
+    my ($limit, $label) = $self->get_limit(%arg);
+    my ($where, undef) = $self->get_param('clients',
+                                         'level',
+                                         'filesets',
+                                         'jobtype',
+                                         'jobid',
+                                         'status');
+
+    my $query="
+SELECT  Job.JobId       AS jobid,
+        Client.Name     AS client,
+        FileSet.FileSet AS fileset,
+       Job.Name        AS jobname,
+        Level           AS level,
+        StartTime       AS starttime,
+        Pool.Name       AS poolname,
+        JobFiles        AS jobfiles, 
+        JobBytes        AS jobbytes,
+       JobStatus       AS jobstatus,
+        JobErrors      AS joberrors
+
+ FROM Client, 
+      Job LEFT JOIN Pool     ON (Job.PoolId    = Pool.PoolId)
+          LEFT JOIN FileSet  ON (Job.FileSetId = FileSet.FileSetId)
+ WHERE Client.ClientId=Job.ClientId
+ $where
+ $limit
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
+
+    foreach (values %$all) {
+       $_->{jobbytes} = human_size($_->{jobbytes}) ;
+    }
+
+    $self->display({ Filter => $label,
+                    ID => $cur_id++,
+                    Jobs => 
+                          [ 
+                            sort { $a->{jobid} <=>  $b->{jobid} } 
+                                       values %$all 
+                            ],
+                  },
+                  "display_job.tpl");
+}
+
+# display job informations
+sub display_job_zoom
+{
+    my ($self, $jobid) = @_ ;
+
+    $jobid = $self->dbh_quote($jobid);
+    
+    my $query="
+SELECT DISTINCT Job.JobId       AS jobid,
+                Client.Name     AS client,
+               Job.Name        AS jobname,
+                FileSet.FileSet AS fileset,
+                Level           AS level,
+               Pool.Name       AS poolname,
+                StartTime       AS starttime,
+                JobFiles        AS jobfiles, 
+                JobBytes        AS jobbytes,
+               JobStatus       AS jobstatus,
+                $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
+                                            - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
+
+ FROM Client,
+      Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
+          LEFT JOIN Pool    ON (Job.PoolId    = Pool.PoolId)
+ WHERE Client.ClientId=Job.ClientId
+ AND Job.JobId = $jobid
+";
+
+    my $row = $self->dbh_selectrow_hashref($query) ;
+
+    $row->{jobbytes} = human_size($row->{jobbytes}) ;
+
+    # display all volumes associate with this job
+    $query="
+SELECT Media.VolumeName as volumename
+FROM Job,Media,JobMedia
+WHERE Job.JobId = $jobid
+ AND JobMedia.JobId=Job.JobId 
+ AND JobMedia.MediaId=Media.MediaId
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'volumename');
+
+    $row->{volumes} = [ values %$all ] ;
+
+    $self->display($row, "display_job_zoom.tpl");
+}
+
+sub display_media
+{
+    my ($self) = @_ ;
+
+    my ($where, %elt) = $self->get_param('pool',
+                                        'location');
+
+    my $query="
+SELECT Media.VolumeName AS volumename, 
+       Media.VolBytes   AS volbytes,
+       Media.VolStatus  AS volstatus,
+       Media.MediaType  AS mediatype,
+       Media.InChanger  AS online,
+       Media.LastWritten AS lastwritten,
+       Location.Location AS location,
+       Pool.Name         AS poolname,
+       $self->{sql}->{FROM_UNIXTIME}(
+          $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+        + $self->{sql}->{TO_SEC}(Media.VolRetention)
+       ) AS expire
+FROM Pool, Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
+WHERE Media.PoolId=Pool.PoolId
+$where
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
+    foreach (values %$all) {
+       $_->{volbytes} = human_size($_->{volbytes}) ;
+    }
+
+    $self->display({ ID => $cur_id++,
+                    Pool => $elt{pool},
+                    Location => $elt{location},
+                    Medias => [ values %$all ]
+                  },
+                  "display_media.tpl");
+}
+
+sub display_medias
+{
+    my ($self) = @_ ;
+
+    my $pool = $self->get_form('db_pools');
+    
+    foreach my $name (@{ $pool->{db_pools} }) {
+       CGI::param('pool', $name->{name});
+       $self->display_media();
+    }
+}
+
+sub display_media_zoom
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_form('jmedias');
+    
+    unless ($medias->{jmedias}) {
+       return $self->error("Can't get media selection");
+    }
+    
+    my $query="
+SELECT InChanger     AS online,
+       VolBytes      AS nb_bytes,
+       VolumeName    AS volumename,
+       VolStatus     AS volstatus,
+       VolMounts     AS nb_mounts,
+       Media.VolUseDuration   AS voluseduration,
+       Media.MaxVolJobs AS maxvoljobs,
+       Media.MaxVolFiles AS maxvolfiles,
+       Media.MaxVolBytes AS maxvolbytes,
+       VolErrors     AS nb_errors,
+       Pool.Name     AS poolname,
+       Location.Location AS location,
+       Media.Recycle AS recycle,
+       Media.VolRetention AS volretention,
+       Media.LastWritten  AS lastwritten,
+       $self->{sql}->{FROM_UNIXTIME}(
+          $self->{sql}->{UNIX_TIMESTAMP}(Media.LastWritten) 
+        + $self->{sql}->{TO_SEC}(Media.VolRetention)
+       ) AS expire
+ FROM Job,Pool,
+      Media LEFT JOIN Location ON (Media.LocationId = Location.LocationId)
+ WHERE Pool.PoolId = Media.PoolId
+ AND VolumeName IN ($medias->{jmedias})
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'volumename') ;
+
+    foreach my $media (values %$all) {
+       $media->{nb_bytes} = human_size($media->{nb_bytes}) ;
+       $media->{voluseduration} = human_sec($media->{voluseduration});
+       $media->{volretention} = human_sec($media->{volretention});
+       my $mq = $self->dbh_quote($media->{volumename});
+
+       $query = "
+SELECT DISTINCT Job.JobId AS jobid,
+                Job.Name  AS name,
+                Job.StartTime AS starttime,
+               Job.Type  AS type,
+                Job.Level AS level,
+                Job.JobFiles AS files,
+               Job.JobBytes AS bytes,
+                Job.jobstatus AS status
+ FROM Media,JobMedia,Job
+ WHERE Media.VolumeName=$mq
+ AND Media.MediaId=JobMedia.MediaId              
+ AND JobMedia.JobId=Job.JobId
+";
+
+       my $jobs = $self->dbh_selectall_hashref($query, 'jobid') ;
+
+       foreach (values %$jobs) {
+           $_->{bytes} = human_size($_->{bytes}) ;
+       }
+
+       $self->display({ jobs => [ values %$jobs ],
+                        %$media },
+                      "display_media_zoom.tpl");
+    }
+}
+
+sub location_edit
+{
+    my ($self) = @_ ;
+
+    my $loc = $self->get_form('qlocation');
+    unless ($loc->{qlocation}) {
+       return $self->error("Can't get location");
+    }
+
+    my $query = "
+SELECT Location.Location AS location, 
+       Location.Cost   AS cost,
+       Location.Enabled AS enabled
+FROM Location
+WHERE Location.Location = $loc->{qlocation}
+";
+
+    my $row = $self->dbh_selectrow_hashref($query);
+
+    $self->display({ ID => $cur_id++,
+                    %$row }, "location_edit.tpl") ;
+
+}
+
+sub location_save
+{
+    my ($self) = @_ ;
+
+    my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
+    unless ($arg->{qlocation}) {
+       return $self->error("Can't get location");
+    }    
+    unless ($arg->{qnewlocation}) {
+       return $self->error("Can't get new location name");
+    }
+    unless ($arg->{cost}) {
+       return $self->error("Can't get new cost");
+    }
+
+    my $enabled = CGI::param('enabled') || '';
+    $enabled = $enabled?1:0;
+
+    my $query = "
+UPDATE Location SET Cost     = $arg->{cost}, 
+                    Location = $arg->{qnewlocation},
+                    Enabled   = $enabled
+WHERE Location.Location = $arg->{qlocation}
+";
+
+    $self->dbh_do($query);
+
+    $self->display_location();
+}
+
+sub location_add
+{
+    my ($self) = @_ ;
+    my $arg = $self->get_form(qw/qlocation cost/) ;
+
+    unless ($arg->{qlocation}) {
+       $self->display({}, "location_add.tpl");
+       return 1;
+    }
+    unless ($arg->{cost}) {
+       return $self->error("Can't get new cost");
+    }
+
+    my $enabled = CGI::param('enabled') || '';
+    $enabled = $enabled?1:0;
+
+    my $query = "
+INSERT INTO Location (Location, Cost, Enabled) 
+       VALUES ($arg->{qlocation}, $arg->{cost}, $enabled)
+";
+
+    $self->dbh_do($query);
+
+    $self->display_location();
+}
+
+sub display_location
+{
+    my ($self) = @_ ;
+
+    my $query = "
+SELECT Location.Location AS location, 
+       Location.Cost     AS cost,
+       Location.Enabled  AS enabled,
+       (SELECT count(Media.MediaId) 
+         FROM Media 
+        WHERE Media.LocationId = Location.LocationId
+       ) AS volnum
+FROM Location
+";
+
+    my $location = $self->dbh_selectall_hashref($query, 'location');
+
+    $self->display({ ID => $cur_id++,
+                    Locations => [ values %$location ] },
+                  "display_location.tpl");
+}
+
+sub update_location
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_selected_media_location();
+    unless ($medias) {
+       return ;
+    }
+
+    my $arg = $self->get_form('db_locations', 'qnewlocation');
+
+    $self->display({ email  => $self->{info}->{email_media},
+                    %$arg,
+                     medias => [ values %$medias ],
+                  },
+                  "update_location.tpl");
+}
+
+sub do_update_media
+{
+    my ($self) = @_ ;
+
+    my $media = CGI::param('media');
+    unless ($media) {
+       return $self->error("Can't find media selection");
+    }
+
+    $media = $self->dbh_quote($media);
+
+    my $update = '';
+
+    my $volstatus = CGI::param('volstatus') || ''; 
+    $volstatus = $self->dbh_quote($volstatus); # is checked by db
+    $update .= " VolStatus=$volstatus, ";
+    
+    my $inchanger = CGI::param('inchanger') || '';
+    if ($inchanger) {
+       $update .= " InChanger=1, " ;
+       my $slot = CGI::param('slot') || '';
+       if ($slot =~ /^(\d+)$/) {
+           $update .= " Slot=$1, ";
+       } else {
+           $update .= " Slot=0, ";
+       }
+    } else {
+       $update = " Slot=0, InChanger=0, ";
+    }
+
+    my $pool = CGI::param('pool') || '';
+    $pool = $self->dbh_quote($pool); # is checked by db
+    $update .= " PoolId=(SELECT PoolId FROM Pool WHERE Name=$pool), ";
+
+    my $volretention = CGI::param('volretention') || '';
+    $volretention = from_human_sec($volretention);
+    unless ($volretention) {
+       return $self->error("Can't get volume retention");
+    }
+
+    $update .= " VolRetention = $volretention, ";
+
+    my $loc = CGI::param('location') || '';
+    $loc = $self->dbh_quote($loc); # is checked by db
+    $update .= " LocationId=(SELECT LocationId FROM Location WHERE Location=$loc), ";
+
+    my $usedu = CGI::param('voluseduration') || '0';
+    $usedu = from_human_sec($usedu);
+    $update .= " VolUseDuration=$usedu, ";
+
+    my $maxj = CGI::param('maxvoljobs') || '0';
+    unless ($maxj =~ /^(\d+)$/) {
+       return $self->error("Can't get max jobs");
+    }
+    $update .= " MaxVolJobs=$1, " ;
+
+    my $maxf = CGI::param('maxvolfiles') || '0';
+    unless ($maxj =~ /^(\d+)$/) {
+       return $self->error("Can't get max files");
+    }
+    $update .= " MaxVolFiles=$1, " ;
+   
+    my $maxb = CGI::param('maxvolbytes') || '0';
+    unless ($maxb =~ /^(\d+)$/) {
+       return $self->error("Can't get max bytes");
+    }
+    $update .= " MaxVolBytes=$1 " ;
+    
+    my $row=$self->dbh_do("UPDATE Media SET $update WHERE VolumeName=$media");
+    
+    if ($row) {
+       print "Update Ok\n";
+       $self->update_media();
+    }
+}
+
+sub update_media
+{
+    my ($self) = @_ ;
+
+    my $media = $self->get_form('qmedia');
+
+    unless ($media->{qmedia}) {
+       return $self->error("Can't get media");
+    }
+
+    my $query = "
+SELECT Media.Slot         AS slot,
+       Pool.Name          AS poolname,
+       Media.VolStatus    AS volstatus,
+       Media.InChanger    AS inchanger,
+       Location.Location  AS location,
+       Media.VolumeName   AS volumename,
+       Media.MaxVolBytes  AS maxvolbytes,
+       Media.MaxVolJobs   AS maxvoljobs,
+       Media.MaxVolFiles  AS maxvolfiles,
+       Media.VolUseDuration AS voluseduration,
+       Media.VolRetention AS volretention
+
+FROM Media INNER JOIN Pool ON (Media.PoolId = Pool.PoolId)
+           LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
+
+WHERE Media.VolumeName = $media->{qmedia}
+";
+
+    my $row = $self->dbh_selectrow_hashref($query);
+    $row->{volretention} = human_sec($row->{volretention});
+    $row->{voluseduration} = human_sec($row->{voluseduration});
+
+    my $elt = $self->get_form(qw/db_pools db_locations/);
+
+    $self->display({
+       %$elt,
+        %$row,
+    },
+                  "update_media.tpl");
+}
+
+sub save_location
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_selected_media();
+
+    unless ($medias) {
+       return 0;
+    }
+    
+    my $loc = $self->get_form('qnewlocation');
+    unless ($loc->{qnewlocation}) {
+       return $self->error("Can't get new location");
+    }
+
+    my $query = "
+ UPDATE Media 
+     SET LocationId = (SELECT LocationId 
+                       FROM Location 
+                       WHERE Location = $loc->{qnewlocation}) 
+     WHERE Media.VolumeName IN ($medias)
+";
+
+    my $nb = $self->dbh_do($query);
+
+    print "$nb media updated";
+}
+
+sub change_location
+{
+    my ($self) = @_ ;
+
+    my $medias = $self->get_selected_media_location();
+    unless ($medias) {
+       return $self->error("Can't get media selection");
+    }
+    my $newloc = CGI::param('newlocation');
+
+    my $user = CGI::param('user') || 'unknow';
+    my $comm = CGI::param('comment') || '';
+    $comm = $self->dbh_quote("$user: $comm");
+
+    my $query;
+
+    foreach my $media (keys %$medias) {
+       $query = "
+INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
+ VALUES(
+       NOW(), $comm, (SELECT MediaId FROM Media WHERE VolumeName = '$media'),
+       (SELECT LocationId FROM Location WHERE Location = '$medias->{$media}->{location}'),
+       (SELECT VolStatus FROM Media WHERE VolumeName = '$media')
+      )
+";
+       
+       $self->debug($query);
+    }
+
+    my $q = new CGI;
+    $q->param('action', 'update_location');
+    my $url = $q->url(-full => 1, -query=>1);
+
+    $self->display({ email  => $self->{info}->{email_media},
+                    url => $url,
+                    newlocation => $newloc,
+                    # [ { volumename => 'vol1' }, { volumename => 'vol2' },..]
+                    medias => [ values %$medias ],
+                  },
+                  "change_location.tpl");
+
+}
+
+sub display_client_stats
+{
+    my ($self, %arg) = @_ ;
+
+    my $client = $self->dbh_quote($arg{clientname});
+    my ($limit, $label) = $self->get_limit(%arg);
+
+    my $query = "
+SELECT 
+    count(Job.JobId)     AS nb_jobs,
+    sum(Job.JobBytes)    AS nb_bytes,
+    sum(Job.JobErrors)   AS nb_err,
+    sum(Job.JobFiles)    AS nb_files,
+    Client.Name          AS clientname
+FROM Job INNER JOIN Client USING (ClientId)
+WHERE 
+    Client.Name = $client
+    $limit 
+GROUP BY Client.Name
+";
+
+    my $row = $self->dbh_selectrow_hashref($query);
+
+    $row->{ID} = $cur_id++;
+    $row->{label} = $label;
+    $row->{nb_bytes}    = human_size($row->{nb_bytes}) ;
+
+    $self->display($row, "display_client_stats.tpl");
+}
+
+# poolname can be undef
+sub display_pool
+{
+    my ($self, $poolname) = @_ ;
+    
+# TODO : afficher les tailles et les dates
+
+    my $query = "
+SELECT Pool.Name     AS name, 
+       Pool.Recycle  AS recycle,
+       Pool.VolRetention AS volretention,
+       Pool.VolUseDuration AS voluseduration,
+       Pool.MaxVolJobs AS maxvoljobs,
+       Pool.MaxVolFiles AS maxvolfiles,
+       Pool.MaxVolBytes AS maxvolbytes, 
+      (SELECT count(Media.MediaId) 
+         FROM Media 
+        WHERE Media.PoolId = Pool.PoolId
+      ) AS volnum
+ FROM Pool
+";     
+
+    my $all = $self->dbh_selectall_hashref($query, 'name') ;
+    foreach (values %$all) {
+       $_->{maxvolbytes}    = human_size($_->{maxvolbytes}) ;
+       $_->{volretention}   = human_sec($_->{volretention}) ;
+       $_->{voluseduration} = human_sec($_->{voluseduration}) ;
+    }
+
+    $self->display({ ID => $cur_id++,
+                    Pools => [ values %$all ]},
+                  "display_pool.tpl");
+}
+
+sub display_running_job
+{
+    my ($self) = @_;
+
+    my $arg = $self->get_form('client', 'jobid');
+
+    if (!$arg->{client} and $arg->{jobid}) {
+
+       my $query = "
+SELECT Client.Name AS name
+FROM Job INNER JOIN Client USING (ClientId)
+WHERE Job.JobId = $arg->{jobid}
+";
+
+       my $row = $self->dbh_selectrow_hashref($query);
+
+       if ($row) {
+           $arg->{client} = $row->{name};
+           CGI::param('client', $arg->{client});
+       }
+    }
+
+    if ($arg->{client}) {
+       my $cli = new Bweb::Client(name => $arg->{client});
+       $cli->display_running_job($self->{info}, $arg->{jobid});
+       if ($arg->{jobid}) {
+           $self->get_job_log();
+       }
+    } else {
+       $self->error("Can't get client or jobid");
+    }
+}
+
+sub display_running_jobs
+{
+    my ($self, $display_action) = @_;
+    
+    my $query = "
+SELECT Job.JobId AS jobid, 
+       Job.Name  AS jobname,
+       Job.Level     AS level,
+       Job.StartTime AS starttime,
+       Job.JobFiles  AS jobfiles,
+       Job.JobBytes  AS jobbytes,
+       Job.JobStatus AS jobstatus,
+$self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(NOW())  
+                            - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
+         AS duration,
+       Client.Name AS clientname
+FROM Job INNER JOIN Client USING (ClientId) 
+WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
+";     
+    my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
+    
+    $self->display({ ID => $cur_id++,
+                    display_action => $display_action,
+                    Jobs => [ values %$all ]},
+                  "running_job.tpl") ;
+}
+
+sub eject_media
+{
+    my ($self) = @_;
+    my $arg = $self->get_form('jmedias', 'slots', 'ach');
+
+    unless ($arg->{jmedias}) {
+       return $self->error("Can't get media selection");
+    }
+    
+    my $query = "
+SELECT Media.VolumeName  AS volumename,
+       Storage.Name      AS storage,
+       Location.Location AS location,
+       Media.Slot        AS slot
+FROM Media INNER JOIN Storage  ON (Media.StorageId  = Storage.StorageId)
+           LEFT  JOIN Location ON (Media.LocationId = Location.LocationId)
+WHERE Media.VolumeName IN ($arg->{jmedias})
+  AND Media.InChanger = 1
+";
+
+    my $all = $self->dbh_selectall_hashref($query, 'volumename');
+
+    my $a = Bweb::Autochanger::get('SDLT-1-2', $self);
+
+    $a->status();
+    foreach my $vol (values %$all) {
+       print "eject $vol->{volumename} from $vol->{storage} : ";
+       if ($a->send_to_io($vol->{slot})) {
+           print "ok</br>";
+       } else {
+           print "err</br>";
+       }
+    }
+}
+
+sub restore
+{
+    my ($self) = @_;
+    
+    my $arg = $self->get_form('jobid', 'client');
+
+    print CGI::header('text/brestore');
+    print "jobid=$arg->{jobid}\n" if ($arg->{jobid});
+    print "client=$arg->{client}\n" if ($arg->{client});
+    print "\n";
+}
+
+# TODO : move this to Bweb::Autochanger ?
+# TODO : make this internal to not eject tape ?
+use Bconsole;
+
+sub delete
+{
+    my ($self) = @_;
+    my $arg = $self->get_form('jobid');
+
+    my $b = new Bconsole(pref => $self->{info});
+
+    if ($arg->{jobid}) {
+       my $ret = $b->send_cmd("delete jobid=\"$arg->{jobid}\"");
+       $self->display({
+           content => $b->send_cmd("delete jobid=\"$arg->{jobid}\""),
+           title => "Delete a job ",
+           name => "delete jobid=$arg->{jobid}",
+       }, "command.tpl");      
+    }
+}
+
+sub update_slots
+{
+    my ($self) = @_;
+
+    my $ach = CGI::param('ach') ;
+    unless ($ach =~ /^([\w\d\.-]+)$/) {
+       return $self->error("Bad autochanger name");
+    }
+
+    my $b = new Bconsole(pref => $self->{info});
+    print "<pre>" . $b->update_slots($ach) . "</pre>";
+}
+
+sub get_job_log
+{
+    my ($self) = @_;
+
+    my $arg = $self->get_form('jobid');
+    unless ($arg->{jobid}) {
+       return $self->error("Can't get jobid");
+    }
+
+    my $t = CGI::param('time') || '';
+
+    my $query = "
+SELECT Job.Name as name, Client.Name as clientname
+ FROM  Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
+ WHERE JobId = $arg->{jobid}
+";
+
+    my $row = $self->dbh_selectrow_hashref($query);
+
+    unless ($row) {
+       return $self->error("Can't find $arg->{jobid} in catalog");
+    }
+    
+
+    $query = "
+SELECT Time AS time, LogText AS log
+ FROM  Log
+ WHERE JobId = $arg->{jobid}
+";
+    my $log = $self->dbh_selectall_arrayref($query);
+    unless ($log) {
+       return $self->error("Can't get log for jobid $arg->{jobid}");
+    }
+
+    if ($t) {
+       # log contains \n
+       $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ; 
+    } else {
+       $logtxt = join("", map { $_->[1] } @$log ) ; 
+    }
+    
+    $self->display({ lines=> $logtxt,
+                    jobid => $arg->{jobid},
+                    name  => $row->{name},
+                    client => $row->{clientname},
+                }, 'display_log.tpl');
+}
+
+
+sub label_barcodes
+{
+    my ($self) = @_ ;
+
+    my $arg = $self->get_form('ach', 'slots', 'drive');
+
+    unless ($arg->{ach}) {
+       return $self->error("Can't find autochanger name");
+    }
+
+    my $slots = '';
+    if ($arg->{slots}) {
+       $slots = join(",", @{ $arg->{slots} });
+    }
+
+    my $t = 60*scalar( @{ $arg->{slots} });
+    my $b = new Bconsole(pref => $self->{info}, timeout => $t,log_stdout => 1);
+    print "<h1>This command can take long time, be patient...</h1>";
+    print "<pre>" ;
+    $b->label_barcodes(storage => $arg->{ach},
+                      drive => $arg->{drive},
+                      pool  => 'Scratch',
+                      slots => $slots) ;
+    print "</pre>";
+}
+
+sub purge
+{
+    my ($self) = @_;
+
+    my @volume = CGI::param('media');
+
+    my $b = new Bconsole(pref => $self->{info}, timeout => 60);
+
+    $self->display({
+       content => $b->purge_volume(@volume),
+       title => "Purge media",
+       name => "purge volume=" . join(' volume=', @volume),
+    }, "command.tpl"); 
+}
+
+sub prune
+{
+    my ($self) = @_;
+
+    my $b = new Bconsole(pref => $self->{info}, timeout => 60);
+
+    my @volume = CGI::param('media');
+    $self->display({
+       content => $b->prune_volume(@volume),
+       title => "Prune media",
+       name => "prune volume=" . join(' volume=', @volume),
+    }, "command.tpl"); 
+}
+
+sub cancel_job
+{
+    my ($self) = @_;
+
+    my $arg = $self->get_form('jobid');
+    unless ($arg->{jobid}) {
+       return $self->error('Bad jobid');
+    }
+
+    my $b = new Bconsole(pref => $self->{info});
+    $self->display({
+       content => $b->cancel($arg->{jobid}),
+       title => "Cancel job",
+       name => "cancel jobid=$arg->{jobid}",
+    }, "command.tpl"); 
+}
+
+sub director_show_sched
+{
+    my ($self) = @_ ;
+
+    my $arg = $self->get_form('days');
+
+    my $b = new Bconsole(pref => $self->{info}) ;
+    
+    my $ret = $b->director_get_sched( $arg->{days} );
+
+    $self->display({
+       id => $cur_id++,
+       list => $ret,
+    }, "scheduled_job.tpl");
+}
+
+sub enable_disable_job
+{
+    my ($self, $what) = @_ ;
+
+    my $name = CGI::param('job') || '';
+    unless ($name =~ /^[\w\d\.\-\s]+$/) {
+       return $self->error("Can't find job name");
+    }
+
+    my $b = new Bconsole(pref => $self->{info}) ;
+
+    my $cmd;
+    if ($what) {
+       $cmd = "enable";
+    } else {
+       $cmd = "disable";
+    }
+
+    $self->display({
+       content => $b->send_cmd("$cmd job=\"$name\""),
+       title => "$cmd $name",
+       name => "$cmd job=\"$name\"",
+    }, "command.tpl"); 
+}
+
+sub run_job_select
+{
+    my ($self) = @_;
+    $b = new Bconsole(pref => $self->{info});
+
+    my $joblist = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".job")) ];
+
+    $self->display({ Jobs => $joblist }, "run_job.tpl");
+}
+
+sub run_parse_job
+{
+    my ($self, $ouput) = @_;
+
+    my %arg;
+    foreach my $l (split(/\r\n/, $ouput)) {
+       if ($l =~ /(\w+): name=([\w\d\.\s-]+?)(\s+\w+=.+)?$/) {
+           $arg{$1} = $2;
+           $l = $3 
+               if ($3) ;
+       } 
+
+       if (my @l = $l =~ /(\w+)=([\w\d*]+)/g) {
+           %arg = (%arg, @l);
+       }
+    }
+
+    my %lowcase ;
+    foreach my $k (keys %arg) {
+       $lowcase{lc($k)} = $arg{$k} ;
+    }
+
+    return \%lowcase;
+}
+
+sub run_job_mod
+{
+    my ($self) = @_;
+    $b = new Bconsole(pref => $self->{info});
+    
+    my $job = CGI::param('job') || '';
+
+    my $info = $b->send_cmd("show job=\"$job\"");
+    my $attr = $self->run_parse_job($info);
+    
+    my $jobs   = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
+
+    my $pools  = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".pool")) ];
+    my $clients = [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".client")) ];
+    my $filesets= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".fileset")) ];
+    my $storages= [ map { { name => $_ } } split(/\r\n/, $b->send_cmd(".storage")) ];
+
+    $self->display({
+       jobs     => $jobs,
+       pools    => $pools,
+       clients  => $clients,
+       filesets => $filesets,
+       storages => $storages,
+       %$attr,
+    }, "run_job_mod.tpl");
+}
+
+sub run_job
+{
+    my ($self) = @_;
+    $b = new Bconsole(pref => $self->{info});
+    
+    my $jobs   = [ map {{ name => $_ }} split(/\r\n/, $b->send_cmd(".job")) ];
+
+    $self->display({
+       jobs     => $jobs,
+    }, "run_job.tpl");
+}
+
+sub run_job_now
+{
+    my ($self) = @_;
+    $b = new Bconsole(pref => $self->{info});
+    
+    # TODO: check input (don't use pool, level)
+
+    my $arg = $self->get_form('pool', 'level', 'client', 'priority');
+    my $job = CGI::param('job') || '';
+    my $storage = CGI::param('storage') || '';
+
+    my $jobid = $b->run(job => $job,
+                       client => $arg->{client},
+                       priority => $arg->{priority},
+                       level => $arg->{level},
+                       storage => $storage,
+                       pool => $arg->{pool},
+                       );
+
+    print $jobid, $b->{error};    
+
+    print "<br>You can follow job execution <a href='?action=dsp_cur_job;client=$arg->{client};jobid=$jobid'> here </a>";
+}
+
+1;
diff --git a/gui/bweb/script/bweb-postgresql.sql b/gui/bweb/script/bweb-postgresql.sql
new file mode 100644 (file)
index 0000000..8bc14bf
--- /dev/null
@@ -0,0 +1,23 @@
+BEGIN;
+
+CREATE FUNCTION SEC_TO_TIME(timestamp with time zone)
+RETURNS timestamp with time zone AS $$
+    select date_trunc('second', $1);
+$$ LANGUAGE SQL;
+
+CREATE FUNCTION SEC_TO_TIME(bigint)
+RETURNS interval AS $$
+    select date_trunc('second', $1 * interval '1 second');
+$$ LANGUAGE SQL;
+
+CREATE FUNCTION UNIX_TIMESTAMP(timestamp with time zone)
+RETURNS double precision AS $$
+    select date_part('epoch', $1);
+$$ LANGUAGE SQL;
+
+CREATE FUNCTION SEC_TO_INT(interval)
+RETURNS double precision AS $$
+    select extract(epoch from $1);
+$$ LANGUAGE SQL;
+
+COMMIT;
\ No newline at end of file
diff --git a/gui/bweb/tpl/about.tpl b/gui/bweb/tpl/about.tpl
new file mode 100644 (file)
index 0000000..6a9e19a
--- /dev/null
@@ -0,0 +1,29 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> About </h1>
+</div>
+<div class='bodydiv'>
+<pre>
+    Bweb Copyright (C) 2006 Eric Bollengier
+        All rights reserved.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+    Bacula Copyright 2000-2006 Kern Sibbald (GPL)
+    nrsTable Copyright 2005 New Roads School (GPL)
+    kaiska css Copyright Willy Morin (BWL)
+</pre>
+</div>
diff --git a/gui/bweb/tpl/ach_content.tpl b/gui/bweb/tpl/ach_content.tpl
new file mode 100644 (file)
index 0000000..da66715
--- /dev/null
@@ -0,0 +1,155 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> 
+Autochanger : <TMPL_VAR NAME=Name> (<TMPL_VAR NAME=nb_drive> Drives
+<TMPL_IF NAME=nb_io><TMPL_VAR NAME=nb_io> IMPORT/EXPORT</TMPL_IF>)</h1>
+ </div>
+ <div class='bodydiv'>
+   <form action='?' method='get'>
+    <input type='hidden' name='ach' value='<TMPL_VAR NAME=name>'>
+    <TMPL_IF NAME="Update">
+    <font color='red'> You must run update slot, Autochanger status is different from bacula slots </font>
+    <br/>
+    </TMPL_IF>
+    <table border='0'>
+    <tr>
+    <td valign='top'>
+    <div class='otherboxtitle'>
+     Tools
+    </div>
+    <div class='otherbox'>
+<button type='submit' name='action' value='label_barcodes' class='formulaire'
+        title='run label barcodes'>Label<br/><img src='/bweb/label.png'>
+</button>
+<TMPL_IF NAME=nb_io>
+<button type='submit' name='action' value='eject' class='formulaire'
+        title='put selected media on i/o'>Eject<br/>
+  <img src='/bweb/extern.png'>
+</button>
+<button type='submit' name='action' value='clear_io' class='formulaire'
+        title='Clear i/o'>Clear I/O<br/>
+  <img src='/bweb/intern.png'>
+</button>
+</TMPL_IF>
+<button type='submit' name='action' value='update_slots' class='formulaire'
+        title='run update slots'>Update<br/>
+  <img src='/bweb/update.png'>
+</button>
+<br/><br/>
+<button type='submit' name='action' value='ach_load' class='formulaire'
+       title='load drive'>&nbsp;Load&nbsp;<br/>
+  <img src='/bweb/load.png'>
+</button>
+<button type='submit' name='action' value='ach_unload' class='formulaire'
+       title='unload drive'>Unload<br/>
+  <img src='/bweb/unload.png'>
+</button>
+
+   </div>
+    <td width='200'/>
+    <td>
+    <b> Drives: </b><br/>
+    <table id='id_drive'></table> <br/>
+    </td>
+    </tr>
+    </table>
+    <b> Content: </b><br/>
+    <table id='id_ach'></table>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Real Slot", "Slot", "Volume Name","Vol Bytes","Vol Status",
+                      "Media Type","Pool Name","Last Written", 
+                       "When expire ?", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=Slots>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.name = 'slot';
+chkbox.value = '<TMPL_VAR NAME=realslot>';
+
+data.push( new Array(
+"<TMPL_VAR NAME=realslot>",
+"<TMPL_VAR NAME=slot>",
+"<TMPL_VAR NAME=volumename>",
+"<TMPL_VAR NAME=volbytes>",
+"<TMPL_VAR NAME=volstatus>",
+"<TMPL_VAR NAME=mediatype>",
+"<TMPL_VAR NAME=name>",
+"<TMPL_VAR NAME=lastwritten>",
+"<TMPL_VAR NAME=expire>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id_ach",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+// page_nav: true,
+ padding: 3,
+// rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+
+var header = new Array("Index", "Drive Name", "Volume Name", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=Drives>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.name = 'drive';
+chkbox.value = '<TMPL_VAR NAME=index>';
+
+data.push( new Array(
+"<TMPL_VAR NAME=index>",
+"<TMPL_VAR NAME=name>",
+"<TMPL_VAR NAME=load>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id_drive",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+// page_nav: true,
+ padding: 3,
+// rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+
+</script>
diff --git a/gui/bweb/tpl/begin.tpl b/gui/bweb/tpl/begin.tpl
new file mode 100644 (file)
index 0000000..da3f774
--- /dev/null
@@ -0,0 +1,38 @@
+<html>
+<head>
+<title>Bweb</title>
+<script language="JavaScript" src="/bweb/natcompare.js"></script>
+<script language="JavaScript" src="/bweb/nrs_table.js"></script>
+<script language="JavaScript" src="/bweb/bweb.js"></script>
+<link type="text/css" rel="stylesheet" href="/bweb/style.css"/>
+<link type="text/css" rel="stylesheet" href="/bweb/kaiska.css"/>
+<link type="text/css" rel="stylesheet" href="/bweb/bweb.css"/>
+</head>
+<body>
+
+    <div class="menubar">
+<a href="?"> Main </a>[
+<a href="?action=client"> Clients </a>|              
+<a href="?action=run_job"> Jobs </a>|
+<a href="?action=running"> Running jobs </a>|
+<a href="?action=next_job"> Next jobs </a|>|
+<a href="?action=job"> Job list </a>|
+<a href="?action=restore"> Restore </a>|
+<a href="?action=graph"> Statistics </a>] [
+<a href="?action=pool"> Pools </a>|
+<a href="?action=location"> Locations </a>|
+<a href="?action=media"> Medias </a>|
+<a href="?action=extern_media"> Eject media </a>| 
+<a href="?action=intern_media"> Load media </a>|
+<a href="?action=ach_view"> View Autochanger </a>] [
+<a href="?action=view_conf"> Configuration </a>|
+<a href="?action=about"> About </a>]
+Logged as <TMPL_VAR NAME=loginname>
+    </div>
+
+<script language="JavaScript">
+if (navigator.appName == 'Konqueror') {
+       alert("Sorry at this moment, bweb works only with mozilla.");
+}
+</script>
+
diff --git a/gui/bweb/tpl/change_location.tpl b/gui/bweb/tpl/change_location.tpl
new file mode 100644 (file)
index 0000000..87f5332
--- /dev/null
@@ -0,0 +1,25 @@
+<br/>
+ <table class='box'>
+  <tr><td> Move media </td></tr>
+  <tr><td>
+      <b>To: </b> <TMPL_VAR NAME=email> <br/>
+      <b>Subject: </b> [BACULA] Move media to <TMPL_VAR NAME=newlocation> <br/>
+<br/>
+Dear,<br/>
+<br/>
+Could you move these media to <TMPL_VAR NAME=newlocation> <br/>
+Media : <br/>
+<ul>
+<TMPL_LOOP NAME=Medias>
+ <li> <TMPL_VAR NAME=VolumeName>  (<TMPL_VAR NAME=location>)
+</TMPL_LOOP>
+</ul>
+
+When it's finish, could you update media location ? (you can use <a
+href="<TMPL_VAR NAME=url>">this link</a>).
+  <br/> 
+Thanks
+
+  </td></tr>
+ </table>
+
diff --git a/gui/bweb/tpl/client_job_status.tpl b/gui/bweb/tpl/client_job_status.tpl
new file mode 100644 (file)
index 0000000..0d39392
--- /dev/null
@@ -0,0 +1,44 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> 
+       Running job <TMPL_VAR JobName> on <TMPL_VAR Client>
+  </h1>
+ </div>
+ <div class='bodydiv'>
+
+<table>
+ <tr>
+  <td> <b> JobName: </b> <td> <td> <TMPL_VAR jobname> (<TMPL_VAR jobid>) <td> 
+ </tr>
+ <tr>
+  <td> <b> Processing file: </b> <td> <td> <TMPL_VAR "processing file"> </td>
+ </tr>
+ <tr>
+  <td> <b> Speed: </b> <td> <td> <TMPL_VAR "bytes/sec"> B/s</td>
+ </tr>
+ <tr>
+  <td> <b> Files Examined: </b> <td> <td> <TMPL_VAR "files examined"></td>
+ </tr>
+ <tr>
+  <td> <b> Bytes: </b> <td> <td> <TMPL_VAR bytes></td>
+ </tr>
+</table>
+
+ </div>
+
+<form name='form1' action='?' method='GET'>
+<button name='action' value='dsp_cur_job' class='formulaire'>
+<img src='/bweb/update.png' title='refresh'>
+</button>
+<input type='hidden' name='client' value='<TMPL_VAR Client>'>
+<input type='hidden' name='jobid' value='<TMPL_VAR JobId>'>
+<button type='submit' name='action' value='cancel_job'  class='formulaire'
+        title='Cancel job'><img src='/bweb/cancel.png'>
+</button>
+</form>
+
+
+<script language="JavaScript">
+  bweb_add_refresh();
+</script>
+
diff --git a/gui/bweb/tpl/client_list.tpl b/gui/bweb/tpl/client_list.tpl
new file mode 100644 (file)
index 0000000..4db687b
--- /dev/null
@@ -0,0 +1,65 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Clients</h1>
+ </div>
+ <div class='bodydiv'>
+<form action='?' method='GET'>
+     <table id='id<TMPL_VAR NAME=ID>'></table>
+       <div class="otherboxtitle">
+          Actions &nbsp;
+        </div>
+        <div class="otherbox">
+<!--        <h1>Actions</h1> -->       
+       <button class='formulaire' name='action' value='job' title='Show last job'><img src='/bweb/zoom.png'>Last jobs</button> &nbsp;
+       <button class='formulaire' name='action' value='dsp_cur_job' title='Show current job'><img src='/bweb/zoom.png'>Current jobs</button> &nbsp;
+       <button class='formulaire' name='action' value='client_status' title='Show client status'><img src='/bweb/zoom.png'>Status</button> &nbsp;
+       <button class='formulaire' name='action' value='client_stats' title='Client stats'><img src='/bweb/chart.png'>Stats</button> &nbsp;
+        </div>
+
+</form>
+ </div>
+
+<script language="JavaScript">
+var header = new Array("Name", "Select", "Desc", "Auto Prune", "File Retention", "Job Retention");
+
+var data = new Array();
+var chkbox ;
+
+<TMPL_LOOP NAME=Clients>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.name = 'client';
+chkbox.value = '<TMPL_VAR NAME=Name>';
+
+data.push( 
+  new Array( "<TMPL_VAR NAME=Name>", 
+             chkbox,
+            "<TMPL_VAR NAME=Uname>",
+            "<TMPL_VAR NAME=AutoPrune>",
+            "<TMPL_VAR NAME=FileRetention>",
+            "<TMPL_VAR NAME=JobRetention>"
+              )
+) ; 
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ disable_sorting: new Array(1)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/client_status.tpl b/gui/bweb/tpl/client_status.tpl
new file mode 100644 (file)
index 0000000..04a5ba2
--- /dev/null
@@ -0,0 +1,44 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> 
+       Running job <TMPL_VAR JobName> on <TMPL_VAR Client>
+  </h1>
+ </div>
+ <div class='bodydiv'>
+
+<table>
+ <tr>
+  <td> <b> JobName: </b> <td> <td> <TMPL_VAR jobname> (<TMPL_VAR jobid>) <td> 
+ </tr>
+ <tr>
+  <td> <b> Processing file: </b> <td> <td> <TMPL_VAR "processing file"> </td>
+ </tr>
+ <tr>
+  <td> <b> Speed: </b> <td> <td> <TMPL_VAR "bytes/sec"> B/s</td>
+ </tr>
+ <tr>
+  <td> <b> Files Examined: </b> <td> <td> <TMPL_VAR "files examined"></td>
+ </tr>
+ <tr>
+  <td> <b> Bytes: </b> <td> <td> <TMPL_VAR bytes></td>
+ </tr>
+</table>
+
+ </div>
+
+<form name='form1' action='?' method='GET'>
+<button name='action' value='dsp_cur_job' class='formulaire'>
+<img src='/bweb/update.png' title='refresh'>
+</button>
+<input type='hidden' name='client' value='<TMPL_VAR Client>'>
+<input type='hidden' name='jobid' value='<TMPL_VAR JobId>'>
+<button type='submit' name='action' value='cancel_job' class='formulaire' 
+        title='Cancel job'><img src='/bweb/cancel.png'>
+</button>
+</form>
+
+
+<script language="JavaScript">
+bweb_add_refresh();
+</script>
+
diff --git a/gui/bweb/tpl/command.tpl b/gui/bweb/tpl/command.tpl
new file mode 100644 (file)
index 0000000..ce9a906
--- /dev/null
@@ -0,0 +1,9 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> <TMPL_VAR title> : <TMPL_VAR name></h1>
+ </div>
+ <div class='bodydiv'>
+  <pre id='log' style='font-size: 10px'>
+<TMPL_VAR content>
+  </pre>
+ </div>
diff --git a/gui/bweb/tpl/config_edit.tpl b/gui/bweb/tpl/config_edit.tpl
new file mode 100644 (file)
index 0000000..8d2e33b
--- /dev/null
@@ -0,0 +1,49 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> Configuration </h1>
+</div>
+<div class='bodydiv'>
+   <form action="?" method='post'>
+    <table>
+     <tr>  <td><b>SQL Connection</b></td>  <td/></tr>
+     <tr><td>DBI :</td>     
+         <td> 
+          <input class="formulaire" type='text' value='<TMPL_VAR NAME=dbi>' size='64' name='dbi'> 
+         </td>
+     </tr>
+     <tr><td>user :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=user>' name='user'>
+         </td>
+     </tr>
+     <tr><td>password :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=password>' name='password'> 
+         </td></tr>
+
+     <tr>  <td><b>General Options</b></td>  <td/></tr>
+
+     <tr><td>email_media :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=email_media>' name='email_media'> 
+         </td></tr>
+         </td></tr>
+
+     <tr>  <td><b>Bweb Configuration</b></td>  <td/></tr>
+
+     <tr><td>graph_font :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=graph_font>' size='64' name='graph_font'> 
+         </td></tr>
+     <tr><td>template_dir :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=template_dir>' size='64' name='template_dir'> 
+         </td></tr>
+     <tr><td>bconsole :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=bconsole>' size='64' name='bconsole'> 
+         </td></tr>
+     <tr><td>debug :</td> 
+         <td> <input class="formulaire" type='checkbox' name='debug'> 
+         </td></tr>
+    </table>
+    <button name='action' value='apply_conf' class='formulaire'>
+     <img src='/bweb/save.png'>
+    </button>
+   </form>
+</div>
diff --git a/gui/bweb/tpl/config_view.tpl b/gui/bweb/tpl/config_view.tpl
new file mode 100644 (file)
index 0000000..37ddc80
--- /dev/null
@@ -0,0 +1,29 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> Configuration </h1>
+</div>
+<div class='bodydiv'>
+   <table>
+    <tr class='header'>  <td>SQL connection </td>  <td/></tr>
+    <tr>  <td><b>SQL Connection</b></td>  <td/></tr>
+    <tr><td>DBI :</td>      <td> <TMPL_VAR NAME=dbi>      </td></tr>
+    <tr><td>user :</td>     <td> <TMPL_VAR NAME=user>     </td></tr>
+    <tr><td>password :</td> <td> <TMPL_VAR NAME=password> </td></tr>
+    <tr>  <td><b>General Options</b></td>  <td/></tr>
+    <tr><td>email_media :</td> <td> <TMPL_VAR NAME=email_media> </td></tr>
+    <tr>  <td><b>Bweb Configuration</b></td>  <td/></tr>
+    <tr><td>template_dir :</td> <td> <TMPL_VAR NAME=template_dir> </td></tr>
+    <tr><td>graph_font :</td> <td> <TMPL_VAR NAME=graph_font> </td></tr>
+    <tr><td>bconsole :</td> <td> <TMPL_VAR NAME=bconsole> </td></tr>
+    <tr><td>debug :</td> <td> <TMPL_VAR NAME=debug> </td></tr>
+   </table>
+
+  info :  <TMPL_VAR NAME=error> </br>
+
+  <form action='?' method='GET'>
+   <button name='action' value='edit_conf' class='formulaire'>
+      <img title='Edit' src='/bweb/edit.png'>
+   </button>
+  </form>
+
+</div>
diff --git a/gui/bweb/tpl/display_client_job.tpl b/gui/bweb/tpl/display_client_job.tpl
new file mode 100644 (file)
index 0000000..f5a6bef
--- /dev/null
@@ -0,0 +1,65 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Last jobs for <TMPL_VAR NAME=clientname> (<TMPL_VAR NAME=Filter>)
+  </h1>
+ </div>
+ <div class='bodydiv'>
+
+   <table id='id<TMPL_VAR NAME=ID>'></table>
+
+<a href="bgraph.pl?client=<TMPL_VAR NAME=clientname>;action=job_size;status=T">
+    <img src="/bweb/chart.png" alt="backup size" title="backup size evolution"/>
+    </a>
+<a href="bgraph.pl?client=<TMPL_VAR NAME=clientname>;action=job_duration;status=T">
+    <img src="/bweb/chart.png" alt="backup duration" title="backup time evolution"/>
+    </a>
+<a href="bgraph.pl?client=<TMPL_VAR NAME=clientname>;action=job_rate;status=T">
+    <img src="/bweb/chart.png" alt="backup rate" title="backup rate evolution"/>
+    </a>                               
+ </div>
+
+
+<script language="JavaScript">
+var header = new Array("JobId", "Job Name", "File Set", "Level", "Start Time", 
+                  "Job Files", "Job Bytes", "Errors");
+
+var data = new Array();
+
+<TMPL_LOOP NAME=Jobs>
+data.push( new Array(
+"<TMPL_VAR NAME=JobId>",
+"<TMPL_VAR NAME=JobName>",    
+"<TMPL_VAR NAME=FileSet>",    
+"<TMPL_VAR NAME=Level>",      
+"<TMPL_VAR NAME=StartTime>",
+"<TMPL_VAR NAME=JobFiles>",   
+"<TMPL_VAR NAME=JobBytes>",   
+"<TMPL_VAR NAME=JobErrors>"   
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: true,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ disable_sorting: new Array(5,6)
+}
+);
+
+// get newest job first
+nrsTables['id<TMPL_VAR NAME=ID>'].fieldSort(0);
+</script>
diff --git a/gui/bweb/tpl/display_client_stats.tpl b/gui/bweb/tpl/display_client_stats.tpl
new file mode 100644 (file)
index 0000000..536cf88
--- /dev/null
@@ -0,0 +1,58 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Client : <TMPL_VAR NAME=clientname> (<TMPL_VAR NAME=label>)</h1>
+ </div>
+ <div class='bodydiv'>
+<form action='?'
+     <table id='id<TMPL_VAR NAME=ID>'></table>
+     <img src="bgraph.pl?client=<TMPL_VAR NAME=clientname>;graph=job_duration;age=2592000;width=420;height=200" alt='Not enough data' > &nbsp;
+     <img src="bgraph.pl?client=<TMPL_VAR NAME=clientname>;graph=job_rate;age=2592000;width=420;height=200" alt='Not enough data'> &nbsp;
+     <img src="bgraph.pl?client=<TMPL_VAR NAME=clientname>;graph=job_size;age=2592000;width=420;height=200" alt='Not enough data'> &nbsp;
+<!--   <div class="otherboxtitle">
+          Actions &nbsp;
+        </div>
+        <div class="otherbox">
+       <h1>Actions</h1> 
+       <button name='action' value='job' title='Show last job'><img src='/bweb/zoom.png'></button> &nbsp;
+       <button name='action' value='dsp_cur_job' title='Show current job'><img src='/bweb/zoom.png'></button> &nbsp;
+       <button name='action' value='client_stat' title='Client stats'><img src='/bweb/zoom.png'></button> &nbsp;
+        </div>
+-->
+</form>
+ </div>
+
+<script language="JavaScript">
+var header = new Array("Name", "Nb Jobs", "Nb Bytes", "Nb Files", "Nb Errors");
+
+var data = new Array();
+
+data.push( 
+  new Array( "<TMPL_VAR NAME=clientname>", 
+            "<TMPL_VAR NAME=nb_jobs>",
+            "<TMPL_VAR NAME=nb_bytes>",
+            "<TMPL_VAR NAME=nb_files>",
+            "<TMPL_VAR NAME=nb_err>"
+             )
+) ; 
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(1)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/display_form_job.tpl b/gui/bweb/tpl/display_form_job.tpl
new file mode 100644 (file)
index 0000000..3901fbf
--- /dev/null
@@ -0,0 +1,114 @@
+<br/>
+
+<div class="otherboxtitle">
+  Filter &nbsp;
+</div>
+<div class="otherbox">
+<form name='form1' action='?' method='GET'>
+<table border='0'>
+<tr>
+  <td valign='top'>
+    <h2>Level</h2>
+    <select name='level' class='formulaire'>
+      <option id='level_Any' value='Any'>Any</option>
+      <option id='level_F' value='F'>Full</option>
+      <option id='level_D' value='D'>Differential</option>
+      <option id='level_I' value='I'>Incremental</option>
+    </select>     
+  </td>
+</tr>
+<tr>
+ <td valign='top'>
+    <h2>Status</h2>
+    <select name='status' class='formulaire'>
+      <option id='status_Any' value='Any'>Any</option>
+      <option id='status_T'   value='T'>Ok</option>
+      <option id='status_f'   value='f'>Error</option>
+      <option id='status_A'   value='A'>Canceled</option>
+    </select>     
+  </td>
+</tr>
+<tr>
+  <td valign='top'>
+    <h2>Age</h2>
+    <select name='age' class='formulaire'>
+      <option id='age_604800'   value='604800'>This week</option>
+      <option id='age_2678400'  value='2678400'>Last 30 days</option>
+      <option id='age_15552000' value='15552000'>Last 6 month</option>
+    </select>     
+  </td>
+ </tr>
+ <tr>
+  <td valign='bottom'> 
+    <h2>Number of items</h2>
+    <input type='text' name='limit' value='<TMPL_VAR NAME=limit>' 
+       class='formulaire' size='4'>
+  </td>
+</tr>
+<tr>
+  <td valign='top'> 
+    <h2>Job Type</h2>
+    <select name='jobtype' class='formulaire'>
+      <option id='jobtype_any' value='all type'>Any</option>
+      <option id='jobtype_B' value='B'>Backup</option>
+      <option id='jobtype_R' value='R'>Restore</option>
+    </select>
+  </td>
+</tr>
+<tr>
+  <td valign='top'> 
+    <h2>Clients</h2>
+    <select name='client' size='15' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_clients>
+      <option id='client_<TMPL_VAR clientname>'><TMPL_VAR clientname></option>
+</TMPL_LOOP>
+    </select>
+  </td>
+</tr>
+<!--
+<tr>
+  <td valign='top'> 
+    <h2>File Set</h2>
+    <select name='fileset' size='15' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_filesets>
+      <option id='client_<TMPL_VAR fileset>'><TMPL_VAR NAME=fileset></option>
+</TMPL_LOOP>
+    </select>
+  </td>
+</tr>
+-->
+</table>
+  <button name='action' value='job' class='formulaire'>
+   <img src='/bweb/update.png'>
+  </button>
+
+</div>
+</form>
+<script language="JavaScript">
+
+  <TMPL_LOOP qclients>
+     document.getElementById('client_' + <TMPL_VAR name>).selected = true;
+  </TMPL_LOOP>
+
+  <TMPL_IF status>
+     document.getElementById('status_<TMPL_VAR status>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF level>
+     document.getElementById('level_<TMPL_VAR level>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF age>
+     document.getElementById('age_<TMPL_VAR age>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF jobtype>
+     document.getElementById('jobtype_<TMPL_VAR jobtype>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_LOOP qfilesets>
+     document.getElementById('fileset_' + <TMPL_VAR name>).selected = true;
+  </TMPL_LOOP>
+
+</script>
+
diff --git a/gui/bweb/tpl/display_job.tpl b/gui/bweb/tpl/display_job.tpl
new file mode 100644 (file)
index 0000000..1fc9fb2
--- /dev/null
@@ -0,0 +1,80 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Last Jobs (<TMPL_VAR Filter>)</h1>
+ </div>
+ <div class='bodydiv'>
+    <table id='id<TMPL_VAR ID>'></table>
+ </div>
+
+<script language="JavaScript">
+<TMPL_IF status>
+document.getElementById('status_<TMPL_VAR status>').checked = true;
+</TMPL_IF>
+
+
+
+var header = new Array("JobId",
+                      "Client",
+                      "Job Name", 
+                      "FileSet",
+//                     "Pool",
+                       "Level",
+                       "StartTime", 
+                       "JobFiles",
+                       "JobBytes", 
+                       "Errors",
+                      "Status");
+
+var data = new Array();
+
+<TMPL_LOOP Jobs>
+a = document.createElement('A');
+a.href='?action=job_zoom;jobid=<TMPL_VAR JobId>';
+
+img = document.createElement("IMG");
+img.src="/bweb/<TMPL_VAR JobStatus>.png";
+img.title=jobstatus['<TMPL_VAR JobStatus>']; 
+
+a.appendChild(img);
+
+data.push( new Array(
+"<TMPL_VAR JobId>",
+"<TMPL_VAR Client>",     
+"<TMPL_VAR JobName>",    
+"<TMPL_VAR FileSet>",    
+//"<TMPL_VAR Pool>",
+"<TMPL_VAR Level>",      
+"<TMPL_VAR StartTime>",
+"<TMPL_VAR JobFiles>",   
+"<TMPL_VAR JobBytes>",
+"<TMPL_VAR joberrors">",   
+a
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: true,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ padding: 3,
+// disable_sorting: new Array(6)
+}
+);
+
+// get newest backup first
+nrsTables['id<TMPL_VAR ID>'].fieldSort(0);
+</script>
diff --git a/gui/bweb/tpl/display_job_zoom.tpl b/gui/bweb/tpl/display_job_zoom.tpl
new file mode 100644 (file)
index 0000000..2e298e1
--- /dev/null
@@ -0,0 +1,72 @@
+ <div class='titlediv'>
+  <h1 class='newstitle'>Information about job</h1>
+ </div>
+ <div class="bodydiv">
+ <table id='id0'></table>
+ <form action='?'>
+  <input type='hidden' name='jobid' value='<TMPL_VAR jobid>'>
+  <button class='formulaire' name='action' value='delete' title='delete this job'>
+     <img src='/bweb/purge.png'>
+  </button>
+ </form>
+</div>
+
+<script language='JavaScript'>
+var header = new Array("JobId",
+                      "Client",
+                      "Job Name", 
+                      "FileSet",
+                       "Level",
+                       "StartTime", 
+                      "Duration",
+                       "JobFiles",
+                       "JobBytes",
+                      "Pool",
+                       "Volume Name",
+                      "Status");
+
+var data = new Array();
+
+img = document.createElement("IMG");
+img.src="/bweb/<TMPL_VAR JobStatus>.png";
+img.title=jobstatus['<TMPL_VAR JobStatus>']; 
+
+data.push( new Array(
+"<TMPL_VAR JobId>",
+"<TMPL_VAR Client>",     
+"<TMPL_VAR JobName>",    
+"<TMPL_VAR FileSet>",    
+"<TMPL_VAR Level>",      
+"<TMPL_VAR StartTime>",
+"<TMPL_VAR duration>",
+"<TMPL_VAR JobFiles>",   
+"<TMPL_VAR JobBytes>",
+"<TMPL_VAR poolname>",
+"<TMPL_LOOP volumes><TMPL_VAR VolumeName>\n</TMPL_LOOP>",   
+img
+ )
+);
+
+nrsTable.setup(
+{
+ table_name:     "id0",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: true,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ padding: 3,
+// disable_sorting: new Array(6)
+}
+);
+
+</script>
\ No newline at end of file
diff --git a/gui/bweb/tpl/display_location.tpl b/gui/bweb/tpl/display_location.tpl
new file mode 100644 (file)
index 0000000..9c22c83
--- /dev/null
@@ -0,0 +1,73 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'>Locations</h1>
+ </div>
+ <div class="bodydiv">
+   <form action='?' method='get'>
+    <table id='id<TMPL_VAR ID>'></table>
+    <button class='formulaire' type='submit' name='action' value='location_add' title='Add a location'>
+     <img src='/bweb/add.png'>
+    </button>
+    <button class='formulaire' type='submit' name='action' value='location_del' title='Remove a location'>
+     <img src='/bweb/remove.png'>
+    </button>
+    <button class='formulaire' type='submit' name='action' value='location_edit' title='Edit a location'>
+     <img src='/bweb/edit.png'>
+    </button>
+
+    <button class='formulaire' type='submit' name='action' value='media' title='Show content'>
+     <img src='/bweb/zoom.png'>
+    </button>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Name","Enabled", "Cost", "Nb volumes", "Select");
+
+var data = new Array();
+var chkbox;
+
+var img;
+
+<TMPL_LOOP Locations>
+img = document.createElement('IMG');
+img.src = '/bweb/inflag<TMPL_VAR enabled>.png';
+
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'radio';
+chkbox.name  = 'location';
+chkbox.value = '<TMPL_VAR Location>';
+
+data.push( new Array(
+"<TMPL_VAR Location>",
+img,
+"<TMPL_VAR Cost>",
+"<TMPL_VAR name=volnum>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/display_log.tpl b/gui/bweb/tpl/display_log.tpl
new file mode 100644 (file)
index 0000000..251a7b0
--- /dev/null
@@ -0,0 +1,9 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Log : <TMPL_VAR name> on <TMPL_VAR client> (<TMPL_VAR jobid>)</h1>
+ </div>
+ <div class='bodydiv'>
+  <pre id='log'>
+<TMPL_VAR lines>
+  </pre>
+ </div>
diff --git a/gui/bweb/tpl/display_media.tpl b/gui/bweb/tpl/display_media.tpl
new file mode 100644 (file)
index 0000000..aa5d411
--- /dev/null
@@ -0,0 +1,88 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> 
+   Media
+  </h1>
+ </div>
+ <div class='bodydiv'>
+
+<TMPL_IF Pool>
+<h2>
+Pool : <a href="?action=pool;pool=<TMPL_VAR Pool>">
+         <TMPL_VAR Pool>
+       </a>
+</h2>
+</TMPL_IF>
+<TMPL_IF Location>
+<h2>
+Location : <TMPL_VAR location>
+</h2>
+</TMPL_IF>
+
+   <form action='?action=test' method='get'>
+    <table id='id_pool_<TMPL_VAR ID>'></table>
+      <button class='formulaire' type='submit' name='action' value='extern'><img src='/bweb/extern.png'></button>
+      <button class='formulaire' type='submit' name='action' value='intern'><img src='/bweb/intern.png'></button> 
+      <button class='formulaire' type='submit' name='action' value='update_media' title='Update media'><img src='/bweb/edit.png'></button> 
+      <button class='formulaire' type='submit' name='action' value='media_zoom' title='Informations'><img src='/bweb/zoom.png'></button>
+<!--
+      <button class='formulaire' type='submit' name='action' value='purge' title='Purge'><img src='/bweb/purge.png'></button>
+-->
+      <button class='formulaire' type='submit' name='action' value='prune' title='Prune'><img src='/bweb/prune.png'></button>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Volume Name","Online","Vol Bytes","Vol Status",
+                      "Pool", "Media Type",
+                      "Last Written", "When expire ?", "Select");
+
+var data = new Array();
+var img;
+var chkbox;
+
+<TMPL_LOOP Medias>
+img = document.createElement('IMG');
+img.src = '/bweb/inflag<TMPL_VAR online>.png';
+
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.name = 'media';
+chkbox.value = '<TMPL_VAR volumename>';
+
+data.push( new Array(
+"<TMPL_VAR volumename>",
+img,
+"<TMPL_VAR volbytes>",
+"<TMPL_VAR volstatus>",
+"<TMPL_VAR poolname>",
+"<TMPL_VAR mediatype>",
+"<TMPL_VAR lastwritten>",
+"<TMPL_VAR expire>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id_pool_<TMPL_VAR ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+ disable_sorting: new Array(1,8)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/display_media_zoom.tpl b/gui/bweb/tpl/display_media_zoom.tpl
new file mode 100644 (file)
index 0000000..bc24ba1
--- /dev/null
@@ -0,0 +1,121 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Media : <TMPL_VAR volumename></h1>
+ </div>
+ <div class='bodydiv'>
+    <b> Media Infos </b><br/>
+    <table id='id_media_<TMPL_VAR volumename>'></table>
+    <b> Job List </b></br>
+    <table id='id_jobs_<TMPL_VAR volumename>'></table>
+    <b> Actions </b></br>
+   <form action='?' method='get'>
+      <input type='hidden' name='media' value='<TMPL_VAR volumename>'>
+<TMPL_IF online>
+      <button class='formulaire' type='submit' name='action' value='intern'><img src='/bweb/intern.png'></button> 
+<TMPL_ELSE>
+      <button class='formulaire' type='submit' name='action' value='extern'><img src='/bweb/extern.png'></button>
+</TMPL_IF>
+      <button class='formulaire' type='submit' name='action' value='update_media' title='Update'><img src='/bweb/edit.png'></button> 
+      <button class='formulaire' type='submit' name='action' value='purge' title='Purge'><img src='/bweb/purge.png'></button>
+      <button class='formulaire' type='submit' name='action' value='prune' title='Prune'><img src='/bweb/prune.png'></button>
+   </form>
+ </div>
+
+
+
+<script language="JavaScript">
+
+var header = new Array("Pool","Online","Location","Vol Status",
+                       "Vol Bytes", "Vol Mounts", "Expire",
+                      "Errors", "Retention", "Max use duration", "Max jobs");
+
+var data = new Array();
+var img;
+
+img = document.createElement('IMG');
+img.src = '/bweb/inflag<TMPL_VAR online>.png';
+
+data.push( new Array(
+"<TMPL_VAR poolname>",
+img,
+"<TMPL_VAR location>",
+"<TMPL_VAR volstatus>",
+"<TMPL_VAR nb_bytes>",
+"<TMPL_VAR nb_mounts>",
+"<TMPL_VAR expire>",
+"<TMPL_VAR nb_errors>",
+"<TMPL_VAR volretention>",
+"<TMPL_VAR voluseduration>",
+"<TMPL_VAR maxvoljobs>"
+ )
+);
+
+nrsTable.setup(
+{
+ table_name:     "id_media_<TMPL_VAR volumename>",
+ table_header: header,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+ table_data: data,
+ header_color: header_color,
+ padding: 3,
+ disable_sorting: new Array(0,1,2,3,4,5)
+}
+);
+
+var header = new Array("JobId","Name","Start Time","Type",
+                      "Level", "Files", "Bytes", "Status");
+
+var data = new Array();
+var a;
+var img;
+
+<TMPL_LOOP jobs>
+a = document.createElement('A');
+a.href='?action=job_zoom;jobid=<TMPL_VAR JobId>';
+
+img = document.createElement("IMG");
+img.src="/bweb/<TMPL_VAR status>.png";
+img.title=jobstatus['<TMPL_VAR status>']; 
+
+a.appendChild(img);
+
+data.push( new Array(
+"<TMPL_VAR jobid>",
+"<TMPL_VAR name>",
+"<TMPL_VAR starttime>",
+"<TMPL_VAR type>",
+"<TMPL_VAR level>",
+"<TMPL_VAR files>",
+"<TMPL_VAR bytes>",
+a
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id_jobs_<TMPL_VAR volumename>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/display_pool.tpl b/gui/bweb/tpl/display_pool.tpl
new file mode 100644 (file)
index 0000000..a657c5f
--- /dev/null
@@ -0,0 +1,67 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'>Pools</h1>
+ </div>
+ <div class="bodydiv">
+   <form action='?' method='get'>
+    <table id='id<TMPL_VAR NAME=ID>'></table>
+    <button class='formulaire' type='submit' name='action' value='media' title='Show content'>
+     <img src='/bweb/zoom.png'>
+    </button>
+   </form>
+   <br/>
+   Tips: To modify pool properties, you have to edit your bacula configuration
+   and reload it. After, you have to run "update pool=mypool" on bconsole.
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Name","Recycle","Retention","Use Duration",
+                      "Max job per volume","Max file per volume", 
+                       "Max volume size","Nb volumes", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=Pools>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'radio';
+chkbox.value = '<TMPL_VAR NAME=Name>';
+chkbox.name  = 'pool';
+
+data.push( new Array(
+"<TMPL_VAR NAME=Name>",
+"<TMPL_VAR NAME=Recycle>",
+"<TMPL_VAR NAME=VolRetention>",
+"<TMPL_VAR NAME=VolUseDuration>",
+"<TMPL_VAR NAME=MaxVolJobs>",
+"<TMPL_VAR NAME=MaxVolFiles>",
+"<TMPL_VAR NAME=MaxVolBytes>",
+"<TMPL_VAR NAME=VolNum>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/end.tpl b/gui/bweb/tpl/end.tpl
new file mode 100644 (file)
index 0000000..308b1d0
--- /dev/null
@@ -0,0 +1,2 @@
+</body>
+</html>
diff --git a/gui/bweb/tpl/error.tpl b/gui/bweb/tpl/error.tpl
new file mode 100644 (file)
index 0000000..154b805
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>An error as occured :</h1>
+<pre>
+<TMPL_VAR NAME=error>
+</pre>
\ No newline at end of file
diff --git a/gui/bweb/tpl/general.tpl b/gui/bweb/tpl/general.tpl
new file mode 100644 (file)
index 0000000..5b6d699
--- /dev/null
@@ -0,0 +1,30 @@
+<br/>
+<script language="JavaScript">
+bweb_add_refresh();
+</script>
+<div class='titlediv'>
+  <h1 class="newstitle">
+   Informations
+  </h1>
+</div>
+<div class="bodydiv">
+  <table>
+   <tr><td>Total clients:</td>      <td> <TMPL_VAR nb_client> </td>
+       <td>Total bytes stored:</td> <td> <TMPL_VAR nb_bytes> </td>
+       <td>Total media:</td>        <td> <TMPL_VAR nb_media> </td>
+   </tr>
+   <tr><td>Database size:</td>      <td> <TMPL_VAR db_size> </td>
+       <td>Total Pool:</td>         <td> <TMPL_VAR nb_pool> </td>
+       <td>Total Job:</td>         <td> <TMPL_VAR nb_job> </td>
+   </tr>
+   <tr><td>Job failed (<TMPL_VAR label>):</td> 
+       
+<td <TMPL_IF nb_err> class='joberr' </TMPL_IF>>
+   <TMPL_VAR nb_err> 
+</td>
+       <td></td>         <td></td>
+       <td></td>         <td></td>
+   </tr>
+  
+  </table>
+</div>
diff --git a/gui/bweb/tpl/graph.tpl b/gui/bweb/tpl/graph.tpl
new file mode 100644 (file)
index 0000000..ee957c8
--- /dev/null
@@ -0,0 +1,150 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'>Statistics</h1>
+ </div>
+ <div class='bodydiv'>
+<table border='0'>
+<td>
+<form name='form1' action='?' method='GET'>
+       <div class="otherboxtitle">
+          Options &nbsp;
+        </div>
+        <div class="otherbox">
+<table border='0'>
+<tr>
+  <td valign='top'>
+    <h2>Level</h2>
+    <select name='level' class='formulaire'>
+      <option id='level_Any' value='Any'>Any</option>
+      <option id='level_F' value='F'>Full</option>
+      <option id='level_D' value='D'>Differential</option>
+      <option id='level_I' value='I'>Incremental</option>
+    </select>     
+  </td><td valign='top'>
+    <h2>Status</h2>
+    <select name='status' class='formulaire'>
+      <option id='status_Any' value='Any'>Any</option>
+      <option id='status_T'   value='T'>Ok</option>
+      <option id='status_f'   value='f'>Error</option>
+      <option id='status_A'   value='A'>Canceled</option>
+    </select>   
+  </td>
+</tr>
+<tr>
+  <td valign='top'>
+    <h2>Age</h2>
+    <select name='age' class='formulaire'>
+      <option id='age_604800'   value='604800'>This week</option>
+      <option id='age_2678400'  value='2678400'>Last 30 days</option>
+      <option id='age_15552000' value='15552000'>Last 6 month</option>
+    </select>     
+  </td>
+  <td  valign='top'>
+    <h2>Size</h2>
+     Width: &nbsp;<input class='formulaire' type='text' 
+                        name='width' value='<TMPL_VAR width>' size='4'><br/>
+     Height:  <input type='text' class='formulaire' 
+               name='height' value='<TMPL_VAR height>' size='4'><br/>
+  </td>
+</tr>
+<tr>
+  <td valign='top'> 
+    <h2>Clients</h2>
+    <select name='client' size='15' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_clients>
+      <option id='client_<TMPL_VAR clientname>'><TMPL_VAR clientname></option>
+</TMPL_LOOP>
+    </select>
+  </td>
+  <td valign='top'> 
+    <h2>File Set</h2>
+    <select name='fileset' size='15' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_filesets>
+      <option><TMPL_VAR NAME=fileset></option>
+</TMPL_LOOP>
+    </select>
+  </td>
+</tr>
+<tr>
+  <td> <h2> Type </h2> 
+ <select name='graph' class='formulaire'>
+   <option id='job_size'     value='job_size'>Job Size</option>
+   <option id='job_duration' value='job_duration'>Job Duration</option>
+   <option id='job_rate' value='job_rate'>Job Rate</option>
+   <option id='job_file' value='job_file'>Job Files</option>
+ </select>
+  </td>
+  <td valign='bottom'> 
+    <h2>Number of items</h2>
+    <input type='text' name='limit' value='<TMPL_VAR NAME=limit>' 
+       class='formulaire' size='4'>
+  </td>
+</tr>
+<tr>
+<td><h2> Graph type </h2> 
+  <select name='gtype' class='formulaire'>
+    <option id='gtype_bars' value='bars'>Bars</option>
+<!--  <option id='gtype_bars3d' value='bars3d'>Bars3d</option> -->
+    <option id='gtype_lines' value='lines'>Lines</option>
+    <option id='gtype_linespoints' value='linespoints'>Lines points</option>
+</td>
+<td>
+  <input type='submit' name='action' value='graph' class='formulaire'> 
+</td>
+</tr>
+</table>
+        </div>
+
+</form>
+</td>
+<td>
+
+ <div class="otherboxtitle">
+ Current &nbsp;
+ </div>
+ <div class="otherbox">
+ <img src='bgraph.pl?<TMPL_VAR NAME=url>' alt='Nothing to display, Try a bigger date range'>
+ </div>
+
+</td>
+</table>
+ </div>
+
+<script language="JavaScript">
+
+  <TMPL_LOOP qclients>
+     document.getElementById('client_' + <TMPL_VAR name>).selected = true;
+  </TMPL_LOOP>
+
+  <TMPL_IF status>
+     document.getElementById('status_<TMPL_VAR status>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF level>
+     document.getElementById('level_<TMPL_VAR level>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF age>
+     document.getElementById('age_<TMPL_VAR age>').selected=true;
+  </TMPL_IF>
+
+<TMPL_IF selfilesets>
+  for (var i=0; i < document.form1.fileset.length; ++i) {
+  <TMPL_LOOP selfilesets>
+     if (document.form1.fileset[i].value == <TMPL_VAR name>) {
+        document.form1.fileset[i].selected = true;
+     }
+  </TMPL_LOOP>
+  }
+</TMPL_IF>
+
+  <TMPL_IF graph>
+     document.getElementById('<TMPL_VAR graph>').selected=true;
+  </TMPL_IF>
+
+  <TMPL_IF gtype>
+     document.getElementById('gtype_<TMPL_VAR gtype>').selected=true;
+  </TMPL_IF>
+
+</script>
+
diff --git a/gui/bweb/tpl/help_extern.tpl b/gui/bweb/tpl/help_extern.tpl
new file mode 100644 (file)
index 0000000..8717410
--- /dev/null
@@ -0,0 +1,46 @@
+<br/>
+<div class='titlediv'>
+ <h1 class='newstitle'> Help to eject media (part 1/2)</h1>
+</div>
+<div class='bodydiv'>
+This tool will select for you best candidate to eject. You will
+be asked for choose inside the selection in the next screen. 
+  <form action="?" method='GET'>
+   <table>
+    <tr><td>Pool:</td>      
+        <td><select name='pool' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_pools>
+             <option><TMPL_VAR NAME=name></option>
+</TMPL_LOOP>
+           </select>
+        </td>
+    </tr>
+    <tr><td>Media Type:</td>      
+        <td><select name='mediatype' class='formulaire' multiple>
+<TMPL_LOOP NAME=db_mediatypes>
+             <option><TMPL_VAR NAME=mediatype></option>
+</TMPL_LOOP>
+           </select>
+        </td>
+    </tr>
+    <tr><td> Location : </td>
+        <td><select name='location' class='formulaire'>
+  <TMPL_LOOP NAME=db_locations>
+      <option id='loc_<TMPL_VAR NAME=location>' value='<TMPL_VAR NAME=location>'><TMPL_VAR NAME=location></option>
+  </TMPL_LOOP>
+    </select>
+        </td>
+    </tr>
+    <tr>
+        <td>Number of media <br/> to eject:</td> 
+        <td> <input type='text' name='limit' size='3' class='formulaire' 
+              value='10'> </td>
+    </tr>
+    <tr>
+        <td><button class='formulaire' name='action' value='compute_extern_media' title='Next'>
+            <img src='/bweb/next.png'>
+        </td><td/>
+    </tr>
+   </table>
+   </form>
+</div>
diff --git a/gui/bweb/tpl/help_extern_compute.tpl b/gui/bweb/tpl/help_extern_compute.tpl
new file mode 100644 (file)
index 0000000..0284788
--- /dev/null
@@ -0,0 +1,71 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Help to eject media (part 2/2)</h1>
+ </div>
+ <div class='bodydiv'>
+  Now, you can verify the selection and eject media.
+   <form action='?' method='get'>
+    <table id='compute'></table>
+    <table><tr>
+    <td style='align: left;'>
+    <button onclick='javascript:window.history.go(-2);' class='formulaire' title='Back'>
+      <img src='/bweb/prev.png'> &nbsp;
+    </button>
+    </td><td style='align: right;'>
+    <button name='action' value='extern' class='formulaire'>
+      <img src='/bweb/extern.png'>
+    </button>
+   </td></tr>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Volume Name","Vol Status",
+                      "Media Type","Pool Name","Last Written", 
+                       "When expire ?", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=Medias>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.value = '<TMPL_VAR NAME=volumename>';
+chkbox.name  = 'media';
+chkbox.checked = 'on';
+
+data.push( new Array(
+"<TMPL_VAR NAME=volumename>",
+"<TMPL_VAR NAME=volstatus>",
+"<TMPL_VAR NAME=mediatype>",
+"<TMPL_VAR NAME=name>",
+"<TMPL_VAR NAME=lastwritten>",
+"<TMPL_VAR NAME=expire>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "compute",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/help_intern.tpl b/gui/bweb/tpl/help_intern.tpl
new file mode 100644 (file)
index 0000000..3c78019
--- /dev/null
@@ -0,0 +1,53 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> Help to load media (part 1/2)</h1>
+</div>
+<div class="bodydiv">
+This tool will select for you best candidate to load. You will
+be asked for choose inside the selection in the next screen. 
+  <form action="?" method='GET'>
+   <table>
+    <tr><td>Pool:</td>      
+        <td><select name='pool' class='formulaire' multiple>
+<TMPL_LOOP db_pools>
+             <option><TMPL_VAR name></option>
+</TMPL_LOOP>
+           </select>
+        </td>
+    </tr>
+    <tr><td>Media Type:</td>      
+        <td><select name='mediatype' class='formulaire' multiple>
+<TMPL_LOOP db_mediatypes>
+             <option><TMPL_VAR mediatype></option>
+</TMPL_LOOP>
+           </select>
+        </td>
+    </tr>
+    <tr><td>
+    Location : 
+    </td><td><select name='location' class='formulaire'>
+  <TMPL_LOOP db_locations>
+      <option value='<TMPL_VAR location>'><TMPL_VAR location></option>
+  </TMPL_LOOP>
+    </select>
+    </td>
+    </tr>
+    <tr>
+        <td>Expired :</td> 
+        <td> <input type='checkbox' name='expired' class='formulaire' 
+               checked> </td>
+    </tr>
+    <tr>
+        <td>Number of media <br/> to load:</td> 
+        <td> <input type='text' name='limit' class='formulaire' 
+               size='3' value='10'> </td>
+    </tr>
+    <tr>
+        <td><button name='action' value='compute_intern_media' 
+               class='formulaire' title='Next'>
+            <img src='/bweb/next.png'>
+        </td><td/>
+    </tr>
+   </table>
+   </form>
+</div>
diff --git a/gui/bweb/tpl/help_intern_compute.tpl b/gui/bweb/tpl/help_intern_compute.tpl
new file mode 100644 (file)
index 0000000..a60241e
--- /dev/null
@@ -0,0 +1,71 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Help to load media (part 2/2)</h1>
+ </div>
+ <div class='bodydiv'>
+  Now, you can verify the selection and load media.
+   <form action='?' method='get'>
+    <table id='compute'></table>
+    <table><tr>
+    <td style='align: left;'>
+    <button onclick='javascript:window.history.go(-2);' class='formulaire' title='Back'>
+      <img src='/bweb/prev.png'> &nbsp;
+    </button>
+    </td><td style='align: right;'>
+    <button name='action' value='move_media' class='formulaire'>
+      <img src='/bweb/intern.png'>
+    </button>
+   </td></tr>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Volume Name","Vol Status",
+                      "Media Type","Pool Name","Last Written", 
+                       "When expire ?", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=Medias>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.name = 'media';
+chkbox.value= '<TMPL_VAR NAME=volumename>';
+chkbox.checked = 'on';
+
+data.push( new Array(
+"<TMPL_VAR NAME=volumename>",
+"<TMPL_VAR NAME=volstatus>",
+"<TMPL_VAR NAME=mediatype>",
+"<TMPL_VAR NAME=name>",
+"<TMPL_VAR NAME=lastwritten>",
+"<TMPL_VAR NAME=expire>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "compute",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/install.tpl b/gui/bweb/tpl/install.tpl
new file mode 100644 (file)
index 0000000..749c5d2
--- /dev/null
@@ -0,0 +1,9 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> Install notes </h1>
+</div>
+<div class='bodydiv'>
+
+
+
+</div>
diff --git a/gui/bweb/tpl/job_select.tpl b/gui/bweb/tpl/job_select.tpl
new file mode 100644 (file)
index 0000000..8a32a27
--- /dev/null
@@ -0,0 +1,77 @@
+    <div class='otherboxtitle'>
+      Find
+    </div>
+    <div class='otherbox'>
+      <form action='?' method='GET' id='form1'>
+      <table border='0'>
+        <tr>
+         <td>
+          Clients:
+         </td>
+         <td>
+           <select name='client'>
+<TMPL_LOOP NAME=db_clients>
+<option value='<TMPL_VAR NAME=clientname>'><TMPL_VAR NAME=clientname></option>
+</TMPL_LOOP>
+           </select>
+         </td>
+         <td>
+          Level:
+         </td>
+         <td>
+           <select name='level'>
+               <option id='level_any' value='Any'>Any</option>
+               <option id='level_F' value='F'>Full</option>
+               <option id='level_F' value='D'>Diff</option>
+               <option id='level_I' value='I'>Incr</option>
+           </select>
+         </td>
+        </tr>
+        <tr>
+         <td>
+          Status:
+         </td>
+         <td>
+           <select name='status'>
+               <option id='status_any' value='Any'>Any</option>
+               <option id='status_A' value='T'>Ok</option>
+           </select>
+         </td>
+        </tr>
+        <tr>
+         <td>
+          JobId:
+         </td>
+         <td>
+         <input type='text' name='jobid' size='4' 
+           value='<TMPL_VAR NAME=jobid>'>
+         </td>
+         <td>
+          Start Time:
+         </td>
+         <td>
+          <input type='text' name='jobid' title='DD/MM/YYYY' size='7'>
+         </td>
+        </tr>
+       <tr>
+         <td>
+          Limit:
+         </td>
+         <td>
+         <input type='text' name='limit' title='number of result' size='2' 
+          value='<TMPL_VAR NAME=limit>'>
+         </td>
+        </tr>
+        <tr>
+         <td>
+          <button name='action' value='job'>Search</button>
+         </td>
+         <td>
+         </td>
+         <td>
+         </td>
+        </tr>
+
+       </table> 
+      </form>
+    </div>
diff --git a/gui/bweb/tpl/location_add.tpl b/gui/bweb/tpl/location_add.tpl
new file mode 100644 (file)
index 0000000..feba84f
--- /dev/null
@@ -0,0 +1,26 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> New location </h1>
+</div>
+<div class='bodydiv'>
+   <form action="?" method='get'>
+    <table>
+     <tr><td>Location :</td>     
+         <td> 
+          <input class="formulaire" type='text' value='' size='32' name='location'> 
+         </td>
+     </tr>
+     <tr><td>Cost :</td> 
+         <td> <input class="formulaire" type='text' value='10' name='cost' size='3'>
+         </td>
+     </tr>
+     <tr><td>Enabled :</td> 
+         <td> <input class="formulaire" type='checkbox' value='10' name='enabled'>
+         </td>
+     </tr>
+    </table>
+    <button name='action' value='location_add' class='formulaire'>
+     <img src='/bweb/save.png'>
+    </button>
+   </form>
+</div>
diff --git a/gui/bweb/tpl/location_edit.tpl b/gui/bweb/tpl/location_edit.tpl
new file mode 100644 (file)
index 0000000..482041b
--- /dev/null
@@ -0,0 +1,27 @@
+<br/>
+<div class='titlediv'>
+  <h1 class='newstitle'> Location : <TMPL_VAR Location></h1>
+</div>
+<div class='bodydiv'>
+   <form action="?" method='get'>
+    <input type='hidden' name='location' value='<TMPL_VAR location>'>
+    <table>
+     <tr><td>Location :</td>     
+         <td> 
+          <input class="formulaire" type='text' value='<TMPL_VAR location>' size='32' name='newlocation'> 
+         </td>
+     </tr>
+     <tr><td>Cost :</td> 
+         <td> <input class="formulaire" type='text' value='<TMPL_VAR cost>' name='cost' size='3'>
+         </td>
+     </tr>
+     <tr><td>Enabled :</td> 
+         <td> <input class="formulaire" type='checkbox' name='enabled' <TMPL_IF enabled> checked </TMPL_IF> >
+         </td>
+     </tr>
+    </table>
+    <button name='action' value='location_save' class='formulaire'>
+     <img src='/bweb/save.png'>
+    </button>
+   </form>
+</div>
diff --git a/gui/bweb/tpl/move_media.tpl b/gui/bweb/tpl/move_media.tpl
new file mode 100644 (file)
index 0000000..30f096c
--- /dev/null
@@ -0,0 +1,85 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'>Move media</h1>
+ </div>
+ <div class="bodydiv">
+   <form action='?' method='get'>
+    <table id='id<TMPL_VAR NAME=ID>'></table>
+    <table border='0'>
+    <tr><td> New location: </td><td>
+<select name='newlocation' class='formulaire'>
+    <TMPL_LOOP NAME=db_locations>
+    <option value='<TMPL_VAR NAME=location>'><TMPL_VAR NAME=location></option>
+    </TMPL_LOOP>
+</select>
+    </td></tr><tr><td> Status: </td><td>
+<select name='volstatus' class='formulaire'>
+    <option value=''>Don't update</option>
+    <option value='Append'>Append</option>
+    <option value='Archive'>Archive</option>
+    <option value='Disabled'>Disabled</option>
+    <option value='Cleaning'>Cleaning</option>
+    <option value='Error'>Error</option>
+    <option value='Full'>Full</option>
+    <option value='Purged'>Purged</option>
+    <option value='Read-Only'>Read-Only</option>
+    <option value='Recycle'>Recycle</option>
+    <option value='Used'>Used</option>
+</select>
+    </td><tr><td> User: </td><td>
+<input type='text' name='user' value='<TMPL_VAR loginname>' class='formulaire'>
+    </td></tr>
+    </td></tr><tr><td> Comment: </td><td>
+<textarea name="comment" class='formulaire'> </textarea>
+    </td></tr>
+    </table>
+    <button class='formulaire' type='submit' name='action' value='change_location'> 
+       <img src='/bweb/apply.png'> Move
+    </button>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Volume Name", "Location", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=medias>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.value = '<TMPL_VAR name=volumename>';
+chkbox.name  = 'media';
+chkbox.checked = 1;
+
+data.push( new Array(
+"<TMPL_VAR NAME=volumename>",
+"<TMPL_VAR NAME=location>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/run_job.tpl b/gui/bweb/tpl/run_job.tpl
new file mode 100644 (file)
index 0000000..cbc8ed6
--- /dev/null
@@ -0,0 +1,32 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Defined jobs : </h1>
+ </div>
+ <div class='bodydiv'>
+  <form name='form1' action='?' method='GET'>  
+  <table border='0'>
+
+   <tr><td>Job Name: </td><td>
+   <select name='job'>
+    <TMPL_LOOP jobs>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+   </td></tr>
+   </table>
+   <br/>
+   <button class='formulaire' name='action' value='enable_job' title='Enable'>
+      Enable<br/>
+      <img src='/bweb/inflag1.png'>
+   </button>
+   <button class='formulaire' name='action' value='disable_job' title='Disable'>
+      Disable<br/>
+      <img src='/bweb/inflag0.png'>
+   </button>
+   <button name='action' value='run_job_mod' title='Run now' class='formulaire'>
+    Run now <br/><img src='/bweb/R.png'>
+   </button>
+  </form>
+ </div>
diff --git a/gui/bweb/tpl/run_job_mod.tpl b/gui/bweb/tpl/run_job_mod.tpl
new file mode 100644 (file)
index 0000000..5a35398
--- /dev/null
@@ -0,0 +1,124 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Run job : <TMPL_VAR job> on <TMPL_VAR client></h1>
+ </div>
+ <div class='bodydiv'>
+  <form name='form1' action='?' method='GET'>  
+  <table border='0'>
+
+   <tr><td>Job Name: </td><td>
+   <select name='job'>
+    <TMPL_LOOP jobs>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+   </td></tr><tr><td>Pool: </td><td>
+
+   <select name='pool'>        
+     <option value=''></option>
+    <TMPL_LOOP pools>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+   </td></tr><tr><td>Client: </td><td>
+
+   <select name='client'>
+    <TMPL_LOOP clients>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+
+   </td></tr><tr><td>FileSet: </td><td>
+   <select name='fileset'>
+    <TMPL_LOOP filesets>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+
+   </td></tr><tr><td>Storage: </td><td>
+   <select name='storage'>
+    <TMPL_LOOP storages>
+     <option value='<TMPL_VAR name>'>
+       <TMPL_VAR name>
+     </option>
+    </TMPL_LOOP>
+   </select>
+
+   </td></tr><tr><td>Level: </td><td>
+   <select name='level'>
+     <option id='level_Incremental' value='Incremental'>Incremental</option>
+     <option id='level_Full' value='Full'>Full</option>
+     <option id='level_Differential' value='Differential'>Differential</option>
+   </select>
+
+   </td></tr><tr><td>Priority: </td><td>
+   <input class='formulaire' type='text' 
+          size='3' name='priority' value='<TMPL_VAR priority>'>
+
+   </td></tr>
+   </table>
+   <br/>
+  <button name='action' value='run_job_now' title='Run job' class='formulaire'>
+   <img src='/bweb/R.png'>
+   </button>
+  </form>
+ </div>
+
+<script language="JavaScript">
+  <TMPL_IF job>
+     ok=1;
+     for(var i=0; ok && i < document.form1.job.length; i++) {
+       if (document.form1.job[i].value == '<TMPL_VAR job>') {
+           document.form1.job[i].selected=true;
+           ok=0;
+       }
+     }
+  </TMPL_IF>
+  <TMPL_IF client>
+     ok=1;
+     for(var i=0; ok && i < document.form1.client.length; i++) {
+       if (document.form1.client[i].value == '<TMPL_VAR client>') {
+           document.form1.client[i].selected=true;
+           ok=0;
+       }
+     }
+  </TMPL_IF>
+  <TMPL_IF pool>
+     ok=1;
+     for(var i=0; ok && i < document.form1.pool.length; i++) {
+       if (document.form1.pool[i].value == '<TMPL_VAR pool>') {
+           document.form1.pool[i].selected=true;
+           ok=0;
+       }
+     }
+  </TMPL_IF>
+  <TMPL_IF storage>
+     ok=1;
+     for(var i=0; ok && i < document.form1.storage.length; i++) {
+       if (document.form1.storage[i].value == '<TMPL_VAR storage>') {
+           document.form1.storage[i].selected=true;
+           ok=0;
+       }
+     }
+  </TMPL_IF>
+  <TMPL_IF level>
+<!--     document.getElementById('level_<TMPL_VAR level>').selected=true; -->
+  </TMPL_IF>
+  <TMPL_IF fileset>
+     ok=1;
+     for(var i=0; ok && i < document.form1.fileset.length; i++) {
+       if (document.form1.fileset[i].value == '<TMPL_VAR fileset>') {
+           document.form1.fileset[i].selected=true;
+           ok=0;
+       }
+     }
+  </TMPL_IF>
+</script>
\ No newline at end of file
diff --git a/gui/bweb/tpl/running_job.tpl b/gui/bweb/tpl/running_job.tpl
new file mode 100644 (file)
index 0000000..d74af2a
--- /dev/null
@@ -0,0 +1,95 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Running Jobs </h1>
+ </div>
+ <div class='bodydiv'>
+   <form action='?' method='GET'>
+   <table id='id<TMPL_VAR NAME=ID>'></table>
+   <br/>
+<button type='submit' name='action' value='cancel_job'
+        class='formulaire'
+        title='Cancel job'><img src='/bweb/cancel.png'>
+</button>
+<button type='submit' name='action' value='dsp_cur_job' 
+        class='formulaire'
+        title='View job'><img src='/bweb/zoom.png'>
+</button>
+   </form>
+
+ </div>
+
+<script language="JavaScript">
+var header = new Array("JobId",
+                      "Client",
+                      "Job Name", 
+                       "Level",
+                       "Start Time", 
+                       "Duration", 
+//                       "Job Files",
+//                       "Job Bytes", 
+                      "Status",
+                      "Select"
+       );
+
+var data = new Array();
+var chkbox;
+var img;
+
+<TMPL_LOOP NAME=Jobs>
+a = document.createElement('A');
+a.href='?action=dsp_cur_job;jobid=<TMPL_VAR JobId>';
+
+img = document.createElement("IMG");
+img.src = '/bweb/<TMPL_VAR NAME=JobStatus>.png';
+img.title = jobstatus['<TMPL_VAR NAME=JobStatus>'];
+
+a.appendChild(img);
+
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'radio';
+chkbox.name = 'jobid';
+chkbox.value = '<TMPL_VAR NAME=jobid>';
+
+data.push( new Array(
+"<TMPL_VAR NAME=JobId>",
+"<TMPL_VAR NAME=ClientName>",     
+"<TMPL_VAR NAME=JobName>",    
+joblevel['<TMPL_VAR NAME=Level>'],      
+"<TMPL_VAR NAME=StartTime>",
+"<TMPL_VAR NAME=duration>",
+//"<TMPL_VAR NAME=JobFiles>",   
+//"<TMPL_VAR NAME=JobBytes>",
+a,
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: true,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ padding: 3,
+// disable_sorting: new Array(6)
+}
+);
+
+// get newest backup first
+nrsTables['id<TMPL_VAR NAME=ID>'].fieldSort(0);
+
+bweb_add_refresh();
+
+</script>
diff --git a/gui/bweb/tpl/scheduled_job.tpl b/gui/bweb/tpl/scheduled_job.tpl
new file mode 100644 (file)
index 0000000..7dc63d2
--- /dev/null
@@ -0,0 +1,76 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'> Next Jobs </h1>
+ </div>
+ <div class='bodydiv'>
+    <form action='?' method='GET'>
+     <table id='id<TMPL_VAR ID>'></table>
+     <button class='formulaire' name='action' value='run_job_mod'>
+      Run now<br/>
+      <img src='/bweb/R.png' title='Run now'>
+     </button>
+<!-- <button class='formulaire' name='action' value='enable_job'>
+      Enable<br/>
+      <img src='/bweb/inflag1.png' title='Enable'>
+     </button>
+-->
+     <button class='formulaire' name='action' value='disable_job'>
+      Disable<br/>
+      <img src='/bweb/inflag0.png' title='Disable'>
+     </button>
+    </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Scheduled",
+                       "Level",
+                      "Type",
+                      "Priority", 
+                       "Name",
+                       "Volume",
+                      "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP list>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'radio';
+chkbox.name = 'job';
+chkbox.value = '<TMPL_VAR name>';
+
+data.push( new Array(
+"<TMPL_VAR date>",    
+"<TMPL_VAR level>",
+"<TMPL_VAR type>",     
+"<TMPL_VAR priority>",    
+"<TMPL_VAR name>",      
+"<TMPL_VAR volume>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: true,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ rows_per_page: rows_per_page,
+ padding: 3,
+// disable_sorting: new Array(6)
+}
+);
+</script>
diff --git a/gui/bweb/tpl/update_location.tpl b/gui/bweb/tpl/update_location.tpl
new file mode 100644 (file)
index 0000000..99c3b33
--- /dev/null
@@ -0,0 +1,67 @@
+<br/>
+ <div class='titlediv'>
+  <h1 class='newstitle'>Update media location</h1>
+ </div> 
+ <div class="bodydiv">
+   <form action='?' method='get'>
+    <table id='id<TMPL_VAR NAME=ID>'></table>
+    New location : <select class='formulaire' name='newlocation'>
+   <TMPL_LOOP NAME=db_locations>
+      <option id='loc_<TMPL_VAR NAME=location>' value='<TMPL_VAR NAME=location>'><TMPL_VAR NAME=location></option>
+   </TMPL_LOOP>
+  </select>
+  <button class='formulaire' type='submit' name='action' value='save_location'>
+     <img src='/bweb/apply.png'>
+    </button>
+   </form>
+ </div>
+
+<script language="JavaScript">
+
+var header = new Array("Volume Name", "Location", "Select");
+
+var data = new Array();
+var chkbox;
+
+<TMPL_LOOP NAME=medias>
+chkbox = document.createElement('INPUT');
+chkbox.type  = 'checkbox';
+chkbox.value = '<TMPL_VAR name=volumename>';
+chkbox.name  = 'media';
+chkbox.checked = 1;
+
+data.push( new Array(
+"<TMPL_VAR NAME=volumename>",
+"<TMPL_VAR NAME=location>",
+chkbox
+ )
+);
+</TMPL_LOOP>
+
+nrsTable.setup(
+{
+ table_name:     "id<TMPL_VAR NAME=ID>",
+ table_header: header,
+ table_data: data,
+ up_icon: up_icon,
+ down_icon: down_icon,
+ prev_icon: prev_icon,
+ next_icon: next_icon,
+ rew_icon:  rew_icon,
+ fwd_icon:  fwd_icon,
+// natural_compare: false,
+ even_cell_color: even_cell_color,
+ odd_cell_color: odd_cell_color, 
+ header_color: header_color,
+ page_nav: true,
+ padding: 3,
+ rows_per_page: rows_per_page,
+// disable_sorting: new Array(5,6)
+}
+);
+
+<TMPL_IF newlocation>
+ document.getElementById('loc_' + <TMPL_VAR qnewlocation>).selected=true;
+</TMPL_IF>
+
+</script>
diff --git a/gui/bweb/tpl/update_media.tpl b/gui/bweb/tpl/update_media.tpl
new file mode 100644 (file)
index 0000000..7038f09
--- /dev/null
@@ -0,0 +1,128 @@
+<br/>
+<div class='titlediv'>
+ <h1 class='newstitle'> Update media <TMPL_VAR volumename></h1>
+</div>
+<div class='bodydiv'>
+  <form name='form1' action="?" method='GET'>
+   <table>
+    <tr><td>Volume Name:</td>
+        <td><input type='text' name='media' class='formulaire' value='<TMPL_VAR volumename>' title='Change this to update an other media'>
+        </td>
+    </tr>
+    <tr><td>Pool:</td>
+        <td><select name='pool' class='formulaire'>
+<TMPL_LOOP db_pools>
+             <option value='<TMPL_VAR name>'><TMPL_VAR name></option>
+</TMPL_LOOP>
+           </select>
+        </td>
+    </tr>
+    <tr><td>Status:</td>
+        <td><select name='volstatus' class='formulaire'>
+           <option value='Append'>Append</option>
+           <option value='Archive'>Archive</option>
+           <option value='Disabled'>Disabled</option>
+           <option value='Cleaning'>Cleaning</option>
+           <option value='Error'>Error</option>
+          <option value='Full'>Full</option>
+           <option value='Purged'>Purged</option>
+           <option value='Read-Only'>Read-Only</option>
+           <option value='Recycle'>Recycle</option>
+           <option value='Used'>Used</option>
+           </select>
+        </td>
+    </tr>
+
+    <tr><td>Slot:</td>
+        <td> 
+          <input class='formulaire' type='text' 
+                 name='slot' value='<TMPL_VAR slot>'>
+        </td>
+    </tr>
+
+    <tr><td>InChanger Flag:</td>
+        <td> 
+          <input class='formulaire' type='checkbox' 
+               name='inchanger' <TMPL_IF inchanger>checked</TMPL_IF>>
+        </td>
+    </tr>
+
+    <tr><td> Location : </td>
+        <td><select name='location' class='formulaire'>
+  <TMPL_LOOP db_locations>
+      <option value='<TMPL_VAR location>'><TMPL_VAR location></option>
+  </TMPL_LOOP>
+    </select>
+        </td>
+    </tr>
+    <tr><td> Retention period: </td>
+        <td>
+          <input class='formulaire' type='text' title='ex: 3 days, 1 month'
+               name='volretention' value='<TMPL_VAR volretention>'>
+        </td>
+    </tr>
+    <tr><td> Use duration: </td>
+        <td>
+          <input class='formulaire' type='text' title='ex: 3 days, 1 month'
+               name='voluseduration' value='<TMPL_VAR voluseduration>'>
+        </td>
+    </tr>
+    <tr><td> Max Jobs: </td>
+        <td>
+          <input class='formulaire' type='text' title='ex: 10'
+               name='maxvoljobs' value='<TMPL_VAR maxvoljobs>'>
+        </td>
+    </tr>
+    <tr><td> Max Files: </td>
+        <td>
+          <input class='formulaire' type='text' title='ex: 10000'
+               name='maxvolfiles' value='<TMPL_VAR maxvolfiles>'>
+        </td>
+    </tr>
+    <tr><td> Max Bytes: </td>
+        <td>
+          <input class='formulaire' type='text' title='ex: 10M, 11G'
+               name='maxvolbytes' value='<TMPL_VAR maxvolbytes>'>
+        </td>
+    </tr>
+    </table>
+<button class='formulaire' name='action' value='do_update_media'>Apply<br/>
+ <img src='/bweb/apply.png'>
+</button>
+<button class='formulaire' name='action' title='Update from pool'
+  value='update_from_pool'>Update<br/>
+ <img src='/bweb/update.png' title='Update from pool'>
+</button>
+<button class='formulaire' name='action' value='media'>View Pool<br/>
+ <img src='/bweb/zoom.png'>
+</button>
+
+   </form>
+</div>
+
+<script language='JavaScript'>
+var ok=1;
+for (var i=0; ok && i < document.form1.pool.length; ++i) {
+   if (document.form1.pool[i].value == '<TMPL_VAR poolname>') {
+      document.form1.pool[i].selected = true;
+      ok=0;
+   }
+}
+
+ok=1;
+for (var i=0; ok && i < document.form1.location.length; ++i) {
+   if (document.form1.location[i].value == '<TMPL_VAR location>') {
+      document.form1.location[i].selected = true;
+      ok=0;
+   }
+}
+
+ok=1;
+for (var i=0; ok && i < document.form1.volstatus.length; ++i) {
+   if (document.form1.volstatus[i].value == '<TMPL_VAR volstatus>') {
+      document.form1.volstatus[i].selected = true;
+      ok=0;
+   }
+}
+
+</script>
\ No newline at end of file