--- loncom/interface/domainprefs.pm	2011/08/09 00:54:43	1.149
+++ loncom/interface/domainprefs.pm	2012/05/30 16:51:34	1.160.6.3
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.149 2011/08/09 00:54:43 raeburn Exp $
+# $Id: domainprefs.pm,v 1.160.6.3 2012/05/30 16:51:34 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -171,6 +171,9 @@ use Locale::Language;
 use DateTime::TimeZone;
 use DateTime::Locale;
 
+my $registered_cleanup;
+my $modified_urls;
+
 sub handler {
     my $r=shift;
     if ($r->header_only) {
@@ -190,6 +193,10 @@ sub handler {
         "/adm/domainprefs:mau:0:0:Cannot modify domain settings";
         return HTTP_NOT_ACCEPTABLE;
     }
+
+    $registered_cleanup=0;
+    @{$modified_urls}=();
+
     &Apache::lonhtmlcommon::clear_breadcrumbs();
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                             ['phase','actions']);
@@ -197,18 +204,22 @@ sub handler {
     if ( exists($env{'form.phase'}) ) {
         $phase = $env{'form.phase'};
     }
+    my %servers = &Apache::lonnet::internet_dom_servers($dom);
     my %domconfig =
       &Apache::lonnet::get_dom('configuration',['login','rolecolors',
                 'quotas','autoenroll','autoupdate','autocreate',
                 'directorysrch','usercreation','usermodification',
                 'contacts','defaults','scantron','coursecategories',
-                'serverstatuses','requestcourses','helpsettings',
-                'coursedefaults','usersessions'],$dom);
+                'serverstatuses','requestcourses','usersessions',
+                'loadbalancing'],$dom);
     my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll',
                        'autoupdate','autocreate','directorysrch','contacts',
                        'usercreation','usermodification','scantron',
-                       'requestcourses','coursecategories','serverstatuses','helpsettings',
-                       'coursedefaults','usersessions');
+                       'requestcourses','coursecategories','serverstatuses',
+                       'usersessions');
+    if (keys(%servers) > 1) {
+        push(@prefs_order,'loadbalancing');
+    }
     my %prefs = (
         'rolecolors' =>
                    { text => 'Default color schemes',
@@ -329,28 +340,6 @@ sub handler {
                               col3 => 'Specific IPs',
                             }],
                  },
-        'helpsettings' =>
-                 {text   => 'Help page settings',
-                  help   => 'Domain_Configuration_Help_Settings',
-                  header => [{col1 => 'Authenticated Help Settings',
-                              col2 => ''},
-                             {col1 => 'Unauthenticated Help Settings',
-                              col2 => ''}],
-                 },
-        'coursedefaults' => 
-                 {text => 'Course/Community defaults',
-                  help => 'Domain_Configuration_Course_Defaults',
-                  header => [{col1 => 'Defaults which can be overridden in each course by a CC',
-                              col2 => 'Value',},
-                             {col1 => 'Defaults which can be overridden for each course by a DC',
-                              col2 => 'Value',},],
-                 },
-        'privacy' => 
-                 {text   => 'User Privacy',
-                  help   => 'Domain_Configuration_User_Privacy',
-                  header => [{col1 => 'Setting',
-                              col2 => 'Value',}],
-                 },
         'usersessions' =>
                  {text  => 'User session hosting/offloading',
                   help  => 'Domain_Configuration_User_Sessions',
@@ -361,8 +350,16 @@ sub handler {
                              {col1 => "Hosting domain's own users elsewhere",
                               col2 => 'Rules'}],
                  },
+         'loadbalancing' =>
+                 {text  => 'Dedicated Load Balancer',
+                  help  => 'Domain_Configuration_Load_Balancing',
+                  header => [{col1 => 'Server',
+                              col2 => 'Default destinations',
+                              col3 => 'User affliation',
+                              col4 => 'Overrides'},
+                            ],
+                 },
     );
