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