--- /dev/null
+################################################################
+# 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 !
--- /dev/null
+#!/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;
+}
+
--- /dev/null
+#!/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',
+ },
+ }
--- /dev/null
+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;}
--- /dev/null
+ 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);
+}
+
--- /dev/null
+/*
+* ----------------------------------------------------------------------------
+* "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;
+}
--- /dev/null
+/*
+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;
+ }
+}
+
--- /dev/null
+/**
+ * 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";
+}
--- /dev/null
+* {
+ 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;
+}
--- /dev/null
+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";
+
+
--- /dev/null
+################################################################
+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;
--- /dev/null
+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
--- /dev/null
+<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>
--- /dev/null
+<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'> Load <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>
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<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
+ </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>
+ <button class='formulaire' name='action' value='dsp_cur_job' title='Show current job'><img src='/bweb/zoom.png'>Current jobs</button>
+ <button class='formulaire' name='action' value='client_status' title='Show client status'><img src='/bweb/zoom.png'>Status</button>
+ <button class='formulaire' name='action' value='client_stats' title='Client stats'><img src='/bweb/chart.png'>Stats</button>
+ </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>
--- /dev/null
+<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>
+
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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' >
+ <img src="bgraph.pl?client=<TMPL_VAR NAME=clientname>;graph=job_rate;age=2592000;width=420;height=200" alt='Not enough data'>
+ <img src="bgraph.pl?client=<TMPL_VAR NAME=clientname>;graph=job_size;age=2592000;width=420;height=200" alt='Not enough data'>
+<!-- <div class="otherboxtitle">
+ Actions
+ </div>
+ <div class="otherbox">
+ <h1>Actions</h1>
+ <button name='action' value='job' title='Show last job'><img src='/bweb/zoom.png'></button>
+ <button name='action' value='dsp_cur_job' title='Show current job'><img src='/bweb/zoom.png'></button>
+ <button name='action' value='client_stat' title='Client stats'><img src='/bweb/zoom.png'></button>
+ </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>
--- /dev/null
+<br/>
+
+<div class="otherboxtitle">
+ Filter
+</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>
+
--- /dev/null
+<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>
--- /dev/null
+ <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
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+</body>
+</html>
--- /dev/null
+<h1>An error as occured :</h1>
+<pre>
+<TMPL_VAR NAME=error>
+</pre>
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+<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
+ </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: <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
+ </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>
+
--- /dev/null
+<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>
--- /dev/null
+<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'>
+ </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>
--- /dev/null
+<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>
--- /dev/null
+<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'>
+ </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>
--- /dev/null
+<br/>
+<div class='titlediv'>
+ <h1 class='newstitle'> Install notes </h1>
+</div>
+<div class='bodydiv'>
+
+
+
+</div>
--- /dev/null
+ <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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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