44185e66cb19f13495587dc111a5a298008148cf
[plewww.git] / planetlab / slices / slice.php
1 <?php
2
3 // Require login
4 require_once 'plc_login.php';
5
6 // Get session and API handles
7 require_once 'plc_session.php';
8 global $plc, $api;
9
10 // Print header
11 require_once 'plc_drupal.php';
12 include 'plc_header.php';
13
14 // Common functions
15 require_once 'plc_functions.php';
16 require_once 'plc_peers.php';
17 require_once 'plc_objects.php';
18 require_once 'plc_visibletags2.php';
19 require_once 'linetabs.php';
20 require_once 'table2.php';
21 require_once 'details.php';
22 require_once 'toggle.php';
23 require_once 'form.php';
24 require_once 'raphael.php';
25 require_once 'columns.php';
26
27 // keep css separate for now
28 drupal_set_html_head('
29 <link href="/planetlab/css/my_slice.css" rel="stylesheet" type="text/css" />
30 ');
31
32 // -------------------- admins potentially need to get full list of users
33 ini_set('memory_limit','32M');
34 //error_reporting(0);
35
36 $profiling=false;
37 if ($_GET['profiling']) $profiling=true;
38
39 if ($profiling)  plc_debug_prof_start();
40
41 // -------------------- 
42 // recognized URL arguments
43 $slice_id=intval($_GET['id']);
44 if ( ! $slice_id ) { plc_error('Malformed URL - id not set'); return; }
45
46 ////////////////////
47 // have to name columns b/c we need the non-native 'omf_control' column
48 $slice_columns=array('slice_id','name','peer_id','site_id','person_ids','node_ids','expires',
49                      'url','description','instantiation','omf_control');
50 $slices= $api->GetSlices( array($slice_id), $slice_columns);
51
52 if (empty($slices)) {
53   drupal_set_message ("Slice " . $slice_id . " not found");
54   return;
55  }
56
57 $slice=$slices[0];
58
59 if ($profiling) plc_debug_prof('1: slice',count($slices));
60 // pull all node info to vars
61 $name= $slice['name'];
62 $expires = date( "d/m/Y", $slice['expires'] );
63 $site_id= $slice['site_id'];
64
65 $person_ids=$slice['person_ids'];
66
67 // get peers
68 $peer_id= $slice['peer_id'];
69 $peers=new Peers ($api);
70 $local_peer = ! $peer_id;
71
72 if ($profiling) plc_debug_prof('2: peers',count($peers));
73
74 // gets site info
75 $sites= $api->GetSites( array( $site_id ) );
76 $site=$sites[0];
77 $site_name= $site['name'];
78 $max_slices = $site['max_slices'];
79
80 if ($profiling) plc_debug_prof('3: sites',count($sites));
81 //////////////////////////////////////// building blocks for the renew area
82 // Constants
83 global $DAY;            $DAY = 24*60*60;
84 global $WEEK;           $WEEK = 7 * $DAY; 
85 global $MAX_WEEKS;      $MAX_WEEKS= 8;          // weeks from today
86 global $GRACE_DAYS;     $GRACE_DAYS=10;         // days for renewal promoted on top
87 global $NOW;            $NOW=mktime();
88
89 //////////////////////////////////////////////////////////// utility for the renew tab
90 // make the renew area on top and open if the expiration time is less than 10 days from now
91 function renew_needed ($slice) {
92   global $DAY, $NOW, $GRACE_DAYS;
93   $current_exp=$slice['expires'];
94
95   $time_left = $current_exp - $NOW;
96   $visible = $time_left/$DAY <= $GRACE_DAYS;
97   return $visible;
98 }
99
100 function renew_area ($slice,$site,$visible) {
101   global $DAY, $WEEK, $MAX_WEEKS, $GRACE_DAYS, $NOW;
102  
103   $current_exp=$slice['expires'];
104   $current_text = gmstrftime("%A %b-%d-%y %T %Z", $current_exp);
105   $max_exp= $NOW + ($MAX_WEEKS * $WEEK); // seconds since epoch
106   $max_text = gmstrftime("%A %b-%d-%y %T %Z", $max_exp);
107
108   // xxx some extra code needed to enable this area only if the slice description is OK:
109   // description and url must be non void
110   $toggle=
111     new PlekitToggle('renew',"Expires $current_text - Renew this slice",
112                      array("bubble"=>
113                            "Enter this zone if you wish to renew your slice",
114                            'visible'=>$visible));
115   $toggle->start();
116
117   // xxx message could take roles into account
118   if ($site['max_slices']<=0) {
119      $message= <<< EOF
120 <p class='my-slice-renewal'>Slice creation and renewal have been temporarily disabled for your
121 <site. This may have occurred because your site's nodes have been down
122 or unreachable for several weeks, and multiple attempts to contact
123 your site's PI(s) and Technical Contact(s) have all failed. If so,
124 contact your site's PI(s) and Technical Contact(s) and ask them to
125 bring up your site's nodes. Please visit your <a
126 href='/db/sites/index.php?id=$site_id'>site details</a> page to find
127 out more about your site's nodes, and how to contact your site's PI(s)
128 and Technical Contact(s).</p>
129 EOF;
130      echo $message;
131  
132   } else {
133     // xxx this is a rough cut and paste from the former UI
134     // showing a datepicker view could be considered as well with some extra work
135     // calculate possible extension lengths
136     $selectors = array();
137     foreach ( array ( 1 => "One more week", 
138                       2 => "Two more weeks", 
139                       3 => "Three more weeks", 
140                       4 => "One more month" ) as $weeks => $text ) {
141       $candidate_exp = $current_exp + $weeks*$WEEK;
142       if ( $candidate_exp < $max_exp) {
143         $selectors []= array('display'=>"$text (" . gmstrftime("%A %b-%d-%y %T %Z", $candidate_exp) . ")",
144                              'value'=>$candidate_exp);
145         $max_renewal_weeks=$weeks;
146         $max_renewal_date= gmstrftime("%A %b-%d-%y %T %Z", $candidate_exp);
147       }
148     }
149
150     if ( empty( $selectors ) ) {
151       print <<< EOF
152 <div class='my-slice-renewal'>
153 Slices cannot be renewed more than $MAX_WEEKS weeks from now, i.e. not beyond $max_text. 
154 For this reason, the current slice cannot be renewed any further into the future, try again closer to expiration date.
155 </div>
156 EOF;
157      } else {
158       print <<< EOF
159 <div class='my-slice-renewal'>
160 <span class='bold'>Important:</span> Please take this opportunity to review and update your slice information in the Details tab.
161 <p>
162 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.
163 </p><p>
164 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. 
165 </p>
166 EOF;
167
168       $form = new PlekitForm (l_actions(),
169                               array('action'=>'renew-slice',
170                                     'slice_id'=>$slice['slice_id']));
171       $form->start();
172       print $form->label_html('expires','Duration:&nbsp;');
173       print $form->select_html('expires',$selectors,array('label'=>'Pick one'));
174       print $form->submit_html('renew-button','Renew');
175       $form->end();
176
177 print("<p><i>NOTE: Slices cannot be renewed beyond another $max_renewal_weeks week(s) ($max_renewal_date).</i>  </p>");
178 print ("</div>");
179     }
180   }
181  
182   $toggle->end();
183 }
184
185 ////////////////////////////////////////////////////////////
186
187 $am_in_slice = in_array(plc_my_person_id(),$person_ids);
188
189 if ($am_in_slice) {
190   drupal_set_title("My slice " . $name);
191  } else {
192   drupal_set_title("Slice " . $name);
193 }
194
195 $privileges = ( $local_peer && (plc_is_admin()  || plc_is_pi() || $am_in_slice));
196 $tags_privileges = $privileges || plc_is_admin();
197
198 $tabs=array();
199 $tabs [] = tab_nodes_slice($slice_id);
200 $tabs [] = tab_site($site);
201
202 // are these the right privileges for deletion ?
203 if ($privileges) {
204   $tabs ['Delete']= array('url'=>l_actions(),
205                           'method'=>'post',
206                           'values'=>array('action'=>'delete-slice','slice_id'=>$slice_id),
207                           'bubble'=>"Delete slice $name",
208                           'confirm'=>"Are you sure to delete slice $name");
209
210   $tabs["Events"]=array_merge(tablook_event(),
211                               array('url'=>l_event("Slice","slice",$slice_id),
212                                     'bubble'=>"Events for slice $name"));
213   $tabs["Comon"]=array_merge(tablook_comon(),
214                              array('url'=>l_comon("slice_id",$slice_id),
215                                    'bubble'=>"Comon page about slice $name"));
216 }
217
218 plekit_linetabs($tabs);
219
220 ////////////////////////////////////////
221 $peers->block_start($peer_id);
222
223 //////////////////////////////////////// renewal area 
224 // (1) close to expiration : show on top and open
225
226 if ($local_peer ) {
227   $renew_visible = renew_needed ($slice);
228   if ($renew_visible) renew_area ($slice,$site,true);
229  }
230
231
232 //////////////////////////////////////////////////////////// tab:details
233 $toggle = 
234   new PlekitToggle ('my-slice-details',"Details",
235                     array('bubble'=>
236                           'Display and modify details for that slice',
237                           'visible'=>get_arg('show_details',NULL)));
238 $toggle->start();
239
240 $details=new PlekitDetails($privileges);
241 $details->form_start(l_actions(),array('action'=>'update-slice',
242                                        'slice_id'=>$slice_id,
243                                        'name'=>$name));
244
245 $details->start();
246 if (! $local_peer) {
247   $details->th_td("Peer",$peers->peer_link($peer_id));
248   $details->space();
249  }
250
251
252 $details->th_td('Name',$slice['name']);
253 $details->th_td('Description',$slice['description'],'description',
254                 array('input_type'=>'textarea',
255                       'width'=>50,'height'=>5));
256 $details->th_td('URL',$slice['url'],'url',array('width'=>50));
257 $details->tr_submit("submit","Update Slice");
258 $details->th_td('Expires',$expires);
259 $details->th_td('Instantiation',$slice['instantiation']);
260 $details->th_td("OMF-friendly", ($slice['omf_control'] ? 'Yes' : 'No') . " [to change: see 'omf_control' in the tags section below]");
261 $details->th_td('Site',l_site_obj($site));
262 // xxx show the PIs here
263 //$details->th_td('PIs',...);
264 $details->end();
265
266 $details->form_end();
267 $toggle->end();
268
269 //////////////////////////////////////////////////////////// tab:persons
270 $person_columns = array('email','person_id','first_name','last_name','roles');
271 // get persons in slice
272 if (!empty($person_ids))
273   $persons=$api->GetPersons(array('person_id'=>$slice['person_ids']),$person_columns);
274 // just propose to add everyone else
275 // xxx this is maybe too much for admins as it slows stuff down 
276 // as regular persons can see only a fraction of the db anyway
277 $potential_persons=
278   $api->GetPersons(array('~person_id'=>$slice['person_ids'],
279                          'peer_id'=>NULL,
280                          'enabled'=>true),
281                    $person_columns);
282 $count=count($persons);
283
284 if ($profiling) plc_debug_prof('4: persons',count($persons));
285 $toggle=
286   new PlekitToggle ('my-slice-persons',"$count users",
287                     array('bubble'=>
288                           'Manage accounts attached to this slice',
289                           'visible'=>get_arg('show_persons',NULL)));
290 $toggle->start();
291
292 ////////// people currently in
293 // visible:
294 // hide if both current+add are included
295 // so user can chose which section is of interest
296 // show otherwise
297 $toggle_persons = new PlekitToggle ('my-slice-persons-current',
298                                     "$count people currently in $name",
299                                     array('visible'=>get_arg('show_persons_current',!$privileges)));
300 $toggle_persons->start();
301
302 $headers=array();
303 $headers['email']='string';
304 $headers['first']='string';
305 $headers['last']='string';
306 $headers['R']='string';
307 if ($privileges) $headers[plc_delete_icon()]="none";
308 $table=new PlekitTable('persons',$headers,'0',
309                        array('notes_area'=>false));
310 $form=new PlekitForm(l_actions(),array('slice_id'=>$slice['slice_id']));
311 $form->start();
312 $table->start();
313 if ($persons) foreach ($persons as $person) {
314   $table->row_start();
315   $table->cell(l_person_obj($person));
316   $table->cell($person['first_name']);
317   $table->cell($person['last_name']);
318   $table->cell(plc_vertical_table ($person['roles']));
319   if ($privileges) $table->cell ($form->checkbox_html('person_ids[]',$person['person_id']));
320   $table->row_end();
321 }
322 // actions area
323 if ($privileges) {
324
325   // remove persons
326   $table->tfoot_start();
327
328   $table->row_start();
329   $table->cell($form->submit_html ("remove-persons-from-slice","Remove selected"),
330                array('hfill'=>true,'align'=>'right'));
331   $table->row_end();
332  }
333 $table->end();
334 $toggle_persons->end();
335
336 ////////// people to add
337 if ($privileges) {
338   $count=count($potential_persons);
339   $toggle_persons = new PlekitToggle ('my-slice-persons-add',
340                                       "$count people may be added to $name",
341                                       array('visible'=>get_arg('show_persons_add',NULL)));
342   $toggle_persons->start();
343   if ( ! $potential_persons ) {
344     // xxx improve style
345     echo "<p class='not-relevant'>No person to add</p>";
346   } else {
347     $headers=array();
348     $headers['email']='string';
349     $headers['first']='string';
350     $headers['last']='string';
351     $headers['R']='string';
352     $headers['+']="none";
353     $options = array('notes_area'=>false,
354                      'search_width'=>15,
355                      'pagesize'=>8);
356     // show search for admins only as other people won't get that many names to add
357     if ( ! plc_is_admin() ) $options['search_area']=false;
358     
359     $table=new PlekitTable('add_persons',$headers,'0',$options);
360     $form=new PlekitForm(l_actions(),array('slice_id'=>$slice['slice_id']));
361     $form->start();
362     $table->start();
363     if ($potential_persons) foreach ($potential_persons as $person) {
364         $table->row_start();
365         $table->cell(l_person_obj($person));
366         $table->cell($person['first_name']);
367         $table->cell($person['last_name']);
368         $table->cell(plc_vertical_table ($person['roles']));
369         $table->cell ($form->checkbox_html('person_ids[]',$person['person_id']));
370         $table->row_end();
371       }
372     // add users
373     $table->tfoot_start();
374     $table->row_start();
375     $table->cell($form->submit_html ("add-persons-in-slice","Add selected"),
376                  array('hfill'=>true,'align'=>'right'));
377     $table->row_end();
378     $table->end();
379     $form->end();
380   }
381   $toggle_persons->end();
382 }
383 $toggle->end();
384
385 //////////////////////////////////////////////////////////// tab:nodes
386 // the nodes details to display here
387 // (1) we search for the tag types for which 'category' matches 'node*/ui*'
388 // all these tags will then be tentatively displayed in this area
389 // (2) further information can also be optionally specified in the category:
390 //     (.) we split the category with '/' and search for assignments of the form var=value
391 //     (.) header can be set to supersede the column header (default is tagname)
392 //     (.) rank can be used for ordering the columns (default is tagname)
393 //     (.) type is passed to the javascript table, for sorting (default is 'string')
394
395 // minimal list as a start
396 $node_fixed_columns = array('hostname','node_id','peer_id','slice_ids_whitelist', 'site_id',
397                             'run_level','boot_state','last_contact','node_type');
398 // create a VisibleTags object : basically the list of tag columns to show
399 //$visibletags = new VisibleTags ($api, 'node');
400 //$visiblecolumns = $visibletags->column_names();
401
402 // optimizing calls to GetNodes
403 //$all_nodes=$api->GetNodes(NULL,$node_columns);
404 //$slice_nodes=$api->GetNodes(array('node_id'=>$slice['node_ids']),$node_columns);
405 //$potential_nodes=$api->GetNodes(array('~node_id'=>$slice['node_ids']),$node_columns);
406
407
408 //NEW CODE FOR ENABLING COLUMN CONFIGURATION
409
410 //prepare fix and configurable columns
411
412 $fix_columns = array();
413 $fix_columns[]=array('tagname'=>'hostname', 'header'=>'hostname', 'type'=>'string', 'title'=>'The name of the node');
414 $fix_columns[]=array('tagname'=>'peer_id', 'header'=>'AU', 'type'=>'string', 'title'=>'Authority');
415 $fix_columns[]=array('tagname'=>'run_level', 'header'=>'ST', 'type'=>'string', 'title'=>'Status');
416 $fix_columns[]=array('tagname'=>'node_type', 'header'=>'RES', 'type'=>'string', 'title'=>'Reservable');
417
418 // columns that correspond to the visible tags for nodes (*node/ui*)
419 $visibletags = new VisibleTags ($api, 'node');
420 $visibletags->columns();
421 $tag_columns = $visibletags->headers();
422
423 //columns that are not defined as extra myslice tags
424 $extra_columns = array();
425 //MyPLC columns
426 $extra_columns[]=array('tagname'=>'sitename', 'header'=>'SN', 'type'=>'string', 'title'=>'Site name', 'fetched'=>true, 'source'=>'myplc');
427 $extra_columns[]=array('tagname'=>'domain', 'header'=>'DN', 'type'=>'string', 'title'=>'Toplevel domain name', 'fetched'=>true, 'source'=>'myplc');
428 $extra_columns[]=array('tagname'=>'ipaddress', 'header'=>'IP', 'type'=>'string', 'title'=>'IP Address', 'fetched'=>true, 'source'=>'myplc');
429 $extra_columns[]=array('tagname'=>'fcdistro', 'header'=>'OS', 'type'=>'string', 'title'=>'Operating system', 'fetched'=>false, 'source'=>'myplc');
430 $extra_columns[]=array('tagname'=>'date_created', 'header'=>'DA', 'source'=>'myplc', 'type'=>'date', 'title'=>'Date added', 'fetched'=>false);
431 $extra_columns[]=array('tagname'=>'arch', 'header'=>'A', 'source'=>'myplc', 'type'=>'string', 'title'=>'Architecture', 'fetched'=>false);
432 if (plc_is_admin()) {
433 $extra_columns[]=array('tagname'=>'deployment', 'header'=>'DL', 'source'=>'myplc', 'type'=>'string', 'title'=>'Deployment', 'fetched'=>false);
434 }
435
436 //CoMon Live data
437
438 if (MYSLICE_COMON_AVAILABLE)
439 {
440 $extra_columns[]=array('tagname'=>'bwlimit', 'header'=>'BW', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Bandwidth limit', 'fetched'=>false);
441 $extra_columns[]=array('tagname'=>'numcores', 'header'=>'CC', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Number of CPU Cores', 'fetched'=>false);
442 $extra_columns[]=array('tagname'=>'cpuspeed', 'header'=>'CR', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'CPU clock rate', 'fetched'=>false);
443 $extra_columns[]=array('tagname'=>'disksize', 'header'=>'DS', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Disk size', 'fetched'=>false);
444 $extra_columns[]=array('tagname'=>'gbfree', 'header'=>'DF', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Currently available disk space', 'fetched'=>false);
445 $extra_columns[]=array('tagname'=>'memsize', 'header'=>'MS', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Memory size', 'fetched'=>false);
446 $extra_columns[]=array('tagname'=>'numslices', 'header'=>'SM', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Number of slices in memory', 'fetched'=>false);
447 $extra_columns[]=array('tagname'=>'uptime', 'header'=>'UT', 'source'=>'comon', 'type'=>'sortAlphaNumericTop', 'title'=>'Continuous uptime until now', 'fetched'=>false);
448 }
449
450 //TopHat Live data
451
452 if (MYSLICE_TOPHAT_AVAILABLE)
453 {
454 $extra_columns[]=array('tagname'=>'asn', 'header'=>'AS', 'source'=>'tophat', 'type'=>'string', 'title'=>'AS Number', 'fetched'=>false);
455 $extra_columns[]=array('tagname'=>'city', 'header'=>'LCY', 'source'=>'tophat', 'type'=>'string', 'title'=>'City', 'fetched'=>false);
456 $extra_columns[]=array('tagname'=>'region', 'header'=>'LRN', 'source'=>'tophat', 'type'=>'string', 'title'=>'Region', 'fetched'=>false);
457 $extra_columns[]=array('tagname'=>'country', 'header'=>'LCN', 'source'=>'tophat', 'type'=>'string', 'title'=>'Country', 'fetched'=>false);
458 $extra_columns[]=array('tagname'=>'continent', 'header'=>'LCT', 'source'=>'tophat', 'type'=>'string', 'title'=>'Continent', 'fetched'=>false);
459 //$extra_columns[]=array('tagname'=>'hopcount', 'header'=>'HC', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Hop count from reference node', 'fetched'=>false);
460 ////$extra_columns[]=array('tagname'=>'rtt', 'header'=>'RTT', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Round trip time from reference node', 'fetched'=>false);
461 //////$extra_columns[]=array('tagname'=>'agents', 'header'=>'MA', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Co-located measurement agents', 'fetched'=>true);
462 ////$extra_columns[]=array('tagname'=>'agents_sonoma', 'header'=>'MAS', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Co-located SONoMA agents', 'fetched'=>true);
463 ////$extra_columns[]=array('tagname'=>'agents_etomic', 'header'=>'MAE', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Co-located ETOMIC agents', 'fetched'=>true);
464 ////$extra_columns[]=array('tagname'=>'agents_tdmi', 'header'=>'MAT', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Co-located TDMI agents', 'fetched'=>true);
465 ////$extra_columns[]=array('tagname'=>'agents_dimes', 'header'=>'MAD', 'source'=>'tophat', 'type'=>'sortAlphaNumericTop', 'title'=>'Co-located DIMES agents', 'fetched'=>true);
466 }
467
468
469
470 //Get user's column configuration
471
472 $first_time_configuration = false;
473 $default_configuration = "hostname:f|ST:f|AU:f|RES:f";
474 //$extra_default = "";
475 $column_configuration = "";
476 $slice_column_configuration = "";
477
478 $show_configuration = "";
479
480 $PersonTags=$api->GetPersonTags (array('person_id'=>$plc->person['person_id']));
481 //plc_debug('ptags',$PersonTags);
482 foreach ($PersonTags as $ptag) {
483   if ($ptag['tagname'] == 'columnconf') {
484     $column_configuration = $ptag['value'];
485     $conf_tag_id = $ptag['person_tag_id'];
486   } else if ($ptag['tagname'] == 'showconf') {
487     $show_configuration = $ptag['value'];
488     $show_tag_id = $ptag['person_tag_id'];
489   }
490 }
491
492 $sliceconf_exists = false;
493 if ($column_configuration == "") {
494   $first_time_configuration = true;
495   $column_configuration = $slice_id.";default";
496   $sliceconf_exists = true;
497 } else {
498   $slice_conf = explode(";",$column_configuration);
499   for ($i=0; $i<count($slice_conf); $i++ ) {
500     if ($slice_conf[$i] == $slice_id) {
501       $i++;
502       $slice_column_configuration = $slice_conf[$i];
503       $sliceconf_exists = true;
504       break;
505     } else {
506       $i++;
507       $slice_column_configuration = $slice_conf[$i];
508     }
509   }        
510 }
511
512 if ($sliceconf_exists == false)
513   $column_configuration = $column_configuration.";".$slice_id.";default";
514
515 if ($slice_column_configuration == "")
516   $full_configuration = $default_configuration;
517 else
518   $full_configuration = $default_configuration."|".$slice_column_configuration;
519
520
521 //instantiate the column configuration class, which prepares the headers array
522 $ConfigureColumns =new PlekitColumns($full_configuration, $fix_columns, $tag_columns, $extra_columns);
523
524 $visiblecolumns = $ConfigureColumns->node_tags();
525
526 $node_columns=array_merge($node_fixed_columns,$visiblecolumns);
527 $all_nodes=$api->GetNodes(NULL,$node_columns);
528
529 $ConfigureColumns->fetch_live_data($all_nodes);
530
531 $show_reservable_info = TRUE;
532 $show_layout_info = '1';
533 $show_conf = explode(";",$show_configuration);
534 foreach ($show_conf as $ss) {
535   if ($ss =="reservable")
536     $show_reservable_info = FALSE;
537   else if ($ss =="columns")
538     $show_layout_info = '0';
539 }        
540
541 $slice_nodes=array();
542 $potential_nodes=array();
543 $reservable_nodes=array();
544 foreach ($all_nodes as $node) {
545   if (in_array($node['node_id'],$slice['node_ids'])) {
546     $slice_nodes[]=$node;
547     if ($node['node_type']=='reservable') $reservable_nodes[]=$node;
548   } else {
549     $potential_nodes[]=$node;
550   }
551 }
552 if ($profiling) plc_debug_prof('5: nodes',count($slice_nodes));
553 ////////////////////
554 // outline the number of reservable nodes
555 $nodes_message=count_english($slice_nodes,"node");
556 if (count($reservable_nodes)) $nodes_message .= " (" . count($reservable_nodes) . " reservable)";
557 $toggle=new PlekitToggle ('my-slice-nodes',$nodes_message,
558                           array('bubble'=>
559                                 'Manage nodes attached to this slice',
560                                 'visible'=>get_arg('show_nodes',NULL)));
561 $toggle->start();
562
563
564 //////////////////// reservable nodes area
565 $leases_info="
566 You have attached one or more reservable nodes to your slice. 
567 Reservable nodes show up with the '$mark' mark. 
568 Your slivers will be available only during timeslots
569 where you have obtained leases. 
570 You can manage your leases in the tab below.
571 <br>
572 This feature is still experimental; feedback is appreciated at <a href='mailto:devel@planet-lab.org'>devel@planet-lab.org</a>
573 ";
574 $count=count($reservable_nodes);
575 if ($count && $privileges) {
576   // include leases.js only if needed
577   drupal_set_html_head('<script src="/planetlab/slices/leases.js" type="text/javascript" charset="utf-8"></script>');
578
579   // having reservable nodes in white lists looks a bit off scope for now...
580   $toggle_nodes=new PlekitToggle('my-slice-nodes-reserve',
581                                  "Leases - " . count($reservable_nodes) . " reservable node(s)",
582                                  array('visible'=>get_arg('show_nodes_resa',NULL), 
583                                        'info-text'=>$leases_info,
584                                        'info-visible'=>$show_reservable_info));
585   $toggle_nodes->start();
586
587   // get settings from environment, otherwise set to defaults
588   // when to start, in hours in the future from now
589   $leases_offset=$_GET['leases_offset'];
590   if ( ! $leases_offset ) $leases_offset=0;
591   // how many timeslots to show
592   $leases_slots=$_GET['leases_slots'];
593   if ( ! $leases_slots ) $leases_slots = 48;
594   // offset in hours (in the future) from now 
595   $leases_w = $_GET['leases_w'];
596   if ( ! $leases_w) $leases_w=14;
597   // number of timeslots to display
598
599   $granularity=$api->GetLeaseGranularity();
600
601   // these elements are for passing data to the javascript layer
602   echo "<span class='hidden' id='leases_slicename'>" . $slice['name'] . "</span>";
603   echo "<span class='hidden' id='leases_slice_id'>" . $slice['slice_id']. "</span>";
604   echo "<span class='hidden' id='leases_granularity'>" . $granularity . "</span>";
605   // ditto, and editable - very rough for now
606   echo "<div class='center' id='leases_settings'>";
607   echo "<label id='leases_offset_label' class='leases_label'>start, in hours from now</label>";
608   echo "<input type='text' class='leases_input' id='leases_offset_input' value='$leases_offset' />";
609   echo "<label id='leases_slots_label' class='leases_label'># of timeslots</label>";
610   echo "<input type='text' class='leases_input' id='leases_slots_input' value='$leases_slots' />";
611   echo "<label id='leases_w_label' class='leases_label'>slot width, in pixels</label>";
612   echo "<input type='text' class='leases_input' id='leases_w_input' value='$leases_w' />";
613   echo "</div>";
614
615   // leases_data is the name used by leases.js to locate this place
616   // first population will be triggered by init_scheduler from leases.js
617   echo "<table id='leases_data' class='hidden'></table>";
618
619   // the general layout for the scheduler
620   echo <<< EOF
621 <div id='leases_area'></div>
622
623 <div id='leases_buttons'>
624   <button id='leases_refresh' type='submit'>Refresh</button>
625   <button id='leases_submit' type='submit'>Submit</button>
626 </div>
627 EOF;
628
629   $toggle_nodes->end();
630  }
631
632
633 //////////////////// node configuration panel
634 if ($first_time_configuration) 
635 $column_conf_visible = '1';
636 else
637 $column_conf_visible = '0';
638
639 $layout_info='
640 This tab allows you to customize the columns in the node tables,
641 below. Information on the nodes comes from a variety of monitoring
642 sources. If you, as either a user or a provider of monitoring data,
643 would like to see additional columns made available, please send us
644 your request in mail to <a
645 href="mailto:support@myslice.info">support@myslice.info</a>. You can
646 find more information about the MySlice project at <a
647 href="http://trac.myslice.info">http://trac.myslice.info</a>.
648 ';
649 $toggle_nodes=new PlekitToggle('my-slice-nodes-configuration',
650                                "Node table layout",
651                                array('visible'=>NULL, 
652                                      'info-text'=>$layout_info,
653                                      'info-visible'=>$show_layout_info));
654 $toggle_nodes->start();
655
656 //usort ($table_headers, create_function('$col1,$col2','return strcmp($col1["header"],$col2["header"]);'));
657 //print("<p>TABLE HEADERS<p>");
658 //print_r($table_headers);
659
660 print("<div id='debug'></div>");
661 print("<input type='hidden' id='slice_id' value='".$slice['slice_id']."' />");
662 print("<input type='hidden' id='person_id' value='".$plc->person['person_id']."' />");
663 print("<input type='hidden' id='conf_tag_id' value='".$conf_tag_id."' />");
664 print("<input type='hidden' id='show_tag_id' value='".$show_tag_id."' />");
665 print("<input type='hidden' id='show_configuration' value='".$show_configuration."' />");
666 print("<input type='hidden' id='column_configuration' value='".$slice_column_configuration."' />");
667 print("<br><input type='hidden' size=80 id='full_column_configuration' value='".$column_configuration."' />");
668 print("<input type='hidden' id='previousConf' value='".$slice_column_configuration."' />");
669 print("<input type='hidden' id='defaultConf' value='".$default_configuration."' />");
670
671 $ConfigureColumns->configuration_panel_html(true);
672
673 $ConfigureColumns->javascript_init();
674
675 $toggle_nodes->end();
676
677
678 $all_sites=$api->GetSites(NULL, array('site_id','login_base'));
679 $site_hash=array();
680 foreach ($all_sites as $tmp_site) $site_hash[$tmp_site['site_id']]=$tmp_site['login_base'];
681
682 $interface_columns=array('ip','node_id','interface_id');
683 $interface_filter=array('is_primary'=>TRUE);
684 $interfaces=$api->GetInterfaces($interface_filter,$interface_columns);
685
686 $interface_hash=array();
687 foreach ($interfaces as $interface) $interface_hash[$interface['node_id']]=$interface;
688
689
690
691
692
693 //////////////////// nodes currently in
694 $toggle_nodes=new PlekitToggle('my-slice-nodes-current',
695                                count_english($slice_nodes,"node") . " currently in $name",
696                                array('visible'=>get_arg('show_nodes_current',!$privileges)));
697 $toggle_nodes->start();
698
699 $headers=array();
700 $notes=array();
701 //$notes=array_merge($notes,$visibletags->notes());
702 $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";
703
704 /*
705 $headers['peer']='string';
706 $headers['hostname']='string';
707 $short="-S-"; $long=Node::status_footnote(); $type='string'; 
708         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
709 $short=reservable_mark(); $long=reservable_legend(); $type='string';
710         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
711 // the extra tags, configured for the UI
712 $headers=array_merge($headers,$visibletags->headers());
713
714 if ($privileges) $headers[plc_delete_icon()]="none";
715 */
716
717 $edit_header = array();
718 if ($privileges) $edit_header[plc_delete_icon()]="none";
719 $headers = array_merge($ConfigureColumns->get_headers(),$edit_header);
720
721 //print("<p>HEADERS<p>");
722 //print_r($headers);
723
724 $table_options = array('notes'=>$notes,
725                        'search_width'=>15,
726                        'pagesize'=>20,
727                         'configurable'=>true);
728
729 $table=new PlekitTable('nodes',$headers,NULL,$table_options);
730
731 $form=new PlekitForm(l_actions(),array('slice_id'=>$slice['slice_id']));
732 $form->start();
733 $table->start();
734 if ($slice_nodes) foreach ($slice_nodes as $node) {
735   $table->row_start();
736
737 $table->cell($node['node_id'], array('display'=>'none'));
738
739   $table->cell(l_node_obj($node));
740   $peers->cell($table,$node['peer_id']);
741   $run_level=$node['run_level'];
742   list($label,$class) = Node::status_label_class_($node);
743   $table->cell ($label,array('class'=>$class));
744   $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
745
746   $hostname=$node['hostname'];
747   $ip=$interface_hash[$node['node_id']]['ip'];
748   $interface_id=$interface_hash[$node['node_id']]['interface_id'];
749
750 //extra columns
751 $node['domain'] = topdomain($hostname);
752 $node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
753 if ($interface_id)
754         $node['ipaddress'] = l_interface_t($interface_id,$ip);
755   else
756         $node['ipaddress'] = "n/a";
757
758  //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
759  $ConfigureColumns->cells($table, $node);
760
761   if ($privileges) $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
762   $table->row_end();
763 }
764 // actions area
765 if ($privileges) {
766
767   // remove nodes
768   $table->tfoot_start();
769
770   $table->row_start();
771   $table->cell($form->submit_html ("remove-nodes-from-slice","Remove selected"),
772                array('hfill'=>true,'align'=>'right'));
773   $table->row_end();
774  }
775 $table->end();
776 $toggle_nodes->end();
777
778 //////////////////// nodes to add
779 if ($privileges) {
780   $new_potential_nodes = array();
781   if ($potential_nodes) foreach ($potential_nodes as $node) {
782       $emptywl=empty($node['slice_ids_whitelist']);
783       $inwl = (!emptywl) and in_array($slice['slice_id'],$node['slice_ids_whitelist']);
784       if ($emptywl or $inwl)
785         $new_potential_nodes[]=$node;
786   }
787   $potential_nodes=$new_potential_nodes;
788
789   $count=count($potential_nodes);
790   $toggle_nodes=new PlekitToggle('my-slice-nodes-add',
791                                  count_english($potential_nodes,"more node") . " available",
792                                  array('visible'=>get_arg('show_nodes_add',NULL)));
793   $toggle_nodes->start();
794
795   if ( $potential_nodes ) {
796     $headers=array();
797     $notes=array();
798
799
800 /*
801     $headers['peer']='string';
802     $headers['hostname']='string';
803     $short="-S-"; $long=Node::status_footnote(); $type='string'; 
804         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
805         $short=reservable_mark(); $long=reservable_legend(); $type='string';
806         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
807     // the extra tags, configured for the UI
808     $headers=array_merge($headers,$visibletags->headers());
809     $headers['+']="none";
810 */
811
812     $add_header = array();
813     $add_header['+']="none";
814     $headers = array_merge($ConfigureColumns->get_headers(),$add_header);
815
816     //$notes=array_merge($notes,$visibletags->notes());
817 $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";
818     
819     $table=new PlekitTable('add_nodes',$headers,NULL, $table_options);
820     $form=new PlekitForm(l_actions(),
821                          array('slice_id'=>$slice['slice_id']));
822     $form->start();
823     $table->start();
824     if ($potential_nodes) foreach ($potential_nodes as $node) {
825         $table->row_start();
826
827 $table->cell($node['node_id'], array('display'=>'none'));
828
829         $table->cell(l_node_obj($node));
830         $peers->cell($table,$node['peer_id']);
831         list($label,$class) = Node::status_label_class_($node);
832         $table->cell ($label,array('class'=>$class));
833         $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
834
835         //extra columns
836           $hostname=$node['hostname'];
837           $ip=$interface_hash[$node['node_id']]['ip'];
838           $interface_id=$interface_hash[$node['node_id']]['interface_id'];
839         $node['domain'] = topdomain($hostname);
840         $node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
841         $node['ipaddress'] = l_interface_t($interface_id,$ip);
842
843         //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
844         $ConfigureColumns->cells($table, $node);
845
846         $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
847         $table->row_end();
848       }
849     // add nodes
850     $table->tfoot_start();
851     $table->row_start();
852     $table->cell($form->submit_html ("add-nodes-in-slice","Add selected"),
853                  array('hfill'=>true,'align'=>'right'));
854     $table->row_end();
855     $table->end();
856     $form->end();
857   }
858   $toggle_nodes->end();
859 }
860
861 $toggle->end();
862
863 //////////////////////////////////////// retrieve all slice tags
864 $tags=$api->GetSliceTags (array('slice_id'=>$slice_id));
865 //////////////////////////////////////////////////////////// tab:initscripts
866 // xxx fixme
867 // * add a message on how to use this:
868 // * explain the 2 mechanisms (initscript_code, initscript)
869 // * explain the interface : initscript start|stop|restart slicename
870 // xxx fixme
871
872 $initscript_info="
873 There are two ways to attach an initscript to a slice:<ul>
874
875 <li> <span class='bold'> Shared initscripts </span> are global to the
876 MyPLC, and managed by the Operations Team. For that reason, regular
877 users cannot change these scripts, but can reference one of the
878 available names in the drop down below.  </li>
879
880 <li> You also have the option to provide <span class='bold'> your own
881 code </span>, with the following conventions: <ul>
882
883 <li> Like regular initscripts, your script must except to receive as a
884 first argument <span class='bold'> start </span>, <span class='bold'>
885 stop </span> or <span class='bold'> restart </span>. It is important
886 to honor this argument, as your slice may be stopped and restarted at
887 any time; also this is used whenever the installed code gets changed.
888 </li>
889
890 <li> As a second argument, you will receive the slicename; in most
891 cases this can be safely ignored.  </li>
892
893 </ul>
894 </li>
895  </ul>
896 The slice-specific setting has precedence on a shared initscript.
897 ";
898
899 $shared_initscripts=$api->GetInitScripts(array('-SORT'=>'name'),array('name'));
900 //$shared_initscripts=$api->GetInitScripts();
901 if ($profiling) plc_debug_prof('6 initscripts',count($initscripts));
902 // xxx expose this even on foreign slices for now
903 if ($local_peer) {
904   $initscript='';
905   $initscript_code='';
906   if ($tags) foreach ($tags as $tag) {
907       if ($tag['tagname']=='initscript') {
908         if ($initscript!='') drupal_set_error("multiple occurrences of 'initscript' tag");
909         $initscript=$tag['value'];
910       }
911       if ($tag['tagname']=='initscript_code') {
912         if ($initscript_code!='') drupal_set_error("multiple occurrences of 'initscript_code' tag");
913         $initscript_code=$tag['value'];
914         // plc_debug_txt('retrieved body',$initscript_code);
915       }
916     }
917   $label="No initscript";
918   $trimmed=trim($initscript_code);
919   if (!empty($trimmed)) $label="Initscript : slice-specific (" . substr($initscript_code,0,20) . " ...)";
920   else if (!empty($initscript)) $label="Initscript: shared " . $initscript;
921
922   $toggle = new PlekitToggle('slice-initscripts',$label,
923                              array('bubble'=>'Manage initscript on that slice',
924                                    'visible'=>get_arg('show_initscripts',NULL),
925                                    'info-text'=>$initscript_info
926                                    // not messing with persontags to guess whether this should be displayed or not
927                                    // hopefully some day toggle will know how to handle that using web storage
928                                    ));
929   $toggle->start();
930
931   $details=new PlekitDetails(TRUE);
932   // we expose the previous values so that actions.php can know if changes are really needed
933   // the code needs to be encoded as it may contain any character
934   // as far as the code, this does not work too well b/c what actions.php receives
935   // seems to have spurrious \r chars, and the comparison between old and new values 
936   // is not reliable, which results in changes being made although the code hasn't changed
937   // hve spent too much time on this, good enough for now...
938   $details->form_start(l_actions(),array('action'=>'update-initscripts',
939                                          'slice_id'=>$slice_id,
940                                          'name'=>$name,
941                                          'previous-initscript'=>$initscript,
942                                          'previous-initscript-code'=>htmlentities($initscript_code)));
943   $details->start();
944   // comppute a pulldown with available names
945   $selectors=array();
946   $is_found=FALSE;
947   if ($shared_initscripts) foreach ($shared_initscripts as $is) {
948       $is_selector=array('display'=>$is['name'],'value'=>$is['name']);
949       if ($is['name']==$initscript) {
950         $is_selector['selected']=TRUE;
951         $is_found=TRUE;
952       }
953       $selectors[]=$is_selector;
954     }
955   // display a warning when initscript references an unknown script
956   $details->tr_submit('unused','Update initscripts');
957   ////////// by name
958   $details->th_td("shared initscript name",
959                   $details->form()->select_html('initscript',$selectors,array('label'=>'none')),
960                   'initscript',
961                   array('input_type'=>'select'));
962   if ($initscript && ! $is_found) 
963     // xxx better rendering ?
964     $details->th_td('WARNING',plc_warning_html("Current name '" . $initscript . "' is not a known shared initscript name"));
965   ////////// by contents
966   $script_height=8;
967   $script_width=60;
968   if ($initscript_code) {
969     $text=explode("\n",$initscript_code);
970     $script_height=count($text);
971     $script_width=10;
972     foreach ($text as $line) $script_width=max($script_width,strlen($line));
973   }
974   $details->th_td('slice initscript',$initscript_code,'initscript-code',
975                   array('input_type'=>'textarea', 'width'=>$script_width,'height'=>$script_height));
976   $details->tr_submit('unused','Update initscripts');
977   $details->form_end();
978   $details->end();  
979   $toggle->end();
980 }
981
982 //////////////////////////////////////////////////////////// tab:tags
983 // very wide values get abbreviated
984 $tag_value_threshold=24;
985 // xxx fixme
986 // * this area could use a help message about some special tags:
987 // * initscript-related should be taken out
988 // * sliverauth-related (ssh_key & hmac) should have a toggle to hide or show
989 // xxx fixme
990
991 // xxx expose this even on foreign slices for now
992 //if ( $local_peer ) {
993   if ($profiling) plc_debug_prof('7 slice tags',count($tags));
994   function get_tagname ($tag) { return $tag['tagname'];}
995   $tagnames = array_map ("get_tagname",$tags);
996   
997   $toggle = new PlekitToggle ('slice-tags',count_english_warning($tags,'tag'),
998                               array('bubble'=>'Inspect and set tags on that slice',
999                                     'visible'=>get_arg('show_tags',NULL)));
1000   $toggle->start();
1001   
1002   $headers=array(
1003     "Name"=>"string",
1004     "Value"=>"string",
1005     "Node"=>"string",
1006     "NodeGroup"=>"string");
1007   if ($tags_privileges) $headers[plc_delete_icon()]="none";
1008   
1009   $table_options=array("notes_area"=>false,"pagesize_area"=>false,"search_width"=>10);
1010   $table=new PlekitTable("slice_tags",$headers,'0',$table_options);
1011   $form=new PlekitForm(l_actions(),
1012                        array('slice_id'=>$slice['slice_id']));
1013   $form->start();
1014   $table->start();
1015   if ($tags) {
1016     // Get hostnames for nodes in a single pass
1017     $_node_ids = array();
1018     foreach ($tags as $tag) {
1019       if ($tag['node_id']) {
1020         array_push($_node_ids, $tag['node_id']);
1021       }
1022     }
1023     $_nodes = $api->GetNodes(array('node_id' => $_node_ids), array('node_id', 'hostname'));
1024     $_hostnames = array();
1025     foreach ($_nodes as $_node) {
1026       $_hostnames[$_node['node_id']] = $_node['hostname'];
1027     }
1028
1029     // Loop through tags again to display
1030     foreach ($tags as $tag) {
1031       $node_name = "ALL";
1032       if ($tag['node_id']) {
1033         $node_name = $_hostnames[$tag['node_id']];
1034       }
1035       $nodegroup_name="n/a";
1036       if ($tag['nodegroup_id']) { 
1037         $nodegroups=$api->GetNodeGroups(array('nodegroup_id'=>$tag['nodegroup_id']));
1038         if ($profiling) plc_debug_prof('8 nodegroup for slice tag',$nodegroup);
1039         if ($nodegroup) {
1040           $nodegroup = $nodegroups[0];
1041           $nodegroup_name = $nodegroup['groupname'];
1042         }
1043       }
1044       $table->row_start();
1045       $table->cell(l_tag_obj($tag));
1046       // very wide values get abbreviated
1047       $table->cell(truncate_and_popup($tag['value'],$tag_value_threshold));
1048       $table->cell($node_name);
1049       $table->cell($nodegroup_name);
1050       if ($tags_privileges) $table->cell ($form->checkbox_html('slice_tag_ids[]',$tag['slice_tag_id']));
1051       $table->row_end();
1052     }
1053   }
1054   if ($tags_privileges) {
1055     $table->tfoot_start();
1056     $table->row_start();
1057     $table->cell($form->submit_html ("delete-slice-tags","Remove selected"),
1058                  array('hfill'=>true,'align'=>'right'));
1059     $table->row_end();
1060     
1061     $table->row_start();
1062     function tag_selector ($tag) {
1063       return array("display"=>$tag['tagname'],"value"=>$tag['tag_type_id']);
1064     }
1065     $all_tags= $api->GetTagTypes( array ("category"=>"*slice*","-SORT"=>"+tagname"), array("tagname","tag_type_id"));
1066     if ($profiling) plc_debug_prof('9 tagtypes',count($all_tags));
1067     $selector_tag=array_map("tag_selector",$all_tags);
1068     
1069     function node_selector($node) { 
1070       return array("display"=>$node["hostname"],"value"=>$node['node_id']);
1071     }
1072     $selector_node=array_map("node_selector",$slice_nodes);
1073     
1074     function nodegroup_selector($ng) {
1075       return array("display"=>$ng["groupname"],"value"=>$ng['nodegroup_id']);
1076     }
1077     $all_nodegroups = $api->GetNodeGroups( array("groupname"=>"*"), array("groupname","nodegroup_id"));
1078     if ($profiling) plc_debug_prof('10 nodegroups',count($all_nodegroups));
1079     $selector_nodegroup=array_map("nodegroup_selector",$all_nodegroups);
1080     
1081     $table->cell($form->select_html("tag_type_id",$selector_tag,array('label'=>"Choose Tag")));
1082     $table->cell($form->text_html("value","",array('width'=>8)));
1083     $table->cell($form->select_html("node_id",$selector_node,array('label'=>"All Nodes")));
1084     $table->cell($form->select_html("nodegroup_id",$selector_nodegroup,array('label'=>"No Nodegroup")));
1085     $table->cell($form->submit_html("add-slice-tag","Set Tag"),array('columns'=>2,'align'=>'left'));
1086     $table->row_end();
1087   }
1088     
1089   $table->end();
1090   $form->end();
1091   $toggle->end();
1092 //}
1093
1094
1095 //////////////////////////////////////////////////////////// tab:renew
1096 if ($local_peer ) {
1097   if ( ! $renew_visible) renew_area ($slice,$site,NULL);
1098  }
1099
1100 $peers->block_end($peer_id);
1101
1102 if ($profiling) plc_debug_prof_end();
1103
1104 // Print footer
1105 include 'plc_footer.php';
1106
1107 ?>