6) get bacula log more useful
7) bweb limitation
8) using sudo with autochanger
+9) using bfileview.pl
################ FILE COPY #####################################
# you must get bweb cvs files
- perl modules
- DBI (with mysql or postgresql support DBD::Pg and DBD::mysql)
- Gd::Graph
+ - Gd
- HTML::Template
- CGI
- Expect
www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer load *
www-data ALL = (root) NOPASSWD: /usr/sbin/mtx -f /dev/changer unload *
+################ BFILEVIEW SETUP ###############################
+
+At this time, bfileview works only with postgresql.
+
+Alias /bweb/fv /var/spool/bweb
+<Directory "/var/spool/bweb">
+ Options None
+ AllowOverride AuthConfig
+ Order allow,deny
+ Allow from all
+</Directory>
+
+mkdir /var/spool/bweb
+chmod 700 /var/spool/bweb
+chown www-data /var/spool/bweb
+
+You must use brestore.pl -b to initialise the database, and
+you can use bfileview.pl mode=batch jobid=xxx where=/ to compute tree size.
+
+At this time, it's a good idea to schedule brestore.pl -g after your BackupCatalog
+job.
+
+To upgrade from an old installation, you can use :
+ALTER TABLE brestore_pathvisibility ADD Size int8;
+
Enjoy !
- Release Notes for bweb 1.39.28
+ Release Notes for bweb 1.39.32
+
+2006/12/29
+ - Add graphical backup view. See INSTALL to enable it. It's usefull
+ for tuning backup.
+ NOTES :
+ - You must use brestore -b to initialise database after BackupCatalog job
+ - If you have an old installation, you must alter your schema (see INSTALL)
2006/12/14
- Add pool filter to job form
--- /dev/null
+#!/usr/bin/perl -w
+
+=head1 LICENSE
+
+ Bweb - A Bacula web interface
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+
+ The main author of Bweb is Eric Bollengier.
+ The main author of Bacula is Kern Sibbald, with contributions from
+ many others, a complete list can be found in the file AUTHORS.
+
+ This program is Free Software; you can redistribute it and/or
+ modify it under the terms of version two of the GNU General Public
+ License as published by the Free Software Foundation plus additions
+ that are listed in the file LICENSE.
+
+ 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 Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+ Bacula® is a registered trademark of John Walker.
+ The licensor of Bacula is the Free Software Foundation Europe
+ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
+ Switzerland, email:ftf@fsfeurope.org.
+
+=head1 VERSION
+
+ $Id$
+
+=cut
+
+# TODO:
+# Si c'est un fichier selectionne, afficher ses attributs
+# ajouter le base_fic et base_url dans les options bweb
+#
+
+use strict;
+use Bweb;
+use CCircle ;
+use Digest::MD5 qw(md5_hex);
+use File::Basename qw/basename dirname/;
+
+my $conf = new Bweb::Config(config_file => '/etc/bweb/config');
+$conf->load();
+my $bweb = new Bweb(info => $conf);
+$bweb->connect_db();
+
+my $arg = $bweb->get_form('where', 'jobid');
+my $where = $arg->{where};
+my $jobid = $arg->{jobid};
+my $jobid_url = "jobid=$jobid";
+my $opt_level = 2 ;
+my $max_file = 20;
+my $batch = CGI::param("mode") || '';
+
+my $md5_rep = md5_hex("$where:$jobid") ;
+my $base_url = '/bweb/fv' ;
+my $base_fich = $conf->{fv_write_path};
+
+die "Can't get where" unless ($where and $jobid);
+
+if ($batch eq 'batch') {
+ my $root = fv_get_root_pathid($where);
+ if ($root) {
+ fv_compute_size($jobid, $root);
+ exit 0;
+ }
+ exit 1;
+}
+
+my $url_action = "bfileview.pl?opt_level=$opt_level" ;
+my $top = new CCircle(
+ display_other => 1,
+ base_url => "$url_action;$jobid_url;where=$where",
+ ) ;
+
+print CGI::header('text/html');
+$bweb->display_begin();
+$bweb->display_job_zoom($jobid);
+
+if (-f "$base_fich/$md5_rep.png" and -f "$base_fich/$md5_rep.tpl")
+{
+ $bweb->display({}, "$base_fich/$md5_rep.tpl");
+ $bweb->display_end();
+ exit 0;
+}
+
+my $attribs = fv_get_file_attribute($jobid, $where);
+if ($attribs->{found}) {
+ $bweb->display($attribs, 'fv_file_attribs.tpl');
+ $bweb->display_end();
+ exit 0;
+}
+
+if ($where !~ m!/$!) {
+ $where = $where . "/" ;
+}
+
+my $root = fv_get_root_pathid($where);
+if (!$root) {
+ $bweb->error("Can't find $where in catalog");
+ $bweb->display_end();
+ exit 0;
+}
+
+my $total = fv_compute_size($jobid, $root);
+
+fv_display_rep($top, $total, $root, $opt_level) ;
+
+$top->draw_labels() ;
+$top->set_title(Bweb::human_size($total)) ;
+
+open(OUT, ">$base_fich/$md5_rep.png") or die "$base_fich/$md5_rep.png $!";
+# make sure we are writing to a binary stream
+binmode OUT;
+# Convert the image to PNG and print it on standard output
+print OUT $CCircle::gd->png;
+close(OUT) ;
+
+open(OUT, ">$base_fich/$md5_rep.tpl") or die "$base_fich/$md5_rep.tpl $!";
+print OUT "
+ <form action='$url_action' method='get'>
+ <div align='right'>
+ <input title='jobids' type='hidden' name='jobid' value='$jobid'>
+ <input title='repertoire' type='text' name='where' value='$where'/>
+ <input type='submit' size='256' name='go' value='go'/>
+ </div>
+ </form>
+ <br/>
+" ;
+
+print OUT $top->get_imagemap($where, "$base_url/$md5_rep.png") ;
+close(OUT) ;
+
+$bweb->display({}, "$base_fich/$md5_rep.tpl");
+$bweb->display_end();
+
+sub fv_display_rep
+{
+ my ($ccircle, $max, $rep, $level) = @_ ;
+ return if ($max < 1);
+
+ my $sum = 0;
+ my $dirs = fv_list_dirs($jobid, $rep); # 0: pathid, 1: pathname
+
+ foreach my $dir (@{$dirs})
+ {
+ my $size = fv_compute_size($jobid, $dir->[0]);
+ $sum += $size;
+
+ my $chld = $ccircle->add_part($size * 100 / $max,
+ basename($dir->[1]) . '/',
+ basename($dir->[1])
+ . "\n"
+ . Bweb::human_size($size)
+ ) ;
+
+ if ($chld and $level > 0) {
+ fv_display_rep($chld, $size, $dir->[0], $level - 1) ;
+ }
+ }
+
+ # 0: name, 1: size
+ my $files = fv_get_big_files($jobid, $rep, 3*100/$max, $max_file/($level+1));
+ foreach my $f (@{$files}) {
+ $ccircle->add_part($f->[1] * 100 / $max,
+ $f->[0],
+ $f->[0] . "\n" . Bweb::human_size($f->[1]));
+ $sum += $f->[1];
+ }
+
+ if ($sum < $max) {
+ $ccircle->add_part(($max - $sum) * 100 / $max,
+ "other files < 3",
+ "other\n" . Bweb::human_size($max - $sum));
+ }
+
+ $ccircle->finalize() ;
+}
+
+sub fv_compute_size
+{
+ my ($jobid, $rep) = @_;
+
+ my $size = fv_get_size($jobid, $rep);
+ if ($size) {
+ return $size;
+ }
+
+ $size = fv_get_files_size($jobid, $rep);
+
+ my $dirs = fv_list_dirs($jobid, $rep);
+ foreach my $dir (@{$dirs}) {
+ $size += fv_compute_size($jobid, $dir->[0]);
+ }
+
+ fv_update_size($jobid, $rep, $size);
+ return $size;
+}
+
+sub fv_list_dirs
+{
+ my ($jobid, $rep) = @_;
+
+ my $ret = $bweb->dbh_selectall_arrayref("
+ SELECT P.PathId,
+ (
+ SELECT Path FROM Path WHERE PathId = P.PathId
+ UNION
+ SELECT Path FROM brestore_missing_path WHERE PathId = P.PathId
+ ) AS Path
+ FROM (
+ SELECT PathId
+ FROM brestore_pathvisibility
+ INNER JOIN brestore_pathhierarchy USING (PathId)
+ WHERE PPathId = $rep
+ AND JobId = $jobid
+ ) AS P
+");
+
+ return $ret;
+}
+
+sub fv_get_file_attribute
+{
+ my ($jobid, $full_name) = @_;
+
+ my $filename = $bweb->dbh_quote(basename($full_name));
+ my $path = $bweb->dbh_quote(dirname($full_name) . "/");
+
+ my $attr = $bweb->dbh_selectrow_hashref("
+ SELECT 1 AS found,
+ base64_decode_lstat(8, lstat) AS size
+ FROM File INNER JOIN Filename USING (FilenameId)
+ INNER JOIN Path USING (PathId)
+ WHERE Name = $filename
+ AND Path = $path
+ AND JobId = $jobid
+");
+
+ $attr->{filename} = $full_name;
+ $attr->{size} = Bweb::human_size($attr->{size});
+ return $attr;
+}
+
+sub fv_get_size
+{
+ my ($jobid, $rep) = @_;
+
+ my $ret = $bweb->dbh_selectrow_hashref("
+ SELECT Size AS size
+ FROM brestore_pathvisibility
+ WHERE PathId = $rep
+ AND JobId = $jobid
+");
+
+ return $ret->{size};
+}
+
+sub fv_get_files_size
+{
+ my ($jobid, $rep) = @_;
+
+ my $ret = $bweb->dbh_selectrow_hashref("
+ SELECT sum(base64_decode_lstat(8,lstat)) AS size
+ FROM File
+ WHERE PathId = $rep
+ AND JobId = $jobid
+");
+
+ return $ret->{size};
+}
+
+sub fv_get_big_files
+{
+ my ($jobid, $rep, $min, $limit) = @_;
+
+ my $ret = $bweb->dbh_selectall_arrayref("
+ SELECT Name, size
+ FROM (
+ SELECT FilenameId,base64_decode_lstat(8,lstat) AS size
+ FROM File
+ WHERE PathId = $rep
+ AND JobId = $jobid
+ ) AS S INNER JOIN Filename USING (FilenameId)
+ WHERE S.size > $min
+ ORDER BY S.size DESC
+ LIMIT $limit
+");
+
+ return $ret;
+}
+
+sub fv_update_size
+{
+ my ($jobid, $rep, $size) = @_;
+
+ my $nb = $bweb->dbh_do("
+ UPDATE brestore_pathvisibility SET Size = $size
+ WHERE JobId = $jobid
+ AND PathId = $rep
+");
+
+ return $nb;
+}
+
+sub fv_get_root_pathid
+{
+ my ($path) = @_;
+ $path = $bweb->dbh_quote($path);
+ my $ret = $bweb->dbh_selectrow_hashref("
+SELECT PathId FROM Path WHERE Path = $path
+ UNION
+SELECT PathId FROM brestore_missing_path WHERE PATH = $path
+");
+ return $ret->{pathid};
+}
+
+__END__
+
+CREATE OR REPLACE FUNCTION base64_decode_lstat(int4, varchar) RETURNS int8 AS $$
+DECLARE
+val int8;
+b64 varchar(64);
+size varchar(64);
+i int;
+BEGIN
+size := split_part($2, ' ', $1);
+b64 := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+val := 0;
+FOR i IN 1..length(size) LOOP
+val := val + (strpos(b64, substr(size, i, 1))-1) * (64^(length(size)-i));
+END LOOP;
+RETURN val;
+END;
+$$ language 'plpgsql';
+
+ALTER TABLE brestore_pathvisibility ADD Size int8;
</script>
<ul id="menu">
- <li><a href="?">Accueil</a> </li>
- <li><a href="?action=client">Clients</a></li>
- <li><a href="?action=run_job">Jobs</a>
+ <li><a href="bweb.pl?">Accueil</a> </li>
+ <li><a href="bweb.pl?action=client">Clients</a></li>
+ <li><a href="bweb.pl?action=run_job">Jobs</a>
<ul>
- <li><a href="?action=run_job">Jobs définis</a>
- <li><a href="?action=job">Historique</a> </li>
- <li><a href="?action=running">Jobs en cours</a>
- <li><a href="?action=next_job">Prochains Jobs</a> </li>
- <li><a href="?action=restore" title="Lancer brestore">Restauration</a> </li>
+ <li><a href="bweb.pl?action=run_job">Jobs définis</a>
+ <li><a href="bweb.pl?action=job">Historique</a> </li>
+ <li><a href="bweb.pl?action=running">Jobs en cours</a>
+ <li><a href="bweb.pl?action=next_job">Prochains Jobs</a> </li>
+ <li><a href="bweb.pl?action=restore" title="Lancer brestore">Restauration</a> </li>
</ul>
</li>
<li style="padding: 0.25em 2em;">Medias
<ul>
- <li><a href="?action=pool">Pools</a> </li>
- <li><a href="?action=location">Localisations</a> </li>
- <li><a href="?action=media">Tous les Medias</a><hr></li>
- <li><a href="?action=extern_media">Externaliser</a> </li>
- <li><a href="?action=intern_media">Internaliser</a> </li>
+ <li><a href="bweb.pl?action=pool">Pools</a> </li>
+ <li><a href="bweb.pl?action=location">Localisations</a> </li>
+ <li><a href="bweb.pl?action=media">Tous les Medias</a><hr></li>
+ <li><a href="bweb.pl?action=extern_media">Externaliser</a> </li>
+ <li><a href="bweb.pl?action=intern_media">Internaliser</a> </li>
</ul>
</li>
<TMPL_IF achs>
<li style="padding: 0.25em 2em;">Robotiques
<ul>
<TMPL_LOOP achs>
- <li><a href="?action=ach_view;ach=<TMPL_VAR name>"><TMPL_VAR name></a></li>
+ <li><a href="bweb.pl?action=ach_view;ach=<TMPL_VAR name>"><TMPL_VAR name></a></li>
</TMPL_LOOP>
</ul>
</li>
</TMPL_IF>
- <li><a href="?action=graph"> Statistiques </a></li>
- <li> <a href="?action=view_conf"> Configuration </a> </li>
- <li> <a href="?action=about"> A propos </a> </li>
+ <li><a href="bweb.pl?action=graph"> Statistiques </a></li>
+ <li> <a href="bweb.pl?action=view_conf"> Configuration </a> </li>
+ <li> <a href="bweb.pl?action=about"> A propos </a> </li>
<li style="padding: 0.25em 2em;float: right;"> Logged as <TMPL_VAR NAME=loginname> </li>
<li style="float: right;white-space: nowrap;">
<input type="image" class="button" title="chercher un media" onclick="search_media();" src="/bweb/tape.png"><input type="image" title="chercher un client" onclick="search_client();" src="/bweb/client.png"> <input class='formulaire' style="margin: 0 2px 0 2px; padding: 0 0 0 0;" id='searchbox' type='text' size='8' value="rechercher..." onclick="this.value='';" title="chercher un media ou un client"></li>
</ul>
-<form name="search" action="?" method='GET'>
+<form name="search" action="bweb.pl?" method='GET'>
<input type="hidden" name="action" value="">
<input type="hidden" name="re_media" value="">
<input type="hidden" name="re_client" value="">
<tr><td>template_dir :</td>
<td> <input class="formulaire" title="/chemin/vers/votre/template_dir" type='text' value='<TMPL_VAR NAME=template_dir>' size='64' name='template_dir'>
</td></tr>
+ <tr><td>fv_write_path :</td>
+ <td> <input class="formulaire" title="Ce répertoire doit être accessible en écriture pour apache et être sous /bweb/fv" type='text' value='<TMPL_VAR fv_write_path>' size='64' name='fv_write_path'>
+ </td></tr>
<tr><td>bconsole :</td>
<td> <input class="formulaire" title="/chemin/vers/bconsole -n -c /chemin/vers/bconsole.conf" type='text' value='<TMPL_VAR NAME=bconsole>' size='64' name='bconsole'>
</td></tr>
<tr> <td><b>Configuration Bweb</b></td> <td/></tr>
<tr><td title="/chemin/vers/votre/template_dir">template_dir :</td> <td> <TMPL_VAR template_dir> </td></tr>
<tr><td title="/chemin/vers/une/font.ttf">graph_font :</td> <td> <TMPL_VAR graph_font> </td></tr>
+ <tr><td title="Ce répertoire doit être accessible en ecriture pour apache et être sous /bweb/fv">fv_write_path :</td> <td> <TMPL_VAR fv_write_path> </td></tr>
<tr><td title="/chemin/vers/bconsole -n -c /chemin/vers/bconsole.conf">bconsole :</td> <td> <TMPL_VAR bconsole> </td></tr>
<tr><td>debug :</td> <td> <TMPL_VAR debug> </td></tr>
<TMPL_IF achs>
<div class="bodydiv">
<table id='id0'></table>
<table><td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='jobid' value='<TMPL_VAR jobid>'>
<label>
<input type="image" name='action' value='delete' title='Supprimer ce job'
</label>
</form>
</td><td>
- <form action='?'>
+ <form action='bweb.pl?'>
<TMPL_LOOP volumes>
<input type='hidden' name='media' value='<TMPL_VAR VolumeName>'>
</TMPL_LOOP>
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='client' value='<TMPL_VAR Client>'>
<label>
<input type="image" name='action' value='job' title='Voir les jobs de <TMPL_VAR Client>' src='/bweb/zoom.png'>Voir les jobs
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='age' value='2678400'>
<input type='hidden' name='client' value='<TMPL_VAR Client>'>
<input type='hidden' name='jobname' value='<TMPL_VAR jobname>'>
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='fileset' value='<TMPL_VAR FileSet>'>
<label>
- <input type="image" name='action' value='fileset_view' title='Voir le fileset associé'
+ <input type="image" name='action' value='fileset_view' title='Voir le fileset associé'
src='/bweb/zoom.png'> Voir le FileSet
</label>
</form>
</td>
+<!-- Enlever ce commentaire pour activer le bfileview
+ <td>
+ <form action='bfileview.pl?'>
+ <input type='hidden' name='jobid' value='<TMPL_VAR jobid>'>
+ <input type='hidden' name='where' value='/'>
+ <label>
+ <input type="image" name='action' value='bfileview' title="Voir la répartition des fichiers"
+ src='/bweb/colorscm.png'> Voir la répartition des fichiers
+ </label>
+ </form>
+ </td>
+-->
</table>
</div>
--- /dev/null
+Filename : <TMPL_VAR filename><br/>
+Size : <TMPL_VAR size><br/>
</label>
<label>
<input type="image" name='action' title='Mettre à jour à partir du pool'
- value='update_from_pool' src='/bweb/update.png'> Mettre à jour
+ value='update_from_pool' src='/bweb/update.png'> Reinitialiser
</label>
</form>
</td>
our %k_re = ( dbi => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
user => qr/^([\w\d\.-]+)$/i,
password => qr/^(.*)$/i,
+ fv_write_path => qr!^([/\w\d\.-]+)$!,
template_dir => qr!^([/\w\d\.-]+)$!,
debug => qr/^(on)?$/,
email_media => qr/^([\w\d\.-]+@[\d\w\.-]+)$/,
--- /dev/null
+package CCircle ;
+
+=head1 LICENSE
+
+ Bweb - A Bacula web interface
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+
+ The main author of Bweb is Eric Bollengier.
+ The main author of Bacula is Kern Sibbald, with contributions from
+ many others, a complete list can be found in the file AUTHORS.
+
+ This program is Free Software; you can redistribute it and/or
+ modify it under the terms of version two of the GNU General Public
+ License as published by the Free Software Foundation plus additions
+ that are listed in the file LICENSE.
+
+ 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 Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+ Bacula® is a registered trademark of John Walker.
+ The licensor of Bacula is the Free Software Foundation Europe
+ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
+ Switzerland, email:ftf@fsfeurope.org.
+
+=head1 VERSION
+
+ $Id$
+
+=cut
+
+use strict ;
+use GD ;
+
+my $pi = 3.14159265;
+
+our $gd ;
+our @color_tab ;
+
+our $black ;
+our $white ;
+
+our $last_level = 1 ;
+our @draw_label ;
+our $height ;
+our $width ;
+
+our $cur_color = 1 ;
+
+my $debug = 0 ;
+my $font_size = 6 ;
+
+our @image_map ;
+
+sub new
+{
+ my ($class, %arg) = @_ ;
+
+ my $self = {
+ start_degrees => 0,
+ degrees_complete => 0,
+ parent_percent => 100,
+ level => 1,
+ center_x => 600,
+ center_y => 300,
+ diameter => 75,
+ percent_total => 0,
+ min_percent => 2,
+ min_total => 0,
+ width => 1200,
+ height => 600,
+ min_label => 4,
+ min_degrees => 2,
+ max_label_level => 2,
+ display_other => 0,
+ base_url => '',
+ } ;
+
+ map { $self->{$_} = $arg{$_} } keys %arg ;
+
+ unless(defined $gd) {
+ $height = $self->{height} ;
+ $width = $self->{width} ;
+
+ $gd = new GD::Image($width,$height);
+ $white = $gd->colorAllocate(255,255,255);
+
+ push @color_tab, ($gd->colorAllocate(135,236,88),
+ $gd->colorAllocate(255,95,95),
+ $gd->colorAllocate(245,207,91),
+ $gd->colorAllocate( 255, 236, 139),
+ $gd->colorAllocate( 255, 174, 185),
+ $gd->colorAllocate( 179, 255, 58),
+ $gd->colorAllocate( 205, 133, 0),
+ $gd->colorAllocate(205, 133, 0 ),
+ $gd->colorAllocate(238, 238, 209),
+
+ ) ;
+
+
+ $black = $gd->colorAllocate(0,0,0);
+ #$gd->>transparent($white);
+ $gd->interlaced('true');
+
+ $gd->arc($self->{center_x},$self->{center_y},
+ $self->{diameter},$self->{diameter},
+ $self->{start_degrees},360, $black) ;
+
+ }
+
+ $self->{rayon} = $self->{diameter} / 2 ;
+
+ bless $self ;
+
+ # pour afficher les labels tout propre
+ if ($self->{level} > $last_level) {
+ $last_level = $self->{level} ;
+ }
+
+ return $self ;
+}
+
+sub calc_xy
+{
+ my ($self, $level, $deg) = @_ ;
+
+ my $x1 = $self->{center_x}+$self->{rayon} * $level * cos($deg*$pi/180) ;
+ my $y1 = $self->{center_y}+$self->{rayon} * $level * sin($deg*$pi/180) ;
+
+ return ($x1, $y1) ;
+
+}
+
+sub add_part
+{
+ my ($self, $percent, $label, $tips) = @_ ;
+
+ $tips = $tips || $label ;
+
+ if (($percent + $self->{percent_total}) > 100.05) {
+ print STDERR "Attention $label ($percent\% + $self->{percent_total}\%) > 100\%\n" ;
+ return undef;
+ }
+
+ if ($percent <= 0) {
+ print STDERR "Attention $label <= 0\%\n" ;
+ return undef;
+ }
+
+ # angle de depart de l'arc
+ my $start_degrees = (($self->{degrees_complete})?
+ $self->{degrees_complete}:$self->{start_degrees}) ;
+
+ # angle de fin de l'arc
+ my $end_degrees = $start_degrees +
+ ($percent * ( ( $self->{parent_percent} * 360 )/100 ) ) /100 ;
+
+# print STDERR "-------- $debug --------
+#percent = $percent%
+#label = $label
+#level = $self->{level}
+#start = $start_degrees
+#end = $end_degrees
+#parent= $self->{parent_percent}
+#" ;
+ if (($end_degrees - $start_degrees) < $self->{min_degrees}) {
+ $self->{min_total} += $percent ;
+ return undef ;
+ }
+
+ if ($percent <= $self->{min_percent}) {
+ $self->{min_total} += $percent ;
+ return undef ;
+ }
+
+ # on totalise les % en cours
+ $self->{percent_total} += $percent ;
+
+ #print STDERR "percent_total = $self->{percent_total}\n" ;
+
+ # position dans le cercle
+ my $n = $self->{level} ; # on ajoute/retire 0.005 pour depasser un peu
+ my $m = $n+1 ;
+
+ # si c'est la premiere tranche de la nouvelle serie, il faut dessiner
+ # la premiere limite
+
+ if ($self->{degrees_complete} == 0) {
+ my ($x1, $y1) = $self->calc_xy($n-0.005, $self->{start_degrees}) ;
+ my ($x2, $y2) = $self->calc_xy($m+0.005, $self->{start_degrees}) ;
+
+ $gd->line($x1, $y1, $x2, $y2, $black) ;
+ }
+
+ # seconde ligne
+ my ($x3, $y3) = $self->calc_xy($n-0.005, $end_degrees) ;
+
+ my ($x4, $y4) = $self->calc_xy($m+0.005, $end_degrees) ;
+
+ # ligne de bord exterieur
+ $gd->line($x3, $y3, $x4, $y4, $black);
+
+ # on dessine le bord
+ $gd->arc($self->{center_x},$self->{center_y},
+ $self->{diameter}*($self->{level}+1),
+ $self->{diameter}*($self->{level}+1),
+
+ $start_degrees-0.5,
+ $end_degrees+0.5, $black) ;
+
+ # on calcule le point qui est au milieu de la tranche
+ # angle = (angle nouvelle tranche)/2
+
+ # rayon = n*rayon - 0.5*rayon
+ # n=1 -> 0.5
+ # n=2 -> 1.5
+ # n=3 -> 2.5
+
+ my $mid_rad = ($end_degrees - $start_degrees) /2 + $start_degrees;
+
+ my $moy_x = ($self->{center_x}+
+ ($self->{rayon}*$m - 0.5*$self->{rayon})
+ *cos($mid_rad*$pi/180)) ;
+
+ my $moy_y = ($self->{center_y}+
+ ($self->{rayon}*$m - 0.5*$self->{rayon})
+ *sin($mid_rad*$pi/180)) ;
+
+ $gd->fillToBorder($moy_x,
+ $moy_y,
+ $black,
+ $cur_color) ;
+
+ # on prend une couleur au hasard
+ $cur_color = ($cur_color % $#color_tab) + 1 ;
+
+ # si le % est assez grand, on affiche le label
+ if ($percent > $self->{min_label}) {
+ push @draw_label, [$label,
+ $moy_x, $moy_y,
+ $self->{level}] ;
+
+ $self->push_image_map($label, $tips, $start_degrees, $end_degrees) ;
+ }
+
+ # pour pourvoir ajouter des sous donnees
+ my $ret = new CCircle(start_degrees => $start_degrees,
+ parent_percent => $percent*$self->{parent_percent}/100,
+ level => $m,
+ center_x => $self->{center_x},
+ center_y => $self->{center_y},
+ min_percent => $self->{min_percent},
+ min_degrees => $self->{min_degrees},
+ base_url => $self->{base_url} . $label,
+ ) ;
+
+ $self->{degrees_complete} = $end_degrees ;
+
+ #print STDERR "$debug : [$self->{level}] $label ($percent)\n" ;
+ #open(FP, sprintf(">/tmp/img.%.3i.png", $debug)) ;
+ #print FP $gd->png;
+ #close(FP) ;
+
+ $debug++ ;
+
+ return $ret ;
+}
+
+# on dessine le restant < min_percent
+sub finalize
+{
+ my ($self) = @_ ;
+
+ $self->add_part($self->{min_total},
+ "other < $self->{min_percent}",
+ $black) ;
+
+}
+
+sub set_title
+{
+ my ($self, $title) = @_ ;
+
+ $gd->string(GD::gdSmallFont, $self->{center_x} - $self->{rayon}*0.7,
+ $self->{center_y}, $title, $black) ;
+}
+
+my $_image_map = '';
+
+sub get_imagemap
+{
+ my ($self, $title, $img) = @_ ;
+
+ return "
+<map name='testmap'>
+ $_image_map
+</map>
+<img src='$img' border=0 usemap='#testmap' alt=''>
+" ;
+
+}
+
+sub push_image_map
+{
+ my ($self, $label, $tips, $start_degrees, $end_degrees) = @_ ;
+
+ if ($label =~ /^other </) {
+ if (!$self->{display_other}) {
+ return ;
+ }
+ $label = '';
+ }
+
+ # on prend des points tous les $delta sur l'arc interieur et exterieur
+
+ my $delta = 3 ;
+
+ if (($end_degrees - $start_degrees) < $delta) {
+ return ;
+ }
+
+ my @pts ;
+
+ for (my $i = $start_degrees ;
+ $i <= $end_degrees ;
+ $i = $i + $delta)
+ {
+ my ($x1, $y1) = $self->calc_xy($self->{level}, $i) ;
+ my ($x2, $y2) = $self->calc_xy($self->{level} + 1, $i) ;
+
+ push @pts, sprintf("%.2f,%.2f",$x1,$y1) ;
+ unshift @pts, sprintf("%.2f,%.2f",$x2, $y2) ;
+ }
+
+ my ($x1, $y1) = $self->calc_xy($self->{level}, $end_degrees) ;
+ my ($x2, $y2) = $self->calc_xy($self->{level} + 1, $end_degrees) ;
+
+ push @pts, sprintf("%.2f,%.2f",$x1,$y1) ;
+ unshift @pts, sprintf("%.2f,%.2f",$x2, $y2) ;
+
+ my $ret = join(",", @pts) ;
+
+ # on refait le traitement avec $i = $end_degrees
+ $_image_map .= "<area shape='polygon' coords='$ret' ".
+ "title='$tips' href='$self->{base_url}$label'>\n" ;
+
+}
+
+sub get_labels_imagemap
+{
+ my ($self) = @_ ;
+ my @ret ;
+
+ for my $l (@draw_label)
+ {
+ # translation
+ my ($label, $x, $y, $level) = @{ $l } ;
+
+ next if ($level > $self->{max_label_level}) ;
+
+ next if (!$self->{display_other} and $label =~ /^other .*</) ;
+
+ my $dy = ($y - $self->{center_y})*($last_level - $level) + $y ;
+
+ my $x2 ;
+ my $xp ;
+
+ if ($x < $self->{center_x}) {
+ $x2 = $self->{center_x}
+ - $self->{rayon} * ($last_level + 3.7) ;
+ $xp = $x2 - (length($label) *6 + 2) ; # moins la taille de la police
+ } else {
+ $x2 = $self->{center_x} + $self->{rayon} * ($last_level + 3.7) ;
+ $xp = $x2 + 10 ;
+ }
+
+ push @ret, $xp - 1 . ";" . $dy - 6 . ";" . $xp + length($label) * $font_size . ";" . $dy + 10 . "\n" ;
+
+ $gd->rectangle($xp - 1, $dy - 6,
+ $xp + length($label) * $font_size,
+ $dy + 10, $black) ;
+ }
+}
+
+my $_label_hauteur ;
+my $_label_max ;
+my $_label_pos ;
+my $_label_init = 0 ;
+
+# on va stocker les positions dans un bitmap $_label_pos
+#
+# si on match pas la position exacte, on essaye la case
+# au dessus ou en dessous
+#
+# on a un bitmap pour les labels de gauche et un pour la droite
+# $_label_pos->[0] et $_label_pos->[1]
+sub get_label_pos
+{
+ my ($self, $x, $y) = @_ ;
+
+ unless ($_label_init) {
+ $_label_hauteur = $self->{rayon} * 2 * $last_level ;
+ # nombre max de label = hauteur max / taille de la font
+ $_label_max = $_label_hauteur / 12 ;
+ $_label_pos = [ [], [] ] ;
+ $_label_init = 1 ;
+ }
+
+ # on calcule la position du label dans le bitmap
+ use integer ;
+ my $num = $y * $_label_max / $_label_hauteur ;
+ no integer ;
+
+ my $n = 0 ; # nombre d'iteration
+ my $l ;
+
+ # on prend le bon bitmap
+ if ($x < $self->{center_x}) {
+ $l = $_label_pos->[0] ;
+ } else {
+ $l = $_label_pos->[1] ;
+ }
+
+ # on parcours le bitmap pour trouver la bonne position
+ while (($num - $n) > 0) {
+ if (not $l->[$num]) {
+ last ;
+ } elsif (not $l->[$num + $n]) {
+ $num = $num + $n ;
+ last ;
+ } elsif (not $l->[$num - $n]) {
+ $num = $num - $n ;
+ last ;
+ }
+ $n++ ;
+ }
+
+ $l->[$num] = 1 ; # on prend la position
+
+ if ($num <= 0) {
+ return 0 ;
+ }
+
+ # calcul de la position
+ $y = $num * $_label_hauteur / $_label_max ;
+
+ return $y ;
+}
+
+sub draw_labels
+{
+ my ($self) = @_ ;
+
+ $gd->fillToBorder(1,
+ 1,
+ $black,
+ $white) ;
+
+ for my $l (@draw_label)
+ {
+ # translation
+ my ($label, $x, $y, $level) = @{ $l } ;
+
+ next if ($level > $self->{max_label_level}) ;
+
+ next if (!$self->{display_other} and $label =~ /^other </) ;
+
+ my $dx = ($x - $self->{center_x})*($last_level - $level) + $x ;
+ my $dy = ($y - $self->{center_y})*($last_level - $level) + $y ;
+
+ $dy = $self->get_label_pos($dx, $dy) ;
+
+ next unless ($dy) ; # pas d'affichage si pas de place
+
+ $gd->line( $x, $y,
+ $dx, $dy,
+ $black) ;
+
+ my $x2 ;
+ my $xp ;
+
+ if ($x < $self->{center_x}) {
+ $x2 = $self->{center_x}
+ - $self->{rayon} * ($last_level + 3.7) ;
+ $xp = $x2 - (length($label) * $font_size + 2) ; # moins la taille de la police
+ } else {
+ $x2 = $self->{center_x} + $self->{rayon} * ($last_level + 3.7) ;
+ $xp = $x2 + 10 ;
+ }
+
+ $gd->line($dx, $dy,
+ $x2, $dy,
+ $black) ;
+
+ $gd->string(GD::gdSmallFont, $xp, $dy - 5, $label, $black) ;
+ }
+}
+
+1;
+__END__
+
+package main ;
+
+my $top = new CCircle() ;
+
+my $chld1 = $top->add_part(50, 'test') ;
+my $chld2 = $top->add_part(20, 'test') ;
+my $chld3 = $top->add_part(10, 'test') ;
+my $chld4 = $top->add_part(20, 'test') ;
+
+
+$chld1->add_part(20, 'test1') ;
+$chld1->add_part(20, 'test1') ;
+
+$chld2->add_part(20, 'test1') ;
+$chld2->add_part(20, 'test1') ;
+
+$chld3->add_part(20, 'test1') ;
+my $chld5 = $chld3->add_part(20, 'test1') ;
+
+$chld5->add_part(50, 'test3') ;
+
+$top->finalize() ;
+$chld1->finalize() ;
+$chld2->finalize() ;
+$chld3->finalize() ;
+$chld4->finalize() ;
+$chld5->finalize() ;
+
+$top->draw_labels() ;
+# make sure we are writing to a binary stream
+binmode STDOUT;
+
+# Convert the image to PNG and print it on standard output
+print $CCircle::gd->png;
</script>
<ul id="menu">
- <li><a href="?">Main</a> </li>
- <li><a href="?action=client">Clients</a></li>
- <li><a href="?action=run_job">Jobs</a>
+ <li><a href="bweb.pl?">Main</a> </li>
+ <li><a href="bweb.pl?action=client">Clients</a></li>
+ <li><a href="bweb.pl?action=run_job">Jobs</a>
<ul>
- <li><a href="?action=run_job">Defined Jobs</a>
- <li><a href="?action=job">Last Jobs</a> </li>
- <li><a href="?action=running">Running Jobs</a>
- <li><a href="?action=next_job">Next Jobs</a> </li>
- <li><a href="?action=restore" title="Launch brestore">Restore</a> </li>
+ <li><a href="bweb.pl?action=run_job">Defined Jobs</a>
+ <li><a href="bweb.pl?action=job">Last Jobs</a> </li>
+ <li><a href="bweb.pl?action=running">Running Jobs</a>
+ <li><a href="bweb.pl?action=next_job">Next Jobs</a> </li>
+ <li><a href="bweb.pl?action=restore" title="Launch brestore">Restore</a> </li>
</ul>
</li>
<li style="padding: 0.25em 2em;">Medias
<ul>
- <li><a href="?action=pool">Pools</a> </li>
- <li><a href="?action=location">Locations</a> </li>
- <li><a href="?action=media">All Medias</a><hr></li>
- <li><a href="?action=extern_media">Eject Medias</a> </li>
- <li><a href="?action=intern_media">Load Medias</a> </li>
+ <li><a href="bweb.pl?action=pool">Pools</a> </li>
+ <li><a href="bweb.pl?action=location">Locations</a> </li>
+ <li><a href="bweb.pl?action=media">All Medias</a><hr></li>
+ <li><a href="bweb.pl?action=extern_media">Eject Medias</a> </li>
+ <li><a href="bweb.pl?action=intern_media">Load Medias</a> </li>
</ul>
</li>
<TMPL_IF achs>
<li style="padding: 0.25em 2em;">Autochanger
<ul>
<TMPL_LOOP achs>
- <li><a href="?action=ach_view;ach=<TMPL_VAR name>"><TMPL_VAR name></a></li>
+ <li><a href="bweb.pl?action=ach_view;ach=<TMPL_VAR name>"><TMPL_VAR name></a></li>
</TMPL_LOOP>
</ul>
</li>
</TMPL_IF>
- <li><a href="?action=graph"> Statistics </a></li>
- <li> <a href="?action=view_conf"> Configuration </a> </li>
- <li> <a href="?action=about"> About </a> </li>
+ <li><a href="bweb.pl?action=graph"> Statistics </a></li>
+ <li> <a href="bweb.pl?action=view_conf"> Configuration </a> </li>
+ <li> <a href="bweb.pl?action=about"> About </a> </li>
<li style="padding: 0.25em 2em;float: right;"> Logged as <TMPL_VAR NAME=loginname> </li>
<li style="float: right;white-space: nowrap;">
<input type="image" class="button" title="search media" onclick="search_media();" src="/bweb/tape.png"><input type="image" title="search client" onclick="search_client();" src="/bweb/client.png"> <input class='formulaire' style="margin: 0 2px 0 2px; padding: 0 0 0 0;" id='searchbox' type='text' size='8' value="search..." onclick="this.value='';" title="search media or client"></li>
</ul>
-<form name="search" action="?" method='GET'>
+<form name="search" action="bweb.pl?" method='GET'>
<input type="hidden" name="action" value="">
<input type="hidden" name="re_media" value="">
<input type="hidden" name="re_client" value="">
<tr><td>DBI :</td>
<td>
- <input class="formulaire" type='text' value='<TMPL_VAR NAME=dbi>' size='64' name='dbi'>
+ <input class="formulaire" type='text' value='<TMPL_VAR 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> <input class="formulaire" type='text' value='<TMPL_VAR user>' name='user'>
</td>
</tr>
<tr><td>password :</td>
- <td> <input class="formulaire" type='password' value='<TMPL_VAR NAME=password>' name='password'>
+ <td> <input class="formulaire" type='password' value='<TMPL_VAR 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> <input class="formulaire" type='text' value='<TMPL_VAR 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> <input class="formulaire" type='text' value='<TMPL_VAR 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> <input class="formulaire" type='text' value='<TMPL_VAR template_dir>' size='64' name='template_dir'>
+ </td></tr>
+ <tr><td>fv_write_path :</td>
+ <td> <input class="formulaire" title="This folder must be writable by apache user and must be accessible on /bweb/fv" type='text' value='<TMPL_VAR fv_write_path>' size='64' name='fv_write_path'>
</td></tr>
<tr><td>bconsole :</td>
- <td> <input class="formulaire" type='text' value='<TMPL_VAR NAME=bconsole>' size='64' name='bconsole'>
+ <td> <input class="formulaire" type='text' value='<TMPL_VAR bconsole>' size='64' name='bconsole'>
</td></tr>
<tr><td>debug :</td>
<td> <input class="formulaire" type='checkbox' name='debug'>
<tr> <td><b>Bweb Configuration</b></td> <td/></tr>
<tr><td title="/path/to/your/template_dir">template_dir :</td> <td> <TMPL_VAR template_dir> </td></tr>
<tr><td title="/path/to/a/font.ttf">graph_font :</td> <td> <TMPL_VAR graph_font> </td></tr>
+ <tr><td title="This folder must be writable by apache user and must be accessible on /bweb/fv">fv_write_path :</td> <td> <TMPL_VAR fv_write_path> </td></tr>
<tr><td title="/path/to/bconsole -n -c /path/to/bconsole.conf">bconsole :</td> <td> <TMPL_VAR bconsole> </td></tr>
<tr><td>debug :</td> <td> <TMPL_VAR debug> </td></tr>
<TMPL_IF achs>
<div class="bodydiv">
<table id='id0'></table>
<table><td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='jobid' value='<TMPL_VAR jobid>'>
<label>
<input type="image" name='action' value='delete' title='delete this job'
</label>
</form>
</td><td>
- <form action='?'>
+ <form action='bweb.pl?'>
<TMPL_LOOP volumes>
<input type='hidden' name='media' value='<TMPL_VAR VolumeName>'>
</TMPL_LOOP>
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='client' value='<TMPL_VAR Client>'>
<label>
<input type="image" name='action' value='job' title='view <TMPL_VAR Client> jobs' src='/bweb/zoom.png'>View jobs
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='age' value='2678400'>
<input type='hidden' name='client' value='<TMPL_VAR Client>'>
<input type='hidden' name='jobname' value='<TMPL_VAR jobname>'>
</form>
</td>
<td>
- <form action='?'>
+ <form action='bweb.pl?'>
<input type='hidden' name='fileset' value='<TMPL_VAR FileSet>'>
<label>
<input type="image" name='action' value='fileset_view' title='view fileset'
</label>
</form>
</td>
+<!-- Remove this to enable fileview
+ <td>
+ <form action='bfileview.pl?'>
+ <input type='hidden' name='jobid' value='<TMPL_VAR jobid>'>
+ <input type='hidden' name='where' value='/'>
+ <label>
+ <input type="image" name='action' value='bfileview' title='view file usage'
+ src='/bweb/colorscm.png'> View file usage
+ </label>
+ </form>
+ </td>
+-->
</table>
</div>
}
);
-</script>
\ No newline at end of file
+</script>
--- /dev/null
+Filename : <TMPL_VAR filename><br/>
+Size : <TMPL_VAR size><br/>
</label>
<label>
<input type="image" name='action' title='Update from pool'
- value='update_from_pool' src='/bweb/update.png'> Update
+ value='update_from_pool' src='/bweb/update.png'> Update from pool
</label>
</form>
</td>