Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Wed, 11 Dec 2013 04:59:42 +0000 (20:59 -0800)
committerIvan Kohler <ivan@freeside.biz>
Wed, 11 Dec 2013 04:59:42 +0000 (20:59 -0800)
Conflicts:
FS/FS/Mason.pm
FS/MANIFEST

21 files changed:
FS/FS/ClientAPI/MyAccount.pm
FS/FS/Conf.pm
FS/FS/IP_Mixin.pm
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/addr_block.pm
FS/FS/addr_range.pm [new file with mode: 0644]
FS/FS/part_export/bulkvs_e911.pm
FS/FS/svc_Common.pm
FS/MANIFEST
FS/bin/freeside-setup
FS/t/addr_range.t [new file with mode: 0644]
bin/cust_pkg-restore_setup
fs_selfservice/php/freeside_signup_example.php
httemplate/browse/addr_range.html [new file with mode: 0644]
httemplate/edit/addr_range.html [new file with mode: 0644]
httemplate/edit/cust_main/billing.html
httemplate/edit/elements/edit.html
httemplate/edit/process/addr_range.html [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/misc/delete-addr_range.html [new file with mode: 0644]

index a580991..aa21ac0 100644 (file)
@@ -7,6 +7,7 @@ use subs qw( _cache _provision );
 use IO::Scalar;
 use Data::Dumper;
 use Digest::MD5 qw(md5_hex);
+use Digest::SHA qw(sha512_hex);
 use Date::Format;
 use Time::Duration;
 use Time::Local qw(timelocal_nocheck);
@@ -278,7 +279,7 @@ sub login {
 
   my $session_id;
   do {
-    $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+    $session_id = sha512_hex(time(). {}. rand(). $$)
   } until ( ! defined _cache->get($session_id) ); #just in case
 
   my $timeout = $conf->config('selfservice-session_timeout') || '1 hour';
@@ -2896,7 +2897,7 @@ sub reset_passwd {
 
   my $reset_session_id;
   do {
-    $reset_session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+    $reset_session_id = sha512_hex(time(). {}. rand(). $$)
   } until ( ! defined _cache->get("reset_passwd_$reset_session_id") ); #just in case
 
   _cache->set( "reset_passwd_$reset_session_id", $reset_session, $timeout );
index 0eed8ee..301d972 100644 (file)
@@ -2150,7 +2150,7 @@ and customer address. Include units.',
     'section'     => 'self-service',
     'description' => 'Acceptable payment types for the signup server',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY PPAL BILL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL BILL COMP) ],
   },
 
   {
@@ -2538,7 +2538,7 @@ and customer address. Include units.',
     'section'     => 'billing',
     'description' => 'Available payment types.',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD PPAL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP) ],
   },
 
   {
@@ -2546,7 +2546,7 @@ and customer address. Include units.',
     'section'     => 'UI',
     'description' => 'Default payment type.  HIDE disables display of billing information and sets customers to BILL.',
     'type'        => 'select',
-    'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD PPAL COMP HIDE) ],
+    'select_enum' => [ '', qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP HIDE) ],
   },
 
   {
index fdeb51d..b3c1052 100644 (file)
@@ -200,12 +200,21 @@ sub check_ip_addr {
   return '' if $addr eq '';
   my $na = $self->NetAddr
     or return "Can't parse address '$addr'";
+  # if there's a chosen address block, check that the address is in it
   if ( my $block = $self->addr_block ) {
     if ( !$block->NetAddr->contains($na) ) {
       return "Address $addr not in block ".$block->cidr;
     }
   }
-  # this returns '' if the address is in use by $self.
+  # if the address is in any designated ranges, check that they don't 
+  # disallow use
+  foreach my $range (FS::addr_range->any_contains($addr)) {
+    if ( !$range->allow_use ) {
+      return "Address $addr is in ".$range->desc." range ".$range->as_string;
+    }
+  }
+  # check that nobody else is sitting on the address
+  # (this returns '' if the address is in use by $self)
   if ( my $dup = $self->is_used($self->ip_addr) ) {
     return "Address $addr in use by $dup";
   }
index d228bb1..4e9c4f1 100644 (file)
@@ -361,6 +361,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::alarm_system;
   use FS::alarm_type;
   use FS::alarm_station;
+  use FS::addr_range;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index 6403782..647e2b1 100644 (file)
@@ -4278,6 +4278,18 @@ sub tables_hashref {
                         ],
     },
 
+    'addr_range' => {
+      'columns' => [
+        'rangenum', 'serial', '', '', '', '',
+        'start',    'varchar', '', 15, '', '',
+        'length',   'int', '', '', '', '',
+        'status',   'varchar', 'NULL', 32, '', '',
+      ],
+      'primary_key' => 'rangenum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
     'svc_broadband' => {
       'columns' => [
         'svcnum',                  'int',     '',        '', '', '', 
index 6a62777..1f4000b 100755 (executable)
@@ -256,7 +256,17 @@ sub next_free_addr {
   # just do a linear search of the block
   my $freeaddr = $selfaddr->network + 1;
   while ( $freeaddr < $selfaddr->broadcast ) {
-    return $freeaddr unless $used{ $freeaddr->addr };
+    # also make sure it's not blocked from assignment by an address range
+    if ( !$used{$freeaddr->addr } ) {
+      my ($range) = grep { !$_->allow_use }
+                  FS::addr_range->any_contains($freeaddr);
+      if ( !$range ) {
+        # then we've found a free address
+        return $freeaddr;
+      }
+      # otherwise, skip to the end of the range
+      $freeaddr = NetAddr::IP->new($range->end, $self->ip_netmask);
+    }
     $freeaddr++;
   }
   return;
diff --git a/FS/FS/addr_range.pm b/FS/FS/addr_range.pm
new file mode 100644 (file)
index 0000000..18ae1e2
--- /dev/null
@@ -0,0 +1,259 @@
+package FS::addr_range;
+
+use strict;
+use base qw( FS::Record );
+use vars qw( %status_desc
+             %status_allow_auto
+             %status_allow_use
+           );
+use FS::Record qw( qsearch qsearchs );
+use NetAddr::IP;
+
+# metadata about status strings:
+# how to describe them
+%status_desc = (
+  ''            => '',
+  'unavailable' => 'unavailable',
+);
+
+# whether addresses in this range are available for use
+%status_allow_use = (
+  ''            => 1,
+  'unavailable' => 0,
+);
+
+=head1 NAME
+
+FS::addr_range - Object methods for addr_range records
+
+=head1 SYNOPSIS
+
+  use FS::addr_range;
+
+  $record = new FS::addr_range \%hash;
+  $record = new FS::addr_range { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::addr_range object represents a contiguous range of IP 
+addresses assigned to a certain purpose.  Unlike L<FS::addr_block>,
+this isn't a routing block; the range doesn't have to be aligned on 
+a subnet boundary, and doesn't have a gateway or broadcast address.
+It's just a range.
+
+=over 4
+
+=item rangenum - primary key
+
+=item start - starting address of the range, as a dotted quad
+
+=item length - number of addresses in the range, including start
+
+=item status - what to do with the addresses in this range; currently can 
+only be "unavailable", which makes the addresses unavailable for assignment 
+to any kind of service.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new range.  To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'addr_range'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('rangenum')
+    || $self->ut_ip('start')
+    || $self->ut_number('length')
+    || $self->ut_textn('status')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item end [ IPADDR ]
+
+Get/set the end IP address in the range.  This isn't actually part of the
+record but it's convenient.
+
+=cut
+
+sub end {
+  my $self = shift;
+  # if there's no start address, just return nothing
+  my $start = NetAddr::IP->new($self->start, 0) or return '';
+
+  my $new = shift;
+  if ( $new ) {
+    my $end = NetAddr::IP->new($new, 0)
+      or die "bad end address $new";
+    if ( $end < $start ) {
+      $self->set('start', $end);
+      ($end, $start) = ($start, $end);
+    }
+    $self->set('length', $end - $start + 1);
+    return $end->addr;
+  }
+  my $end = $start + $self->get('length') - 1;
+  $end->addr;
+}
+
+=item contains IPADDR
+
+Checks whether IPADDR (a dotted-quad IPv4 address) is within the range.
+
+=cut
+
+sub contains {
+  my $self = shift;
+  my $addr = shift;
+  $addr = NetAddr::IP->new($addr, 0)
+    unless ref($addr) and UNIVERSAL::isa($addr, 'NetAddr::IP');
+  return 0 unless $addr;
+
+  my $start = NetAddr::IP->new($self->start, 0);
+
+  return ($addr >= $start and $addr - $start < $self->length) ? 1 : 0;
+} 
+
+=item as_string
+
+Returns a readable string showing the address range.
+
+=cut
+
+sub as_string {
+  my $self = shift;
+  my $start = NetAddr::IP->new($self->start, 0);
+  my $end   = $start + $self->length;
+
+  if ( $self->length == 1 ) {
+    # then just the address
+    return $self->start;
+  } else { # we have to get tricksy
+    my @end_octets = split('\.', $end->addr);
+    $start = ($start->numeric)[0] + 0;
+    $end   = ($end->numeric)[0] + 0;
+    # which octets are different between start and end?
+    my $delta = $end ^ $start;
+    foreach (0xffffff, 0xffff, 0xff) {
+      if ( $delta <= $_ ) {
+      # then they are identical in the first 8/16/24 bits
+        shift @end_octets;
+      }
+    }
+    return $self->start . '-' . join('.', @end_octets);
+  }
+}
+
+=item desc
+
+Returns a semi-friendly description of the block status.
+
+=item allow_use
+
+Returns true if addresses in this range can be used by services, etc.
+
+=cut
+
+sub desc {
+  my $self = shift;
+  $status_desc{ $self->status };
+}
+
+sub allow_use {
+  my $self = shift;
+  $status_allow_use{ $self->status };
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=sub any_contains IPADDR
+
+Returns all address ranges that contain IPADDR.
+
+=cut
+
+sub any_contains {
+  my $self = shift;
+  my $addr = shift;
+  return grep { $_->contains($addr) } qsearch('addr_range', {});
+}
+
+=head1 DEVELOPER NOTE
+
+L<NetAddr::IP> objects have netmasks.  When using them to represent 
+range endpoints, be sure to set the netmask to I<zero> so that math on 
+the address doesn't stop at the subnet boundary.  (The default is /32, 
+which doesn't work very well.  Address ranges ignore subnet boundaries.)
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_IP_Mixin>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index c66305b..a8af3a0 100644 (file)
@@ -57,7 +57,7 @@ sub _export_insert {
   my ($self, $svc_phone) = @_;
   my @login = $self->login;
 
-  my $location = $svc_phone->cust_location
+  my $location = $svc_phone->cust_location_or_main
     or return 'no e911 location defined for this phone service';
 
   warn "$me validating address for svcnum ".$svc_phone->svcnum."\n"
index 56567e8..659255e 100644 (file)
@@ -336,6 +336,7 @@ sub _check_duplcate { ''; }
 sub preinsert_hook { ''; }
 sub table_dupcheck_fields { (); }
 sub prereplace_hook { ''; }
+sub prereplace_hook_first { ''; }
 sub predelete_hook { ''; }
 sub predelete_hook_first { ''; }
 
index ba93399..1f2dfcc 100644 (file)
@@ -735,3 +735,5 @@ FS/alarm_type.pm
 t/alarm_type.t
 FS/alarm_station.pm
 t/alarm_station.t
+FS/addr_range.pm
+t/addr_range.t
index 07da88d..a6908e1 100755 (executable)
@@ -37,51 +37,8 @@ getsecrets();
 #needs to match FS::Record
 my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc;
 
-###
-
 my $username_len = 32;
 
-#print "\n\n", <<END, ":";
-#Freeside tracks the RADIUS User-Name, check attribute Password and
-#reply attribute Framed-IP-Address for each user.  You can specify additional
-#check and reply attributes (or you can add them later with the
-#fs-radius-add-check and fs-radius-add-reply programs).
-#
-#First enter any additional RADIUS check attributes you need to track for each 
-#user, separated by whitespace.
-#END
-#my @check_attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
-#                         split(" ",&getvalue);
-#
-#print "\n\n", <<END, ":";
-#Now enter any additional reply attributes you need to track for each user,
-#separated by whitespace.
-#END
-#my @attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
-#                   split(" ",&getvalue);
-#
-#print "\n\n", <<END, ":";
-#Do you wish to enable the tracking of a second, separate shipping/service
-#address?
-#END
-#my $ship = &_yesno;
-#
-#sub getvalue {
-#  my($x)=scalar(<STDIN>);
-#  chop $x;
-#  $x;
-#}
-#
-#sub _yesno {
-#  print " [y/N]:";
-#  my $x = scalar(<STDIN>);
-#  $x =~ /^y/i;
-#}
-
-#my @check_attributes = (); #add later
-#my @attributes = (); #add later
-#my $ship = $opt_s;
-
 ###
 # create a dbdef object from the old data structure
 ###
diff --git a/FS/t/addr_range.t b/FS/t/addr_range.t
new file mode 100644 (file)
index 0000000..6747d67
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::addr_range;
+$loaded=1;
+print "ok 1\n";
index 5467ead..a3761d1 100755 (executable)
@@ -23,7 +23,7 @@ my $oldAutoCommit = $FS::UID::AutoCommit;
 local $FS::UID::AutoCommit = 0;
 my $dbh = dbh;
 
-my $fuzz = 2;
+#my $fuzz = 2;
 
 my $changed = 0;
 
@@ -35,14 +35,10 @@ foreach my $cust_pkg (
          })
 ) {
 
-  #XXX only canceled packages?
-  #XXX only suspended packages?
-
   my $h_cust_pkg =
     qsearchs({ table       => 'h_cust_pkg',
                hashref     => { 
                                 pkgnum         => $cust_pkg->pkgnum,
-                                history_action => 'replace_old',
                                 setup          => { op=>'!=', value=>'' },
                                 ($opt_u ? ('susp' => { op=>'!=', value=>'' })
                                         : ()
@@ -51,7 +47,8 @@ foreach my $cust_pkg (
                                         : ()
                                 ),
                               },
-               extra_sql   => ' AND history_date >= ? AND history_date <= ? ',
+               extra_sql   => " AND history_action IN ('insert','replace_old')".
+                              ' AND history_date >= ? AND history_date <= ? ',
                extra_param => [ [$sdate,'int'], [$edate,'int'] ],
                order_by    => 'ORDER BY history_date DESC LIMIT 1',
             })
@@ -60,14 +57,20 @@ foreach my $cust_pkg (
   $changed++;
 
   #if ( $opt_r ) {
-    print "restoring setup for pkgnum ". $cust_pkg->pkgnum.
-          " (custnum ". $cust_pkg->custnum.
-          ") to ". time2str('%D', $h_cust_pkg->setup). "\n";
+    #print "restoring setup for pkgnum ". $cust_pkg->pkgnum.
+    #      " (custnum ". $cust_pkg->custnum.
+    #      ") to ". time2str('%D', $h_cust_pkg->setup). "\n";
+    print $cust_pkg->pkgnum. ','.
+          time2str('%D', $h_cust_pkg->setup). ','.
+          $cust_pkg->custnum. ','.
+          '"'. $cust_pkg->cust_main->name. '"'. "\n";
   #}
 
-  $cust_pkg->set('setup', $h_cust_pkg->setup);
-  my $error = $cust_pkg->replace;
-  die $error if $error;
+  #don't actually do it yet ...
+  #$cust_pkg->set('setup', $h_cust_pkg->setup);
+  #my $error = $cust_pkg->replace;
+  ##die $error if $error;
+  #warn "error changing pkgnum ". $cust_pkg->pkgnum.': '. $error."\n";
 
 }
 
index 8b1dc19..4805877 100644 (file)
@@ -34,7 +34,7 @@ if ( ! $error ) {
 
     $custnum = $response['custnum'];
 
-    error_log("[new_customer] signup up with custnum $custnum");
+    error_log("[new_customer] new signup with custnum $custnum");
 
 } else {
 
diff --git a/httemplate/browse/addr_range.html b/httemplate/browse/addr_range.html
new file mode 100644 (file)
index 0000000..d657f32
--- /dev/null
@@ -0,0 +1,66 @@
+<& elements/browse.html,
+  'title'         => 'Address Ranges',
+  'name_singular' => 'address range',
+  'html_init'     => $html_init,
+  'html_foot'     => $html_foot,
+  'query'         => { 'table'     => 'addr_range',
+                       'order_by'  => $order_by,
+                     },
+  'count_query'   => "SELECT count(*) from addr_range",
+  'header'        => [ 'From',
+                       '', # the dash
+                       'To',
+                       'Status',
+                       # would be nice to show whether any addresses in the 
+                       # range are assigned, but that's ugly
+                     ],
+  'fields'        => [ 'start',
+                       sub { '&ndash;' },
+                       'end',
+                       'desc',
+                     ],
+  'links'         => [
+                       [ '#' ],
+                       '',
+                       [ '#' ],
+                     ],
+  'link_onclicks' => [ $edit_link,
+                       '',
+                       $edit_link,
+                       '',
+                     ],
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+# addr_ranges are ALWAYS global, else there will be chaos
+
+my $order_by = "ORDER BY inet(start)"; # Pg-ism
+# though we could also make the field itself inet-type...
+# this would simplify a lot of things.
+
+my $html_init = include('/elements/error.html');
+
+my $edit_link = sub {
+  my $addr_range = shift;
+  include('/elements/popup_link_onclick.html',
+    action      => $p.'edit/addr_range.html?rangenum='.
+                   $addr_range->rangenum,
+    actionlabel => 'Edit address range',
+    width       => 650,
+    height      => 420,
+  );
+};
+
+my $add_link = include('/elements/popup_link_onclick.html',
+    action      => $p.'edit/addr_range.html',
+    actionlabel => 'Edit address range',
+    width       => 650,
+    height      => 420,
+);
+
+my $html_foot = qq!<A HREF="#" onclick="$add_link">
+<I>Add a new address range</I></A>!;
+
+</%init>
diff --git a/httemplate/edit/addr_range.html b/httemplate/edit/addr_range.html
new file mode 100644 (file)
index 0000000..68efa5d
--- /dev/null
@@ -0,0 +1,27 @@
+<& elements/edit.html,
+  'name_singular' => 'address range',
+  'popup'  => 1,
+  'table'  => 'addr_range',
+  'labels' => { 'start'   => 'From',
+                'end'     => 'To',
+                'status'  => 'Status',
+                'rangenum'=> 'Range',
+              },  
+  'fields' => [ 'start',
+                'end',
+                { field => 'status',
+                  type  => 'select',
+                  labels  => \%FS::addr_range::status_desc,
+                  options => [ sort { $a cmp $b } 
+                               keys(%FS::addr_range::status_desc) ],
+                  disable_empty => 1,
+                },
+              ],
+  'delete_url' => $p.'misc/delete-addr_range.html',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+</%init>
index da5f0f2..1088cf5 100644 (file)
@@ -679,8 +679,8 @@ my $conf = new FS::Conf;
 my $payby_default = $conf->config('payby-default');
 
 my @payby = grep /\w/, $conf->config('payby');
-#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
-@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+#@payby = (qw( CARD DCRD CHEK DCHK BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK BILL CASH COMP ))
   unless @payby;
 
 my $show_term = '';
index 9e27f2a..16d0817 100644 (file)
@@ -108,6 +108,9 @@ Example:
     # overrides default popurl(1)."process/$table.html"
     'post_url' => popurl(1).'process/something', 
 
+    # optional link to delete this object; primary key will be appended
+    'delete_url' => $p.'misc/delete-something.html?',
+
     #we're in a popup (no title/menu/searchboxes)
     'popup' => 1,
 
@@ -211,6 +214,7 @@ Example:
 %     );
 %   }
 
+
   <% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
                 $title,
                 include( '/elements/menubar.html', @menubar ),
@@ -773,6 +777,23 @@ Example:
                               : "Add ". ($opt{'name'} || $opt{'name_singular'})
                          %>"
       >
+%     if ( $opt{'delete_url'} and $object->get($pkey) ) {
+%       my $delete_msg = 'Delete this '.
+%          ($opt{'name_singular'} || $opt{'name'});
+%       my $delete_url = $opt{'delete_url'};
+%       $delete_url .= '?' unless $delete_url =~ /\?/;
+%       $delete_url .= $object->get($pkey);
+        <SCRIPT TYPE="text/javascript">
+        function confirm_delete() {
+          if(confirm(<% $delete_msg . '?' |js_string %>)) {
+            window.location.href = <% $delete_url |js_string %>;
+          }
+        }
+        </SCRIPT>
+        <INPUT TYPE     = "button"
+               VALUE    = "<% $delete_msg |h %>"
+               onclick  = "confirm_delete()">
+%     }
 %   }
 
   </FORM>
diff --git a/httemplate/edit/process/addr_range.html b/httemplate/edit/process/addr_range.html
new file mode 100644 (file)
index 0000000..6b05d23
--- /dev/null
@@ -0,0 +1,22 @@
+<& elements/process.html,
+  'table'           => 'addr_range',
+  'popup_reload'    => 'Address range changed',
+  'precheck_callback' => sub {
+    my ($cgi) = @_;
+    my $start = NetAddr::IP->new($cgi->param('start'), 0)
+      or return 'Illegal or empty (IP address) start: '.$cgi->param('start');
+    if ( length($cgi->param('end')) ) {
+      my $end = NetAddr::IP->new($cgi->param('end'), 0)
+        or return 'Illegal or empty (IP address) end: '.$cgi->param('end');
+      if ( $end < $start ) {
+        ($start, $end) = ($end, $start);
+        $cgi->param('end', $end->addr);
+        $cgi->param('start', $start->addr);
+      }
+      $cgi->param('length', $end - $start + 1);
+    } else {
+      $cgi->param('length', 1);
+    }
+    '';
+  },
+&>
index b4fff22..2ae216c 100644 (file)
@@ -508,6 +508,10 @@ tie my %config_broadband, 'Tie::IxHash',
   'Routers'        => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
   'Address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
 ;
+if ( $curuser->access_right('Broadband global configuration') ) {
+  $config_broadband{'Address ranges'} = 
+                      [ $fsurl.'browse/addr_range.html', 'Designate special address ranges' ];
+}
 
 tie my %config_phone, 'Tie::IxHash',
   'View/Edit phone device types' => [ $fsurl.'browse/part_device.html', 'Phone device types' ],
diff --git a/httemplate/misc/delete-addr_range.html b/httemplate/misc/delete-addr_range.html
new file mode 100644 (file)
index 0000000..c6310e9
--- /dev/null
@@ -0,0 +1,21 @@
+% if ( $error ) {
+<& /elements/errorpage-popup.html, $error &>
+% } else {
+<& /elements/header-popup.html, "Address range deleted" &>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+</BODY>
+</HTML>
+% }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+my ($rangenum) = $cgi->keywords;
+$rangenum =~ /^\d+$/ or die "bad rangenum '$rangenum'";
+my $addr_range = FS::addr_range->by_key($rangenum);
+die "unknown rangenum $rangenum" unless $addr_range;
+my $error = $addr_range->delete;
+</%init>