-    my %servers = &Apache::lonnet::internet_dom_servers($dom);
     if (keys(%servers) > 1) {
         $prefs{'login'}  = { text   => 'Log-in page options',
                              help   => 'Domain_Configuration_Login_Page',
@@ -381,7 +378,16 @@ sub handler {
     if ($phase eq 'process') {
         &Apache::lonconfigsettings::make_changes($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,\@roles);
     } elsif ($phase eq 'display') {
-        &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname);
+        my $js;
+        if (keys(%servers) > 1) {
+            my ($othertitle,$usertypes,$types) =
+                &Apache::loncommon::sorted_inst_types($dom);
+            $js = &lonbalance_targets_js($dom,$types,\%servers).
+                  &new_spares_js().
+                  &common_domprefs_js().
+                  &Apache::loncommon::javascript_array_indexof();
+        }
+        &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,$js);
     } else {
         if (keys(%domconfig) == 0) {
             my $primarylibserv = &Apache::lonnet::domain($dom,'primary');
@@ -459,12 +465,10 @@ sub process_changes {
         $output = &modify_serverstatuses($dom,%domconfig);
     } elsif ($action eq 'requestcourses') {
         $output = &modify_quotas($dom,$action,%domconfig);
-    } elsif ($action eq 'helpsettings') {
-        $output = &modify_helpsettings($r,$dom,$confname,%domconfig);
-    } elsif ($action eq 'coursedefaults') {
-        $output = &modify_coursedefaults($dom,%domconfig);
     } elsif ($action eq 'usersessions') {
         $output = &modify_usersessions($dom,%domconfig);
+    } elsif ($action eq 'loadbalancing') {
+        $output = &modify_loadbalancing($dom,%domconfig);
     }
     return $output;
 }
@@ -519,14 +523,10 @@ sub print_config_box {
             $colspan = ' colspan="2"';
         } elsif ($action eq 'requestcourses') {
             $output .= &print_quotas($dom,$settings,\$rowtotal,$action);
-        } elsif ($action eq 'helpsettings') {
-            $output .= &print_helpsettings('top',$dom,$confname,$settings,\$rowtotal);
         } elsif ($action eq 'usersessions') {
             $output .= &print_usersessions('top',$dom,$settings,\$rowtotal); 
         } elsif ($action eq 'rolecolors') {
             $output .= &print_rolecolors($phase,'student',$dom,$confname,$settings,\$rowtotal);
-        } elsif ($action eq 'coursedefaults') {
-            $output .= &print_coursedefaults('top',$dom,$settings,\$rowtotal);
         }
         $output .= '
            </table>
@@ -661,13 +661,22 @@ sub print_config_box {
         }
         $output .= '</td>';
         if ($item->{'header'}->[0]->{'col3'}) {
-            $output .= '<td class="LC_right_item" valign="top">'.
-                       &mt($item->{'header'}->[0]->{'col3'});
+            if (defined($item->{'header'}->[0]->{'col4'})) {
+                $output .= '<td class="LC_left_item" valign="top">'.
+                            &mt($item->{'header'}->[0]->{'col3'});
+            } else {
+                $output .= '<td class="LC_right_item" valign="top">'.
+                           &mt($item->{'header'}->[0]->{'col3'});
+            }
             if ($action eq 'serverstatuses') {
                 $output .= '<br />(<tt>'.&mt('IP1,IP2 etc.').'</tt>)';
             }
             $output .= '</td>';
         }
+        if ($item->{'header'}->[0]->{'col4'}) {
+            $output .= '<td class="LC_right_item" valign="top">'.
+                       &mt($item->{'header'}->[0]->{'col4'});
+        }
         $output .= '</tr>';
         $rowtotal ++;
         if ($action eq 'login') {
@@ -691,6 +700,8 @@ sub print_config_box {
             $output .= &print_serverstatuses($dom,$settings,\$rowtotal);
         } elsif ($action eq 'helpsettings') {
             $output .= &print_helpsettings('top',$dom,$confname,$settings,\$rowtotal);
+        } elsif ($action eq 'loadbalancing') {
+            $output .= &print_loadbalancing($dom,$settings,\$rowtotal);
         }
     }
     $output .= '
@@ -1017,6 +1028,7 @@ sub print_rolecolors {
 sub display_color_options {
     my ($dom,$confname,$phase,$role,$itemcount,$choices,$is_custom,$defaults,$designs,
         $images,$bgs,$links,$alt_text,$rowtotal,$logintext) = @_;
+    my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
     my $css_class = $itemcount%2?' class="LC_odd_row"':'';
     my $datatable = '<tr'.$css_class.'>'.
         '<td>'.$choices->{'font'}.'</td>';
@@ -1094,11 +1106,11 @@ sub display_color_options {
                 $showfile = $imgfile;
                 my $imgdir = $1;
                 my $filename = $2;
-                if (-e "/home/httpd/html/$imgdir/tn-".$filename) {
+                if (-e "$londocroot/$imgdir/tn-".$filename) {
                     $showfile = "/$imgdir/tn-".$filename;
                 } else {
-                    my $input = "/home/httpd/html".$imgfile;
-                    my $output = '/home/httpd/html/'.$imgdir.'/tn-'.$filename;
+                    my $input = $londocroot.$imgfile;
+                    my $output = "$londocroot/$imgdir/tn-".$filename;
                     if (!-e $output) {
                         my ($width,$height) = &thumb_dimensions();
                         my ($fullwidth,$fullheight) = &check_dimensions($input);
@@ -1106,7 +1118,7 @@ sub display_color_options {
                             if ($fullwidth > $width && $fullheight > $height) { 
                                 my $size = $width.'x'.$height;
                                 system("convert -sample $size $input $output");
-                                $showfile = '/'.$imgdir.'/tn-'.$filename;
+                                $showfile = "/$imgdir/tn-".$filename;
                             }
                         }
                     }
@@ -2306,21 +2318,22 @@ sub print_usersessions {
     &build_location_hashes(\@intdoms,\%by_ip,\%by_location);
 
     my @alldoms = &Apache::lonnet::all_domains();
-    my %uniques = &Apache::lonnet::get_unique_servers(\@alldoms);
+    my %serverhomes = %Apache::lonnet::serverhomeIDs;
     my %servers = &Apache::lonnet::internet_dom_servers($dom);
+    my %altids = &id_for_thisdom(%servers);
     my $itemcount = 1;
     if ($position eq 'top') {
-        if (keys(%uniques) > 1) {
+        if (keys(%serverhomes) > 1) {
             my %spareid = &current_offloads_to($dom,$settings,\%servers);
-            $datatable .= &spares_row(\%servers,\%spareid,\%uniques,$rowtotal);
+            $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids,$rowtotal);
         } else {
             $datatable .= '<tr'.$css_class.'><td colspan="2">'.
-                          &mt('Nothing to set here, as the cluster to which this domain belongs only contains this server.');
+                          &mt('Nothing to set here, as the cluster to which this domain belongs only contains one server.');
         }
     } else {
         if (keys(%by_location) == 0) {
             $datatable .= '<tr'.$css_class.'><td colspan="2">'.
-                          &mt('Nothing to set here, as the cluster to which this domain belongs only contains this institution.');
+                          &mt('Nothing to set here, as the cluster to which this domain belongs only contains one institution.');
         } else {
             my %lt = &usersession_titles();
             my $numinrow = 5;
@@ -2485,14 +2498,16 @@ sub build_location_hashes {
 sub current_offloads_to {
     my ($dom,$settings,$servers) = @_;
     my (%spareid,%otherdomconfigs);
-    if ((ref($settings) eq 'HASH') && (ref($servers) eq 'HASH')) {
+    if (ref($servers) eq 'HASH') {
         foreach my $lonhost (sort(keys(%{$servers}))) {
             my $gotspares;
-            if (ref($settings->{'spares'}) eq 'HASH') {
-                if (ref($settings->{'spares'}{$lonhost}) eq 'HASH') {
-                    $spareid{$lonhost}{'primary'} = $settings->{'spares'}{$lonhost}{'primary'};
-                    $spareid{$lonhost}{'default'} = $settings->{'spares'}{$lonhost}{'default'};
-                    $gotspares = 1;
+            if (ref($settings) eq 'HASH') {
+                if (ref($settings->{'spares'}) eq 'HASH') {
+                    if (ref($settings->{'spares'}{$lonhost}) eq 'HASH') {
+                        $spareid{$lonhost}{'primary'} = $settings->{'spares'}{$lonhost}{'primary'};
+                        $spareid{$lonhost}{'default'} = $settings->{'spares'}{$lonhost}{'default'};
+                        $gotspares = 1;
+                    }
                 }
             }
             unless ($gotspares) {
@@ -2538,13 +2553,18 @@ sub current_offloads_to {
                         $spareid{$lonhost}{'primary'} = $Apache::lonnet::spareid{'primary'};
                         $spareid{$lonhost}{'default'} = $Apache::lonnet::spareid{'default'};
                     } else {
-                        my %requested;
-                        $requested{'spareid'} = 'HASH';
-                        my %returnhash = &Apache::lonnet::get_remote_globals($lonhost,\%requested);
-                        my $spareshash = $returnhash{'spareid'};
-                        if (ref($spareshash) eq 'HASH') {
-                            $spareid{$lonhost}{'primary'} = $spareshash->{'primary'};
-                            $spareid{$lonhost}{'default'} = $spareshash->{'default'};
+                        my %what = (
+                             spareid => 1,
+                        );
+                        my ($result,$returnhash) = 
+                            &Apache::lonnet::get_remote_globals($lonhost,\%what);
+                        if ($result eq 'ok') { 
+                            if (ref($returnhash) eq 'HASH') {
+                                if (ref($returnhash->{'spareid'}) eq 'HASH') {
+                                    $spareid{$lonhost}{'primary'} = $returnhash->{'spareid'}->{'primary'};
+                                    $spareid{$lonhost}{'default'} = $returnhash->{'spareid'}->{'default'};
+                                }
+                            }
                         }
                     }
                 }
@@ -2555,54 +2575,101 @@ sub current_offloads_to {
 }
 
 sub spares_row {
-    my ($servers,$spareid,$uniques,$rowtotal) = @_;
+    my ($dom,$servers,$spareid,$serverhomes,$altids,$rowtotal) = @_;
     my $css_class;
     my $numinrow = 4;
     my $itemcount = 1;
     my $datatable;
-    if ((ref($servers) eq 'HASH') && (ref($spareid) eq 'HASH')) {
+    my %typetitles = &sparestype_titles();
+    if ((ref($servers) eq 'HASH') && (ref($spareid) eq 'HASH') && (ref($altids) eq 'HASH')) {
         foreach my $server (sort(keys(%{$servers}))) {
+            my $serverhome = &Apache::lonnet::get_server_homeID($servers->{$server});
+            my ($othercontrol,$serverdom);
+            if ($serverhome ne $server) {
+                $serverdom = &Apache::lonnet::host_domain($serverhome);
+                $othercontrol = &mt('Session offloading controlled by domain: [_1]','<b>'.$serverdom.'</b>');
+            } else {
+                $serverdom = &Apache::lonnet::host_domain($server);
+                if ($serverdom ne $dom) {
+                    $othercontrol = &mt('Session offloading controlled by domain: [_1]','<b>'.$serverdom.'</b>');
+                }
+            }
+            next unless (ref($spareid->{$server}) eq 'HASH');
             $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
             $datatable .= '<tr'.$css_class.'>
                            <td rowspan="2">
-                            <span class="LC_nobreak"><b>'.$server.'</b> when busy, offloads to:</span></td>';
+                            <span class="LC_nobreak"><b>'.$server.'</b> when busy, offloads to:</span></td>'."\n";
             my (%current,%canselect);
-            if (ref($spareid->{$server}) eq 'HASH') {
-                foreach my $type ('primary','default') {
+            my @choices = 
+                &possible_newspares($server,$spareid->{$server},$serverhomes,$altids);
+            foreach my $type ('primary','default') {
+                if (ref($spareid->{$server}) eq 'HASH') {
                     if (ref($spareid->{$server}{$type}) eq 'ARRAY') {
                         my @spares = @{$spareid->{$server}{$type}};
                         if (@spares > 0) {
-                            $current{$type} .= '<table>';
-                            for (my $i=0;  $i<@spares; $i++) {
-                                my $rem = $i%($numinrow);
-                                if ($rem == 0) {
-                                    if ($i > 0) {
-                                        $current{$type} .= '</tr>';
+                            if ($othercontrol) {
+                                $current{$type} = join(', ',@spares);
+                            } else {
+                                $current{$type} .= '<table>';
+                                my $numspares = scalar(@spares);
+                                for (my $i=0;  $i<@spares; $i++) {
+                                    my $rem = $i%($numinrow);
+                                    if ($rem == 0) {
+                                        if ($i > 0) {
+                                            $current{$type} .= '</tr>';
+                                        }
+                                        $current{$type} .= '<tr>';
                                     }
-                                    $current{$type} .= '<tr>';
+                                    $current{$type} .= '<td><label><input type="checkbox" name="spare_'.$type.'_'.$server.'" id="spare_'.$type.'_'.$server.'_'.$i.'" checked="checked" value="'.$spareid->{$server}{$type}[$i].'" onclick="updateNewSpares(this.form,'."'$server'".');" />&nbsp;'.
+                                                       $spareid->{$server}{$type}[$i].
+                                                       '</label></td>'."\n";
                                 }
-                                $current{$type} .= '<td><label><input type="checkbox" name="spare_'.$type.'_'.$server.'" checked="checked" value="'.$spareid->{$server}{$type}[$i].'" />&nbsp;'.
-                                                   $spareid->{$server}{$type}[$i].
-                                                   '</label></td>';
+                                my $rem = @spares%($numinrow);
+                                my $colsleft = $numinrow - $rem;
+                                if ($colsleft > 1 ) {
+                                    $current{$type} .= '<td colspan="'.$colsleft.
+                                                       '" class="LC_left_item">'.
+                                                       '&nbsp;</td>';
+                                } elsif ($colsleft == 1) {
+                                    $current{$type} .= '<td class="LC_left_item">&nbsp;</td>'."\n";
+                                }
+                                $current{$type} .= '</tr></table>';
                             }
-                            $current{$type} .= '</tr></table>';
                         }
                     }
                     if ($current{$type} eq '') {
                         $current{$type} = &mt('None specified');
                     }
-                    $canselect{$type} =
-                        &newspare_select($server,$type,$spareid->{$server}{$type},$uniques);
+                    if ($othercontrol) {
+                        if ($type eq 'primary') {
+                            $canselect{$type} = $othercontrol;
+                        }
+                    } else {
+                        $canselect{$type} = 
+                            &mt('Add new [_1]'.$type.'[_2]:','<i>','</i>').'&nbsp;'.
+                            '<select name="newspare_'.$type.'_'.$server.'" '.
+                            'id="newspare_'.$type.'_'.$server.'" onchange="checkNewSpares('."'$server','$type'".');">'."\n".
+                            '<option value="" selected ="selected">'.&mt('Select').'</option>'."\n";
+                        if (@choices > 0) {
+                            foreach my $lonhost (@choices) {
+                                $canselect{$type} .= '<option value="'.$lonhost.'">'.$lonhost.'</option>'."\n";
+                            }
+                        }
+                        $canselect{$type} .= '</select>'."\n";
+                    }
+                } else {
+                    $current{$type} = &mt('Could not be determined');
+                    if ($type eq 'primary') {
+                        $canselect{$type} =  $othercontrol;
+                    }
+                }
+                if ($type eq 'default') {
+                    $datatable .= '<tr'.$css_class.'>';
                 }
+                $datatable .= '<td><i>'.$typetitles{$type}.'</i></td>'."\n".
+                              '<td>'.$current{$type}.'</td>'."\n".
+                              '<td>'.$canselect{$type}.'</td></tr>'."\n";
             }
-            $datatable .= '<td><i>'.&mt('primary').'</i><td>'.$current{'primary'}.'</td>'.
-                          '<td>'.&mt('Add new [_1]primary[_2]:','<i>','</i>').'&nbsp;'.
-                          $canselect{'primary'}.'</td></tr>'.
-                          '<tr'.$css_class.'>'.
-                          '<td><i>'.&mt('default').'</i></td>'.
-                          '<td>'.$current{'default'}.'</td>'.
-                          '<td>'.&mt('Add new [_1]default[_2]:','<i>','</i>').'&nbsp;'.
-                          $canselect{'default'}.'</td></tr>';
             $itemcount ++;
         }
     }
@@ -2610,28 +2677,283 @@ sub spares_row {
     return $datatable;
 }
 
-sub newspare_select {
-    my ($server,$type,$currspares,$uniques) = @_;
-    my $output;
-    if (ref($uniques) eq 'HASH') {
-        if (keys(%{$uniques}) > 1) {
-            $output = '<select name="newspare_'.$type.'_'.$server.'">'."\n".
-                      '<option value="" selected ="selected">'.&mt('Select').'</option>'."\n";
-            foreach my $lonhost (sort(keys(%{$uniques}))) {
-                next if ($lonhost eq $server);
-                if (ref($currspares) eq 'ARRAY') {
-                    if (@{$currspares} > 0) {
-                        next if (grep(/^\Q$lonhost\E$/,@{$currspares}));
+sub possible_newspares {
+    my ($server,$currspares,$serverhomes,$altids) = @_;
+    my $serverhostname = &Apache::lonnet::hostname($server);
+    my %excluded;
+    if ($serverhostname ne '') {
+        %excluded = (
+                       $serverhostname => 1,
+                    );
+    }
+    if (ref($currspares) eq 'HASH') {
+        foreach my $type (keys(%{$currspares})) {
+            if (ref($currspares->{$type}) eq 'ARRAY') {
+                if (@{$currspares->{$type}} > 0) {
+                    foreach my $curr (@{$currspares->{$type}}) {
+                        my $hostname = &Apache::lonnet::hostname($curr);
+                        $excluded{$hostname} = 1;
                     }
                 }
-                $output .= '<option value="'.$lonhost.'">'.$lonhost.'</option>'."\n";
             }
-            $output .= '<select>';
+        }
+    }
+    my @choices;
+    if ((ref($serverhomes) eq 'HASH') && (ref($altids) eq 'HASH')) {
+        if (keys(%{$serverhomes}) > 1) {
+            foreach my $name (sort(keys(%{$serverhomes}))) {
+                unless ($excluded{$name}) {
+                    if (exists($altids->{$serverhomes->{$name}})) {
+                        push(@choices,$altids->{$serverhomes->{$name}});
+                    } else {
+                        push(@choices,$serverhomes->{$name});
+                    }
+                }
+            }
+        }
+    }
+    return sort(@choices);
+}
+
+sub print_loadbalancing {
+    my ($dom,$settings,$rowtotal) = @_;
+    my $primary_id = &Apache::lonnet::domain($dom,'primary');
+    my $intdom = &Apache::lonnet::internet_dom($primary_id);
+    my $numinrow = 1;
+    my $datatable;
+    my %servers = &Apache::lonnet::internet_dom_servers($dom);
+    my ($currbalancer,$currtargets,$currrules);
+    if (keys(%servers) > 1) {
+        if (ref($settings) eq 'HASH') {
+            $currbalancer = $settings->{'lonhost'};
+            $currtargets = $settings->{'targets'};
+            $currrules = $settings->{'rules'};
+        } else {
+            ($currbalancer,$currtargets) = 
+                &Apache::lonnet::get_lonbalancer_config(\%servers);
+        }
+    } else {
+        return;
+    }
+    my ($othertitle,$usertypes,$types) =
+        &Apache::loncommon::sorted_inst_types($dom);
+    my $rownum = 6;
+    if (ref($types) eq 'ARRAY') {
+        $rownum += scalar(@{$types});
+    }
+    my $css_class = ' class="LC_odd_row"';
+    my $targets_div_style = 'display: none';
+    my $disabled_div_style = 'display: block';
+    my $homedom_div_style = 'display: none';
+    $datatable = '<tr'.$css_class.'>'.
+                 '<td rowspan="'.$rownum.'" valign="top">'.
+                 '<p><select name="loadbalancing_lonhost" onchange="toggleTargets();">'."\n".
+                 '<option value=""';
+    if (($currbalancer eq '') || (!grep(/^\Q$currbalancer\E$/,keys(%servers)))) {
+        $datatable .= ' selected="selected"';
+    } else {
+        $targets_div_style = 'display: block';
+        $disabled_div_style = 'display: none';
+        if ($dom eq &Apache::lonnet::host_domain($currbalancer)) {
+            $homedom_div_style = 'display: block'; 
+        }
+    }
+    $datatable .= '>'.&mt('None').'</option>'."\n";
+    foreach my $lonhost (sort(keys(%servers))) {
+        my $selected;
+        if ($lonhost eq $currbalancer) {
+            $selected .= ' selected="selected"';
+        }
+        $datatable .= '<option value="'.$lonhost.'"'.$selected.'>'.$lonhost.'</option>'."\n";
+    }
+    $datatable .= '</select></p></td><td rowspan="'.$rownum.'" valign="top">'.
+                  '<div id="loadbalancing_disabled" style="'.$disabled_div_style.'">'.&mt('No dedicated Load Balancer').'</div>'."\n".
+                  '<div id="loadbalancing_targets" style="'.$targets_div_style.'">'.&mt('Offloads to:').'<br />';
+    my ($numspares,@spares) = &count_servers($currbalancer,%servers);
+    my @sparestypes = ('primary','default');
+    my %typetitles = &sparestype_titles();
+    foreach my $sparetype (@sparestypes) {
+        my $targettable;
+        for (my $i=0; $i<$numspares; $i++) {
+            my $checked;
+            if (ref($currtargets) eq 'HASH') {
+                if (ref($currtargets->{$sparetype}) eq 'ARRAY') {
+                    if (grep(/^\Q$spares[$i]\E$/,@{$currtargets->{$sparetype}})) {
+                        $checked = ' checked="checked"';
+                    }
+                }
+            }
+            my $chkboxval;
+            if (($currbalancer ne '') && (grep((/^\Q$currbalancer\E$/,keys(%servers))))) {
+                $chkboxval = $spares[$i];
+            }
+            $targettable .= '<td><label><input type="checkbox" name="loadbalancing_target_'.$sparetype.'"'.
+                      $checked.' value="'.$chkboxval.'" id="loadbalancing_target_'.$sparetype.'_'.$i.'" onclick="checkOffloads('."this,'$sparetype'".');" /><span id="loadbalancing_targettxt_'.$sparetype.'_'.$i.'">&nbsp;'.$chkboxval.
+                      '</span></label></td>';
+            my $rem = $i%($numinrow);
+            if ($rem == 0) {
+                if ($i > 0) {
+                    $targettable .= '</tr>';
+                }
+                $targettable .= '<tr>';
+            }
+        }
+        if ($targettable ne '') {
+            my $rem = $numspares%($numinrow);
+            my $colsleft = $numinrow - $rem;
+            if ($colsleft > 1 ) {
+                $targettable .= '<td colspan="'.$colsleft.'" class="LC_left_item">'.
+                                '&nbsp;</td>';
+            } elsif ($colsleft == 1) {
+                $targettable .= '<td class="LC_left_item">&nbsp;</td>';
+            }
+            $datatable .=  '<i>'.$typetitles{$sparetype}.'</i><br />'.
+                           '<table><tr>'.$targettable.'</table><br />';
+        }
+    }
+    $datatable .= '</div></td></tr>'.
+                  &loadbalancing_rules($dom,$intdom,$currrules,$othertitle,
+                                       $usertypes,$types,\%servers,$currbalancer,
+                                       $targets_div_style,$homedom_div_style,$css_class);
+    $$rowtotal += $rownum;
+    return $datatable;
+}
+
+sub loadbalancing_rules {
+    my ($dom,$intdom,$currrules,$othertitle,$usertypes,$types,$servers,
+        $currbalancer,$targets_div_style,$homedom_div_style,$css_class) = @_;
+    my $output;
+    my ($alltypes,$othertypes,$titles) = 
+        &loadbalancing_titles($dom,$intdom,$usertypes,$types);
+    if ((ref($alltypes) eq 'ARRAY') && (ref($titles) eq 'HASH'))  {
+        foreach my $type (@{$alltypes}) {
+            my $current;
+            if (ref($currrules) eq 'HASH') {
+                $current = $currrules->{$type};
+            }
+            if (($type eq '_LC_external') || ($type eq '_LC_internetdom')) {
+                if ($dom ne &Apache::lonnet::host_domain($currbalancer)) {
+                    $current = '';
+                }
+            }
+            $output .= &loadbalance_rule_row($type,$titles->{$type},$current,
+                                             $servers,$currbalancer,$dom,
+                                             $targets_div_style,$homedom_div_style,$css_class);
         }
     }
     return $output;
 }
 
+sub loadbalancing_titles {
+    my ($dom,$intdom,$usertypes,$types) = @_;
+    my %othertypes = (
+           '_LC_adv'         => &mt('Advanced users from [_1]',$dom),
+           '_LC_author'      => &mt('Users from [_1] with author role',$dom),
+           '_LC_internetdom' => &mt('Users not from [_1], but from [_2]',$dom,$intdom),
+           '_LC_external'    => &mt('Users not from [_1]',$intdom),
+                     );
+    my @alltypes = ('_LC_adv','_LC_author','_LC_internetdom','_LC_external');
+    if (ref($types) eq 'ARRAY') {
+        unshift(@alltypes,@{$types},'default');
+    }
+    my %titles;
+    foreach my $type (@alltypes) {
+        if ($type =~ /^_LC_/) {
+            $titles{$type} = $othertypes{$type};
+        } elsif ($type eq 'default') {
+            $titles{$type} = &mt('All users from [_1]',$dom);
+            if (ref($types) eq 'ARRAY') {
+                if (@{$types} > 0) {
+                    $titles{$type} = &mt('Other users from [_1]',$dom);
+                }
+            }
+        } elsif (ref($usertypes) eq 'HASH') {
+            $titles{$type} = $usertypes->{$type};
+        }
+    }
+    return (\@alltypes,\%othertypes,\%titles);
+}
+
+sub loadbalance_rule_row {
+    my ($type,$title,$current,$servers,$currbalancer,$dom,$targets_div_style,
+        $homedom_div_style,$css_class) = @_;
+    my @rulenames = ('default','homeserver');
+    my %ruletitles = &offloadtype_text();
+    if ($type eq '_LC_external') {
+        push(@rulenames,'externalbalancer');
+    } else {
+        push(@rulenames,'specific');
+    }
+    push(@rulenames,'none');
+    my $style = $targets_div_style;
+    if (($type eq '_LC_external') || ($type eq '_LC_internetdom')) {
+        $style = $homedom_div_style;
+    }
+    my $output = 
+        '<tr'.$css_class.'><td valign="top"><div id="balanceruletitle_'.$type.'" style="'.$style.'">'.$title.'</div></td>'."\n".
+        '<td><div id="balancerule_'.$type.'" style="'.$style.'">'."\n";
+    for (my $i=0; $i<@rulenames; $i++) {
+        my $rule = $rulenames[$i];
+        my ($checked,$extra);
+        if ($rulenames[$i] eq 'default') {
+            $rule = '';
+        }
+        if ($rulenames[$i] eq 'specific') {
+            if (ref($servers) eq 'HASH') {
+                my $default;
+                if (($current ne '') && (exists($servers->{$current}))) {
+                    $checked = ' checked="checked"';
+                }
+                unless ($checked) {
+                    $default = ' selected="selected"';
+                }
+                $extra = ':&nbsp;<select name="loadbalancing_singleserver_'.$type.
+                         '" id="loadbalancing_singleserver_'.$type.
+                         '" onchange="singleServerToggle('."'$type'".')">'."\n".
+                         '<option value=""'.$default.'></option>'."\n";
+                foreach my $lonhost (sort(keys(%{$servers}))) {
+                    next if ($lonhost eq $currbalancer);
+                    my $selected;
+                    if ($lonhost eq $current) {
+                        $selected = ' selected="selected"';
+                    }
+                    $extra .= '<option value="'.$lonhost.'"'.$selected.'>'.$lonhost.'</option>';
+                }
+                $extra .= '</select>';
+            }
+        } elsif ($rule eq $current) {
+            $checked = ' checked="checked"';
+        }
+        $output .= '<span class="LC_nobreak"><label>'.
+                   '<input type="radio" name="loadbalancing_rules_'.$type.
+                   '" id="loadbalancing_rules_'.$type.'_'.$i.'" value="'.
+                   $rule.'" onclick="balanceruleChange('."this.form,'$type'".
+                   ')"'.$checked.' />&nbsp;'.$ruletitles{$rulenames[$i]}.
+                   '</label>'.$extra.'</span><br />'."\n";
+    }
+    $output .= '</div></td></tr>'."\n";
+    return $output;
+}
+
+sub offloadtype_text {
+    my %ruletitles = &Apache::lonlocal::texthash (
+           'default'          => 'Offloads to default destinations',
+           'homeserver'       => "Offloads to user's home server",
+           'externalbalancer' => "Offloads to Load Balancer in user's domain",
+           'specific'         => 'Offloads to specific server',
+           'none'             => 'No offload',
+    );
+    return %ruletitles;
+}
+
+sub sparestype_titles {
+    my %typestitles = &Apache::lonlocal::texthash (
+                          'primary' => 'primary',
+                          'default' => 'default',
+                      );
+    return %typestitles;
+}
+
 sub contact_titles {
     my %titles = &Apache::lonlocal::texthash (
                    'supportemail' => 'Support E-mail address',
@@ -3517,7 +3839,7 @@ sub print_serverstatuses {
 sub serverstatus_pages {
     return ('userstatus','lonstatus','loncron','server-status','codeversions',
             'clusterstatus','metadata_keywords','metadata_harvest',
-            'takeoffline','takeonline','showenv','toggledebug');
+            'takeoffline','takeonline','showenv','toggledebug','ping','domconf');
 }
 
 sub coursecategories_javascript {
@@ -4585,7 +4907,7 @@ sub publishlogo {
 # See if there is anything left
     unless ($fname) { return ('error: no uploaded file'); }
     $fname="$subdir/$fname";
-    my $filepath='/home/'.$confname.'/public_html';
+    my $filepath=$r->dir_config('lonDocRoot')."/priv/$dom/$confname";
     my ($fnamepath,$file,$fetchthumb);
     $file=$fname;
     if ($fname=~m|/|) {
@@ -4663,8 +4985,15 @@ $env{'user.name'}.':'.$env{'user.domain'
             if (copy($source,$copyfile)) {
                 print $logfile "\nCopied original source to ".$copyfile."\n";
                 $output = 'ok';
-                &write_metadata($dom,$confname,$formname,$targetdir,$file,$logfile);
                 $logourl = '/res/'.$dom.'/'.$confname.'/'.$fname;
+                push(@{$modified_urls},[$copyfile,$source]);
+                my $metaoutput = 
+                    &write_metadata($dom,$confname,$formname,$targetdir,$file,$logfile);
+                unless ($registered_cleanup) {
+                    my $handlers = $r->get_handlers('PerlCleanupHandler');
+                    $r->set_handlers('PerlCleanupHandler' => [\&notifysubscribed,@{$handlers}]);
+                    $registered_cleanup=1;
+                }
             } else {
                 print $logfile "\nUnable to write ".$copyfile.':'.$!."\n";
                 $output = &mt('Failed to copy file to RES space').", $!";
@@ -4682,8 +5011,15 @@ $env{'user.name'}.':'.$env{'user.domain'
                             my $copyfile=$targetdir.'/tn-'.$file;
                             if (copy($outfile,$copyfile)) {
                                 print $logfile "\nCopied source to ".$copyfile."\n";
-                                &write_metadata($dom,$confname,$formname,
-                                                $targetdir,'tn-'.$file,$logfile);
+                                my $thumb_metaoutput = 
+                                    &write_metadata($dom,$confname,$formname,
+                                                    $targetdir,'tn-'.$file,$logfile);
+                                push(@{$modified_urls},[$copyfile,$outfile]);
+                                unless ($registered_cleanup) {
+                                    my $handlers = $r->get_handlers('PerlCleanupHandler');
+                                    $r->set_handlers('PerlCleanupHandler' => [\&notifysubscribed,@{$handlers}]);
+                                    $registered_cleanup=1;
+                                }
                             } else {
                                 print $logfile "\nUnable to write ".$copyfile.
                                                ':'.$!."\n";
@@ -4748,30 +5084,79 @@ sub write_metadata {
     {
         print $logfile "\nWrite metadata file for ".$targetdir.'/'.$file;
         my $mfh;
-        unless (open($mfh,'>'.$targetdir.'/'.$file.'.meta')) {
+        if (open($mfh,'>'.$targetdir.'/'.$file.'.meta')) {
+            foreach (sort keys %metadatafields) {
+                unless ($_=~/\./) {
+                    my $unikey=$_;
+                    $unikey=~/^([A-Za-z]+)/;
+                    my $tag=$1;
+                    $tag=~tr/A-Z/a-z/;
+                    print $mfh "\n\<$tag";
+                    foreach (split(/\,/,$metadatakeys{$unikey})) {
+                        my $value=$metadatafields{$unikey.'.'.$_};
+                        $value=~s/\"/\'\'/g;
+                        print $mfh ' '.$_.'="'.$value.'"';
+                    }
+                    print $mfh '>'.
+                        &HTML::Entities::encode($metadatafields{$unikey},'<>&"')
+                            .'</'.$tag.'>';
+                }
+            }
+            $output = 'ok';
+            print $logfile "\nWrote metadata";
+            close($mfh);
+        } else {
+            print $logfile "\nFailed to open metadata file";
             $output = &mt('Could not write metadata');
         }
-        foreach (sort keys %metadatafields) {
-            unless ($_=~/\./) {
-                my $unikey=$_;
-                $unikey=~/^([A-Za-z]+)/;
-                my $tag=$1;
-                $tag=~tr/A-Z/a-z/;
-                print $mfh "\n\<$tag";
-                foreach (split(/\,/,$metadatakeys{$unikey})) {
-                    my $value=$metadatafields{$unikey.'.'.$_};
-                    $value=~s/\"/\'\'/g;
-                    print $mfh ' '.$_.'="'.$value.'"';
-                }
-                print $mfh '>'.
-                    &HTML::Entities::encode($metadatafields{$unikey},'<>&"')
-                        .'</'.$tag.'>';
-            }
-        }
-        $output = 'ok';
-        print $logfile "\nWrote metadata";
-        close($mfh);
     }
+    return $output;
+}
+
+sub notifysubscribed {
+    foreach my $targetsource (@{$modified_urls}){
+        next unless (ref($targetsource) eq 'ARRAY');
+        my ($target,$source)=@{$targetsource};
+        if ($source ne '') {
+            if (open(my $logfh,'>>'.$source.'.log')) {
+                print $logfh "\nCleanup phase: Notifications\n";
+                my @subscribed=&subscribed_hosts($target);
+                foreach my $subhost (@subscribed) {
+                    print $logfh "\nNotifying host ".$subhost.':';
+                    my $reply=&Apache::lonnet::critical('update:'.$target,$subhost);
+                    print $logfh $reply;
+                }
+                my @subscribedmeta=&subscribed_hosts("$target.meta");
+                foreach my $subhost (@subscribedmeta) {
+                    print $logfh "\nNotifying host for metadata only ".$subhost.':';
+                    my $reply=&Apache::lonnet::critical('update:'.$target.'.meta',
+                                                        $subhost);
+                    print $logfh $reply;
+                }
+                print $logfh "\n============ Done ============\n";
+                close($logfh);
+            }
+        }
+    }
+    return OK;
+}
+
+sub subscribed_hosts {
+    my ($target) = @_;
+    my @subscribed;
+    if (open(my $fh,"<$target.subscription")) {
+        while (my $subline=<$fh>) {
+            if ($subline =~ /^($match_lonid):/) {
+                my $host = $1;
+                if ($host ne $Apache::lonnet::perlvar{'lonHostID'}) {
+                    unless (grep(/^\Q$host\E$/,@subscribed)) {
+                        push(@subscribed,$host);
+                    }
+                }
+            }
+        }
+    }
+    return @subscribed;
 }
 
 sub check_switchserver {
@@ -7061,7 +7446,6 @@ sub modify_usersessions {
     }
 
     my @alldoms = &Apache::lonnet::all_domains();
-    my %uniques = &Apache::lonnet::get_unique_servers(\@alldoms);
     my %servers = &Apache::lonnet::internet_dom_servers($dom);
     my %spareid = &current_offloads_to($dom,$domconfig{'usersessions'},\%servers);
     my $savespares;
@@ -7069,34 +7453,26 @@ sub modify_usersessions {
     foreach my $lonhost (sort(keys(%servers))) {
         my $serverhomeID =
             &Apache::lonnet::get_server_homeID($servers{$lonhost});
+        my $serverhostname = &Apache::lonnet::hostname($lonhost);
         $defaultshash{'usersessions'}{'spares'}{$lonhost} = {};
         my %spareschg;
         foreach my $type (@{$types{'spares'}}) {
             my @okspares;
             my @checked = &Apache::loncommon::get_env_multiple('form.spare_'.$type.'_'.$lonhost);
             foreach my $server (@checked) {
-                unless (($server eq $lonhost) || ($server eq $serverhomeID)) {
-                    if ($uniques{$server}) {
-                        push(@okspares,$server);
+                if (&Apache::lonnet::hostname($server) ne '') {
+                    unless (&Apache::lonnet::hostname($server) eq $serverhostname) {
+                        unless (grep(/^\Q$server\E$/,@okspares)) {
+                            push(@okspares,$server);
+                        }
                     }
                 }
             }
             my $new = $env{'form.newspare_'.$type.'_'.$lonhost};
             my $newspare;
-            if (($new ne '') && ($uniques{$new})) {
-                unless (($new eq $lonhost) || ($new eq $serverhomeID)) {
+            if (($new ne '') && (&Apache::lonnet::hostname($new))) {
+                unless (&Apache::lonnet::hostname($new) eq $serverhostname) {
                     $newspare = $new;
-                    $spareschg{$type} = 1;
-                }
-            }
-            if (ref($spareid{$lonhost}) eq 'HASH') {
-                if (ref($spareid{$lonhost}{$type}) eq 'ARRAY') {
-                    my @diffs = &Apache::loncommon::compare_arrays($domconfig{'usersessions'}{'spares'}{$lonhost}{$type},\@okspares);
-                    if (@diffs > 0) {
-                        $spareschg{$type} = 1;
-                    } elsif ($new ne '') {
-                        $spareschg{$type} = 1;
-                    }
                 }
             }
             my @spares;
@@ -7106,6 +7482,14 @@ sub modify_usersessions {
                 @spares = sort(@okspares);
             }
             $defaultshash{'usersessions'}{'spares'}{$lonhost}{$type} = \@spares;
+            if (ref($spareid{$lonhost}) eq 'HASH') {
+                if (ref($spareid{$lonhost}{$type}) eq 'ARRAY') {
+                    my @diffs = &Apache::loncommon::compare_arrays($spareid{$lonhost}{$type},\@spares);
+                    if (@diffs > 0) {
+                        $spareschg{$type} = 1;
+                    }
+                }
+            }
         }
         if (keys(%spareschg) > 0) {
             $changes{'spares'}{$lonhost} = \%spareschg;
@@ -7214,6 +7598,200 @@ sub modify_usersessions {
     return $resulttext;
 }
 
+sub modify_loadbalancing {
+    my ($dom,%domconfig) = @_;
+    my $primary_id = &Apache::lonnet::domain($dom,'primary');
+    my $intdom = &Apache::lonnet::internet_dom($primary_id);
+    my ($othertitle,$usertypes,$types) =
+        &Apache::loncommon::sorted_inst_types($dom);
+    my %servers = &Apache::lonnet::internet_dom_servers($dom);
+    my @sparestypes = ('primary','default');
+    my %typetitles = &sparestype_titles();
+    my $resulttext;
+    if (keys(%servers) > 1) {
+        my ($currbalancer,$currtargets,$currrules);
+        if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
+            $currbalancer = $domconfig{'loadbalancing'}{'lonhost'};
+            $currtargets = $domconfig{'loadbalancing'}{'targets'};
+            $currrules = $domconfig{'loadbalancing'}{'rules'};
+        } else {
+            ($currbalancer,$currtargets) = 
+                &Apache::lonnet::get_lonbalancer_config(\%servers);
+        }
+        my ($saveloadbalancing,%defaultshash,%changes);
+        my ($alltypes,$othertypes,$titles) =
+            &loadbalancing_titles($dom,$intdom,$usertypes,$types);
+        my %ruletitles = &offloadtype_text();
+        my $balancer = $env{'form.loadbalancing_lonhost'};
+        if (!$servers{$balancer}) {
+            undef($balancer);
+        }
+        if ($currbalancer ne $balancer) {
+            $changes{'lonhost'} = 1;
+        }
+        $defaultshash{'loadbalancing'}{'lonhost'} = $balancer;
+        if ($balancer ne '') {
+            unless (ref($domconfig{'loadbalancing'}) eq 'HASH') {
+                $saveloadbalancing = 1;
+            }
+            foreach my $sparetype (@sparestypes) {
+                my @targets = &Apache::loncommon::get_env_multiple('form.loadbalancing_target_'.$sparetype);
+                my @offloadto;
+                foreach my $target (@targets) {
+                    if (($servers{$target}) && ($target ne $balancer)) {
+                        if ($sparetype eq 'default') {
+                            if (ref($defaultshash{'loadbalancing'}{'targets'}{'primary'}) eq 'ARRAY') {
+                                next if (grep(/^\Q$target\E$/,@{$defaultshash{'loadbalancing'}{'targets'}{'primary'}}));
+                            }
+                        }
+                        unless(grep(/^\Q$target\E$/,@offloadto)) {
+                            push(@offloadto,$target);
+                        }
+                    }
+                    $defaultshash{'loadbalancing'}{'targets'}{$sparetype} = \@offloadto;
+                }
+            }
+        } else {
+            foreach my $sparetype (@sparestypes) {
+                $defaultshash{'loadbalancing'}{'targets'}{$sparetype} = [];
+            }
+        }
+        if (ref($currtargets) eq 'HASH') {
+            foreach my $sparetype (@sparestypes) {
+                if (ref($currtargets->{$sparetype}) eq 'ARRAY') {
+                    my @targetdiffs = &Apache::loncommon::compare_arrays($currtargets->{$sparetype},$defaultshash{'loadbalancing'}{'targets'}{$sparetype});
+                    if (@targetdiffs > 0) {
+                        $changes{'targets'} = 1;
+                    }
+                } elsif (ref($defaultshash{'loadbalancing'}{'targets'}{$sparetype}) eq 'ARRAY') {
+                    if (@{$defaultshash{'loadbalancing'}{'targets'}{$sparetype}} > 0) {
+                        $changes{'targets'} = 1;
+                    }
+                }
+            }
+        } else {
+            foreach my $sparetype (@sparestypes) {
+                if (ref($defaultshash{'loadbalancing'}{'targets'}{$sparetype}) eq 'ARRAY') {
+                    if (@{$defaultshash{'loadbalancing'}{'targets'}{$sparetype}} > 0) {
+                        $changes{'targets'} = 1;  
+                    }
+                }
+            }  
+        }
+        my $ishomedom;
+        if ($balancer ne '') {
+            if (&Apache::lonnet::host_domain($balancer) eq $dom) {
+                $ishomedom = 1;
+            }
+        }
+        if (ref($alltypes) eq 'ARRAY') {
+            foreach my $type (@{$alltypes}) {
+                my $rule;
+                if ($balancer ne '') {
+                    unless ((($type eq '_LC_external') || ($type eq '_LC_internetdom')) && 
+                         (!$ishomedom)) {
+                        $rule = $env{'form.loadbalancing_rules_'.$type};
+                    }
+                    if ($rule eq 'specific') {
+                        $rule = $env{'form.loadbalancing_singleserver_'.$type};
+                    }
+                }
+                $defaultshash{'loadbalancing'}{'rules'}{$type} = $rule;
+                if (ref($currrules) eq 'HASH') {
+                    if ($rule ne $currrules->{$type}) {
+                        $changes{'rules'}{$type} = 1;
+                    }
+                } elsif ($rule ne '') {
+                    $changes{'rules'}{$type} = 1;
+                }
+            }
+        }
+        my $nochgmsg = &mt('No changes made to Load Balancer settings.');
+        if ((keys(%changes) > 0) || ($saveloadbalancing)) {
+            my $putresult = &Apache::lonnet::put_dom('configuration',
+                                                     \%defaultshash,$dom);
+            if ($putresult eq 'ok') {
+                if (keys(%changes) > 0) {
+                    if ($changes{'lonhost'}) {
+                        if ($currbalancer ne '') {
+                            &Apache::lonnet::remote_devalidate_cache($currbalancer,'loadbalancing',$dom);
+                        }
+                        if ($balancer eq '') {
+                            $resulttext .= '<li>'.&mt('Load Balancing with dedicated server discontinued').'</li>'; 
+                        } else {
+                            &Apache::lonnet::remote_devalidate_cache($balancer,'loadbalancing',$dom);
+                            $resulttext .= '<li>'.&mt('Dedicated Load Balancer server set to [_1]',$balancer);
+                        }
+                    } else {
+                        &Apache::lonnet::remote_devalidate_cache($balancer,'loadbalancing',$dom);
+                    }
+                    if (($changes{'targets'}) && ($balancer ne '')) {
+                        my %offloadstr;
+                        foreach my $sparetype (@sparestypes) {
+                            if (ref($defaultshash{'loadbalancing'}{'targets'}{$sparetype}) eq 'ARRAY') {
+                                if (@{$defaultshash{'loadbalancing'}{'targets'}{$sparetype}} > 0) {
+                                    $offloadstr{$sparetype} = join(', ',@{$defaultshash{'loadbalancing'}{'targets'}{$sparetype}});
+                                }
+                            }
+                        }
+                        if (keys(%offloadstr) == 0) {
+                            $resulttext .= '<li>'.&mt("Servers to which Load Balance server offloads set to 'None', by default").'</li>';
+                        } else {
+                            my $showoffload;
+                            foreach my $sparetype (@sparestypes) {
+                                $showoffload .= '<i>'.$typetitles{$sparetype}.'</i>:&nbsp;';
+                                if (defined($offloadstr{$sparetype})) {
+                                    $showoffload .= $offloadstr{$sparetype};
+                                } else {
+                                    $showoffload .= &mt('None');
+                                }
+                                $showoffload .= ('&nbsp;'x3);
+                            }
+                            $resulttext .= '<li>'.&mt('By default, Load Balancer server set to offload to: [_1]',$showoffload).'</li>';
+                        }
+                    }
+                    if ((ref($changes{'rules'}) eq 'HASH') && ($balancer ne '')) {
+                        if ((ref($alltypes) eq 'ARRAY') && (ref($titles) eq 'HASH')) {
+                            foreach my $type (@{$alltypes}) {
+                                if ($changes{'rules'}{$type}) {
+                                    my $rule = $defaultshash{'loadbalancing'}{'rules'}{$type};
+                                    my $balancetext;
+                                    if ($rule eq '') {
+                                        $balancetext =  $ruletitles{'default'};
+                                    } elsif (($rule eq 'homeserver') || ($rule eq 'externalbalancer')) {
+                                        $balancetext =  $ruletitles{$rule};
+                                    } else {
+                                        $balancetext = &mt('offload to [_1]',$defaultshash{'loadbalancing'}{'rules'}{$type});
+                                    }
+                                    $resulttext .= '<li>'.&mt('Load Balancing for [_1] set to: [_2]',$titles->{$type},$balancetext).'</li>';     
+                                }
+                            }
+                        }
+                    }
+                    if ($resulttext ne '') {
+                        $resulttext = &mt('Changes made:').'<ul>'.$resulttext.'</ul>';
+                    } else {
+                        $resulttext = $nochgmsg;
+                    }
+                } else {
+                    $resulttext = $nochgmsg;
+                    if ($balancer ne '') {
+                        &Apache::lonnet::remote_devalidate_cache($balancer,'loadbalancing',$dom);
+                    }
+                }
+            } else {
+                $resulttext = '<span class="LC_error">'.
+                              &mt('An error occurred: [_1]',$putresult).'</span>';
+            }
+        } else {
+            $resulttext = $nochgmsg;
+        }
+    } else {
+        $resulttext =  &mt('Load Balancing unavailable as this domain only has one server.');
+    }
+    return $resulttext;
+}
+
 sub recurse_check {
     my ($chkcats,$categories,$depth,$name) = @_;
     if (ref($chkcats->[$depth]{$name}) eq 'ARRAY') {
@@ -7327,15 +7905,336 @@ sub active_dc_picker {
 sub usersession_titles {
     return &Apache::lonlocal::texthash(
                hosted => 'Hosting of sessions for users from other domains on servers in this domain',
-
                remote => 'Hosting of sessions for users in this domain on servers in other domains',
                spares => 'Servers offloaded to, when busy',
                version => 'LON-CAPA version requirement',
                excludedomain => 'Allow all, but exclude specific domains',
                includedomain => 'Deny all, but include specific domains',
                primary => 'Primary (checked first)',
-               default => 'Default', 
+               default => 'Default',
            );
 }
 
+sub id_for_thisdom {
+    my (%servers) = @_;
+    my %altids;
+    foreach my $server (keys(%servers)) {
+        my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server});
+        if ($serverhome ne $server) {
+            $altids{$serverhome} = $server;
+        }
+    }
+    return %altids;
+}
+
+sub count_servers {
+    my ($currbalancer,%servers) = @_;
+    my (@spares,$numspares);
+    foreach my $lonhost (sort(keys(%servers))) {
+        next if ($currbalancer eq $lonhost);
+        push(@spares,$lonhost);
+    }
+    if ($currbalancer) {
+        $numspares = scalar(@spares);
+    } else {
+        $numspares = scalar(@spares) - 1;
+    }
+    return ($numspares,@spares);
+}
+
+sub lonbalance_targets_js {
+    my ($dom,$types,$servers) = @_;
+    my $select = &mt('Select');
+    my ($alltargets,$allishome,$allinsttypes,@alltypes);
+    if (ref($servers) eq 'HASH') {
+        $alltargets = join("','",sort(keys(%{$servers})));
+        my @homedoms;
+        foreach my $server (sort(keys(%{$servers}))) {
+            if (&Apache::lonnet::host_domain($server) eq $dom) {
+                push(@homedoms,'1');
+            } else {
+                push(@homedoms,'0');
+            }
+        }
+        $allishome = join("','",@homedoms);
+    }
+    if (ref($types) eq 'ARRAY') {
+        if (@{$types} > 0) {
+            @alltypes = @{$types};
+        }
+    }
+    push(@alltypes,'default','_LC_adv','_LC_author','_LC_internetdom','_LC_external');
+    $allinsttypes = join("','",@alltypes);
+    return <<"END";
+
+<script type="text/javascript">
+// <![CDATA[
+
+function toggleTargets() {
+    var balancer = document.display.loadbalancing_lonhost.options[document.display.loadbalancing_lonhost.selectedIndex].value;
+    if (balancer == '') {
+        hideSpares();
+    } else {
+        var homedoms = new Array('$allishome');
+        var ishomedom = homedoms[document.display.loadbalancing_lonhost.selectedIndex];
+        showSpares(balancer,ishomedom);
+    }
+    return;
+}
+
+function showSpares(balancer,ishomedom) {
+    var alltargets = new Array('$alltargets');
+    var insttypes = new Array('$allinsttypes');
+    var offloadtypes = new Array('primary','default');
+
+    document.getElementById('loadbalancing_targets').style.display='block';
+    document.getElementById('loadbalancing_disabled').style.display='none';
+ 
+    for (var i=0; i<offloadtypes.length; i++) {
+        var count = 0;
+        for (var j=0; j<alltargets.length; j++) {
+            if (alltargets[j] != balancer) {
+                document.getElementById('loadbalancing_target_'+offloadtypes[i]+'_'+count).value = alltargets[j];
+                document.getElementById('loadbalancing_targettxt_'+offloadtypes[i]+'_'+count).style.textAlign='left';
+                document.getElementById('loadbalancing_targettxt_'+offloadtypes[i]+'_'+count).style.textFace='normal';
+                document.getElementById('loadbalancing_targettxt_'+offloadtypes[i]+'_'+count).innerHTML = alltargets[j];
+                count ++;
+            }
+        }
+    }
+    for (var k=0; k<insttypes.length; k++) {
+        if ((insttypes[k] == '_LC_external') || (insttypes[k] == '_LC_internetdom')) {
+            if (ishomedom == 1) {
+                document.getElementById('balanceruletitle_'+insttypes[k]).style.display='block';
+                document.getElementById('balancerule_'+insttypes[k]).style.display='block';
+            } else {
+                document.getElementById('balanceruletitle_'+insttypes[k]).style.display='none';
+                document.getElementById('balancerule_'+insttypes[k]).style.display='none';
+
+            }
+        } else {
+            document.getElementById('balanceruletitle_'+insttypes[k]).style.display='block';
+            document.getElementById('balancerule_'+insttypes[k]).style.display='block';
+        }
+        if ((insttypes[k] != '_LC_external') && 
+            ((insttypes[k] != '_LC_internetdom') ||
+             ((insttypes[k] == '_LC_internetdom') && (ishomedom == 1)))) {
+            document.getElementById('loadbalancing_singleserver_'+insttypes[k]).options[0] = new Option("","",true,true);
+            for (var m=0; m<alltargets.length; m++) {
+                var idx = m+1;
+                if (alltargets[m] != balancer) {
+                    document.getElementById('loadbalancing_singleserver_'+insttypes[k]).options[idx] = new Option(alltargets[m],alltargets[m],false,false);
+                }
+            }
+        }
+    }
+    return;
+}
+
+function hideSpares() {
+    var alltargets = new Array('$alltargets');
+    var insttypes = new Array('$allinsttypes');
+    var offloadtypes = new Array('primary','default');
+
+    document.getElementById('loadbalancing_targets').style.display='none';
+    document.getElementById('loadbalancing_disabled').style.display='block';
+
+    var total = alltargets.length - 1;
+    for (var i=0; i<offloadtypes; i++) {
+        for (var j=0; j<total; j++) {
+           document.getElementById('loadbalancing_target_'+offloadtypes[i]+'_'+j).checked = false;
+           document.getElementById('loadbalancing_target_'+offloadtypes[i]+'_'+j).value = '';
+           document.getElementById('loadbalancing_targettxt_'+offloadtypes[i]+'_'+j).innerHTML = '';
+        }
+    }
+    for (var k=0; k<insttypes.length; k++) {
+        document.getElementById('balanceruletitle_'+insttypes[k]).style.display='none';
+        document.getElementById('balancerule_'+insttypes[k]).style.display='none';
+        if (insttypes[k] != '_LC_external') {
+            document.getElementById('loadbalancing_singleserver_'+insttypes[k]).length = 0;
+            document.getElementById('loadbalancing_singleserver_'+insttypes[k]).options[0] = new Option("","",true,true);
+        }
+    }
+    return;
+}
+
+function checkOffloads(item,type) {
+    var alltargets = new Array('$alltargets');
+    var offloadtypes = new Array('primary','default');
+    if (item.checked) {
+        var total = alltargets.length - 1;
+        var other;
+        if (type == offloadtypes[0]) {
+            other = offloadtypes[1];
+        } else {
+            other = offloadtypes[0];
+        }
+        for (var i=0; i<total; i++) {
+            var server = document.getElementById('loadbalancing_target_'+other+'_'+i).value;
+            if (server == item.value) {
+                if (document.getElementById('loadbalancing_target_'+other+'_'+i).checked) {
+                    document.getElementById('loadbalancing_target_'+other+'_'+i).checked = false;
+                }
+            }
+        }
+    }
+    return;
+}
+
+function singleServerToggle(type) {
+    var offloadtoSelIdx = document.getElementById('loadbalancing_singleserver_'+type).selectedIndex;
+    if (offloadtoSelIdx == 0) {
+        document.getElementById('loadbalancing_rules_'+type+'_0').checked = true;
+        document.getElementById('loadbalancing_singleserver_'+type).options[0].text = '';
+
+    } else {
+        document.getElementById('loadbalancing_rules_'+type+'_2').checked = true;
+        document.getElementById('loadbalancing_singleserver_'+type).options[0].text = '$select';
+    }
+    return;
+}
+
+function balanceruleChange(formname,type) {
+    if (type == '_LC_external') {
+        return; 
+    }
+    var typesRules = getIndicesByName(formname,'loadbalancing_rules_'+type);
+    for (var i=0; i<typesRules.length; i++) {
+        if (formname.elements[typesRules[i]].checked) {
+            if (formname.elements[typesRules[i]].value != 'specific') {
+                document.getElementById('loadbalancing_singleserver_'+type).selectedIndex = 0;
+                document.getElementById('loadbalancing_singleserver_'+type).options[0].text = '';
+            } else {
+                document.getElementById('loadbalancing_singleserver_'+type).options[0].text = '$select';
+            }
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+
+END
+}
+
+sub new_spares_js {
+    my @sparestypes = ('primary','default');
+    my $types = join("','",@sparestypes);
+    my $select = &mt('Select');
+    return <<"END";
+
+<script type="text/javascript">
+// <![CDATA[
+
+function updateNewSpares(formname,lonhost) {
+    var types = new Array('$types');
+    var include = new Array();
+    var exclude = new Array();
+    for (var i=0; i<types.length; i++) {
+        var spareboxes = getIndicesByName(formname,'spare_'+types[i]+'_'+lonhost);
+        for (var j=0; j<spareboxes.length; j++) {
+            if (formname.elements[spareboxes[j]].checked) {
+                exclude.push(formname.elements[spareboxes[j]].value);
+            } else {
+                include.push(formname.elements[spareboxes[j]].value);
+            }
+        }
+    }
+    for (var i=0; i<types.length; i++) {
+        var newSpare = document.getElementById('newspare_'+types[i]+'_'+lonhost);
+        var selIdx = newSpare.selectedIndex;
+        var currnew = newSpare.options[selIdx].value;
+        var okSpares = new Array();
+        for (var j=0; j<newSpare.options.length; j++) {
+            var possible = newSpare.options[j].value;
+            if (possible != '') {
+                if (exclude.indexOf(possible) == -1) {
+                    okSpares.push(possible);
+                } else {
+                    if (currnew == possible) {
+                        selIdx = 0;
+                    }
+                }
+            }
+        }
+        for (var k=0; k<include.length; k++) {
+            if (okSpares.indexOf(include[k]) == -1) {
+                okSpares.push(include[k]);
+            }
+        }
+        okSpares.sort();
+        newSpare.options.length = 0;
+        if (selIdx == 0) {
+            newSpare.options[0] = new Option("$select","",true,true);
+        } else {
+            newSpare.options[0] = new Option("$select","",false,false);
+        }
+        for (var m=0; m<okSpares.length; m++) {
+            var idx = m+1;
+            var selThis = 0;
+            if (selIdx != 0) {
+                if (okSpares[m] == currnew) {
+                    selThis = 1;
+                }
+            }
+            if (selThis == 1) {
+                newSpare.options[idx] = new Option(okSpares[m],okSpares[m],true,true);
+            } else {
+                newSpare.options[idx] = new Option(okSpares[m],okSpares[m],false,false);
+            }
+        }
+    }
+    return;
+}
+
+function checkNewSpares(lonhost,type) {
+    var newSpare = document.getElementById('newspare_'+type+'_'+lonhost);
+    var chosen =  newSpare.options[newSpare.selectedIndex].value;
+    if (chosen != '') { 
+        var othertype;
+        var othernewSpare;
+        if (type == 'primary') {
+            othernewSpare = document.getElementById('newspare_default_'+lonhost);
+        }
+        if (type == 'default') {
+            othernewSpare = document.getElementById('newspare_primary_'+lonhost);
+        }
+        if (othernewSpare.options[othernewSpare.selectedIndex].value == chosen) {
+            othernewSpare.selectedIndex = 0;
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+
+END
+
+}
+
+sub common_domprefs_js {
+    return <<"END";
+
+<script type="text/javascript">
+// <![CDATA[
+
+function getIndicesByName(formname,item) {
+    var group = new Array();
+    for (var i=0;i<formname.elements.length;i++) {
+        if (formname.elements[i].name == item) {
+            group.push(formname.elements[i].id);
+        }
+    }
+    return group;
+}
+
+// ]]>
+</script>
+
+END
+
+}
+
 1;