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