more explicit labels
[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 (Pull)</button>
625     <button id='leases_submit' type='submit'>Submit (Push)</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('info-text'=>$layout_info,
652                                      'info-visible'=>$show_layout_info));
653 $toggle_nodes->start();
654
655 //usort ($table_headers, create_function('$col1,$col2','return strcmp($col1["header"],$col2["header"]);'));
656 //print("<p>TABLE HEADERS<p>");
657 //print_r($table_headers);
658
659 print("<div id='debug'></div>");
660 print("<input type='hidden' id='slice_id' value='".$slice['slice_id']."' />");
661 print("<input type='hidden' id='person_id' value='".$plc->person['person_id']."' />");
662 print("<input type='hidden' id='conf_tag_id' value='".$conf_tag_id."' />");
663 print("<input type='hidden' id='show_tag_id' value='".$show_tag_id."' />");
664 print("<input type='hidden' id='show_configuration' value='".$show_configuration."' />");
665 print("<input type='hidden' id='column_configuration' value='".$slice_column_configuration."' />");
666 print("<br><input type='hidden' size=80 id='full_column_configuration' value='".$column_configuration."' />");
667 print("<input type='hidden' id='previousConf' value='".$slice_column_configuration."' />");
668 print("<input type='hidden' id='defaultConf' value='".$default_configuration."' />");
669
670 $ConfigureColumns->configuration_panel_html(true);
671
672 $ConfigureColumns->javascript_init();
673
674 $toggle_nodes->end();
675
676
677 $all_sites=$api->GetSites(NULL, array('site_id','login_base'));
678 $site_hash=array();
679 foreach ($all_sites as $tmp_site) $site_hash[$tmp_site['site_id']]=$tmp_site['login_base'];
680
681 $interface_columns=array('ip','node_id','interface_id');
682 $interface_filter=array('is_primary'=>TRUE);
683 $interfaces=$api->GetInterfaces($interface_filter,$interface_columns);
684
685 $interface_hash=array();
686 foreach ($interfaces as $interface) $interface_hash[$interface['node_id']]=$interface;
687
688
689
690
691
692 //////////////////// nodes currently in
693 $toggle_nodes=new PlekitToggle('my-slice-nodes-current',
694                                count_english($slice_nodes,"node") . " currently in $name",
695                                array('visible'=>get_arg('show_nodes_current',!$privileges)));
696 $toggle_nodes->start();
697
698 $headers=array();
699 $notes=array();
700 //$notes=array_merge($notes,$visibletags->notes());
701 $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";
702
703 /*
704 $headers['peer']='string';
705 $headers['hostname']='string';
706 $short="-S-"; $long=Node::status_footnote(); $type='string'; 
707         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
708 $short=reservable_mark(); $long=reservable_legend(); $type='string';
709         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
710 // the extra tags, configured for the UI
711 $headers=array_merge($headers,$visibletags->headers());
712
713 if ($privileges) $headers[plc_delete_icon()]="none";
714 */
715
716 $edit_header = array();
717 if ($privileges) $edit_header[plc_delete_icon()]="none";
718 $headers = array_merge($ConfigureColumns->get_headers(),$edit_header);
719
720 //print("<p>HEADERS<p>");
721 //print_r($headers);
722
723 $table_options = array('notes'=>$notes,
724                        'search_width'=>15,
725                        'pagesize'=>20,
726                         'configurable'=>true);
727
728 $table=new PlekitTable('nodes',$headers,NULL,$table_options);
729
730 $form=new PlekitForm(l_actions(),array('slice_id'=>$slice['slice_id']));
731 $form->start();
732 $table->start();
733 if ($slice_nodes) foreach ($slice_nodes as $node) {
734   $table->row_start();
735
736 $table->cell($node['node_id'], array('display'=>'none'));
737
738   $table->cell(l_node_obj($node));
739   $peers->cell($table,$node['peer_id']);
740   $run_level=$node['run_level'];
741   list($label,$class) = Node::status_label_class_($node);
742   $table->cell ($label,array('class'=>$class));
743   $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
744
745   $hostname=$node['hostname'];
746   $ip=$interface_hash[$node['node_id']]['ip'];
747   $interface_id=$interface_hash[$node['node_id']]['interface_id'];
748
749 //extra columns
750 $node['domain'] = topdomain($hostname);
751 $node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
752 if ($interface_id)
753         $node['ipaddress'] = l_interface_t($interface_id,$ip);
754   else
755         $node['ipaddress'] = "n/a";
756
757  //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
758  $ConfigureColumns->cells($table, $node);
759
760   if ($privileges) $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
761   $table->row_end();
762 }
763 // actions area
764 if ($privileges) {
765
766   // remove nodes
767   $table->tfoot_start();
768
769   $table->row_start();
770   $table->cell($form->submit_html ("remove-nodes-from-slice","Remove selected"),
771                array('hfill'=>true,'align'=>'right'));
772   $table->row_end();
773  }
774 $table->end();
775 $toggle_nodes->end();
776
777 //////////////////// nodes to add
778 if ($privileges) {
779   $new_potential_nodes = array();
780   if ($potential_nodes) foreach ($potential_nodes as $node) {
781       $emptywl=empty($node['slice_ids_whitelist']);
782       $inwl = (!emptywl) and in_array($slice['slice_id'],$node['slice_ids_whitelist']);
783       if ($emptywl or $inwl)
784         $new_potential_nodes[]=$node;
785   }
786   $potential_nodes=$new_potential_nodes;
787
788   $count=count($potential_nodes);
789   $toggle_nodes=new PlekitToggle('my-slice-nodes-add',
790                                  count_english($potential_nodes,"more node") . " available",
791                                  array('visible'=>get_arg('show_nodes_add',NULL)));
792   $toggle_nodes->start();
793
794   if ( $potential_nodes ) {
795     $headers=array();
796     $notes=array();
797
798
799 /*
800     $headers['peer']='string';
801     $headers['hostname']='string';
802     $short="-S-"; $long=Node::status_footnote(); $type='string'; 
803         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
804         $short=reservable_mark(); $long=reservable_legend(); $type='string';
805         $headers[$short]=array('type'=>$type,'title'=>$long); $notes []= "$short = $long";
806     // the extra tags, configured for the UI
807     $headers=array_merge($headers,$visibletags->headers());
808     $headers['+']="none";
809 */
810
811     $add_header = array();
812     $add_header['+']="none";
813     $headers = array_merge($ConfigureColumns->get_headers(),$add_header);
814
815     //$notes=array_merge($notes,$visibletags->notes());
816 $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";
817     
818     $table=new PlekitTable('add_nodes',$headers,NULL, $table_options);
819     $form=new PlekitForm(l_actions(),
820                          array('slice_id'=>$slice['slice_id']));
821     $form->start();
822     $table->start();
823     if ($potential_nodes) foreach ($potential_nodes as $node) {
824         $table->row_start();
825
826 $table->cell($node['node_id'], array('display'=>'none'));
827
828         $table->cell(l_node_obj($node));
829         $peers->cell($table,$node['peer_id']);
830         list($label,$class) = Node::status_label_class_($node);
831         $table->cell ($label,array('class'=>$class));
832         $table->cell( ($node['node_type']=='reservable')?reservable_mark():"" );
833
834         //extra columns
835           $hostname=$node['hostname'];
836           $ip=$interface_hash[$node['node_id']]['ip'];
837           $interface_id=$interface_hash[$node['node_id']]['interface_id'];
838         $node['domain'] = topdomain($hostname);
839         $node['sitename'] = l_site_t($node['site_id'],$site_hash[$node['site_id']]);
840         $node['ipaddress'] = l_interface_t($interface_id,$ip);
841
842         //foreach ($visiblecolumns as $tagname) $table->cell($node[$tagname]);
843         $ConfigureColumns->cells($table, $node);
844
845         $table->cell ($form->checkbox_html('node_ids[]',$node['node_id']));
846         $table->row_end();
847       }
848     // add nodes
849     $table->tfoot_start();
850     $table->row_start();
851     $table->cell($form->submit_html ("add-nodes-in-slice","Add selected"),
852                  array('hfill'=>true,'align'=>'right'));
853     $table->row_end();
854     $table->end();
855     $form->end();
856   }
857   $toggle_nodes->end();
858 }
859
860 $toggle->end();
861
862 //////////////////////////////////////// retrieve all slice tags
863 $tags=$api->GetSliceTags (array('slice_id'=>$slice_id));
864 //////////////////////////////////////////////////////////// tab:initscripts
865 // xxx fixme
866 // * add a message on how to use this:
867 // * explain the 2 mechanisms (initscript_code, initscript)
868 // * explain the interface : initscript start|stop|restart slicename
869 // xxx fixme
870
871 $initscript_info="
872 There are two ways to attach an initscript to a slice:<ul>
873
874 <li> <span class='bold'> Shared initscripts </span> are global to the
875 MyPLC, and managed by the Operations Team. For that reason, regular
876 users cannot change these scripts, but can reference one of the
877 available names in the drop down below.  </li>
878
879 <li> You also have the option to provide <span class='bold'> your own
880 code </span>, with the following conventions: <ul>
881
882 <li> Like regular initscripts, your script must except to receive as a
883 first argument <span class='bold'> start </span>, <span class='bold'>
884 stop </span> or <span class='bold'> restart </span>. It is important
885 to honor this argument, as your slice may be stopped and restarted at
886 any time; also this is used whenever the installed code gets changed.
887 </li>
888
889 <li> As a second argument, you will receive the slicename; in most
890 cases this can be safely ignored.  </li>
891
892 </ul>
893 </li>
894  </ul>
895 The slice-specific setting has precedence on a shared initscript.
896 ";
897
898 $shared_initscripts=$api->GetInitScripts(array('-SORT'=>'name'),array('name'));
899 //$shared_initscripts=$api->GetInitScripts();
900 if ($profiling) plc_debug_prof('6 initscripts',count($initscripts));
901 // xxx expose this even on foreign slices for now
902 if ($local_peer) {
903   $initscript='';
904   $initscript_code='';
905   if ($tags) foreach ($tags as $tag) {
906       if ($tag['tagname']=='initscript') {
907         if ($initscript!='') drupal_set_error("multiple occurrences of 'initscript' tag");
908         $initscript=$tag['value'];
909       }
910       if ($tag['tagname']=='initscript_code') {
911         if ($initscript_code!='') drupal_set_error("multiple occurrences of 'initscript_code' tag");
912         $initscript_code=$tag['value'];
913         // plc_debug_txt('retrieved body',$initscript_code);
914       }
915     }
916   $label="No initscript";
917   $trimmed=trim($initscript_code);
918   if (!empty($trimmed)) $label="Initscript : slice-specific (" . substr($initscript_code,0,20) . " ...)";
919   else if (!empty($initscript)) $label="Initscript: shared " . $initscript;
920
921   $toggle = new PlekitToggle('slice-initscripts',$label,
922                              array('bubble'=>'Manage initscript on that slice',
923                                    'visible'=>get_arg('show_initscripts',NULL),
924                                    'info-text'=>$initscript_info
925                                    // not messing with persontags to guess whether this should be displayed or not
926                                    // hopefully some day toggle will know how to handle that using web storage
927                                    ));
928   $toggle->start();
929
930   $details=new PlekitDetails(TRUE);
931   // we expose the previous values so that actions.php can know if changes are really needed
932   // the code needs to be encoded as it may contain any character
933   // as far as the code, this does not work too well b/c what actions.php receives
934   // seems to have spurrious \r chars, and the comparison between old and new values 
935   // is not reliable, which results in changes being made although the code hasn't changed
936   // hve spent too much time on this, good enough for now...
937   $details->form_start(l_actions(),array('action'=>'update-initscripts',
938                                          'slice_id'=>$slice_id,
939                                          'name'=>$name,
940                                          'previous-initscript'=>$initscript,
941                                          'previous-initscript-code'=>htmlentities($initscript_code)));
942   $details->start();
943   // comppute a pulldown with available names
944   $selectors=array();
945   $is_found=FALSE;
946   if ($shared_initscripts) foreach ($shared_initscripts as $is) {
947       $is_selector=array('display'=>$is['name'],'value'=>$is['name']);
948       if ($is['name']==$initscript) {
949         $is_selector['selected']=TRUE;
950         $is_found=TRUE;
951       }
952       $selectors[]=$is_selector;
953     }
954   // display a warning when initscript references an unknown script
955   $details->tr_submit('unused','Update initscripts');
956   ////////// by name
957   $details->th_td("shared initscript name",
958                   $details->form()->select_html('initscript',$selectors,array('label'=>'none')),
959                   'initscript',
960                   array('input_type'=>'select'));
961   if ($initscript && ! $is_found) 
962     // xxx better rendering ?
963     $details->th_td('WARNING',plc_warning_html("Current name '" . $initscript . "' is not a known shared initscript name"));
964   ////////// by contents
965   $script_height=8;
966   $script_width=60;
967   if ($initscript_code) {
968     $text=explode("\n",$initscript_code);
969     $script_height=count($text);
970     $script_width=10;
971     foreach ($text as $line) $script_width=max($script_width,strlen($line));
972   }
973   $details->th_td('slice initscript',$initscript_code,'initscript-code',
974                   array('input_type'=>'textarea', 'width'=>$script_width,'height'=>$script_height));
975   $details->tr_submit('unused','Update initscripts');
976   $details->form_end();
977   $details->end();  
978   $toggle->end();
979 }
980
981 //////////////////////////////////////////////////////////// tab:tags
982 // very wide values get abbreviated
983 $tag_value_threshold=24;
984 // xxx fixme
985 // * this area could use a help message about some special tags:
986 // * initscript-related should be taken out
987 // * sliverauth-related (ssh_key & hmac) should have a toggle to hide or show
988 // xxx fixme
989
990 // xxx expose this even on foreign slices for now
991 //if ( $local_peer ) {
992   if ($profiling) plc_debug_prof('7 slice tags',count($tags));
993   function get_tagname ($tag) { return $tag['tagname'];}
994   $tagnames = array_map ("get_tagname",$tags);
995   
996   $toggle = new PlekitToggle ('slice-tags',count_english_warning($tags,'tag'),
997                               array('bubble'=>'Inspect and set tags on that slice',
998                                     'visible'=>get_arg('show_tags',NULL)));
999   $toggle->start();
1000   
1001   $headers=array(
1002     "Name"=>"string",
1003     "Value"=>"string",
1004     "Node"=>"string",
1005     "NodeGroup"=>"string");
1006   if ($tags_privileges) $headers[plc_delete_icon()]="none";
1007   
1008   $table_options=array("notes_area"=>false,"pagesize_area"=>false,"search_width"=>10);
1009   $table=new PlekitTable("slice_tags",$headers,'0',$table_options);
1010   $form=new PlekitForm(l_actions(),
1011                        array('slice_id'=>$slice['slice_id']));
1012   $form->start();
1013   $table->start();
1014   if ($tags) {
1015     // Get hostnames for nodes in a single pass
1016     $_node_ids = array();
1017     foreach ($tags as $tag) {
1018       if ($tag['node_id']) {
1019         array_push($_node_ids, $tag['node_id']);
1020       }
1021     }
1022     $_nodes = $api->GetNodes(array('node_id' => $_node_ids), array('node_id', 'hostname'));
1023     $_hostnames = array();
1024     foreach ($_nodes as $_node) {
1025       $_hostnames[$_node['node_id']] = $_node['hostname'];
1026     }
1027
1028     // Loop through tags again to display
1029     foreach ($tags as $tag) {
1030       $node_name = "ALL";
1031       if ($tag['node_id']) {
1032         $node_name = $_hostnames[$tag['node_id']];
1033       }
1034       $nodegroup_name="n/a";
1035       if ($tag['nodegroup_id']) { 
1036         $nodegroups=$api->GetNodeGroups(array('nodegroup_id'=>$tag['nodegroup_id']));
1037         if ($profiling) plc_debug_prof('8 nodegroup for slice tag',$nodegroup);
1038         if ($nodegroup) {
1039           $nodegroup = $nodegroups[0];
1040           $nodegroup_name = $nodegroup['groupname'];
1041         }
1042       }
1043       $table->row_start();
1044       $table->cell(l_tag_obj($tag));
1045       // very wide values get abbreviated
1046       $table->cell(truncate_and_popup($tag['value'],$tag_value_threshold));
1047       $table->cell($node_name);
1048       $table->cell($nodegroup_name);
1049       if ($tags_privileges) $table->cell ($form->checkbox_html('slice_tag_ids[]',$tag['slice_tag_id']));
1050       $table->row_end();
1051     }
1052   }
1053   if ($tags_privileges) {
1054     $table->tfoot_start();
1055     $table->row_start();
1056     $table->cell($form->submit_html ("delete-slice-tags","Remove selected"),
1057                  array('hfill'=>true,'align'=>'right'));
1058     $table->row_end();
1059     
1060     $table->row_start();
1061     function tag_selector ($tag) {
1062       return array("display"=>$tag['tagname'],"value"=>$tag['tag_type_id']);
1063     }
1064     $all_tags= $api->GetTagTypes( array ("category"=>"*slice*","-SORT"=>"+tagname"), array("tagname","tag_type_id"));
1065     if ($profiling) plc_debug_prof('9 tagtypes',count($all_tags));
1066     $selector_tag=array_map("tag_selector",$all_tags);
1067     
1068     function node_selector($node) { 
1069       return array("display"=>$node["hostname"],"value"=>$node['node_id']);
1070     }
1071     $selector_node=array_map("node_selector",$slice_nodes);
1072     
1073     function nodegroup_selector($ng) {
1074       return array("display"=>$ng["groupname"],"value"=>$ng['nodegroup_id']);
1075     }
1076     $all_nodegroups = $api->GetNodeGroups( array("groupname"=>"*"), array("groupname","nodegroup_id"));
1077     if ($profiling) plc_debug_prof('10 nodegroups',count($all_nodegroups));
1078     $selector_nodegroup=array_map("nodegroup_selector",$all_nodegroups);
1079     
1080     $table->cell($form->select_html("tag_type_id",$selector_tag,array('label'=>"Choose Tag")));
1081     $table->cell($form->text_html("value","",array('width'=>8)));
1082     $table->cell($form->select_html("node_id",$selector_node,array('label'=>"All Nodes")));
1083     $table->cell($form->select_html("nodegroup_id",$selector_nodegroup,array('label'=>"No Nodegroup")));
1084     $table->cell($form->submit_html("add-slice-tag","Set Tag"),array('columns'=>2,'align'=>'left'));
1085     $table->row_end();
1086   }
1087     
1088   $table->end();
1089   $form->end();
1090   $toggle->end();
1091 //}
1092
1093
1094 //////////////////////////////////////////////////////////// tab:renew
1095 if ($local_peer ) {
1096   if ( ! $renew_visible) renew_area ($slice,$site,NULL);
1097  }
1098
1099 $peers->block_end($peer_id);
1100
1101 if ($profiling) plc_debug_prof_end();
1102
1103 // Print footer
1104 include 'plc_footer.php';
1105
1106 ?>