Changeset 633 for trunk/cgi-bin


Ignore:
Timestamp:
10/08/14 18:01:55 (10 years ago)
Author:
Kris Deugau
Message:

/trunk

Commit 7/mumble for work done intermittently over the past ~year.
See #5, comment 25:

  • Netblock assignment
    • Update several supporting data-grabber subs in IPDB.pm
    • Update assignment sub
    • Update data-entry, confirmation, and acceptance/error page templates and related main.cgi sections
Location:
trunk/cgi-bin
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/cgi-bin/IPDB.pm

    r632 r633  
    738738  my $failmsg = "No suitable free block found\n";
    739739
     740  my @vallist;
     741  my $sql;
     742
     743  # Free pool IPs should be easy.
     744  if ($type =~ /^.i$/) {
     745    # User may get an IP from the wrong VRF.  User should not be using admin tools to allocate static IPs.
     746    $sql = "SELECT id, ip, parent_id FROM poolips WHERE ip = ?";
     747    @vallist = ($optargs{gimme});
     748  } else {
     749
    740750## Set up the SQL to find out what freeblock we can (probably) use for an allocation.
    741751## Very large systems will require development of a reserve system (possibly an extension
     
    743753## Also populate a value list for the DBI call.
    744754
    745   my @vallist = ($maskbits);
    746   my $sql = "SELECT cidr,rdepth FROM freeblocks WHERE masklen(cidr) <= ?";
     755    @vallist = ($maskbits);
     756    $sql = "SELECT id,cidr,parent_id FROM freeblocks WHERE masklen(cidr) <= ?";
    747757
    748758# cases, strict rules
     
    765775##fixme:  strict-or-not flag
    766776
     777##fixme:  config or UI flag for "Strict" mode
     778# if ($strictmode) {
     779if (0) {
    767780  if ($type =~ /^(.)r$/) {
    768781    push @vallist, $1;
     
    773786    $sql .= " AND routed = 'r'";
    774787  }
    775 
    776   # for PPP(oE) and container types, the POP city is the one attached to the pool.
    777   # individual allocations get listed with the customer city site.
    778   ##fixme:  chain cities to align roughly with a full layer-2 node graph
    779   $city = $pop if $type !~ /^.[pc]$/;
    780   if ($type ne 'rm' && $city) {
    781     $sql .= " AND city = ?";
    782     push @vallist, $city;
    783   }
    784   # Allow specifying an arbitrary full block, instead of a master
    785   if ($optargs{gimme}) {
    786     $sql .= " AND cidr >>= ?";
    787     push @vallist, $optargs{gimme};
    788   }
    789   # if a specific master was requested, allow the requestor to self->shoot(foot)
    790   if ($optargs{master} && $optargs{master} ne '-') {
    791     $sql .= " AND cidr <<= ?" if $optargs{master} ne '-';
    792     push @vallist, $optargs{master};
    793   } else {
    794     # if a specific master was NOT requested, filter out the RFC 1918 private networks
    795     if (!$optargs{allowpriv}) {
    796       $sql .= " AND NOT (cidr <<= '192.168.0.0/16' OR cidr <<= '10.0.0.0/8' OR cidr <<= '172.16.0.0/12')";
    797     }
    798   }
    799   # Sorting and limiting, since we don't (currently) care to provide a selection of
    800   # blocks to carve up.  This preserves something resembling optimal usage of the IP
    801   # space by forcing contiguous allocations and free blocks as much as possible.
    802   $sql .= " ORDER BY maskbits DESC,cidr LIMIT 1";
    803 
    804   my ($fbfound,$fbdepth) = $dbh->selectrow_array($sql, undef, @vallist);
    805   return $fbfound,$fbdepth;
     788}
     789
     790    # for PPP(oE) and container types, the POP city is the one attached to the pool.
     791    # individual allocations get listed with the customer city site.
     792    ##fixme:  chain cities to align roughly with a full layer-2 node graph
     793    $city = $pop if $type !~ /^.[pc]$/;
     794    if ($type ne 'rm' && $city) {
     795      $sql .= " AND city = ?";
     796      push @vallist, $city;
     797    }
     798    # Allow specifying an arbitrary full block, instead of a master
     799    if ($optargs{gimme}) {
     800      $sql .= " AND cidr >>= ?";
     801      push @vallist, $optargs{gimme};
     802    }
     803    # if a specific master was requested, allow the requestor to self->shoot(foot)
     804    if ($optargs{master} && $optargs{master} ne '-') {
     805      $sql .= " AND master_id = ?";
     806# if $optargs{master} ne '-';
     807      push @vallist, $optargs{master};
     808    } else {
     809      # if a specific master was NOT requested, filter out the RFC 1918 private networks
     810      if (!$optargs{allowpriv}) {
     811        $sql .= " AND NOT (cidr <<= '192.168.0.0/16' OR cidr <<= '10.0.0.0/8' OR cidr <<= '172.16.0.0/12')";
     812      }
     813    }
     814    # Sorting and limiting, since we don't (currently) care to provide a selection of
     815    # blocks to carve up.  This preserves something resembling optimal usage of the IP
     816    # space by forcing contiguous allocations and free blocks as much as possible.
     817    $sql .= " ORDER BY masklen(cidr) DESC,cidr LIMIT 1";
     818  } # done setting up SQL for free CIDR block
     819
     820  my ($fbid,$fbfound,$fbparent) = $dbh->selectrow_array($sql, undef, @vallist);
     821  return $fbid,$fbfound,$fbparent;
    806822} # end findAllocateFrom()
    807823
     
    872888
    873889  $args{cidr} = new NetAddr::IP $args{cidr};
    874   $args{alloc_from} = new NetAddr::IP $args{alloc_from};
    875890
    876891  $args{desc} = '' if !$args{desc};
     
    883898  my $sth;
    884899
    885   # Snag the "type" of the freeblock (alloc_from) "just in case"
    886   $sth = $dbh->prepare("select routed from freeblocks where cidr='$args{alloc_from}'");
    887   $sth->execute;
    888   my ($alloc_from_type) = $sth->fetchrow_array;
     900  # Snag the "type" of the freeblock and its CIDR
     901  my ($alloc_from_type, $alloc_from, $fbparent, $fcity, $fbmaster) =
     902        $dbh->selectrow_array("SELECT routed,cidr,parent_id,city,master_id FROM freeblocks WHERE id = ?",
     903        undef, $args{fbid});
     904  $alloc_from = new NetAddr::IP $alloc_from;
    889905
    890906  # To contain the error message, if any.
     
    908924                undef, ($args{alloc_from}) );
    909925      }
    910       $dbh->do("UPDATE poolips SET custid=?,city=?,available='n',description=?,notes=?,circuitid=?,privdata=?,vrf=?,rdns=? ".
    911         "WHERE ip=?", undef, ($args{custid}, $args{city}, $args{desc}, $args{notes}, $args{circid},
    912                 $args{privdata}, $args{vrf}, $args{rdns}, $args{cidr}) );
     926      $dbh->do("UPDATE poolips SET custid = ?, city = ?,available='n', description = ?, notes = ?, ".
     927        "circuitid = ?, privdata = ?, vrf = ?, rdns = ? ".
     928        "WHERE ip = ? AND parent_id = ?", undef,
     929                ($args{custid}, $args{city}, $args{desc}, $args{notes},
     930                $args{circid}, $args{privdata}, $args{vrf}, $args{rdns},
     931                $args{cidr}, $args{parent}) );
    913932
    914933# node hack
     
    931950  } else { # end IP-from-pool allocation
    932951
    933     if ($args{cidr} == $args{alloc_from}) {
     952    if ($args{cidr} == $alloc_from) {
    934953      # Easiest case- insert in one table, delete in the other, and go home.  More or less.
    935954      # insert into allocations values (cidr,custid,type,city,desc) and
     
    940959        $msg = "Unable to allocate $args{cidr} as '$disp_alloctypes{$args{type}}'";
    941960
    942         # Get old freeblocks parent/depth/routed for new entries... before we delete it.
    943         my ($fparent) = $dbh->selectrow_array("SELECT parent FROM freeblocks WHERE cidr=? AND rdepth=?",
    944                 undef, ($args{alloc_from}, $args{rdepth}) );
     961        # Insert the allocations entry
     962        $dbh->do("INSERT INTO allocations ".
     963                "(cidr,parent_id,master_id,vrf,custid,type,city,description,notes,circuitid,privdata,rdns)".
     964                " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
     965                ($args{cidr}, $fbparent, $fbmaster, $args{vrf}, $args{custid}, $args{type}, $args{city},
     966                $args{desc}, $args{notes}, $args{circid}, $args{privdata}, $args{rdns}) );
     967        my ($bid) = $dbh->selectrow_array("SELECT currval('allocations_id_seq')");
    945968
    946969        # Munge freeblocks
     
    948971          # special case - block is a routed or container/"reserve" block
    949972          my $rtype = $1;
    950           $dbh->do("UPDATE freeblocks SET routed=?,rdepth=rdepth+1,city=?,parent=? WHERE cidr=? AND rdepth=?",
    951                 undef, ($rtype, $args{city}, $args{cidr}, $args{cidr}, $args{rdepth}) );
     973          $dbh->do("UPDATE freeblocks SET routed = ?,city = ?,parent_id = ? WHERE id = ?",
     974                undef, ($rtype, $args{city}, $bid, $args{fbid}) );
    952975        } else {
    953976          # "normal" case
    954           $dbh->do("DELETE FROM freeblocks WHERE cidr=? AND rdepth=?", undef, ($args{cidr}, $args{rdepth}));
     977          $dbh->do("DELETE FROM freeblocks WHERE id = ?", undef, ($args{fbid}) );
    955978        }
    956 
    957         # Insert the allocations entry
    958         $dbh->do("INSERT INTO allocations ".
    959                 "(cidr,parent,vrf,rdepth,custid,type,city,description,notes,circuitid,privdata,rdns)".
    960                 " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
    961                 ($args{cidr}, $fparent, $args{vrf}, $args{rdepth}, $args{custid}, $args{type}, $args{city},
    962                 $args{desc}, $args{notes}, $args{circid}, $args{privdata}, $args{rdns}) );
    963979
    964980        # And initialize the pool, if necessary
     
    967983        if ($args{type} =~ /^.p$/) {
    968984          $msg = "Could not initialize IPs in new $disp_alloctypes{$args{type}} $args{cidr}";
    969           my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "all", $args{rdepth}+1);
     985          my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "all", $bid);
    970986          die $rmsg if $code eq 'FAIL';
    971987        } elsif ($args{type} =~ /^.d$/) {
    972988          $msg = "Could not initialize IPs in new $disp_alloctypes{$args{type}} $args{cidr}";
    973           my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "normal", $args{rdepth}+1);
     989          my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "normal", $bid);
    974990          die $rmsg if $code eq 'FAIL';
    975991        }
     
    9921008
    9931009      # Hard case.  Allocation is smaller than free block.
     1010
     1011      # make sure new allocation is in fact within freeblock.  *sigh*
     1012      return ('FAIL',"Requested allocation $args{cidr} is not within $alloc_from")
     1013        if !$alloc_from->contains($args{cidr});
    9941014      my $wantmaskbits = $args{cidr}->masklen;
    995       my $maskbits = $args{alloc_from}->masklen;
     1015      my $maskbits = $alloc_from->masklen;
    9961016
    9971017      my @newfreeblocks;        # Holds free blocks generated from splitting the source freeblock.
     
    10011021      # block is in, and repeat until the wanted block is equal to one of the halves.
    10021022      my $i=0;
    1003       my $tmp_from = $args{alloc_from}; # So we don't munge $args{alloc_from}
     1023      my $tmp_from = $alloc_from;       # So we don't munge $args{alloc_from}
    10041024      while ($maskbits++ < $wantmaskbits) {
    10051025        my @subblocks = $tmp_from->split($maskbits);
     
    10121032        $msg = "Unable to allocate $args{cidr} as '$disp_alloctypes{$args{type}}'";
    10131033
    1014         # Get old freeblocks parent/depth/routed for new entries
    1015         my ($fparent,$fcity,$wasrouted) = $dbh->selectrow_array("SELECT parent,city,routed FROM freeblocks".
    1016                 " WHERE cidr=? AND rdepth=?", undef, ($args{alloc_from}, $args{rdepth}) );
    1017 
    10181034        # Delete old freeblocks entry
    1019         $dbh->do("DELETE FROM freeblocks WHERE cidr=? AND rdepth=?", undef, ($args{alloc_from}, $args{rdepth}) );
     1035        $dbh->do("DELETE FROM freeblocks WHERE id = ?", undef, ($args{fbid}) );
    10201036
    10211037        # Insert new list of smaller free blocks left over
    1022         $sth = $dbh->prepare("INSERT INTO freeblocks (cidr,city,routed,vrf,parent,rdepth) VALUES (?,?,?,?,?,?)");
     1038        $sth = $dbh->prepare("INSERT INTO freeblocks (cidr,city,routed,vrf,parent_id,master_id) VALUES (?,?,?,?,?,?)");
    10231039        foreach my $block (@newfreeblocks) {
    1024           $sth->execute($block, $fcity, $wasrouted, $args{vrf}, $fparent, $args{rdepth});
     1040          $sth->execute($block, $fcity, $alloc_from_type, $args{vrf}, $fbparent, $fbmaster);
    10251041        }
     1042
     1043        # Insert the allocations entry
     1044        $dbh->do("INSERT INTO allocations ".
     1045                "(cidr,parent_id,master_id,vrf,custid,type,city,description,notes,circuitid,privdata,rdns)".
     1046                " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
     1047                ($args{cidr}, $fbparent, $fbmaster, $args{vrf}, $args{custid}, $args{type}, $args{city},
     1048                $args{desc}, $args{notes}, $args{circid}, $args{privdata}, $args{rdns}) );
     1049        my ($bid) = $dbh->selectrow_array("SELECT currval('allocations_id_seq')");
    10261050
    10271051        # For routed/container types, add a freeblock within the allocated block so we can subdivide it further
    10281052        if ($args{type} =~ /(.)[mc]/) { # rm and .c types - containers
    10291053          my $rtype = $1;
    1030           $sth->execute($args{cidr}, $args{city}, $rtype, $args{vrf}, $args{cidr}, $args{rdepth}+1);
     1054          $sth->execute($args{cidr}, $args{city}, $rtype, $args{vrf}, $bid, $fbmaster);
    10311055        }
    1032 
    1033         # Insert the allocations entry
    1034         $dbh->do("INSERT INTO allocations ".
    1035                 "(cidr,parent,vrf,rdepth,custid,type,city,description,notes,circuitid,privdata,rdns)".
    1036                 " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
    1037                 ($args{cidr}, $fparent, $args{vrf}, $args{rdepth}, $args{custid}, $args{type}, $args{city},
    1038                 $args{desc}, $args{notes}, $args{circid}, $args{privdata}, $args{rdns}) );
    10391056
    10401057        # And initialize the pool, if necessary
     
    10431060        if ($args{type} =~ /^.p$/) {
    10441061          $msg = "Could not initialize IPs in new $disp_alloctypes{$args{type}} $args{cidr}";
    1045           my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "all", $args{rdepth}+1);
     1062          my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "all", $bid);
    10461063          die $rmsg if $code eq 'FAIL';
    10471064        } elsif ($args{type} =~ /^.d$/) {
    10481065          $msg = "Could not initialize IPs in new $disp_alloctypes{$args{type}} $args{cidr}";
    1049           my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "normal", $args{rdepth}+1);
     1066          my ($code,$rmsg) = initPool($dbh, $args{cidr}, $args{type}, $args{city}, "normal", $bid);
    10501067          die $rmsg if $code eq 'FAIL';
    10511068        }
     
    10861103# function and should ONLY EVER get called from allocateBlock()
    10871104sub initPool {
    1088   my ($dbh,undef,$type,$city,$class,$rdepth) = @_;
     1105  my ($dbh,undef,$type,$city,$class,$parent) = @_;
    10891106  my $pool = new NetAddr::IP $_[1];
    10901107
     
    10951112  return ('FAIL',"Refusing to create oversized static IP pool") if $pool->masklen <= 20;
    10961113
    1097 ##fixme Need to just replace 2nd char of type with i rather than capturing 1st char of type
     1114  my ($pcustid) = $dbh->selectrow_array("SELECT def_custid FROM alloctypes WHERE type=?", undef, ($type) );
    10981115  $type =~ s/[pd]$/i/;
    10991116  my $sth;
     
    11081125  eval {
    11091126    # have to insert all pool IPs into poolips table as "unallocated".
    1110     $sth = $dbh->prepare("INSERT INTO poolips (pool,ip,custid,city,type,rdepth)".
    1111         " VALUES ('$pool', ?, '$defcustid', ?, '$type', $rdepth)");
     1127    $sth = $dbh->prepare("INSERT INTO poolips (ip,custid,city,type,parent_id) VALUES (?,?,?,?,?)");
    11121128    my @poolip_list = $pool->hostenum;
    11131129    if ($class eq 'all') { # (DSL-ish block - *all* IPs available
    11141130      if ($pool->addr !~ /\.0$/) {      # .0 causes weirdness.
    1115         $sth->execute($pool->addr, $city);
     1131        $sth->execute($pool->addr, $pcustid, $city, $type, $parent);
    11161132      }
    11171133      for (my $i=0; $i<=$#poolip_list; $i++) {
    1118         $sth->execute($poolip_list[$i]->addr, $city);
     1134        $sth->execute($poolip_list[$i]->addr, $pcustid, $city, $type, $parent);
    11191135      }
    11201136      $pool--;
    11211137      if ($pool->addr !~ /\.255$/) {    # .255 can cause weirdness.
    1122         $sth->execute($pool->addr, $city);
     1138        $sth->execute($pool->addr, $pcustid, $city, $type, $parent);
    11231139      }
    11241140    } else { # (real netblock)
    11251141      for (my $i=1; $i<=$#poolip_list; $i++) {
    1126         $sth->execute($poolip_list[$i]->addr, $city);
    1127       }
    1128     }
    1129     $dbh->commit;
     1142        $sth->execute($poolip_list[$i]->addr, $pcustid, $city, $type, $parent);
     1143      }
     1144    }
     1145# don't commit here!  the caller may not be done.
     1146#    $dbh->commit;
    11301147  };
    11311148  if ($@) {
    11321149    $msg = $@;
    1133     eval { $dbh->rollback; };
     1150# Don't roll back!  It's up to the caller to handle this.
     1151#    eval { $dbh->rollback; };
    11341152    return ('FAIL',$msg);
    11351153  } else {
  • trunk/cgi-bin/main.cgi

    r631 r633  
    300300
    301301  # hack pthbttt eww
     302  $webvar{parent} = 0 if !$webvar{parent};
    302303  $webvar{block} = '' if !$webvar{block};
    303304
     
    307308  $page->param(allocfrom => $webvar{block});    # fb-assign flag, if block is set, we're in fb-assign
    308309
    309   if ($webvar{block} ne '') {
     310  if ($webvar{fbid} || $webvar{fbtype}) {
    310311
    311312    # Common case, according to reported usage.  Block to assign is specified.
    312313    my $block = new NetAddr::IP $webvar{block};
    313     $page->param(rdepth => $webvar{rdepth});
    314 
    315     my $rdns = getBlockRDNS($ip_dbh, $webvar{block}, $webvar{rdepth}, vrf => $webvar{vrf}, user => $authuser);
     314
     315    my $rdns = getBlockRDNS($ip_dbh, id => $webvar{parent}, type => $webvar{fbtype}, user => $authuser);
    316316    $page->param(rdns => $rdns) if $rdns;
     317    $page->param(parent => $webvar{parent});
     318    $page->param(fbid => $webvar{fbid});
    317319
    318320    $webvar{fbtype} = '' if !$webvar{fbtype};
    319321    if ($webvar{fbtype} eq 'i') {
    320       my $ipinfo = getBlockData($ip_dbh, $block);
     322      my $ipinfo = getBlockData($ip_dbh, $webvar{block}, 'i');
     323      my $pinfo = getBlockData($ip_dbh, $webvar{parent});
    321324      $page->param(
    322325        fbip => 1,
    323         block => $block,
     326        block => $ipinfo->{block},
    324327        fbdisptype => $list_alloctypes{$ipinfo->{type}},
    325328        type => $ipinfo->{type},
    326         allocfrom => $ipinfo->{pool},
     329        allocfrom => $pinfo->{block},
    327330        );
    328331    } else {
     
    340343
    341344    my @pops;
    342     foreach my $pop (@poplist) {
     345    foreach my $pop (@citylist) {
    343346      my %row = (pop => $pop);
    344347      push (@pops, \%row);
     
    378381  my $cidr;
    379382  my $alloc_from;
     383  my $fbid = $webvar{fbid};
     384  my $p_id = $webvar{parent};
    380385
    381386  # Going to manually validate some items.
     
    428433
    429434## fixme:  add rdepth?
    430       ($cidr,$webvar{rdepth}) = findAllocateFrom($ip_dbh, $webvar{maskbits}, $webvar{alloctype}, $webvar{city},
    431         $webvar{pop}, (master => $webvar{allocfrom}, allowpriv => $webvar{allowpriv}) );
     435      ($fbid,$cidr,$p_id) = findAllocateFrom($ip_dbh, $webvar{maskbits}, $webvar{alloctype},
     436        $webvar{city}, $webvar{pop}, (master => $webvar{allocfrom}, allowpriv => $webvar{allowpriv}) );
    432437      if (!$cidr) {
    433438        $page->param(err => $failmsg);
     
    464469  $page->param(typefull => $q->escapeHTML($disp_alloctypes{$webvar{alloctype}}));
    465470  $page->param(alloc_from => $alloc_from);
    466   $page->param(rdepth => $webvar{rdepth});
     471  $page->param(parent => $p_id);
     472  $page->param(fbid => $fbid);
    467473  $page->param(cidr => $cidr);
    468474  $page->param(rdns => $webvar{rdns});
     
    514520  # IP, or the error message if an error occurred.
    515521
    516   my ($code,$msg) = allocateBlock($ip_dbh, cidr => $webvar{fullcidr}, alloc_from => $webvar{alloc_from},
    517         rdepth => $webvar{rdepth}, custid => $webvar{custid}, type => $webvar{alloctype}, city => $webvar{city},
     522  my ($code,$msg) = allocateBlock($ip_dbh, cidr => $webvar{fullcidr}, fbid => $webvar{fbid},
     523        parent => $webvar{parent}, custid => $webvar{custid}, type => $webvar{alloctype}, city => $webvar{city},
    518524        desc => $webvar{desc}, notes => $webvar{notes}, circid => $webvar{circid},
    519525        privdata => $webvar{privdata}, nodeid => $webvar{node}, rdns => $webvar{rdns}, user => $authuser);
     
    524530      $page->param(staticip => $msg);
    525531      $page->param(custid => $webvar{custid});
    526       $page->param(parent => $webvar{alloc_from}, rdepth => $webvar{rdepth}-1);
     532      $page->param(parent => $webvar{parent}, pool => $webvar{alloc_from});
    527533      $page->param(billinguser => $webvar{billinguser});
    528534      mailNotify($ip_dbh, "a$webvar{alloctype}", "ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
     
    535541      $page->param(custid => $webvar{custid});
    536542      # breadcrumbs lite!  provide at least a link to the parent of the block we just allocated.
    537       my $binfo = getBlockData($ip_dbh, $webvar{fullcidr}, $webvar{rdepth});
    538       $page->param(parent => $binfo->{parent}, rdepth => $binfo->{rdepth});
     543      my $binfo = getBlockData($ip_dbh, $webvar{parent});
     544      $page->param(parentid => $webvar{parent});
     545      $page->param(parentblock => $binfo->{block});
    539546      if ($webvar{alloctype} eq 'pr' && $webvar{billinguser}) {
    540547        $page->param(billinguser => $webvar{billinguser});
Note: See TracChangeset for help on using the changeset viewer.