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