A new version of the column configuration interface including
[plewww.git] / planetlab / slices / slice.php
index 64a5d05..3975fbb 100644 (file)
@@ -17,13 +17,14 @@ include 'plc_header.php';
 require_once 'plc_functions.php';
 require_once 'plc_peers.php';
 require_once 'plc_objects.php';
-require_once 'plc_visibletags.php';
+require_once 'plc_visibletags2.php';
 require_once 'linetabs.php';
-require_once 'table.php';
+require_once 'table2.php';
 require_once 'details.php';
 require_once 'toggle.php';
 require_once 'form.php';
 require_once 'raphael.php';
+require_once 'columns.php';
 
 // keep css separate for now
 drupal_set_html_head('
@@ -34,14 +35,21 @@ drupal_set_html_head('
 // -------------------- admins potentially need to get full list of users
 ini_set('memory_limit','32M');
 
+$profiling=false;
+if ($_GET['profiling']) $profiling=true;
+
+if ($profiling)  plc_debug_prof_start();
+
 // -------------------- 
 // recognized URL arguments
 $slice_id=intval($_GET['id']);
 if ( ! $slice_id ) { plc_error('Malformed URL - id not set'); return; }
 
 ////////////////////
-// Get all columns as we focus on only one entry
-$slices= $api->GetSlices( array($slice_id));
+// have to name columns b/c we need the non-native 'omf_control' column
+$slice_columns=array('slice_id','name','peer_id','site_id','person_ids','node_ids','expires',
+                    'url','description','instantiation','omf_control');
+$slices= $api->GetSlices( array($slice_id), $slice_columns);
 
 if (empty($slices)) {
   drupal_set_message ("Slice " . $slice_id . " not found");
@@ -50,6 +58,7 @@ if (empty($slices)) {
 
 $slice=$slices[0];
 
+if ($profiling) plc_debug_prof('2: slice',count($slices));
 // pull all node info to vars
 $name= $slice['name'];
 $expires = date( "d/m/Y", $slice['expires'] );
@@ -62,12 +71,15 @@ $peer_id= $slice['peer_id'];
 $peers=new Peers ($api);
 $local_peer = ! $peer_id;
 
+if ($profiling) plc_debug_prof('3: peers',count($peers));
+
 // gets site info
 $sites= $api->GetSites( array( $site_id ) );
 $site=$sites[0];
 $site_name= $site['name'];
 $max_slices = $site['max_slices'];
 
+if ($profiling) plc_debug_prof('4: sites',count($sites));
 //////////////////////////////////////// building blocks for the renew area
 // Constants
 global $DAY;           $DAY = 24*60*60;
@@ -140,31 +152,32 @@ EOF;
     if ( empty( $selectors ) ) {
       print <<< EOF
 <div class='my-slice-renewal'>
-Slices annot be renewed more than $MAX_WEEKS weeks from now, i.e. not beyond $max_text. 
+Slices cannot be renewed more than $MAX_WEEKS weeks from now, i.e. not beyond $max_text. 
 For this reason, the current slice cannot be renewed any further into the future, try again closer to expiration date.
 </div>
 EOF;
      } else {
       print <<< EOF
 <div class='my-slice-renewal'>
-<p>You must provide a short description as well as a link to a project website before renewing it.
-Do <span class='bold'>not</span> provide bogus information; if a complaint is lodged against your slice 
-and PlanetLab Operations is unable to determine what the normal behavior of your slice is, 
-your slice may be deleted to resolve the complaint.</p>
-<p><span class='bold'>NOTE:</span> 
-Slices cannot be renewed beyond another $max_renewal_weeks week(s) ($max_renewal_date).
+<span class='bold'>Important:</span> Please take this opportunity to review and update your slice information in the Details tab.
+<p>
+PlanetLab's security model requires that anyone who is concerned about a slice's activity be able to immediately learn about that slice. The details that you provide are your public explanation about why the slice behaves as it does. Be sure to describe the <span class='bold'>kind of traffic</span> that your slice generates, and how it handles material that is under <span class='bold'>copyright</span>, if relevant.
+</p><p>
+The PlanetLab Operations Centres regularly respond to concerns raised by third parties about site behaviour. Most incidents are resolved rapidly based upon the publicly posted slice details. However, when these details are not sufficiently clear or accurate, and we cannot immediately reach the slice owner, we must delete the slice. 
 </p>
-</div>
 EOF;
 
       $form = new PlekitForm (l_actions(),
                              array('action'=>'renew-slice',
                                    'slice_id'=>$slice['slice_id']));
       $form->start();
-      print $form->label_html('expires','Duration');
+      print $form->label_html('expires','Duration:&nbsp;');
       print $form->select_html('expires',$selectors,array('label'=>'Pick one'));
       print $form->submit_html('renew-button','Renew');
       $form->end();
+
+print("<p><i>NOTE: Slices cannot be renewed beyond another $max_renewal_weeks week(s) ($max_renewal_date).</i>  </p>");
+print ("</div>");
     }
   }
  
@@ -186,7 +199,7 @@ $tags_privileges = $privileges || plc_is_admin();
 
 $tabs=array();
 $tabs [] = tab_nodes_slice($slice_id);
-$tabs [] = tab_site($site_id);
+$tabs [] = tab_site($site);
 
 // are these the right privileges for deletion ?
 if ($privileges) {
@@ -253,6 +266,7 @@ $details->th_td('URL',$slice['url'],'url',array('width'=>50));
 $details->tr_submit("submit","Update Slice");
 $details->th_td('Expires',$expires);
 $details->th_td('Instantiation',$slice['instantiation']);
+$details->th_td("OMF-friendly", ($slice['omf_control'] ? 'Yes' : 'No') . " [to change: see 'omf_control' in the tags section below]");
 $details->th_td('Site',l_site_obj($site));
 // xxx show the PIs here
 //$details->th_td('PIs',...);
@@ -276,8 +290,9 @@ $potential_persons=
                   $person_columns);
 $count=count($persons);
 
+if ($profiling) plc_debug_prof('4: persons',count($persons));
 $toggle=
-  new PlekitToggle ('my-slice-persons',"$count Users",
+  new PlekitToggle ('my-slice-persons',"$count users",
                    array('bubble'=>
                          'Manage accounts attached to this slice',
                          'visible'=>get_arg('show_persons',false)));
@@ -387,62 +402,411 @@ $toggle->end();
 //     (.) type is passed to the javascript table, for sorting (default is 'string')
 
 // minimal list as a start
-$node_fixed_columns = array('hostname','node_id','peer_id','slice_ids_whitelist','run_level','boot_state','last_contact','node_type');
+$node_fixed_columns = array('hostname','node_id','peer_id','slice_ids_whitelist', 'site_id',
+                           'run_level','boot_state','last_contact','node_type');
 // create a VisibleTags object : basically the list of tag columns to show
+//$visibletags = new VisibleTags ($api, 'node');
+//$visiblecolumns = $visibletags->column_names();
+
+// optimizing calls to GetNodes
+//$all_nodes=$api->GetNodes(NULL,$node_columns);
+//$slice_nodes=$api->GetNodes(array('node_id'=>$slice['node_ids']),$node_columns);
+//$potential_nodes=$api->GetNodes(array('~node_id'=>$slice['node_ids']),$node_columns);
+
+
+//NEW CODE FOR ENABLING COLUMN CONFIGURATION
+
+//prepare fix and configurable columns
+
+$fix_columns = array();
+$fix_columns[]=array('tagname'=>'hostname', 'header'=>'hostname', 'type'=>'string', 'title'=>'The name of the node');
+$fix_columns[]=array('tagname'=>'peer_id', 'header'=>'AU', 'type'=>'string', 'title'=>'Authority');
+$fix_columns[]=array('tagname'=>'run_level', 'header'=>'ST', 'type'=>'string', 'title'=>'Status');
+$fix_columns[]=array('tagname'=>'node_type', 'header'=>'RES', 'type'=>'string', 'title'=>'Reservable');
+
+// columns that correspond to the visible tags for nodes (*node/ui*)
 $visibletags = new VisibleTags ($api, 'node');
-$visiblecolumns = $visibletags->column_names();
+$visibletags->columns();
+$tag_columns = $visibletags->headers();
+
+$extra_columns = array();
+$extra_columns[]=array('tagname'=>'sitename', 'header'=>'SN', 'type'=>'string', 'title'=>'Site name', 'fetched'=>true);
+$extra_columns[]=array('tagname'=>'domain', 'header'=>'DN', 'type'=>'string', 'title'=>'Toplevel domain name', 'fetched'=>true);
+$extra_columns[]=array('tagname'=>'ipaddress', 'header'=>'IP', 'type'=>'string', 'title'=>'IP Address', 'fetched'=>true);
+$extra_columns[]=array('tagname'=>'fcdistro', 'header'=>'OS', 'type'=>'string', 'title'=>'Operating system', 'fetched'=>false);
+$extra_columns[]=array('tagname'=>'uptime', 'header'=>'UT', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Continuous uptime until now', 'fetched'=>false);
+$extra_columns[]=array('tagname'=>'date_created', 'header'=>'DA', 'source'=>'myplc', 'type'=>'date', 'title'=>'Date added', 'fetched'=>false);
+
+
+//Get user's column configuration
+
+$first_time_configuration = false;
+$default_configuration = "hostname:f|ST:f|AU:f|RES:f";
+//$extra_default = "";
+$column_configuration = "";
+$slice_column_configuration = "";
+
+$show_configuration = "";
+$show_reservable_message = '1';
+$show_columns_message = '1';
+
+
+//$PersonTags=$api->GetPersonTags (array('person_id'=>$plc->person['person_id']));
+$PersonTags=$api->GetPersonTags (array('person_id'=>$plc->person['person_id']));
+//print_r($PersonTags);
+foreach ($PersonTags as $ptag) {
+       if ($ptag['tagname'] == 'columnconf')
+       {
+                $column_configuration = $ptag['value'];
+               $conf_tag_id = $ptag['person_tag_id'];
+       }
+       if ($ptag['tagname'] == 'showconf')
+       {
+                $show_configuration = $ptag['value'];
+               $show_tag_id = $ptag['person_tag_id'];
+       }
+}
+
+//print("<br>person column configuration = ".$column_configuration);
+//print("<br>person show configuration = ".$show_configuration);
+
+$sliceconf_exists = false;
+if ($column_configuration == "")
+{
+       $first_time_configuration = true;
+       $column_configuration = $slice_id.";default";
+       $sliceconf_exists = true;
+}
+else {
+       $slice_conf = explode(";",$column_configuration);
+       for ($i=0; $i<count($slice_conf); $i++ ) {
+               if ($slice_conf[$i] == $slice_id)
+               {
+                       $i++;
+                       $slice_column_configuration = $slice_conf[$i];
+                       $sliceconf_exists = true;
+                       break;
+               }
+               else
+               {
+                       $i++;
+                       $slice_column_configuration = $slice_conf[$i];
+               }
+       }        
+}
+
+if ($sliceconf_exists == false)
+       $column_configuration = $column_configuration.";".$slice_id.";default";
+
+//print("<br>slice configuration = ".$slice_column_configuration);
+
+
+if ($slice_column_configuration == "")
+       $full_configuration = $default_configuration;
+else
+       $full_configuration = $default_configuration."|".$slice_column_configuration;
+
+
+//instantiate the column configuration class, which prepares the headers array
+$ConfigureColumns =new PlekitColumns($full_configuration, $fix_columns, $tag_columns, $extra_columns);
+
+$visiblecolumns = $ConfigureColumns->node_tags();
+
 $node_columns=array_merge($node_fixed_columns,$visiblecolumns);
-$nodes=$api->GetNodes(array('node_id'=>$slice['node_ids']),$node_columns);
-$potential_nodes=$api->GetNodes(array('~node_id'=>$slice['node_ids']),$node_columns);
-// reservable nodes: display only the ones in the slice to avoid confusion - also avoid an extra API call
-$reservable_nodes=array();
-foreach ($nodes as $node) { if ($node['node_type']=='reservable') $reservable_nodes[]=$node; }
+//print_r($node_columns);
+$all_nodes=$api->GetNodes(NULL,$node_columns);
+
+$ConfigureColumns->fetch_live_data($all_nodes);
+
+//print("<br>person show configuration = ".$show_configuration);
+
+$show_conf = explode(";",$show_configuration);
+foreach ($show_conf as $ss) {
+       if ($ss =="reservable")
+               $show_reservable_message = '0';
+       if ($ss =="columns")
+               $show_columns_message = '0';
+}        
+
+//print("res:".$show_reservable_message." - cols:".$show_columns_message);
 
+$slice_nodes=array();
+$potential_nodes=array();
+$reservable_nodes=array();
+foreach ($all_nodes as $node) {
+  if (in_array($node['node_id'],$slice['node_ids'])) {
+    $slice_nodes[]=$node;
+    if ($node['node_type']=='reservable') $reservable_nodes[]=$node;
+  } else {
+    $potential_nodes[]=$node;
+  }
+}
+if ($profiling) plc_debug_prof('5: nodes',count($slice_nodes));
 ////////////////////
-$count=count($nodes);
-$toggle=new PlekitToggle ('my-slice-nodes',"$count Nodes",
+// outline the number of reservable nodes
+$nodes_message=count_english($slice_nodes,"node");
+if (count($reservable_nodes)) $nodes_message .= " (" . count($reservable_nodes) . " reservable)";
+$toggle=new PlekitToggle ('my-slice-nodes',$nodes_message,
                          array('bubble'=>
                                'Manage nodes attached to this slice',
                                'visible'=>get_arg('show_nodes',false)));
 $toggle->start();
 
+
+//////////////////// reservable nodes area
+
+$count=count($reservable_nodes);
+if ($count && $privileges) {
+  // having reservable nodes in white lists looks a bit off scope for now...
+  $toggle_nodes=new PlekitToggle('my-slice-nodes-reserve',
+                                "Leases - " . count($reservable_nodes) . " reservable node(s)",
+                                array('visible'=>$show_reservable_message, 'info_div'=>'note_reservable_div'));
+  $toggle_nodes->start();
+
+if ($show_reservable_message) 
+$note_display = "";
+else
+$note_display = "display:none;";
+
+////////// show a notice to people having attached a reservable node
+if (count($reservable_nodes) && $privileges) {
+  $mark=reservable_mark();
+  print <<<EOF
+<br>
+<div id='note_reservable_div' style="align:center; background-color:#CAE8EA; padding:4px; width:800px; $note_display">
+<table align=center><tr><td valign=top>
+You have attached one or more reservable nodes to your slice. 
+Reservable nodes show up with the '$mark' mark. 
+Your slivers will be available only during timeslots
+where you have obtained leases. 
+You can manage your leases in the tab below.
+<br>
+Please note that as of August 2010 this feature is experimental. 
+Feedback is appreciated at <a href="mailto:devel@planet-lab.org">devel@planet-lab.org</a>
+</td><td valign=top><span onClick=closeMessage('reservable')><img class='reset' src="/planetlab/icons/clear.png" alt="hide message"></span>
+</td></tr></table>
+</div>
+EOF;
+}  
+
+  $grain=$api->GetLeaseGranularity();
+  if ($profiling) plc_debug_prof('6 granul',$grain);
+  // where to start from, expressed as an offset in hours from now
+  $resa_offset=$_GET['resa_offset'];
+  if ( ! $resa_offset ) $resa_offset=0;
+  $rough_start=time()+$resa_offset*3600;
+  // xxx should be configurable
+  $resa_slots=$_GET['resa_slots'];
+  if ( ! $resa_slots ) $resa_slots = 36;
+  // for now, show the next 72 hours, or 72 grains, which ever is smaller
+  $duration=$resa_slots*$grain;
+  $steps=$duration/$grain;
+  $start=intval($rough_start/$grain)*$grain;
+  $end=$rough_start+$duration;
+  $lease_columns=array('lease_id','name','t_from','t_until','hostname','name');
+  $leases=$api->GetLeases(array(']t_until'=>$rough_start,'[t_from'=>$end,'-SORT'=>'t_from'),$lease_columns);
+  if ($profiling) plc_debug_prof('7 leases',count($leases));
+  // hash nodes -> leases
+  $host_hash=array();
+  foreach ($leases as $lease) {
+    $hostname=$lease['hostname'];
+    if ( ! $host_hash[$hostname] ) {
+       $host_hash[$hostname]=array();
+    }
+    // resync within the table
+    $lease['nfrom']=($lease['t_from']-$start)/$grain;
+    $lease['nuntil']=($lease['t_until']-$start)/$grain;
+    $host_hash[$hostname] []= $lease;
+  }
+  # leases_data is the name used by leases.js to locate this table
+  echo "<table id='leases_data'>";
+  # pass (slice_id,slicename) as the [0,0] coordinate as thead>tr>td
+  echo "<thead><tr><td>" . $slice['slice_id'] . '&' . $slice['name'] . "</td>";
+  # the timeslot headers read (timestamp,label)
+  $day_names=array('Su','M','Tu','W','Th','F','Sa');
+  for ($i=0; $i<$steps; $i++) {
+    $timestamp=($start+$i*$grain);
+    $day=$day_names[intval(strftime("%w",$timestamp))];
+    $label=$day . strftime(" %H:%M",$timestamp);
+    // expose in each header cell the full timestamp, and how to display it - use & as a separator*/
+    echo "<th>" . implode("&",array($timestamp,$label)) . "</th>";
+  }
+  echo "</tr></thead><tbody>";
+  // todo - sort on hostnames
+  function sort_hostname ($a,$b) { return ($a['hostname']<$b['hostname'])?-1:1;}
+  usort($reservable_nodes,sort_hostname);
+  foreach ($reservable_nodes as $node) {
+    echo "<tr><th scope='row'>". $node['hostname'] . "</th>";
+    $hostname=$node['hostname'];
+    $leases=$host_hash[$hostname];
+    $counter=0;
+    while ($counter<$steps) {
+      if ($leases && ($leases[0]['nfrom']<=$counter)) {
+       $lease=array_shift($leases);
+       /* nicer display, merge two consecutive leases for the same slice 
+          avoid doing that for now, as it might makes things confusing */
+       /* while ($leases && ($leases[0]['name']==$lease['name']) && ($leases[0]['nfrom']==$lease['nuntil'])) {
+         $lease['nuntil']=$leases[0]['nuntil'];
+         array_shift($leases);
+         }*/
+       $duration=$lease['nuntil']-$counter;
+       echo "<td colspan='$duration'>" . $lease['lease_id'] . '&' . $lease['name'] . "</td>";
+       $counter=$lease['nuntil']; 
+      } else {
+       echo "<td></td>";
+       $counter+=1;
+      }
+    }
+    echo "</tr>";
+  }
+  echo "</tbody></table>\n";
+
+  // the general layout for the scheduler
+  echo <<< EOF
+<div id='leases_area'></div>
+
+<div id='leases_buttons'>
+  <button id='leases_clear' type='submit'>Clear</button>
+  <button id='leases_submit' type='submit'>Submit</button>
+</div>
+EOF;
+
+  $toggle_nodes->end();
+ }
+
+
+//////////////////// node configuration panel
+
+if ($first_time_configuration) 
+$column_conf_visible = '1';
+else
+$column_conf_visible = '0';
+
+
+$toggle_nodes=new PlekitToggle('my-slice-nodes-configuration',
+                               "Node table layout",
+                               array('visible'=>$column_conf_visible, 'info_div'=>'note_columns_div'));
+$toggle_nodes->start();
+
+//usort ($table_headers, create_function('$col1,$col2','return strcmp($col1["header"],$col2["header"]);'));
+//print("<p>TABLE HEADERS<p>");
+//print_r($table_headers);
+
+print("<div id='debug'></div>");
+print("<input type='hidden' id='slice_id' value='".$slice['slice_id']."' />");
+print("<input type='hidden' id='person_id' value='".$plc->person['person_id']."' />");
+print("<input type='hidden' id='conf_tag_id' value='".$conf_tag_id."' />");
+print("<input type='hidden' id='show_tag_id' value='".$show_tag_id."' />");
+print("<input type='hidden' id='show_configuration' value='".$show_configuration."' />");
+print("<input type='hidden' id='column_configuration' value='".$slice_column_configuration."' />");
+print("<br><input type='hidden' size=80 id='full_column_configuration' value='".$column_configuration."' />");
+print("<input type='hidden' id='previousConf' value='".$slice_column_configuration."'></input>");
+print("<input type='hidden' id='defaultConf' value='".$default_configuration."'></input>");
+
+//print ("showing column message = ".$show_columns_message);
+if ($show_columns_message == '0') 
+$note_display = "display:none;";
+else
+$note_display = "";
+
+  print <<<EOF
+<div id='note_columns_div' style="align:center; background-color:#CAE8EA; padding:4px; width:800px; $note_display">
+<table align=center><tr><td valign=top>
+This tab allows you to customize the columns in the node tables, below. Information on the nodes comes from a variety of monitoring sources. If you, as either a user or a provider of monitoring data, would like to see additional columns made available, please send us your request in mail to <a href="mailto:support@myslice.info">support@myslice.info</a>. You can find more information about the MySlice project at <a href="http://trac.myslice.info">http://trac.myslice.info</a>.
+</td><td valign=top><span onClick=closeMessage('columns')><img class='reset' src="/planetlab/icons/clear.png" alt="hide message permanently"></span>
+</td></tr></table>
+</div>
+EOF;
+
+$ConfigureColumns->configuration_panel_html(true);
+
+$ConfigureColumns->javascript_init();
+
+$toggle_nodes->end();
+
+
+$all_sites=$api->GetSites(NULL, array('site_id','login_base'));
+$site_hash=array();
+foreach ($all_sites as $tmp_site) $site_hash[$tmp_site['site_id']]=$tmp_site['login_base'];
+
+$interface_columns=array('ip','node_id','interface_id');
+$interface_filter=array('is_primary'=>TRUE);
+$interfaces=$api->GetInterfaces($interface_filter,$interface_columns);
+
+$interface_hash=array();
+foreach ($interfaces as $interface) $interface_hash[$interface['node_id']]=$interface;
+
+
+
+
+
 //////////////////// nodes currently in
 $toggle_nodes=new PlekitToggle('my-slice-nodes-current',
-                              count_english($nodes,"node") . " currently in $name",
+                              count_english($slice_nodes,"node") . " currently in $name",
                               array('visible'=>get_arg('show_nodes_current',!$privileges)));
 $toggle_nodes->start();
 
 $headers=array();
 $notes=array();
+//$notes=array_merge($notes,$visibletags->notes());
+$notes [] = "For information about the different columns please see the <b>node table layout</b> tab above or <b>mouse over</b> the column headers";
+
+/*
 $headers['peer']='string';
 $headers['hostname']='string';
-$short="ST"; $long=Node::status_footnote(); $type='string'; 
+$short="-S-"; $long=Node::status_footnote(); $type='string'; 
        $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
-$short="R"; $long="reservable nodes"; $type='string';
+$short=reservable_mark(); $long=reservable_legend(); $type='string';
        $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
 // the extra tags, configured for the UI
 $headers=array_merge($headers,$visibletags->headers());
-$notes=array_merge($notes,$visibletags->notes());
 
 if ($privileges) $headers[plc_delete_icon()]="none";
+*/
+
+$edit_header = array();
+if ($privileges) $edit_header[plc_delete_icon()]="none";
+$headers = array_merge($ConfigureColumns->get_headers(),$edit_header);
+
+//print("<p>HEADERS<p>");
+//print_r($headers);
 
 $table_options = array('notes'=>$notes,
                        'search_width'=>15,
-                       'pagesize'=>20);
-$table=new PlekitTable('nodes',$headers,'1',$table_options);
+                       'pagesize'=>20,
+                       'configurable'=>true);
+
+$table=new PlekitTable('nodes',$headers,NULL,$table_options);
 
 $form=new PlekitForm(l_actions(),array('slice_id'=>$slice['slice_id']));
 $form->start();
 $table->start();
-if ($nodes) foreach ($nodes as $node) {
+if ($slice_nodes) foreach ($slice_nodes as $node) {
   $table->row_start();
-  $peers->cell($table,$node['peer_id']);
+
+$table->cell($node['node_id'], array('display'=>'none'));
+
   $table->cell(l_node_obj($node));
+  $peers->cell($table,$node['peer_id']);
   $run_level=$node['run_level'];
   list($label,$class) = Node::status_label_class_($node);
   $table->cell ($label,array('class'=>$class));
-  $table->cell( ($node['node_type']=='reservable')?"-R-":"" );
-  foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
+  $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
+
+  $hostname=$node['hostname'];
+  $ip=$interface_hash[$node['node_id']]['ip'];
+  $interface_id=$interface_hash[$node['node_id']]['interface_id'];
+
+//extra columns
+$node['domain'] = topdomain($hostname);
+$node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
+if ($interface_id)
+        $node['ipaddress'] = l_interface_t($interface_id,$ip);
+  else
+        $node['ipaddress'] = "n/a";
+
+ //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
+ $ConfigureColumns->cells($table, $node);
 
   if ($privileges) $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
   $table->row_end();
@@ -481,30 +845,54 @@ if ($privileges) {
   if ( $potential_nodes ) {
     $headers=array();
     $notes=array();
+
+
+/*
     $headers['peer']='string';
     $headers['hostname']='string';
-    $short="ST"; $long=Node::status_footnote(); $type='string'; 
+    $short="-S-"; $long=Node::status_footnote(); $type='string'; 
        $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
-    $short="R"; $long="reservable nodes"; $type='string';
+       $short=reservable_mark(); $long=reservable_legend(); $type='string';
        $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
     // the extra tags, configured for the UI
     $headers=array_merge($headers,$visibletags->headers());
-    $notes=array_merge($notes,$visibletags->notes());
     $headers['+']="none";
+*/
+
+    $add_header = array();
+    $add_header['+']="none";
+    $headers = array_merge($ConfigureColumns->get_headers(),$add_header);
+
+    //$notes=array_merge($notes,$visibletags->notes());
+$notes [] = "For information about the different columns please see the <b>node table layout</b> tab above or <b>mouse over</b> the column headers";
     
-    $table=new PlekitTable('add_nodes',$headers,'1', $table_options);
+    $table=new PlekitTable('add_nodes',$headers,NULL, $table_options);
     $form=new PlekitForm(l_actions(),
                         array('slice_id'=>$slice['slice_id']));
     $form->start();
     $table->start();
     if ($potential_nodes) foreach ($potential_nodes as $node) {
        $table->row_start();
-       $peers->cell($table,$node['peer_id']);
+
+$table->cell($node['node_id'], array('display'=>'none'));
+
        $table->cell(l_node_obj($node));
+       $peers->cell($table,$node['peer_id']);
        list($label,$class) = Node::status_label_class_($node);
        $table->cell ($label,array('class'=>$class));
-       $table->cell( ($node['node_type']=='reservable')?"-R-":"" );
-       foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
+       $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
+
+       //extra columns
+         $hostname=$node['hostname'];
+         $ip=$interface_hash[$node['node_id']]['ip'];
+         $interface_id=$interface_hash[$node['node_id']]['interface_id'];
+       $node['domain'] = topdomain($hostname);
+       $node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
+       $node['ipaddress'] = l_interface_t($interface_id,$ip);
+
+       //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
+       $ConfigureColumns->cells($table, $node);
+
        $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
        $table->row_end();
       }
@@ -520,78 +908,14 @@ if ($privileges) {
   $toggle_nodes->end();
 }
 
-//////////////////// reservable nodes area
-$count=count($reservable_nodes);
-if ($count && $privileges) {
-  // having reservable nodes in white lists looks a bit off scope for now...
-  $toggle_nodes=new PlekitToggle('my-slice-nodes-reserve',
-                                count_english($reservable_nodes,"reservable node") . " in slice",
-                                array('visible'=>get_arg('show_nodes_resa',false)));
-  $toggle_nodes->start();
-  $grain=$api->GetLeaseGranularity();
-  // xxx should be configurable
-  $now=time(); 
-  // xxx ditto, for now, show the next 48 hours, or 96 grains, which ever is smaller
-  $duration=min(24*3600,24*$grain);
-  $steps=$duration/$grain;
-  $start=intval($now/$grain)*$grain;
-  $end=$now+$duration;
-  $lease_columns=array('name','t_from','t_until','hostname','name');
-  $leases=$api->GetLeases(array(']t_until'=>$now,'[t_from'=>$end,'-SORT'=>'t_from'),$lease_columns);
-  // hash nodes -> leases
-  $host_hash=array();
-  foreach ($leases as $lease) {
-    $hostname=$lease['hostname'];
-    if ( ! $host_hash[$hostname] ) {
-       $host_hash[$hostname]=array();
-    }
-    // resync within the table
-    $lease['nfrom']=($lease['t_from']-$start)/$grain;
-    $lease['nuntil']=($lease['t_until']-$start)/$grain;
-    $host_hash[$hostname] []= $lease;
-  }
-  # leases_data is the name used by leases.js to locate this table
-  echo "<table id='leases_data'>";
-  # pass the slicename as the [0,0] coordinate as thead>tr>td
-  echo "<thead><tr><td>" . $slice['name'] . "</td>";
-  for ($i=0; $i<$steps; $i++) 
-    // expose in each header cell the full timestamp, and how to display it - use & as a separator*/
-    echo "<th>" . ($start+$i*$grain) . "&" . strftime("%H:%M",$start+$i*$grain). "</th>";
-  echo "</tr></thead><tbody>";
-  // todo - sort on hostnames
-  foreach ($reservable_nodes as $node) {
-    echo "<tr><th scope='row'>". $node['hostname'] . "</th>";
-    $hostname=$node['hostname'];
-    $leases=$host_hash[$hostname];
-    $counter=0;
-    while ($counter<$steps) {
-      if ($leases && ($leases[0]['nfrom']<=$counter)) {
-       $lease=array_shift($leases);
-       /* nicer display, merge two consecutive leases for the same slice */
-       while ($leases && ($leases[0]['name']==$lease['name']) && ($leases[0]['nfrom']==$lease['nuntil'])) {
-         $lease['nuntil']=$leases[0]['nuntil'];
-         array_shift($leases);
-       }
-       $duration=$lease['nuntil']-$counter;
-       echo "<td colspan='$duration'>" . $lease['name'] . "</td>";
-       $counter=$lease['nuntil']; 
-      } else {
-       echo "<td></td>";
-       $counter+=1;
-      }
-    }
-    echo "</tr>";
-  }
-
-  echo "</tbody></table>";
-  echo "<div id='leases_area'></div>";
-  $toggle_nodes->end();
- }
 $toggle->end();
 
+// very wide values get abbreviated
+$tag_value_threshold=24;
 //////////////////////////////////////////////////////////// Tags
 //if ( $local_peer ) {
   $tags=$api->GetSliceTags (array('slice_id'=>$slice_id));
+  if ($profiling) plc_debug_prof('8 slice tags',count($tags));
   function get_tagname ($tag) { return $tag['tagname'];}
   $tagnames = array_map ("get_tagname",$tags);
   
@@ -614,26 +938,38 @@ $toggle->end();
   $form->start();
   $table->start();
   if ($tags) {
+    // Get hostnames for nodes in a single pass
+    $_node_ids = array();
+    foreach ($tags as $tag) {
+      if ($tag['node_id']) {
+        array_push($_node_ids, $tag['node_id']);
+      }
+    }
+    $_nodes = $api->GetNodes(array('node_id' => $_node_ids), array('node_id', 'hostname'));
+    $_hostnames = array();
+    foreach ($_nodes as $_node) {
+      $_hostnames[$_node['node_id']] = $_node['hostname'];
+    }
+
+    // Loop through tags again to display
     foreach ($tags as $tag) {
       $node_name = "ALL";
       if ($tag['node_id']) {
-        $nodes = $api->GetNodes(array('node_id'=>$tag['node_id']));
-        if($nodes) {
-          $node = $nodes[0];
-          $node_name = $node['hostname'];
-        }
+        $node_name = $_hostnames[$tag['node_id']];
       }
       $nodegroup_name="n/a";
       if ($tag['nodegroup_id']) { 
-        $nodegroup=$api->GetNodeGroups(array('nodegroup_id'=>$tag['nodegroup_id']));
+        $nodegroups=$api->GetNodeGroups(array('nodegroup_id'=>$tag['nodegroup_id']));
+       if ($profiling) plc_debug_prof('10 nodegroup for slice tag',$nodegroup);
         if ($nodegroup) {
-          $nodegroup = $nodegroup[0];
+          $nodegroup = $nodegroups[0];
           $nodegroup_name = $nodegroup['groupname'];
         }
       }
       $table->row_start();
       $table->cell(l_tag_obj($tag));
-      $table->cell($tag['value']);
+      // very wide values get abbreviated
+      $table->cell(truncate_and_popup($tag['value'],$tag_value_threshold));
       $table->cell($node_name);
       $table->cell($nodegroup_name);
       if ($tags_privileges) $table->cell ($form->checkbox_html('slice_tag_ids[]',$tag['slice_tag_id']));
@@ -652,18 +988,19 @@ $toggle->end();
       return array("display"=>$tag['tagname'],"value"=>$tag['tag_type_id']);
     }
     $all_tags= $api->GetTagTypes( array ("category"=>"slice*","-SORT"=>"+tagname"), array("tagname","tag_type_id"));
+    if ($profiling) plc_debug_prof('11 tagtypes',count($all_tags));
     $selector_tag=array_map("tag_selector",$all_tags);
     
     function node_selector($node) { 
       return array("display"=>$node["hostname"],"value"=>$node['node_id']);
     }
-    $all_nodes = $api->GetNodes( array ("node_id" => $slice['node_ids']), array("hostname","node_id"));
-    $selector_node=array_map("node_selector",$all_nodes);
+    $selector_node=array_map("node_selector",$slice_nodes);
     
     function nodegroup_selector($ng) {
       return array("display"=>$ng["groupname"],"value"=>$ng['nodegroup_id']);
     }
     $all_nodegroups = $api->GetNodeGroups( array("groupname"=>"*"), array("groupname","nodegroup_id"));
+    if ($profiling) plc_debug_prof('13 nodegroups',count($all_nodegroups));
     $selector_nodegroup=array_map("nodegroup_selector",$all_nodegroups);
     
     $table->cell($form->select_html("tag_type_id",$selector_tag,array('label'=>"Choose Tag")));
@@ -687,6 +1024,8 @@ if ($local_peer ) {
 
 $peers->block_end($peer_id);
 
+if ($profiling) plc_debug_prof_end();
+
 // Print footer
 include 'plc_footer.php';