]> git.sur5r.net Git - bacula/bacula/commitdiff
ebl Add brestore ajax port.
authorEric Bollengier <eric@eb.homelinux.org>
Wed, 26 Sep 2007 21:39:27 +0000 (21:39 +0000)
committerEric Bollengier <eric@eb.homelinux.org>
Wed, 26 Sep 2007 21:39:27 +0000 (21:39 +0000)
git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@5664 91ce42f0-d328-0410-95d8-f526ca767f89

gui/bweb/INSTALL
gui/bweb/ReleaseNotes
gui/bweb/cgi/bresto.pl [new file with mode: 0755]
gui/bweb/html/bresto.html [new file with mode: 0644]
gui/bweb/html/bresto.js [new file with mode: 0644]

index 620ee32c4be6e07fd78054978f0c5feaf5f11009..857070914ac890e74ccc77fecc11ac1d70347d67 100644 (file)
@@ -17,6 +17,7 @@ Bweb works well with 1.39 release or later.
 11) setting mysql read-only account
 12) get more statistics
 13) use groups with bweb
+14) setup restoration in bweb (not yet working)
 
 ################ FILE COPY #####################################
  # you must get bweb svn files
@@ -322,6 +323,25 @@ It works with postgresql and mysql5 (4 not tested).
 With mysql, load bweb/script/bweb-mysql.sql into your catalog
 For postgresql, it will be done with bweb/script/bweb-postgresql.sql (already done)
 
+################ MADE RESTORATION WITH BWEB ####################
+
+Warning, this function is not for production use at this time !
+It will do some basics things on a working bweb/brestore setup.
+
+1) Go to http://extjs.com and download their toolkit (last 1.X release)
+2) Install files in /bweb/ext web root 
+ example on debian : 
+  root@localhost:~# mv ext-1.1.1 /usr/share/bweb/html/ext
+
+3) Make sure that brestore cache tables are updated with brestore.pl
+  bacula@localhost:~$ brestore.pl -b
+
+4) Enable bresto.pl cgi. 
+  edit the bweb/cgi/bresto.pl script and change $bresto_enable=0; to $bresto_enable=1;
+  on the top of the file.
+
+4) Go on http://you-director/bweb/bresto.html
+
 ################################################################
 
 Enjoy !
index a4c3f51ee111c32661cc2b31f49ad0fcfb32799e..afacabf50acb82d00dab9ce3c5bf0239a07c433b 100644 (file)
@@ -1,5 +1,8 @@
           Release Notes for bweb 2.2
 
+2007/09/26
+ - add bresto.pl, bresto.html and bresto.js
+
 2007/08/20
  - fix update of locationid field during label barcodes 
  - use update recyclepool= instead of UPDATE Media SET RecyclePoolId
diff --git a/gui/bweb/cgi/bresto.pl b/gui/bweb/cgi/bresto.pl
new file mode 100755 (executable)
index 0000000..c6a54f3
--- /dev/null
@@ -0,0 +1,535 @@
+#!/usr/bin/perl -w
+
+my $bresto_enable = 0;
+die "bresto is not enabled" if (not $bresto_enable);
+
+=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 Bweb;
+
+package Bvfs;
+use base qw/Bweb/;
+
+sub get_root
+{
+    my ($self, $dir) = @_;
+    return $self->get_pathid('');
+}
+
+sub ch_dir
+{
+    my ($self, $pathid) = @_;
+    $self->{cwdid} = $pathid;
+}
+
+sub up_dir
+{
+    my ($self) = @_ ;
+    my $query = "
+  SELECT PPathId 
+    FROM brestore_pathhierarchy 
+   WHERE PathId IN ($self->{cwdid}) ";
+
+    my $all = $self->dbh_selectall_arrayref($query);
+    return unless ($all);      # already at root
+
+    my $dir = join(',', map { $_->[0] } @$all);
+    if ($dir) {
+       $self->ch_dir($dir);
+    }
+}
+
+sub pwd
+{
+    my ($self) = @_;
+    return $self->get_path($self->{cwdid});
+}
+
+sub get_path
+{
+    my ($self, $pathid) = @_;
+    $self->debug("Call with pathid = $pathid");
+    my $query = 
+       "SELECT Path FROM Path WHERE PathId IN (?)";
+
+    my $sth = $self->dbh_prepare($query);
+    $sth->execute($pathid);
+    my $result = $sth->fetchrow_arrayref();
+    $sth->finish();
+    return $result->[0];    
+}
+
+sub set_curjobids
+{
+    my ($self, @jobids) = @_;
+    $self->{curjobids} = join(',', @jobids);
+#    $self->update_brestore_table(@jobids);
+}
+
+sub get_pathid
+{
+    my ($self, $dir) = @_;
+    my $query = 
+       "SELECT PathId FROM Path WHERE Path = ?";
+    my $sth = $self->dbh_prepare($query);
+    $sth->execute($dir);
+    my $result = $sth->fetchall_arrayref();
+    $sth->finish();
+    
+    return join(',', map { $_->[0] } @$result);
+}
+
+sub set_limits
+{
+    my ($self, $offset, $limit) = @_;
+    $self->{limit}  = $limit  || 100;
+    $self->{offset} = $offset || 0;
+}
+
+sub ls_files
+{
+    my ($self) = @_;
+
+    return undef unless ($self->{curjobids});
+
+    my $inclause   = $self->{curjobids};
+    my $inlistpath = $self->{cwdid};
+
+    my $query = 
+"SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
+ FROM
+       (SELECT Filename.Name, max(File.FileId) as id
+        FROM File, Filename
+        WHERE File.FilenameId = Filename.FilenameId
+          AND Filename.Name != ''
+          AND File.PathId IN ($inlistpath)
+          AND File.JobId IN ($inclause)
+        GROUP BY Filename.Name
+        ORDER BY Filename.Name) AS listfiles,
+File
+WHERE File.FileId = listfiles.id";
+       
+    $self->debug($query);
+    my $result = $self->dbh_selectall_arrayref($query);
+    $self->debug($result);
+       
+    return $result;
+}
+
+
+# return ($dirid,$dir_basename,$lstat,$jobid)
+sub ls_dirs
+{
+    my ($self) = @_;
+
+    return undef unless ($self->{curjobids});
+
+    my $pathid = $self->{cwdid};
+    my $jobclause = $self->{curjobids};
+
+    # Let's retrieve the list of the visible dirs in this dir ...
+    # First, I need the empty filenameid to locate efficiently the dirs in the file table
+    my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
+    my $sth = $self->dbh_prepare($query);
+    $sth->execute();
+    my $result = $sth->fetchrow_arrayref();
+    $sth->finish();
+    my $dir_filenameid = $result->[0];
+     
+    # Then we get all the dir entries from File ...
+    $query = "
+SELECT PathId, Path, JobId, Lstat FROM (
+    
+    SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
+           listfile1.JobId, listfile1.Lstat
+    FROM (
+        SELECT DISTINCT brestore_pathhierarchy1.PathId
+        FROM brestore_pathhierarchy AS brestore_pathhierarchy1
+        JOIN Path AS Path2
+            ON (brestore_pathhierarchy1.PathId = Path2.PathId)
+        JOIN brestore_pathvisibility AS brestore_pathvisibility1
+            ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
+        WHERE brestore_pathhierarchy1.PPathId = $pathid
+        AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
+    JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
+    LEFT JOIN (
+        SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
+        WHERE File1.FilenameId = $dir_filenameid
+        AND File1.JobId IN ($jobclause)) AS listfile1
+        ON (listpath1.PathId = listfile1.PathId)
+     ) AS A ORDER BY 2,3 DESC
+";
+    $self->debug($query);
+    $sth=$self->dbh_prepare($query);
+    $sth->execute();
+    $result = $sth->fetchall_arrayref();
+    my @return_list;
+    my $prev_dir='';
+    foreach my $refrow (@{$result})
+    {
+       my $dirid = $refrow->[0];
+        my $dir = $refrow->[1];
+        my $lstat = $refrow->[3];
+        my $jobid = $refrow->[2] || 0;
+        next if ($dirid eq $prev_dir);
+        # We have to clean up this dirname ... we only want it's 'basename'
+        my $return_value;
+        if ($dir ne '/')
+        {
+            my @temp = split ('/',$dir);
+            $return_value = pop @temp;
+        }
+        else
+        {
+            $return_value = '/';
+        }
+        my @return_array = ($dirid,$return_value,$lstat,$jobid);
+        push @return_list,(\@return_array);
+        $prev_dir = $dirid;
+    }
+    $self->debug(\@return_list);
+    return \@return_list;    
+}
+
+
+# Returns list of versions of a file that could be restored
+# returns an array of 
+# (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
+# there will be only one jobid in the array of jobids...
+sub get_all_file_versions
+{
+    my ($self,$pathid,$fileid,$client,$see_all)=@_;
+    
+    defined $see_all or $see_all=0;
+    
+    my @versions;
+    my $query;
+    $query = 
+"SELECT File.JobId, File.FileId, File.Lstat, 
+        File.Md5, Media.VolumeName, Media.InChanger
+ FROM File, Job, Client, JobMedia, Media
+ WHERE File.FilenameId = $fileid
+   AND File.PathId=$pathid
+   AND File.JobId = Job.JobId
+   AND Job.ClientId = Client.ClientId
+   AND Job.JobId = JobMedia.JobId
+   AND File.FileIndex >= JobMedia.FirstIndex
+   AND File.FileIndex <= JobMedia.LastIndex
+   AND JobMedia.MediaId = Media.MediaId
+   AND Client.Name = '$client'";
+
+    $self->debug($query);
+       
+    my $result = $self->dbh_selectall_arrayref($query);
+       
+    foreach my $refrow (@$result)
+    {
+       my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
+       my @attribs = parse_lstat($lstat);
+       my $mtime = array_attrib('st_mtime',\@attribs);
+       my $size = array_attrib('st_size',\@attribs);
+               
+       my @list = ($pathid,$fileid,$jobid,
+                   $fid, $mtime, $size, $inchanger,
+                   $md5, $volname);
+       push @versions, (\@list);
+    }
+       
+    # We have the list of all versions of this file.
+    # We'll sort it by mtime desc, size, md5, inchanger desc
+    # the rest of the algorithm will be simpler
+    # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
+    @versions = sort { $b->[4] <=> $a->[4] 
+                   || $a->[5] <=> $b->[5] 
+                   || $a->[7] cmp $a->[7] 
+                   || $b->[6] <=> $a->[6]} @versions;
+
+       
+    my @good_versions;
+    my %allready_seen_by_mtime;
+    my %allready_seen_by_md5;
+    # Now we should create a new array with only the interesting records
+    foreach my $ref (@versions)
+    {  
+       if ($ref->[7])
+       {
+           # The file has a md5. We compare his md5 to other known md5...
+           # We take size into account. It may happen that 2 files
+           # have the same md5sum and are different. size is a supplementary
+           # criterion
+            
+            # If we allready have a (better) version
+           next if ( (not $see_all) 
+                     and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}); 
+
+           # we never met this one before...
+           $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
+       }
+       # Even if it has a md5, we should also work with mtimes
+        # We allready have a (better) version
+       next if ( (not $see_all)
+                 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]}); 
+       $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
+       
+       # We reached there. The file hasn't been seen.
+       push @good_versions,($ref);
+    }
+       
+    # To be nice with the user, we re-sort good_versions by
+    # inchanger desc, mtime desc
+    @good_versions = sort { $b->[4] <=> $a->[4] 
+                         || $b->[2] <=> $a->[2]} @good_versions;
+       
+    return \@good_versions;
+}
+{
+    my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
+                         'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
+                         'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
+                         'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
+                         'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
+                         'data_stream' => 15);;
+    sub array_attrib
+    {
+       my ($attrib,$ref_attrib)=@_;
+       return $ref_attrib->[$attrib_name_id{$attrib}];
+    }
+       
+    sub file_attrib
+    {   # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
+
+       my ($file, $attrib)=@_;
+       
+       if (defined $attrib_name_id{$attrib}) {
+
+           my @d = split(' ', $file->[3]) ; # TODO : cache this
+           
+           return from_base64($d[$attrib_name_id{$attrib}]);
+
+       } elsif ($attrib eq 'jobid') {
+
+           return $file->[4];
+
+        } elsif ($attrib eq 'name') {
+
+           return $file->[2];
+           
+       } else  {
+           die "Attribute not known : $attrib.\n";
+       }
+    }
+    
+    sub lstat_attrib
+    {
+        my ($lstat,$attrib)=@_;
+        if ($lstat and defined $attrib_name_id{$attrib}) 
+        {
+           my @d = split(' ', $lstat) ; # TODO : cache this
+           return from_base64($d[$attrib_name_id{$attrib}]);
+       }
+       return 0;
+    }
+}
+
+{
+    # Base 64 functions, directly from recover.pl.
+    # Thanks to
+    # Karl Hakimian <hakimian@aha.com>
+    # This section is also under GPL v2 or later.
+    my @base64_digits;
+    my @base64_map;
+    my $is_init=0;
+    sub init_base64
+    {
+       @base64_digits = (
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+       'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+       'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+       'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+                         );
+       @base64_map = (0) x 128;
+       
+       for (my $i=0; $i<64; $i++) {
+           $base64_map[ord($base64_digits[$i])] = $i;
+       }
+       $is_init = 1;
+    }
+
+    sub from_base64 {
+       if(not $is_init)
+       {
+           init_base64();
+       }
+       my $where = shift;
+       my $val = 0;
+       my $i = 0;
+       my $neg = 0;
+       
+       if (substr($where, 0, 1) eq '-') {
+           $neg = 1;
+           $where = substr($where, 1);
+       }
+       
+       while ($where ne '') {
+           $val *= 64;
+           my $d = substr($where, 0, 1);
+           $val += $base64_map[ord(substr($where, 0, 1))];
+           $where = substr($where, 1);
+       }
+       
+       return $val;
+    }
+
+    sub parse_lstat {
+       my ($lstat)=@_;
+       my @attribs = split(' ',$lstat);
+       foreach my $element (@attribs)
+       {
+           $element = from_base64($element);
+       }
+       return @attribs;
+    }
+}
+
+
+################################################################
+
+
+package main;
+use strict;
+use Bweb;
+
+my $conf = new Bweb::Config(config_file => $Bweb::config_file);
+$conf->load();
+
+my $bvfs = new Bvfs(info => $conf);
+$bvfs->connect_db();
+
+my $action = CGI::param('action') || '';
+
+my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid',
+                          'limit', 'offset', 'client');
+
+my @jobid = grep { /^\d+$/ } CGI::param('jobid');
+
+$bvfs->set_curjobids(@jobid);
+$bvfs->set_limits($args->{limit}, $args->{offset});
+#$bvfs->{debug}=1;
+
+print CGI::header('application/x-javascript');
+
+if ($action eq 'list_client') {
+
+  my $q = 'SELECT Name FROM Client';
+  my $ret = $bvfs->dbh_selectall_arrayref($q);
+
+  print "[";
+  print join(',', map { "['$_->[0]']" } @$ret);
+  print "]\n";
+  exit 0;
+
+} elsif ($action eq 'list_job') {
+
+    my $query = "
+ SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
+  FROM Job,Client,FileSet
+  WHERE Job.ClientId=Client.ClientId
+  AND Client.Name = '$args->{client}'
+  AND Job.Type = 'B'
+  AND JobStatus IN ('f', 'T')
+  AND Job.FileSetId = FileSet.FileSetId
+  ORDER BY EndTime desc";
+    my $result = $bvfs->dbh_selectall_arrayref($query);
+
+    print "[";
+
+    print join(',', map {
+      "[$_->[4], '$_->[0] $_->[1] $_->[2] ($_->[3])']"
+      } @$result);
+
+    print "]\n";
+}
+
+my $pathid = CGI::param('node') || 0;
+if ($pathid =~ /^(\d+)$/) {
+    $pathid = $1;
+} else {
+    $pathid = $bvfs->get_root();
+}
+
+$bvfs->ch_dir($pathid);
+
+if ($action eq 'list_files') {
+    print "[";
+    my $files = $bvfs->ls_files();
+#      [ 1, 2, 3, "Bill",  10, '2007-01-01 00:00:00'], 
+#   File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId 
+
+    print join(',',
+              map { "[$_->[1], $_->[0], $pathid, \"$_->[2]\", 10, \"2007-01-01 00:00:00\"]" }
+              @$files);
+    print "]\n";
+
+} elsif ($action eq 'list_dirs') {
+
+    print "[";
+    my $dirs = $bvfs->ls_dirs();
+       # return ($dirid,$dir_basename,$lstat,$jobid)
+
+    print join(',', 
+              map { "{ 'id': '$_->[0]', 'text': '$_->[1]', 'cls':'folder'}" }
+              @$dirs);
+
+    print "]\n";
+
+} elsif ($action eq 'list_versions') {
+
+    print "[";
+    #   0       1       2        3   4       5      6           7      8
+    #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
+    my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, 1);
+    print join(',', 
+              map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5], $_->[4] ]" }
+              @$files);
+    print "]\n";
+
+}
+
+
+
diff --git a/gui/bweb/html/bresto.html b/gui/bweb/html/bresto.html
new file mode 100644 (file)
index 0000000..0c7f76a
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+       <head>
+       <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+       <title>Bweb - restore</title>
+
+       <link rel="stylesheet" type="text/css" href="/bweb/ext/resources/css/ext-all.css" />
+
+    <script type="text/javascript" src="/bweb/ext/adapter/ext/ext-base.js"></script>     <!-- ENDLIBS -->
+
+<!--    <script type="text/javascript" src="/bweb/ext/ext-all.js"></script> -->
+    <script type="text/javascript" src="/bweb/ext/ext-all-debug.js"></script>
+       <script type="text/javascript" src="bweb.js"></script>
+       <script type="text/javascript" src="bresto.js"></script>
+       
+       </head>
+       <body>
+<div id="div-container"          >
+    <div id="div-toolbar"        ></div>
+    <div id="div-tb-sel"         ></div>
+    <div id="div-files"          ></div>
+    <div id="div-file-versions"  ></div>
+    <div id="div-file-selection" ></div>
+    <div id="div-tree"           ></div>
+</div>         
+       </body>
+</html>
diff --git a/gui/bweb/html/bresto.js b/gui/bweb/html/bresto.js
new file mode 100644 (file)
index 0000000..a46a9c9
--- /dev/null
@@ -0,0 +1,524 @@
+
+//   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.
+
+// render if vol is online/offline
+function rd_vol_is_online(val)
+{
+   return '<img src="/bweb/inflag' + val + '.png">';
+}
+
+// TODO: fichier ou rep
+function rd_file_or_dir(val)
+{
+   if (val == 'F') {
+      return '<img src="/bweb/A.png">';
+   } else {
+      return '<img src="/bweb/R.png">';
+   }
+}
+
+Ext.namespace('Ext.brestore');
+
+Ext.brestore.jobid=0;
+Ext.brestore.client='';
+
+function ext_init()
+{
+//////////////////////////////////////////////////////////////:
+    var Tree = Ext.tree;
+    var tree_loader = new Ext.tree.TreeLoader({
+            baseParams:{}, //action:'list_dirs', pathid:103, jobid:9 },
+            dataUrl:'/cgi-bin/bweb/bresto.pl'
+        });
+
+    var tree = new Ext.tree.TreePanel('div-tree', {
+        animate:true, 
+        loader: tree_loader,
+        enableDD:true,
+        enableDragDrop: true,
+        containerScroll: true
+    });
+
+    // set the root node
+    var root = new Ext.tree.AsyncTreeNode({
+        text: 'Root',
+        draggable:false,
+        id:'source'
+    });
+    tree.setRootNode(root);
+
+    // render the tree
+    tree.render();
+//    root.expand();
+
+    tree.on('click', function(e) { 
+       file_store.removeAll();
+        file_store.load({params:{action: 'list_files',
+                                jobid:Ext.brestore.jobid, 
+                                 node:e.id}
+                       });
+       return true;
+    });
+
+    tree.on('beforeload', function(e) {
+       file_store.removeAll();
+       return true;
+    });
+
+
+////////////////////////////////////////////////////////////////
+
+  var file_store = new Ext.data.Store({
+        proxy: new Ext.data.HttpProxy({
+            url: '/cgi-bin/bweb/bresto.pl',
+           method: 'GET',
+            params:{action: 'list_files', offset:0, limit:50 }
+        }),
+
+       reader: new Ext.data.ArrayReader({
+        }, Ext.data.Record.create([
+   {name: 'fileid'    },
+   {name: 'filenameid'},
+   {name: 'pathid'    },
+   {name: 'name'      },
+   {name: 'size',     type: 'int'  },
+   {name: 'mtime',    type: 'date', dateFormat: 'Y-m-d h:i:s'}
+        ]))
+   });
+
+   var cm = new Ext.grid.ColumnModel([{
+           id:        'name', // id assigned so we can apply custom css (e.g. .x-grid-col-topic b { color:#333 })
+           header:    'File',
+           dataIndex: 'name',
+           width:     250,
+           css:       'white-space:normal;'
+        },{
+           header:    "Size",
+           dataIndex: 'size',
+          renderer:  human_size,
+           width:     50
+        },{
+           header:    "Date",
+           dataIndex: 'mtime',
+           width:     50
+        },{
+          dataIndex: 'pathid',
+          hidden: true
+        },{
+          dataIndex: 'filenameid',
+          hidden: true
+        },{
+          dataIndex: 'fileid',
+          hidden: true
+       }
+        ]);
+
+    // by default columns are sortable
+   cm.defaultSortable = true;
+
+    // create the grid
+   var files_grid = new Ext.grid.Grid('div-files', {
+        ds: file_store,
+        cm: cm,
+        ddGroup : 'TreeDD',
+        enableDrag: true,
+        enableDragDrop: true,
+        selModel: new Ext.grid.RowSelectionModel(),
+        loadMask: true,
+        enableColLock:false
+        
+    });
+
+    // when we reload the view,
+    // we clear the file version box
+    file_store.on('beforeload', function(e) {
+       file_versions_store.removeAll();
+       return true;
+    });
+
+    files_grid.selModel.on('rowselect', function(e,i,r) { 
+       Ext.brestore.filename = r.json[3];
+       file_versions_store.load({params:{action: 'list_versions',
+                                          client: Ext.brestore.client,
+                                         pathid: r.json[2],
+                                         filenameid: r.json[1]
+                                         }
+                                 });
+       return true;
+    });
+    files_grid.render();
+
+//////////////////////////////////////////////////////////////:
+
+  var file_selection_store = new Ext.data.Store({
+        proxy: new Ext.data.HttpProxy({
+            url: '/cgi-bin/bweb/bresto.pl',
+           method: 'GET',
+            params:{offset:0, limit:50 }
+        }),
+
+       reader: new Ext.data.ArrayReader({
+        }, Ext.data.Record.create([
+   {name: 'jobid'     },
+   {name: 'fileid'    },
+   {name: 'filenameid'},
+   {name: 'pathid'    },
+   {name: 'size',     type: 'int'  },
+   {name: 'mtime',    type: 'date', dateFormat: 'Y-m-d h:i:s'}
+        ]))
+   });
+
+   var file_selection_cm = new Ext.grid.ColumnModel([{
+           id:        'name', // id assigned so we can apply custom css (e.g. .x-grid-col-topic b { color:#333 })
+           dataIndex: 'name',
+          hidden: true
+        },{
+           header:    "JobId",
+           dataIndex: 'jobid'
+        },{
+           header:    "Size",
+           dataIndex: 'size',
+          renderer:  human_size,
+           width:     50
+        },{
+           header:    "Date",
+           dataIndex: 'mtime',
+           width:     50
+        },{
+          dataIndex: 'pathid',
+          hidden: true
+        },{
+          dataIndex: 'filenameid',
+          hidden: true
+        },{
+          dataIndex: 'fileid',
+          hidden: true
+       }
+        ]);
+
+
+    // create the grid
+   var file_selection_grid = new Ext.grid.Grid('div-file-selection', {
+        cm: file_selection_cm,
+        ds: file_selection_store,
+        ddGroup : 'TreeDD',
+        enableDragDrop: true,
+       enableDrop: true,
+        selModel: new Ext.grid.RowSelectionModel(),
+        loadMask: true,
+        enableColLock:false
+        
+    });
+
+// http://extjs.com/forum/showthread.php?t=12582&highlight=drag+drop
+//    var ddrow = new Ext.dd.DropTarget(grid.container, {
+//     ddGroup : 'TreeDD',
+//     copy:false,
+//     notifyDrop : function(dd, e, data){
+//             var sm=grid.getSelectionModel();
+//             var rows=sm.getSelections();
+//             var cindex=dd.getDragData(e).rowIndex;
+//             for(i = 0; i < rows.length; i++) {
+//                     rowData=ds.getById(rows[i].id);
+//                     if(!this.copy) 
+//                             ds.remove(ds.getById(rows[i].id));
+//                     ds.insert(cindex,rowData);
+//             };
+//     }
+//    });
+
+
+   file_selection_grid.on('enddrag', function(dd,e) { 
+       alert(e) ; return true;
+    });
+   file_selection_grid.on('notifyDrop', function(dd,e) { 
+       alert(e) ; return true;
+    });
+   file_selection_grid.render();
+
+///////////////////////////////////////////////////////
+
+  var file_versions_store = new Ext.data.Store({
+        proxy: new Ext.data.HttpProxy({
+            url: '/cgi-bin/bweb/bresto.pl',
+           method: 'GET',
+            params:{offset:0, limit:50 }
+        }),
+
+       reader: new Ext.data.ArrayReader({
+        }, Ext.data.Record.create([
+   {name: 'fileid'    },
+   {name: 'filenameid'},
+   {name: 'pathid'    },
+   {name: 'jobid'     },
+   {name: 'volume'    },
+   {name: 'inchanger' },
+   {name: 'md5'       },
+   {name: 'size',     type: 'int'  },
+   {name: 'mtime'} //,    type: 'date', dateFormat: 'Y-m-d h:i:s'}
+        ]))
+   });
+
+   var file_versions_cm = new Ext.grid.ColumnModel([{
+           id:        'name', // id assigned so we can apply custom css (e.g. .x-grid-col-topic b { color:#333 })
+           dataIndex: 'name',
+          hidden: true
+        },{
+           header:    "InChanger",
+           dataIndex: 'inchanger',
+          renderer:  rd_vol_is_online
+        },{
+           header:    "Volume",
+           dataIndex: 'volume'
+        },{
+           header:    "JobId",
+           dataIndex: 'jobid'
+        },{
+           header:    "Size",
+           dataIndex: 'size',
+          renderer:  human_size,
+           width:     50
+        },{
+           header:    "Date",
+           dataIndex: 'mtime',
+           width:     50
+        },{
+           header:    "MD5",
+           dataIndex: 'md5',
+           width:     50
+        },{
+          dataIndex: 'pathid',
+          hidden: true
+        },{
+          dataIndex: 'filenameid',
+          hidden: true
+        },{
+          dataIndex: 'fileid',
+          hidden: true
+       }
+   ]);
+
+    // by default columns are sortable
+   file_versions_cm.defaultSortable = true;
+
+    // create the grid
+   var file_versions_grid = new Ext.grid.Grid('div-file-versions', {
+        ds: file_versions_store,
+        cm: file_versions_cm,
+        ddGroup : 'TreeDD',
+        enableDrag: true,
+        enableDrop: false,
+        selModel: new Ext.grid.RowSelectionModel(),
+        loadMask: true,
+        enableColLock:false
+        
+    });
+
+    file_versions_grid.on('rowdblclick', function(e) { 
+       alert(e) ; file_versions_store.removeAll(); return true;
+    });
+    file_versions_grid.render();
+
+//////////////////////////////////////////////////////////////:
+
+
+    var client_store = new Ext.data.Store({
+        proxy: new Ext.data.HttpProxy({
+            url: '/cgi-bin/bweb/bresto.pl',
+           method: 'GET',
+            params:{action:'list_client'}
+        }),
+
+       reader: new Ext.data.ArrayReader({
+        }, Ext.data.Record.create([
+          {name: 'name' }
+        ]))
+    });
+
+    var client_combo = new Ext.form.ComboBox({
+       fieldLabel: 'Clients',
+        store: client_store,
+        displayField:'name',
+        typeAhead: true,
+        mode: 'local',
+        triggerAction: 'all',
+        emptyText:'Select a client...',
+        selectOnFocus:true,
+       forceSelection: true,
+        width:135
+    });
+
+    client_combo.on('valid', function(e) { 
+       Ext.brestore.client = e.getValue();
+       job_store.load( {params:{action: 'list_job',client:Ext.brestore.client}});
+       return true;
+    });
+
+//////////////////////////////////////////////////////////////:
+
+    var job_store = new Ext.data.Store({
+        proxy: new Ext.data.HttpProxy({
+            url: '/cgi-bin/bweb/bresto.pl',
+           method: 'GET',
+            params:{offset:0, limit:50 }
+        }),
+
+       reader: new Ext.data.ArrayReader({
+        }, Ext.data.Record.create([
+          {name: 'jobid' },
+          {name: 'jobname' }
+        ]))
+    });
+
+    var job_combo = new Ext.form.ComboBox({
+       fieldLabel: 'Jobs',
+        store: job_store,
+        displayField:'jobname',
+        typeAhead: true,
+        mode: 'local',
+        triggerAction: 'all',
+        emptyText:'Select a job...',
+        selectOnFocus:true,
+       forceSelection: true,
+        width:300
+    });
+
+    job_combo.on('select', function(e,c) {
+       Ext.brestore.jobid = c.json[0];
+
+       tree_loader.baseParams = { action:'list_dirs',
+                                  jobid:Ext.brestore.jobid };
+        root.reload();
+    });
+
+////////////////////////////////////////////////////////////////:
+
+    // create the primary toolbar
+    var tb2 = new Ext.Toolbar('div-tb-sel');
+    tb2.add({
+        id:'save',
+        text:'Save',
+        disabled:true,
+//        handler:save,
+        cls:'x-btn-text-icon save',
+        tooltip:'Saves all components to the server'
+    },'-', {
+        id:'add',
+        text:'Component',
+//        handler:addComponent,
+        cls:'x-btn-text-icon add-cmp',
+        tooltip:'Add a new Component to the dependency builder'
+    }, {
+        id:'option',
+        text:'Option',
+        disabled:true,
+//        handler:addOption,
+        cls:'x-btn-text-icon add-opt',
+        tooltip:'Add a new optional dependency to the selected component'
+    },'-',{
+        id:'remove',
+        text:'Remove',
+        disabled:true,
+//        handler:removeNode,
+        cls:'x-btn-text-icon remove',
+        tooltip:'Remove the selected item'
+    });
+
+    var tb = new Ext.Toolbar('div-toolbar', [
+       client_combo,
+       job_combo,
+       '-',
+       {
+                id: 'tb_home',
+//             icon: '/bweb/up.gif',
+               text: 'Change location',
+               cls:'x-btn-text-icon',
+               handler: function() { alert('do chdir') }
+       },
+       new Ext.form.TextField({
+            fieldLabel: 'Location',
+            name: 'where',
+            width:175,
+            allowBlank:false
+        })
+    ]);
+
+////////////////////////////////////////////////////////////////
+
+    var layout = new Ext.BorderLayout(document.body, {
+        north: {
+//            split: true
+        },
+        south: {
+            split: true, initialSize: 300
+        },
+        east: {
+            split: true, initialSize: 500
+        },
+        west: {
+            split: true, initialSize: 300
+        },
+        center: {
+           initialSize: 600
+        }        
+       
+    });
+
+layout.beginUpdate();
+  layout.add('north', new Ext.ContentPanel('div-toolbar', {
+      fitToFrame: true, autoCreate:true,closable: false 
+  }));
+  layout.add('south', new Ext.ContentPanel('div-file-selection', {
+      toolbar: tb2,resizeEl:'div-container',
+      fitToFrame: true, autoCreate:true,closable: false
+  }));
+  layout.add('east', new Ext.ContentPanel('div-file-versions', {
+      fitToFrame: true, autoCreate:true,closable: false
+  }));
+  layout.add('west', new Ext.ContentPanel('div-tree', {
+      autoScroll:true, fitToFrame: true, 
+      autoCreate:true,closable: false
+  }));
+  layout.add('center', new Ext.ContentPanel('div-files', {
+      autoScroll:true,autoCreate:true,fitToFrame: true
+  }));
+layout.endUpdate();    
+
+
+////////////////////////////////////////////////////////////////
+
+//    job_store.load();
+    client_store.load({params:{action: 'list_client'}});
+//    file_store.load({params:{offset:0, limit:50}});
+//    file_versions_store.load({params:{offset:0, limit:50}});
+//    file_selection_store.load();
+
+}
+Ext.onReady( ext_init );