5112b8b3f774e2a0ea75398a755a50b533ac99fb
[racktables] / wwwroot / inc / interface.php
1 <?php
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 /*
8 *
9 * This file contains frontend functions for RackTables.
10 *
11 */
12
13 require_once 'ajax-interface.php';
14 require_once 'slb-interface.php';
15
16 // Interface function's special.
17 $nextorder['odd'] = 'even';
18 $nextorder['even'] = 'odd';
19
20 // address allocation type
21 $aat = array
22 (
23 'regular' => 'Connected',
24 'virtual' => 'Loopback',
25 'shared' => 'Shared',
26 'router' => 'Router',
27 'sharedrouter' => 'Shared router',
28 'point2point' => 'Point-to-point',
29 );
30 // address allocation code, IPv4 addresses and objects view
31 $aac_right = array
32 (
33 'regular' => '',
34 'virtual' => '<span class="aac-right" title="' . $aat['virtual'] . '">L</span>',
35 'shared' => '<span class="aac-right" title="' . $aat['shared'] . '">S</span>',
36 'router' => '<span class="aac-right" title="' . $aat['router'] . '">R</span>',
37 'sharedrouter' => '<span class="aac-right" title="' . $aat['sharedrouter'] . '">R</span>',
38 'point2point' => '<span class="aac-right" title="' . $aat['point2point'] . '">P</span>',
39 );
40 // address allocation code, IPv4 networks view
41 $aac_left = array
42 (
43 'regular' => '',
44 'virtual' => '<span class="aac-left" title="' . $aat['virtual'] . '">L:</span>',
45 'shared' => '<span class="aac-left" title="' . $aat['shared'] . '">S:</span>',
46 'router' => '<span class="aac-left" title="' . $aat['router'] . '">R:</span>',
47 'sharedrouter' => '<span class="aac-left" title="' . $aat['sharedrouter'] . '">R:</span>',
48 'point2point' => '<span class="aac-left" title="' . $aat['point2point'] . '">P:</span>',
49 );
50
51 $vtdecoder = array
52 (
53 'ondemand' => '',
54 'compulsory' => 'P',
55 # 'alien' => 'NT',
56 );
57
58 $vtoptions = array
59 (
60 'ondemand' => 'auto',
61 'compulsory' => 'permanent',
62 # 'alien' => 'never touch',
63 );
64
65 $attrtypes = array
66 (
67 'uint' => '[U] unsigned integer',
68 'float' => '[F] floating point',
69 'string' => '[S] string',
70 'dict' => '[D] dictionary record',
71 'date' => '[T] date'
72 );
73
74 function showLogoutURL ()
75 {
76 $https = (isset ($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 's' : '';
77 $port = (! in_array ($_SERVER['SERVER_PORT'], array (80, 443))) ? ':' . $_SERVER['SERVER_PORT'] : '';
78 printf ('http%s://logout@%s%s%s?logout', $https, $_SERVER['SERVER_NAME'], $port, $_SERVER['PHP_SELF']);
79 }
80
81 $quick_links = NULL; // you can override this in your local.php, but first initialize it with getConfiguredQuickLinks()
82
83 function renderQuickLinks()
84 {
85 global $quick_links;
86 if (! isset ($quick_links))
87 $quick_links = getConfiguredQuickLinks();
88 echo '<ul class="qlinks">';
89 foreach ($quick_links as $link)
90 echo '<li><a href="' . $link['href'] . '">' . str_replace (' ', '&nbsp;', $link['title']) . '</a></li>';
91 echo '</ul>';
92 }
93
94 function renderInterfaceHTML ($pageno, $tabno, $payload)
95 {
96 global $remote_displayname;
97 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
98 echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
99 echo '<head><title>' . getTitle ($pageno) . '</title>';
100 printPageHeaders();
101 echo '</head>';
102 echo '<body>';
103 echo '<div class=maintable>';
104 echo '<div class=mainheader>';
105 echo '<div style="float: right" class=greeting>';
106 echo mkA ($remote_displayname, 'myaccount', NULL, 'default');
107 echo ' [ <a href="';
108 showLogoutURL();
109 echo '">logout</a> ]</div>'; // greeting
110 echo getConfigVar ('enterprise') . ' RackTables ';
111 echo '<a href="http://racktables.org" title="Visit RackTables site">' . CODE_VERSION . '</a>';
112 renderQuickLinks();
113 echo '</div>'; // mainheader
114 echo '<div class=menubar>';
115 showPathAndSearch ($pageno, $tabno);
116 echo '</div>';
117 echo '<div class=tabbar>';
118 showTabs ($pageno, $tabno);
119 echo '</div>';
120 echo '<div class=msgbar>';
121 showMessageOrError();
122 echo '</div>';
123 echo "<div class=pagebar>${payload}</div>";
124 echo '</div>'; // maintable
125 echo '</body>';
126 echo '</html>';
127 }
128
129 // Main menu.
130 function renderIndexItem ($ypageno)
131 {
132 echo (! permitted ($ypageno)) ? " <td>&nbsp;</td>\n" :
133 " <td>\n" .
134 " <h1><a href='" . makeHref (array ('page' => $ypageno)) . "'>" .
135 getPageName ($ypageno) . "<br>\n" . getImageHREF ($ypageno) .
136 "</a></h1>\n" .
137 " </td>\n";
138 }
139
140 function renderIndex ()
141 {
142 global $indexlayout;
143 echo '<table border=0 cellpadding=0 cellspacing=0 width="100%">';
144 echo '<tr><td><div style="text-align: center; margin: 10px; ">';
145 echo '<table width="100%" cellspacing=0 cellpadding=20 class=mainmenu border=0>';
146 foreach ($indexlayout as $row)
147 {
148 echo '<tr>';
149 foreach ($row as $column)
150 if ($column === NULL)
151 echo '<td>&nbsp;</td>';
152 else
153 renderIndexItem ($column);
154 echo '</tr>';
155 }
156 echo '</table>';
157 echo '</div></td></tr>';
158 echo '</table>';
159 }
160
161 function getRenderedAlloc ($object_id, $alloc)
162 {
163 $ret = array
164 (
165 'tr_class' => '',
166 'td_name_suffix' => '',
167 'td_ip' => '',
168 'td_network' => '',
169 'td_routed_by' => '',
170 'td_peers' => '',
171 );
172 $dottedquad = $alloc['addrinfo']['ip'];
173 $ip_bin = $alloc['addrinfo']['ip_bin'];
174
175 $hl_ip_bin = NULL;
176 if (isset ($_REQUEST['hl_ip']))
177 {
178 $hl_ip_bin = ip_parse ($_REQUEST['hl_ip']);
179 addAutoScrollScript ("ip-" . $_REQUEST['hl_ip']);
180 }
181
182 $ret['tr_class'] = $alloc['addrinfo']['class'];
183 if ($hl_ip_bin === $ip_bin)
184 $ret['tr_class'] .= ' highlight';
185
186 // render IP change history
187 $ip_title = '';
188 $ip_class = '';
189 if (isset ($alloc['addrinfo']['last_log']))
190 {
191 $log = $alloc['addrinfo']['last_log'];
192 $ip_title = "title='" .
193 htmlspecialchars
194 (
195 $log['user'] . ', ' . formatAge ($log['time']),
196 ENT_QUOTES
197 ) . "'";
198 $ip_class = 'hover-history underline';
199 }
200
201 // render IP address td
202 global $aac_right;
203 $netinfo = spotNetworkByIP ($ip_bin);
204 $ret['td_ip'] = "<td class='tdleft'>";
205 if (isset ($netinfo))
206 {
207 $title = $dottedquad;
208 if (getConfigVar ('EXT_IPV4_VIEW') != 'yes')
209 $title .= '/' . $netinfo['mask'];
210 $ret['td_ip'] .= "<a name='ip-$dottedquad' class='$ip_class' $ip_title href='" .
211 makeHref (
212 array
213 (
214 'page' => 'ipaddress',
215 'hl_object_id' => $object_id,
216 'ip' => $dottedquad,
217 )
218 ) . "'>$title</a>";
219 }
220 else
221 $ret['td_ip'] .= "<span class='$ip_class' $ip_title>$dottedquad</span>";
222 $ret['td_ip'] .= $aac_right[$alloc['type']];
223 if ($alloc['addrinfo']['name'] != '')
224 $ret['td_ip'] .= ' (' . stringForLabel ($alloc['addrinfo']['name']) . ')';
225 $ret['td_ip'] .= '</td>';
226
227 // render network and routed_by tds
228 $td_class = 'tdleft';
229 if (! isset ($netinfo))
230 {
231 $ret['td_network'] = "<td class='$td_class sparenetwork'>N/A</td>";
232 $ret['td_routed_by'] = $ret['td_network'];
233 }
234 else
235 {
236 $ret['td_network'] = "<td class='$td_class'>" .
237 getOutputOf ('renderCell', $netinfo) . '</td>';
238
239 // render "routed by" td
240 if ($display_routers = (getConfigVar ('IPV4_TREE_RTR_AS_CELL') == 'none'))
241 $ret['td_routed_by'] = '<td>&nbsp;</td>';
242 else
243 {
244 $other_routers = array();
245 foreach (findNetRouters ($netinfo) as $router)
246 if ($router['id'] != $object_id)
247 $other_routers[] = $router;
248 if (count ($other_routers))
249 $ret['td_routed_by'] = getOutputOf ('printRoutersTD', $other_routers, $display_routers);
250 else
251 $ret['td_routed_by'] = "<td class='$td_class'>&nbsp;</td>";
252 }
253 }
254
255 // render peers td
256 $ret['td_peers'] = "<td class='$td_class'>";
257 $prefix = '';
258 $separator = '; ';
259 if ($alloc['addrinfo']['reserved'] == 'yes')
260 {
261 $ret['td_peers'] .= $prefix . '<strong>RESERVED</strong>';
262 $prefix = $separator;
263 }
264 foreach ($alloc['addrinfo']['allocs'] as $allocpeer)
265 if ($allocpeer['object_id'] != $object_id)
266 {
267 $ret['td_peers'] .= $prefix . makeIPAllocLink ($ip_bin, $allocpeer);
268 $prefix = $separator;
269 }
270 elseif ($allocpeer['type'] == 'point2point' && isset ($netinfo))
271 {
272 // show PtP peers in the IP network
273 $addrlist = isset ($netinfo['own_addrlist']) ? $netinfo['own_addrlist'] : getIPAddrList ($netinfo, IPSCAN_DO_ALLOCS);
274 foreach (getPtPNeighbors ($ip_bin, $addrlist) as $p_ip_bin => $p_alloc_list)
275 foreach ($p_alloc_list as $p_alloc)
276 {
277 $ret['td_peers'] .= $prefix . '&harr;&nbsp;' . makeIPAllocLink ($p_ip_bin, $p_alloc);
278 $prefix = $separator;
279 }
280 }
281
282 $ret['td_peers'] .= '</td>';
283
284 return $ret;
285 }
286
287 function renderLocationFilterPortlet ()
288 {
289 // Recursive function used to build the location tree
290 function renderLocationCheckbox ($subtree, $level = 0)
291 {
292 $self = __FUNCTION__;
293
294 foreach ($subtree as $location_id => $location)
295 {
296 echo "<div class=tagbox style='text-align:left; padding-left:" . ($level * 16) . "px;'>";
297 $checked = (! isset ($_SESSION['locationFilter']) || in_array ($location['id'], $_SESSION['locationFilter'])) ? 'checked' : '';
298 echo "<label><input type=checkbox name='location_id[]' class=${level} value='${location['id']}'${checked} onClick=checkAll(this)>${location['name']}";
299 echo '</label>';
300 if ($location['kidc'])
301 {
302 echo "<a id='lfa" . $location['id'] . "' onclick=\"expand('${location['id']}')\" href\"#\" > - </a>";
303 echo "<div id='lfd" . $location['id'] . "'>";
304 $self ($location['kids'], $level + 1);
305 echo '</div>';
306 }
307 echo '</div>';
308 }
309 }
310
311 addJSText (<<<'END'
312 function checkAll(bx) {
313 for (var tbls=document.getElementsByTagName("table"), i=tbls.length; i--;)
314 if (tbls[i].id == "locationFilter") {
315 var bxs=tbls[i].getElementsByTagName("input");
316 var in_tree = false;
317 for (var j=0; j<bxs.length; j++) {
318 if(in_tree == false && bxs[j].value == bx.value)
319 in_tree = true;
320 else if(parseInt(bxs[j].className) <= parseInt(bx.className))
321 in_tree = false;
322 if (bxs[j].type=="checkbox" && in_tree == true)
323 bxs[j].checked = bx.checked;
324 }
325 }
326 }
327
328 function collapseAll(bx) {
329 for (var tbls=document.getElementsByTagName("table"), i=tbls.length; i--;)
330 if (tbls[i].id == "locationFilter") {
331 var bxs=tbls[i].getElementsByTagName("div");
332 //loop through divs to hide unchecked
333 for (var j=0; j<bxs.length; j++) {
334 var is_checked = -1;
335 var in_div=bxs[j].getElementsByTagName("input");
336 //loop through input to find if any is checked
337 for (var k=0; k<in_div.length; k++) {
338 if(in_div[k].type="checkbox") {
339 if (in_div[k].checked == true) {
340 is_checked = true;
341 break;
342 }
343 else
344 is_checked = false;
345 }
346 }
347 // nothing selected and element id is lfd, collapse it
348 if (is_checked == false && !bxs[j].id.indexOf("lfd"))
349 expand(bxs[j].id.substr(3));
350 }
351 }
352 }
353
354 function expand(id) {
355 var divid = document.getElementById("lfd" + id);
356 var iconid = document.getElementById("lfa" + id);
357 if (divid.style.display == 'none') {
358 divid.style.display = 'block';
359 iconid.innerHTML = ' - ';
360 } else {
361 divid.style.display = 'none';
362 iconid.innerHTML = ' + ';
363 }
364 }
365 END
366 ); // addJSText()
367 startPortlet ('Location filter');
368 echo <<<'END'
369 <table border=0 align=center cellspacing=0 class="tagtree" id="locationFilter">
370 <form method=post>
371 <input type=hidden name=page value=rackspace>
372 <input type=hidden name=tab value=default>
373 <input type=hidden name=changeLocationFilter value=true>
374 END;
375
376 $locationlist = listCells ('location');
377 if (count ($locationlist))
378 {
379 echo "<tr><td class=tagbox style='padding-left: 0px'><label>";
380 echo "<input type=checkbox name='location' onClick=checkAll(this)> Toggle all";
381 echo "<img src=?module=chrome&uri=pix/1x1t.gif onLoad=collapseAll(this)>"; // dirty hack to collapse all when page is displayed
382 echo "</label></td></tr>\n";
383 echo "<tr><td class=tagbox><hr>\n";
384 renderLocationCheckbox (treeFromList (addTraceToNodes ($locationlist)));
385 echo "<hr></td></tr>\n";
386 echo '<tr><td>';
387 printImageHREF ('setfilter', 'set filter', TRUE);
388 echo "</td></tr>\n";
389 }
390 else
391 {
392 echo "<tr><td class='tagbox sparenetwork'>(no locations exist)</td></tr>\n";
393 echo "<tr><td>";
394 printImageHREF ('setfilter gray');
395 echo "</td></tr>\n";
396 }
397
398 echo "</form></table>\n";
399 finishPortlet ();
400 }
401
402 function rackspaceCmp ($a, $b)
403 {
404 $ret = strnatcasecmp ($a['location_tree'], $b['location_tree']);
405 if (!$ret)
406 $ret = strnatcasecmp ($a['row_name'], $b['row_name']);
407 return $ret;
408 }
409
410 function getRackThumbLink ($rack, $scale = 1, $object_id = NULL)
411 {
412 if (! is_int ($scale) || $scale <= 0)
413 throw new InvalidArgException ('scale', $scale, 'must be a natural number');
414 $width = getRackImageWidth() * $scale;
415 $height = getRackImageHeight ($rack['height']) * $scale;
416 $title = "${rack['height']} units";
417 $src = '?module=image' .
418 ($scale == 1 && $object_id === NULL ? '&img=minirack' : "&img=midirack&scale=${scale}") .
419 "&rack_id=${rack['id']}" .
420 ($object_id === NULL ? '' : "&object_id=${object_id}");
421 $img = "<img border=0 width=${width} height=${height} title='${title}' src='${src}'>";
422 return mkA ($img, 'rack', $rack['id']);
423 }
424
425 function renderRackspace ()
426 {
427 global $pageno;
428 // Handle the location filter
429 startSession();
430 if (isset ($_REQUEST['changeLocationFilter']))
431 unset ($_SESSION['locationFilter']);
432 if (isset ($_REQUEST['location_id']))
433 $_SESSION['locationFilter'] = $_REQUEST['location_id'];
434 session_commit();
435
436 echo "<table class=objview border=0 width='100%'><tr><td class=pcleft>";
437
438 $found_racks = array();
439 $cellfilter = getCellFilter();
440 if (! ($cellfilter['is_empty'] && !isset ($_SESSION['locationFilter']) && renderEmptyResults ($cellfilter, 'racks', getEntitiesCount ('rack'))))
441 {
442 $rows = array();
443 $rackCount = 0;
444 foreach (listCells ('row') as $row_id => $rowInfo)
445 {
446 $rackList = applyCellFilter ('rack', $cellfilter, $row_id);
447 $found_racks = array_merge ($found_racks, $rackList);
448 $location_id = $rowInfo['location_id'];
449 $locationIdx = 0;
450 // contains location names in the form of 'grandparent parent child', used for sorting
451 $locationTree = '';
452 // contains location names as well as links
453 $hrefLocationTree = '';
454 while ($location_id)
455 {
456 if ($locationIdx == 20)
457 {
458 showWarning ("Warning: There is likely a circular reference in the location tree. Investigate location ${location_id}.");
459 break;
460 }
461 $parentLocation = spotEntity ('location', $location_id);
462 $locationTree = sprintf ('%s %s', $parentLocation['name'], $locationTree);
463 $hrefLocationTree = "&raquo; <a href='" .
464 makeHref(array('page'=>'location', 'location_id'=>$parentLocation['id'])) .
465 "${cellfilter['urlextra']}'>${parentLocation['name']}</a> " .
466 $hrefLocationTree;
467 $location_id = $parentLocation['parent_id'];
468 $locationIdx++;
469 }
470 $hrefLocationTree = substr ($hrefLocationTree, 8);
471 $rows[] = array (
472 'location_id' => $rowInfo['location_id'],
473 'location_tree' => $locationTree,
474 'href_location_tree' => $hrefLocationTree,
475 'row_id' => $row_id,
476 'row_name' => $rowInfo['name'],
477 'racks' => $rackList
478 );
479 $rackCount += count($rackList);
480 }
481
482 // sort by location, then by row
483 usort ($rows, 'rackspaceCmp');
484
485 if (! renderEmptyResults($cellfilter, 'racks', $rackCount))
486 {
487 // generate thumb gallery
488 global $nextorder;
489 // Zero value effectively disables the limit.
490 $maxPerRow = getConfigVar ('RACKS_PER_ROW');
491 $order = 'odd';
492 if (! count ($rows))
493 echo "<h2>No rows found</h2>\n";
494 else
495 {
496 echo '<table border=0 cellpadding=10 class=cooltable>';
497 echo '<tr><th class=tdleft>Location</th><th class=tdleft>Row</th><th class=tdleft>Racks</th></tr>';
498 foreach ($rows as $row)
499 {
500 $rackList = $row['racks'];
501 $location_id = $row['location_id'];
502
503 if (
504 $location_id != '' && isset ($_SESSION['locationFilter']) && ! in_array ($location_id, $_SESSION['locationFilter']) ||
505 empty ($rackList) && ! $cellfilter['is_empty']
506 )
507 continue;
508 $rackListIdx = 0;
509 echo "<tr class=row_${order}><th class=tdleft>${row['href_location_tree']}</th>";
510 echo "<th class=tdleft><a href='".makeHref(array('page'=>'row', 'row_id'=>$row['row_id']))."${cellfilter['urlextra']}'>${row['row_name']}</a></th>";
511 echo "<th class=tdleft><table border=0 cellspacing=5><tr>";
512 if (! count ($rackList))
513 echo '<td>(empty row)</td>';
514 else
515 foreach ($rackList as $rack)
516 {
517 if ($rackListIdx > 0 && $maxPerRow > 0 && $rackListIdx % $maxPerRow == 0)
518 {
519 echo '</tr></table></th></tr>';
520 echo "<tr class=row_${order}><th class=tdleft></th><th class=tdleft>${row['row_name']} (continued)";
521 echo "</th><th class=tdleft><table border=0 cellspacing=5><tr>";
522 }
523 echo '<td align=center valign=bottom>' . getRackThumbLink ($rack);
524 echo '<br>' . mkA (stringForLabel ($rack['name']), 'rack', $rack['id']) . '</td>';
525 $rackListIdx++;
526 }
527 $order = $nextorder[$order];
528 echo "</tr></table></th></tr>\n";
529 }
530 echo "</table>\n";
531 }
532 }
533 }
534 echo "</td><td class='pcright ${pageno}' width='25%'>";
535 renderCellFilterPortlet ($cellfilter, 'rack', $found_racks);
536 echo "<br>\n";
537 renderLocationFilterPortlet ();
538 echo "</td></tr></table>\n";
539 }
540
541 function renderLocationRowForEditor ($subtree, $level = 0)
542 {
543 $self = __FUNCTION__;
544 foreach ($subtree as $locationinfo)
545 {
546 echo "<tr><td align=left style='padding-left: " . ($locationinfo['kidc'] ? $level : ($level + 1) * 16) . "px;'>";
547 if ($locationinfo['kidc'])
548 printImageHREF ('node-expanded-static');
549 if ($locationinfo['refcnt'] > 0 || $locationinfo['kidc'] > 0)
550 printImageHREF ('nodestroy');
551 else
552 echo getOpLink (array ('op' => 'deleteLocation', 'location_id' => $locationinfo['id']), '', 'destroy', 'Delete location');
553 echo '</td><td class=tdleft>';
554 printOpFormIntro ('updateLocation', array ('location_id' => $locationinfo['id']));
555 $parent = isset ($locationinfo['parent_id']) ? $locationinfo['parent_id'] : 0;
556 echo getSelect
557 (
558 array ( $parent => $parent ? htmlspecialchars ($locationinfo['parent_name']) : '-- NONE --'),
559 array ('name' => 'parent_id', 'id' => 'locationid_' . $locationinfo['id'], 'class' => 'locationlist-popup'),
560 $parent,
561 FALSE
562 );
563 echo "</td><td class=tdleft>";
564 echo "<input type=text size=48 name=name value='${locationinfo['name']}'>";
565 echo '</td><td>' . getImageHREF ('save', 'Save changes', TRUE) . "</form></td></tr>\n";
566 if ($locationinfo['kidc'])
567 $self ($locationinfo['kids'], $level + 1);
568 }
569 }
570
571 function renderLocationSelectTree ($select_name, $selected_id = NULL)
572 {
573 echo "<select name='${select_name}'>";
574 echo '<option value=0>-- NONE --</option>';
575 $locationlist = listCells ('location');
576 foreach (treeFromList (addTraceToNodes ($locationlist)) as $location)
577 {
578 echo "<option value=${location['id']} style='font-weight: bold' ";
579 if ($location['id'] == $selected_id )
580 echo ' selected';
581 echo ">${location['name']}</option>";
582 printLocationChildrenSelectOptions ($location, $selected_id);
583 }
584 echo '</select>';
585 }
586
587 function renderRackspaceLocationEditor ()
588 {
589 addJSText (<<<'JSTXT'
590 function locationeditor_showselectbox(e) {
591 $(this).load('index.php', {module: 'ajax', ac: 'get-location-select', locationid: this.id});
592 $(this).unbind('mousedown', locationeditor_showselectbox);
593 }
594 $(document).ready(function () {
595 $('select.locationlist-popup').bind('mousedown', locationeditor_showselectbox);
596 });
597 JSTXT
598 ); // addJSText()
599
600 function printNewItemTR ()
601 {
602 printOpFormIntro ('addLocation');
603 echo '<tr><td>';
604 printImageHREF ('create', 'Add new location', TRUE);
605 echo '</td><td>';
606 renderLocationSelectTree ('parent_id');
607 echo '</td><td><input type=text size=48 name=name></td><td>';
608 printImageHREF ('create', 'Add new location', TRUE);
609 echo "</td></tr></form>\n";
610 }
611
612 startPortlet ('Locations');
613 echo "<table border=0 cellspacing=0 cellpadding=5 align=center class=widetable>\n";
614 echo "<tr><th>&nbsp;</th><th>Parent</th><th>Name</th><th>&nbsp;</th></tr>\n";
615 if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
616 printNewItemTR();
617
618 $locations = listCells ('location');
619 renderLocationRowForEditor (treeFromList (addTraceToNodes ($locations)));
620
621 if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
622 printNewItemTR();
623 echo "</table><br>\n";
624 finishPortlet();
625 }
626
627 function renderRackspaceRowEditor ()
628 {
629 function printNewItemTR ()
630 {
631 printOpFormIntro ('addRow');
632 echo '<tr><td>';
633 printImageHREF ('create', 'Add new row', TRUE);
634 echo '</td><td>&nbsp;';
635 echo '</td><td>&nbsp;';
636 echo '</td><td>';
637 renderLocationSelectTree ('location_id');
638 echo '</td><td><input type=text name=name></td><td>';
639 printImageHREF ('create', 'Add new row', TRUE);
640 echo '</td><td>&nbsp;';
641 echo '</td><td>&nbsp;</td></tr></form>';
642 }
643 startPortlet ('Rows');
644 echo "<table border=0 cellspacing=0 cellpadding=5 align=center class=widetable>\n";
645 echo "<tr><th>&nbsp;</th><th># Racks</th><th># Devices</th><th>Location</th><th>Name</th><th>&nbsp;</th><th>&nbsp;</th><th>Row link</th></tr>\n";
646 if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
647 printNewItemTR ();
648 foreach (listCells ('row') as $row_id => $rowInfo)
649 {
650 echo '<tr><td>';
651 $rc = $rowInfo['rackc'];
652 $delete_racks_str = $rc ? " and $rc rack(s)" : '';
653 echo getOpLink (array ('op'=>'deleteRow', 'row_id'=>$row_id), '', 'destroy', 'Delete row'.$delete_racks_str, 'need-confirmation');
654 printOpFormIntro ('updateRow', array ('row_id' => $row_id));
655 echo '</td><td class=tdright>';
656 echo $rc;
657 echo '</td><td class=tdright>';
658 echo getRowMountsCount ($row_id);
659 echo '</td><td>';
660 renderLocationSelectTree ('location_id', $rowInfo['location_id']);
661 echo "</td><td><input type=text name=name value='${rowInfo['name']}'></td><td>";
662 printImageHREF ('save', 'Save changes', TRUE);
663 echo "</form></td>";
664 echo '<td>&nbsp;</td>';
665 echo '<td class=tdleft>' . mkCellA ($rowInfo) . '</td>';
666 echo "</tr>\n";
667 }
668 if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
669 printNewItemTR ();
670 echo "</table><br>\n";
671 finishPortlet();
672 }
673
674 function renderRow ($row_id)
675 {
676 $rowInfo = getRowInfo ($row_id);
677 $cellfilter = getCellFilter();
678 $rackList = applyCellFilter ('rack', $cellfilter, $row_id);
679
680 $summary = array ();
681 $summary['Name'] = $rowInfo['name'];
682 if ($rowInfo['location_id'])
683 $summary['Location'] = mkA ($rowInfo['location'], 'location', $rowInfo['location_id']);
684 $summary['Racks'] = $rowInfo['count'];
685 $summary['Units'] = $rowInfo['sum'];
686 $summary['% used'] = getProgressBar (getRSUforRow ($rackList));
687 foreach (getAttrValuesSorted ($row_id) as $record)
688 if
689 (
690 $record['value'] != '' &&
691 permitted (NULL, NULL, NULL, array (array ('tag' => '$attr_' . $record['id'])))
692 )
693 $summary['{sticker}' . $record['name']] = formatAttributeValue ($record, 1561);
694
695 // Main layout starts.
696 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
697
698 // Left portlet with row information.
699 echo "<tr><td class=pcleft>";
700 renderEntitySummary ($rowInfo, 'Summary', $summary);
701 renderCellFilterPortlet ($cellfilter, 'rack', $rackList, array ('row_id' => $row_id));
702 renderFilesPortlet ('row',$row_id);
703 echo "</td><td class=pcright>";
704
705 global $nextorder;
706 // Maximum number of racks per row is proportionally less, but at least 1.
707 $maxPerRow = max (floor (getConfigVar ('RACKS_PER_ROW') / getConfigVar ('ROW_SCALE')), 1);
708 $rackListIdx = 0;
709 $order = 'odd';
710 startPortlet ('Racks');
711 echo "<table border=0 cellspacing=5 align='center'><tr>";
712 foreach ($rackList as $rack)
713 {
714 if ($rackListIdx % $maxPerRow == 0)
715 {
716 if ($rackListIdx > 0)
717 echo '</tr>';
718 echo '<tr>';
719 }
720 $class = ($rack['has_problems'] == 'yes') ? 'error' : $order;
721 echo "<td align=center valign=bottom class=row_${class}>" .
722 getRackThumbLink ($rack, getConfigVar ('ROW_SCALE')) .
723 '<br>' . mkA (stringForLabel ($rack['name']), 'rack', $rack['id']) . '</td>';
724 $order = $nextorder[$order];
725 $rackListIdx++;
726 }
727 echo "</tr></table>\n";
728 finishPortlet();
729 echo "</td></tr></table>";
730 }
731
732 function renderEditAttributeTRs ($update_op, $values, $objtype_id, $skip_ids = array())
733 {
734 $datehint = ' (' . datetimeFormatHint (getConfigVar ('DATETIME_FORMAT')) . ')';
735 $i = 0;
736 foreach ($values as $record)
737 {
738 $annex = array (array ('tag' => '$attr_' . $record['id']));
739 $can_view = permitted (NULL, NULL, NULL, $annex);
740 if (in_array ($record['id'], $skip_ids) || ! $can_view)
741 continue;
742 $can_update = permitted (NULL, NULL, $update_op, $annex);
743 $can_clear = permitted (NULL, NULL, 'clearSticker', $annex);
744 // Ability to update ultimately includes ability to set to an empty value,
745 // i.e. to clear, but making the check this way in the ophandler is complicated,
746 // so let's keep it consistently imperfect for the time being and maybe
747 // fix it later.
748 $clear_html = ($record['value'] != '' && ($can_clear /* || $can_update*/)) ?
749 getOpLink (array ('op' => 'clearSticker', 'attr_id' => $record['id']), '', 'clear', 'Clear value', 'need-confirmation') :
750 '&nbsp;';
751 echo "<tr><td>${clear_html}</td>";
752 echo '<th class=sticker>' . $record['name'] . ($record['type'] == 'date' ? $datehint : '') . ':</th>';
753 echo '<td class=tdleft>';
754 switch ($record['type'])
755 {
756 case 'uint':
757 case 'float':
758 case 'string':
759 $ro_or_rw = $can_update ? "name=${i}_value" : 'disabled';
760 echo "<input type=text ${ro_or_rw} value='${record['value']}'>";
761 break;
762 case 'date':
763 $ro_or_rw = $can_update ? "name=${i}_value" : 'disabled';
764 $date_value = $record['value'] ? datetimestrFromTimestamp ($record['value']) : '';
765 echo "<input type=text ${ro_or_rw} value='${date_value}'>";
766 break;
767 case 'dict':
768 $ro_or_rw = $can_update ? array ('name' => "${i}_value") : array ('name' => "${i}_value", 'disabled' => 1);
769 $chapter = readChapter ($record['chapter_id'], 'o');
770 $chapter[0] = '-- NOT SET --';
771 $chapter = cookOptgroups ($chapter, $objtype_id, $record['key']);
772 printNiftySelect ($chapter, $ro_or_rw, $record['key']);
773 break;
774 default:
775 throw new InvalidArgException ('record[type]', $record['type']);
776 } // switch
777 if ($can_update)
778 {
779 echo "<input type=hidden name=${i}_attr_id value=${record['id']}>";
780 $i++;
781 }
782 echo '</td></tr>';
783 } // foreach
784 echo "<input type=hidden name=num_attrs value=${i}>";
785 }
786
787 function renderEditRowForm ($row_id)
788 {
789 $row = getRowInfo ($row_id);
790
791 startPortlet ('Attributes');
792 printOpFormIntro ('updateRow');
793 echo '<table border=0 align=center>';
794 echo '<tr><td>&nbsp;</td><th class=tdright>Location:</th><td class=tdleft>';
795 $locations = array ();
796 $locations[0] = '-- NOT SET --';
797 foreach (listCells ('location') as $id => $locationInfo)
798 $locations[$id] = $locationInfo['name'];
799 natcasesort ($locations);
800 printSelect ($locations, array ('name' => 'location_id'), $row['location_id']);
801 echo "</td></tr>\n";
802 echo "<tr><td>&nbsp;</td><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name value='${row['name']}'></td></tr>\n";
803 renderEditAttributeTRs ('updateRow', getAttrValuesSorted ($row_id), 1561);
804 if ($row['count'] == 0)
805 {
806 echo '<tr><td>&nbsp;</td><th class=tdright>Actions:</th><td class=tdleft>';
807 echo getOpLink (array ('op'=>'deleteRow'), '', 'destroy', 'Delete row', 'need-confirmation');
808 echo "&nbsp;</td></tr>\n";
809 }
810 echo "<tr><td class=submit colspan=3>";
811 printImageHREF ('SAVE', 'Save changes', TRUE);
812 echo "</td></tr>\n";
813 echo '</form></table><br>';
814 finishPortlet();
815
816 startPortlet ('History');
817 renderObjectHistory ($row_id);
818 finishPortlet();
819 }
820
821 // Used by renderRack()
822 function printObjectDetailsForRenderRack ($object_id, $hl_obj_id = 0)
823 {
824 $objectData = spotEntity ('object', $object_id);
825 if ($objectData['asset_no'] != '')
826 $prefix = "<div title='${objectData['asset_no']}";
827 else
828 $prefix = "<div title='no asset tag";
829 // Don't tell about label, if it matches common name.
830 $body = '';
831 if ($objectData['name'] != $objectData['label'] && $objectData['label'] != '')
832 $body = ", visible label is \"${objectData['label']}\"";
833 // Display list of child objects, if any
834 $objectChildren = getChildren ($objectData, 'object');
835 $slotRows = $slotCols = $slotInfo = $slotData = $slotTitle = $slotClass = array ();
836 if (! count ($objectChildren))
837 $suffix = "'>";
838 else
839 {
840 foreach ($objectChildren as $childData)
841 {
842 $childNames[] = $childData['name'];
843 $attrData = getAttrValues ($childData['id']);
844 $numRows = $numCols = 1;
845 if (isset ($attrData[2])) // HW type
846 {
847 extractLayout ($attrData[2]);
848 if (isset ($attrData[2]['rows']))
849 {
850 $numRows = $attrData[2]['rows'];
851 $numCols = $attrData[2]['cols'];
852 }
853 }
854 if (isset ($attrData['28'])) // slot number
855 {
856 $slot = $attrData['28']['value'];
857 if (preg_match ('/\d+/', $slot, $matches))
858 $slot = $matches[0];
859 $slotRows[$slot] = $numRows;
860 $slotCols[$slot] = $numCols;
861 $slotInfo[$slot] = $childData['dname'];
862 $slotData[$slot] = $childData['id'];
863 if ($childData['asset_no'] != '')
864 $slotTitle[$slot] = "<div title='${childData['asset_no']}";
865 else
866 $slotTitle[$slot] = "<div title='no asset tag";
867 if ($childData['label'] != '' && $childData['label'] != $childData['dname'])
868 $slotTitle[$slot] .= ", visible label is \"${childData['label']}\"";
869 $slotTitle[$slot] .= "'>";
870 $slotClass[$slot] = 'state_T';
871 if ($childData['id'] == $hl_obj_id)
872 $slotClass[$slot] .= 'h';
873 if ($childData['has_problems'] == 'yes')
874 $slotClass[$slot] .= 'w';
875
876 $child = spotEntity ('object', $childData['id']);
877 setEntityColors ($child);
878 $class_context = $childData['id'] == $hl_obj_id ? 'atom_selected' : 'atom_plain';
879 $slotClass[$slot] .= getCellClass ($child, $class_context);
880
881 }
882 }
883 natsort($childNames);
884 $suffix = sprintf(", contains %s'>", implode(', ', $childNames));
885 }
886 echo "${prefix}${body}${suffix}" . mkCellA ($objectData) . '</div>';
887 if (in_array ($objectData['objtype_id'], array (1502,1503))) // server chassis, network chassis
888 {
889 $objAttr = getAttrValues ($objectData['id']);
890 if (isset ($objAttr[2])) // HW type
891 {
892 extractLayout ($objAttr[2]);
893 if (isset ($objAttr[2]['rows']))
894 {
895 $rows = $objAttr[2]['rows'];
896 $cols = $objAttr[2]['cols'];
897 $layout = $objAttr[2]['layout'];
898 echo "<table width='100%' border='1'>";
899 for ($r = 0; $r < $rows; $r++)
900 {
901 echo '<tr>';
902 for ($c = 0; $c < $cols; $c++)
903 {
904 $s = ($r * $cols) + $c + 1;
905 if (isset ($slotData[$s]))
906 {
907 if ($slotData[$s] >= 0)
908 {
909 for ($lr = 0; $lr < $slotRows[$s]; $lr++)
910 for ($lc = 0; $lc < $slotCols[$s]; $lc++)
911 {
912 $skip = ($lr * $cols) + $lc;
913 if ($skip > 0)
914 $slotData[$s + $skip] = -1;
915 }
916 echo '<td';
917 if ($slotRows[$s] > 1)
918 echo " rowspan=$slotRows[$s]";
919 if ($slotCols[$s] > 1)
920 echo " colspan=$slotCols[$s]";
921 echo " class='${slotClass[$s]}'>${slotTitle[$s]}";
922 if ($layout == 'V')
923 {
924 $tmp = mb_substr($slotInfo[$s], 0, 1);
925 for($i = 1; $i < mb_strlen($slotInfo[$s]); $i++)
926 {
927 $tmp .= '<br>' . mb_substr($slotInfo[$s], $i, 1);
928 }
929 $slotInfo[$s] = $tmp;
930 }
931 echo mkA ($slotInfo[$s], 'object', $slotData[$s]);
932 echo '</div></td>';
933 }
934 }
935 else
936 echo "<td class='state_F'><div title=\"Free slot\">&nbsp;</div></td>";
937 }
938 echo '</tr>';
939 }
940 echo '</table>';
941 }
942 }
943 }
944 }
945
946 // This function renders rack as HTML table.
947 function renderRack ($rack_id, $hl_obj_id = 0)
948 {
949 $rackData = spotEntity ('rack', $rack_id);
950 amplifyCell ($rackData);
951 markAllSpans ($rackData);
952 setEntityColors ($rackData);
953 if ($hl_obj_id > 0)
954 highlightObject ($rackData, $hl_obj_id);
955 $neighbors = getRackNeighbors ($rackData['row_id'], $rack_id);
956 $prev_id = $neighbors['prev'];
957 $next_id = $neighbors['next'];
958 echo "<center><table border=0><tr valign=middle>";
959 echo '<td><h2>' . mkA ($rackData['row_name'], 'row', $rackData['row_id']) . ' :</h2></td>';
960 if ($prev_id != NULL)
961 echo '<td>' . mkA (getImageHREF ('prev', 'previous rack'), 'rack', $prev_id) . '</td>';
962 echo '<td><h2>' . mkA ($rackData['name'], 'rack', $rackData['id']) . '</h2></td>';
963 if ($next_id != NULL)
964 echo '<td>' . mkA (getImageHREF ('next', 'next rack'), 'rack', $next_id) . '</td>';
965 echo "</h2></td></tr></table>\n";
966 echo "<table class=rack border=0 cellspacing=0 cellpadding=1>\n";
967 echo "<tr><th width='10%'>&nbsp;</th><th width='20%'>Front</th>";
968 echo "<th width='50%'>Interior</th><th width='20%'>Back</th></tr>\n";
969 $reverse = considerConfiguredConstraint ($rackData, 'REVERSED_RACKS_LISTSRC');
970 for ($i = $rackData['height']; $i > 0; $i--)
971 {
972 echo '<tr><th>' . inverseRackUnit ($rackData['height'], $i, $reverse) . '</th>';
973 for ($locidx = 0; $locidx < 3; $locidx++)
974 {
975 if (isset ($rackData[$i][$locidx]['skipped']))
976 continue;
977 $state = $rackData[$i][$locidx]['state'];
978
979 $class = "atom state_${state}";
980
981 if (isset ($rackData[$i][$locidx]['hl']))
982 $class .= $rackData[$i][$locidx]['hl'];
983
984 if($state == 'T')
985 {
986 $objectData = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
987 setEntityColors ($objectData);
988 $class_context = $rackData[$i][$locidx]['object_id'] == $hl_obj_id ? 'atom_selected' : 'atom_plain';
989 $class .= getCellClass ($objectData, $class_context);
990 }
991
992 echo "<td class='${class}'";
993
994 if (isset ($rackData[$i][$locidx]['colspan']))
995 echo ' colspan=' . $rackData[$i][$locidx]['colspan'];
996 if (isset ($rackData[$i][$locidx]['rowspan']))
997 echo ' rowspan=' . $rackData[$i][$locidx]['rowspan'];
998 echo ">";
999 switch ($state)
1000 {
1001 case 'T':
1002 printObjectDetailsForRenderRack ($rackData[$i][$locidx]['object_id'], $hl_obj_id);
1003 break;
1004 case 'A':
1005 echo '<div title="This rackspace does not exist">&nbsp;</div>';
1006 break;
1007 case 'F':
1008 echo '<div title="Free rackspace">&nbsp;</div>';
1009 break;
1010 case 'U':
1011 echo '<div title="Problematic rackspace, you CAN\'T mount here">&nbsp;</div>';
1012 break;
1013 default:
1014 echo '<div title="No data">&nbsp;</div>';
1015 break;
1016 }
1017 echo '</td>';
1018 }
1019 echo "</tr>\n";
1020 }
1021 echo "</table>\n";
1022 // Get a list of all of objects Zero-U mounted to this rack
1023 $zeroUObjects = getChildren ($rackData, 'object');
1024 uasort ($zeroUObjects, 'compare_name');
1025 if (count ($zeroUObjects) > 0)
1026 {
1027 echo "<br><table width='75%' class=rack border=0 cellspacing=0 cellpadding=1>\n";
1028 echo "<tr><th>Zero-U:</th></tr>\n";
1029 foreach ($zeroUObjects as $zeroUObject)
1030 {
1031 $state = ($zeroUObject['id'] == $hl_obj_id) ? 'Th' : 'T';
1032 if ($zeroUObject['has_problems'] == 'yes')
1033 $state .= 'w';
1034
1035 $class = "atom state_${state}";
1036 setEntityColors ($zeroUObject);
1037 $class_context = $zeroUObject['id'] == $hl_obj_id ? 'atom_selected' : 'atom_plain';
1038 $class .= getCellClass ($zeroUObject, $class_context);
1039
1040 echo "<tr><td class='${class}'>";
1041 printObjectDetailsForRenderRack ($zeroUObject['id']);
1042 echo "</td></tr>\n";
1043 }
1044 echo "</table>\n";
1045 }
1046 echo "</center>\n";
1047 }
1048
1049 function renderRackSortForm ($row_id)
1050 {
1051 includeJQueryUIJS();
1052 // Heredoc, not nowdoc!
1053 $js = <<<"JSTXT"
1054 $(document).ready(
1055 function () {
1056 $("#sortRacks").sortable({
1057 update : function () {
1058 serial = $('#sortRacks').sortable('serialize');
1059 $.ajax({
1060 url: 'index.php?module=ajax&ac=upd-rack-sort-order&row_id=${row_id}',
1061 type: 'post',
1062 data: serial,
1063 });
1064 }
1065 });
1066 }
1067 );
1068 JSTXT;
1069 addJSText ($js);
1070
1071 startPortlet ('Racks');
1072 echo "<table border=0 cellspacing=0 cellpadding=5 align=center class=widetable>\n";
1073 echo "<tr><th>Drag to change order</th></tr>\n";
1074 echo "<tr><td class=tdleft><ul class='uflist' id='sortRacks'>\n";
1075 foreach (getRacks($row_id) as $rack_id => $rackInfo)
1076 echo "<li id=racks_${rack_id}>${rackInfo['name']}</li>\n";
1077 echo "</ul></td></tr></table>\n";
1078 finishPortlet();
1079 }
1080
1081 function renderNewRackForm()
1082 {
1083 $default_height = emptyStrIfZero (getConfigVar ('DEFAULT_RACK_HEIGHT'));
1084 startPortlet ('Add one');
1085 printOpFormIntro ('addRack', array ('mode' => 'one'));
1086 echo '<table border=0 align=center>';
1087 echo "<tr><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name></td>";
1088 echo "<tr><th class=tdright>Height in units (required):</th><td class=tdleft><input type=text name=height1 value='${default_height}'></td></tr>\n";
1089 echo "<tr><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=asset_no></td></tr>\n";
1090 echo "<tr><th class=tdright>Tags:</td><td class=tdleft>";
1091 printTagsPicker ();
1092 echo "</td></tr>\n";
1093 echo "<tr><td class=submit colspan=2>";
1094 printImageHREF ('CREATE', 'Add', TRUE);
1095 echo "</td></tr></table></form>";
1096 finishPortlet();
1097
1098 startPortlet ('Add many');
1099 printOpFormIntro ('addRack', array ('mode' => 'many'));
1100 echo '<table border=0 align=center>';
1101 echo "<tr><th class=tdright>Height in units (*):</th><td class=tdleft><input type=text name=height2 value='${default_height}'></td>";
1102 echo "<tr><th class=tdright>Assign tags:</td><td class=tdleft>";
1103 printTagsPicker ();
1104 echo "</td></tr>\n";
1105 echo "<tr><th class=tdright>Rack names (required):</th><td class=tdleft><textarea name=names cols=40 rows=25></textarea></td></tr>\n";
1106 echo "<tr><td class=submit colspan=2>";
1107 printImageHREF ('CREATE', 'Add', TRUE);
1108 echo '</form></table>';
1109 finishPortlet();
1110 }
1111
1112 function renderEditObjectForm()
1113 {
1114 $object_id = getBypassValue();
1115 $object = spotEntity ('object', $object_id);
1116 startPortlet ();
1117 printOpFormIntro ('update');
1118
1119 // static attributes
1120 echo '<table border=0 cellspacing=0 cellpadding=3 align=center>';
1121 echo "<tr><td>&nbsp;</td><th colspan=2><h2>Attributes</h2></th></tr>";
1122 echo '<tr><td>&nbsp;</td><th class=tdright>Type:</th><td class=tdleft>';
1123 printSelect (getObjectTypeChangeOptions ($object['id']), array ('name' => 'object_type_id'), $object['objtype_id']);
1124 echo '</td></tr>';
1125 // baseline info
1126 echo "<tr><td>&nbsp;</td><th class=tdright>Common name:</th><td class=tdleft><input type=text name=object_name value='${object['name']}'></td></tr>\n";
1127 echo "<tr><td>&nbsp;</td><th class=tdright>Visible label:</th><td class=tdleft><input type=text name=object_label value='${object['label']}'></td></tr>\n";
1128 echo "<tr><td>&nbsp;</td><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=object_asset_no value='${object['asset_no']}'></td></tr>\n";
1129 echo "<tr><td>&nbsp;</td><th class=tdright>Tags:</th><td class=tdleft>";
1130 printTagsPicker ();
1131 echo "</td></tr>\n";
1132 // parent selection
1133 if (objectTypeMayHaveParent ($object['objtype_id']))
1134 {
1135 $parents = getParents ($object, 'object');
1136 // lookup the human-readable object type, sort by it
1137 foreach ($parents as $parent_id => $parent)
1138 $parents[$parent_id]['object_type'] = decodeObjectType ($parent['objtype_id']);
1139 $grouped_parents = groupBy ($parents, 'object_type');
1140 ksort ($grouped_parents);
1141 foreach ($grouped_parents as $parents_group)
1142 {
1143 uasort ($parents_group, 'compare_name');
1144 $label = $parents_group[key ($parents_group)]['object_type'] . (count($parents_group) > 1 ? ' containers:' : ' container:');
1145 foreach ($parents_group as $link_id => $parent_cell)
1146 {
1147 echo "<tr><td>&nbsp;</td>";
1148 echo "<th class=tdright>${label}</th><td class=tdleft>";
1149 echo mkCellA ($parent_cell);
1150 echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
1151 echo getOpLink (array('op'=>'unlinkObjects', 'link_id'=>$link_id), '', 'cut', 'Unlink container');
1152 echo "</td></tr>\n";
1153 $label = '&nbsp;';
1154 }
1155 }
1156 echo "<tr><td>&nbsp;</td>";
1157 echo "<th class=tdright>Select container:</th><td class=tdleft>";
1158 echo getPopupLink ('objlist', array(), 'findlink', 'attach', 'Select a container');
1159 echo "</td></tr>\n";
1160 }
1161 renderEditAttributeTRs ('update', getAttrValuesSorted ($object_id), $object['objtype_id']);
1162 echo '<tr><td>&nbsp;</td><th class=tdright><label for=object_has_problems>Has problems:</label></th>';
1163 echo '<td class=tdleft><input type=checkbox name=object_has_problems id=object_has_problems';
1164 if ($object['has_problems'] == 'yes')
1165 echo ' checked';
1166 echo "></td></tr>\n";
1167 echo "<tr><td>&nbsp;</td><th class=tdright>Actions:</th><td class=tdleft>";
1168 echo getOpLink (array ('op'=>'deleteObject', 'page'=>'depot', 'tab'=>'addmore', 'object_id'=>$object_id), '' ,'destroy', 'Delete object', 'need-confirmation');
1169 echo "&nbsp;";
1170 echo getOpLink (array ('op'=>'resetObject'), '' ,'clear', 'Reset (cleanup) object', 'need-confirmation');
1171 echo "</td></tr>\n";
1172 echo "<tr><td colspan=3><b>Comment:</b><br><textarea name=object_comment rows=10 cols=80>${object['comment']}</textarea></td></tr>";
1173
1174 echo "<tr><th class=submit colspan=3>";
1175 printImageHREF ('SAVE', 'Save changes', TRUE);
1176 echo "</form></th></tr></table>\n";
1177 finishPortlet();
1178
1179 echo '<table border=0 width=100%><tr><td>';
1180 startPortlet ('history');
1181 renderObjectHistory ($object_id);
1182 finishPortlet();
1183 echo '</td></tr></table>';
1184 }
1185
1186 function renderEditRackForm ($rack_id)
1187 {
1188 $rack = spotEntity ('rack', $rack_id);
1189 amplifyCell ($rack);
1190
1191 startPortlet ('Attributes');
1192 printOpFormIntro ('updateRack');
1193 echo '<table border=0 align=center>';
1194 echo "<tr><td>&nbsp;</td><th class=tdright>Rack row:</th><td class=tdleft>";
1195 foreach (listCells ('row') as $row_id => $rowInfo)
1196 {
1197 $trail = getLocationTrail ($rowInfo['location_id'], FALSE);
1198 $rows[$row_id] = empty ($trail) ? $rowInfo['name'] : $rowInfo['name'] . ' [' . $trail . ']';
1199 }
1200 natcasesort ($rows);
1201 printSelect ($rows, array ('name' => 'row_id'), $rack['row_id']);
1202 echo "</td></tr>\n";
1203 echo "<tr><td>&nbsp;</td><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name value='${rack['name']}'></td></tr>\n";
1204 echo "<tr><td>&nbsp;</td><th class=tdright>Height (required):</th><td class=tdleft><input type=text name=height value='${rack['height']}'></td></tr>\n";
1205 echo "<tr><td>&nbsp;</td><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=asset_no value='${rack['asset_no']}'></td></tr>\n";
1206 echo "<tr><td>&nbsp;</td><th class=tdright>Tags:</th><td class=tdleft>";
1207 printTagsPicker ();
1208 echo "</td></tr>\n";
1209 renderEditAttributeTRs ('updateRack', getAttrValuesSorted ($rack_id), 1560, array (27, 29));
1210 echo '<tr><td>&nbsp;</td><th class=tdright><label for=has_problems>Has problems:</label></th>';
1211 echo '<td class=tdleft><input type=checkbox name=has_problems id=has_problems';
1212 if ($rack['has_problems'] == 'yes')
1213 echo ' checked';
1214 echo "></td></tr>\n";
1215 echo "<tr><td>&nbsp;</td><th class=tdright>Actions:</th><td class=tdleft>";
1216 if ($rack['isDeletable'])
1217 {
1218 echo getOpLink (array ('op'=>'deleteRack'), '', 'destroy', 'Delete rack', 'need-confirmation');
1219 echo "&nbsp;";
1220 }
1221 else
1222 {
1223 echo getOpLink (array ('op'=>'cleanRack'), '' ,'clear', 'Reset (cleanup) rack mounts', 'need-confirmation');
1224 echo "&nbsp;";
1225 }
1226 echo "</td></tr>\n";
1227 echo "<tr><td colspan=3><b>Comment:</b><br><textarea name=comment rows=10 cols=80>${rack['comment']}</textarea></td></tr>";
1228 echo "<tr><td class=submit colspan=3>";
1229 printImageHREF ('SAVE', 'Save changes', TRUE);
1230 echo "</td></tr>\n";
1231 echo '</form></table><br>';
1232 finishPortlet();
1233
1234 startPortlet ('History');
1235 renderObjectHistory ($rack_id);
1236 finishPortlet();
1237 }
1238
1239 // populates the $summary array with the sum of power attributes of the objects mounted into the rack
1240 function populateRackPower ($rackData, &$summary)
1241 {
1242 $power_attrs = array(
1243 7, // 'float','max. current, Ampers'
1244 13, // 'float','max power, Watts'
1245 );
1246 $sum = array();
1247 if (! isset ($rackData['mountedObjects']))
1248 amplifyCell ($rackData);
1249 foreach ($rackData['mountedObjects'] as $object_id)
1250 {
1251 $attrs = getAttrValues ($object_id);
1252 foreach ($power_attrs as $attr_id)
1253 if (isset ($attrs[$attr_id]) && $attrs[$attr_id]['type'] == 'float')
1254 {
1255 if (! isset ($sum[$attr_id]))
1256 {
1257 $sum[$attr_id]['sum'] = 0.0;
1258 $sum[$attr_id]['name'] = $attrs[$attr_id]['name'];
1259 }
1260 $sum[$attr_id]['sum'] += $attrs[$attr_id]['value'];
1261 }
1262 }
1263 foreach ($sum as $attr)
1264 if ($attr['sum'] > 0.0)
1265 $summary[$attr['name']] = $attr['sum'];
1266 }
1267
1268 // used by renderGridForm() and renderRackPage()
1269 function renderRackInfoPortlet ($rackData)
1270 {
1271 $summary = array();
1272 $summary['Rack row'] = mkA ($rackData['row_name'], 'row', $rackData['row_id']);
1273 $summary['Name'] = $rackData['name'];
1274 $summary['Height'] = $rackData['height'];
1275 if ($rackData['asset_no'] != '')
1276 $summary['Asset tag'] = $rackData['asset_no'];
1277 if ($rackData['has_problems'] == 'yes')
1278 $summary[] = array ('<tr><td colspan=2 class=msg_error>Has problems</td></tr>');
1279 populateRackPower ($rackData, $summary);
1280 // Display populated attributes, but skip 'height' since it's already displayed above
1281 // and skip 'sort_order' because it's modified using AJAX
1282 foreach (getAttrValuesSorted ($rackData['id']) as $record)
1283 if
1284 (
1285 $record['id'] != 27 && $record['id'] != 29 &&
1286 $record['value'] != '' &&
1287 permitted (NULL, NULL, NULL, array (array ('tag' => '$attr_' . $record['id'])))
1288 )
1289 $summary['{sticker}' . $record['name']] = formatAttributeValue ($record, 1560);
1290 $summary['% used'] = getProgressBar (getRSUforRack ($rackData));
1291 $summary['Objects'] = count ($rackData['mountedObjects']);
1292 $summary['tags'] = '';
1293 renderEntitySummary ($rackData, 'summary', $summary);
1294 if ($rackData['comment'] != '')
1295 {
1296 startPortlet ('Comment');
1297 echo '<div class=commentblock>' . string_insert_hrefs ($rackData['comment']) . '</div>';
1298 finishPortlet ();
1299 }
1300 }
1301
1302 // This is a universal editor of rack design/waste.
1303 function renderGridForm ($rack_id, $filter, $header, $submit, $state1, $state2)
1304 {
1305 $rackData = spotEntity ('rack', $rack_id);
1306 amplifyCell ($rackData);
1307 $filter ($rackData);
1308
1309 // Render the result whatever it is.
1310 // Main layout.
1311 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
1312 echo "<tr><td colspan=2 align=center><h1>${rackData['name']}</h1></td></tr>\n";
1313
1314 // Left column with information portlet.
1315 echo "<tr><td class=pcleft height='1%' width='50%'>";
1316 renderRackInfoPortlet ($rackData);
1317 echo "</td>\n";
1318 echo "<td class=pcright>";
1319
1320 // Grid form.
1321 $is_ro = !rackModificationPermitted ($rackData, 'updateRack');
1322 startPortlet ($header);
1323 includeJQueryUIJS();
1324 addJSInternal ('js/racktables.js');
1325 $table_id = 'selectableRack';
1326 addBulkSelectorJS ($table_id);
1327 echo "<center>\n";
1328 $read_only_text = $is_ro ? '(read-only)' : '&nbsp;';
1329 echo "<p style='color: red; margin-top:0px'>${read_only_text}</p>\n";
1330 echo "<table class=rack id={$table_id} border=0 cellspacing=0 cellpadding=1>\n";
1331 echo "<tr><th width='10%'>&nbsp;</th>";
1332 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '0', ${rackData['height']})\">Front</a></th>";
1333 echo "<th width='50%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '1', ${rackData['height']})\">Interior</a></th>";
1334 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '2', ${rackData['height']})\">Back</a></th></tr>\n";
1335 printOpFormIntro ('updateRack');
1336 markupAtomGrid ($rackData, $state2);
1337 renderAtomGrid ($rackData, $is_ro);
1338 echo "</table></center>\n";
1339 echo "<br><input type=submit name=do_update value='${submit}'></form><br><br>\n";
1340 finishPortlet();
1341 echo "</td></tr></table>\n";
1342 }
1343
1344 function renderRackDesign ($rack_id)
1345 {
1346 renderGridForm ($rack_id, 'applyRackDesignMask', 'Rack design', 'Set rack design', 'A', 'F');
1347 }
1348
1349 function renderRackProblems ($rack_id)
1350 {
1351 renderGridForm ($rack_id, 'applyRackProblemMask', 'Rack problems', 'Mark unusable atoms', 'F', 'U');
1352 }
1353
1354 function renderObjectPortHeaderRow()
1355 {
1356 // Renders the headers for the ports table on the default page
1357
1358 echo '<tr><th class=tdleft>Local name</th><th class=tdleft>Visible label</th>';
1359 echo '<th class=tdleft>Interface</th><th class=tdleft>L2 address</th>';
1360 echo '<th class=tdcenter colspan=2>Remote object and port</th>';
1361 echo '<th class=tdleft>Cable ID</th></tr>';
1362 }
1363
1364 function renderObjectPortRow ($port, $is_highlighted)
1365 {
1366 // highlight port name with yellow if its name is not canonical
1367 $canon_pn = shortenPortName ($port['name'], $port['object_id']);
1368 $name_class = $canon_pn == $port['name'] ? '' : 'trwarning';
1369
1370 echo '<tr';
1371 if ($is_highlighted)
1372 echo ' class=highlight';
1373 $a_class = isEthernetPort ($port) ? 'port-menu' : '';
1374 echo "><td class='tdleft $name_class' NOWRAP><a name='port-${port['id']}' class='interactive-portname nolink $a_class'>${port['name']}</a></td>";
1375 echo "<td class=tdleft>${port['label']}</td>";
1376 echo "<td class=tdleft>" . formatPortIIFOIF ($port) . "</td><td class='tdleft l2address'>${port['l2address']}</td>";
1377 if ($port['remote_object_id'])
1378 {
1379 $dname = formatObjectDisplayedName ($port['remote_object_name'], $port['remote_object_tid']);
1380 echo "<td class=tdleft>" .
1381 formatPortLink ($port['remote_object_id'], $dname, $port['remote_id'], NULL) .
1382 "</td>";
1383 echo "<td class=tdleft>" . formatLoggedSpan ($port['last_log'], $port['remote_name'], 'underline') . "</td>";
1384 $editable = permitted ('object', 'ports', 'editPort')
1385 ? 'editable'
1386 : '';
1387 echo "<td class=tdleft><span class='rsvtext $editable id-${port['id']} op-upd-reservation-cable'>${port['cableid']}</span></td>";
1388 }
1389 else
1390 echo implode ('', formatPortReservation ($port)) . '<td></td>';
1391 echo "</tr>";
1392 }
1393
1394 function renderObject ($object_id)
1395 {
1396 global $nextorder, $virtual_obj_types;
1397 $info = spotEntity ('object', $object_id);
1398 amplifyCell ($info);
1399 // Main layout starts.
1400 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
1401 echo "<tr><td colspan=2 align=center><h1>${info['dname']}</h1></td></tr>\n";
1402 // A mandatory left column with varying number of portlets.
1403 echo "<tr><td class=pcleft>";
1404
1405 // display summary portlet
1406 $summary = array();
1407 if ($info['name'] != '')
1408 $summary['Common name'] = $info['name'];
1409 elseif (considerConfiguredConstraint ($info, 'NAMEWARN_LISTSRC'))
1410 $summary[] = array ('<tr><td colspan=2 class=msg_error>Common name is missing.</td></tr>');
1411 $summary['Object type'] = '<a href="' . makeHref (array (
1412 'page' => 'depot',
1413 'tab' => 'default',
1414 'cfe' => '{$typeid_' . $info['objtype_id'] . '}'
1415 )) . '">' . decodeObjectType ($info['objtype_id']) . '</a>';
1416 if ($info['label'] != '')
1417 $summary['Visible label'] = $info['label'];
1418 if ($info['asset_no'] != '')
1419 $summary['Asset tag'] = $info['asset_no'];
1420 elseif (considerConfiguredConstraint ($info, 'ASSETWARN_LISTSRC'))
1421 $summary[] = array ('<tr><td colspan=2 class=msg_error>Asset tag is missing.</td></tr>');
1422 $parents = getParents ($info, 'object');
1423 // lookup the human-readable object type, sort by it
1424 foreach ($parents as $parent_id => $parent)
1425 $parents[$parent_id]['object_type'] = decodeObjectType ($parent['objtype_id']);
1426 $grouped_parents = groupBy ($parents, 'object_type');
1427 ksort ($grouped_parents);
1428 foreach ($grouped_parents as $parents_group)
1429 {
1430 uasort ($parents_group, 'compare_name');
1431 $label = $parents_group[key ($parents_group)]['object_type'] . (count($parents_group) > 1 ? ' containers' : ' container');
1432 $fmt_parents = array();
1433 foreach ($parents_group as $parent)
1434 $fmt_parents[] = mkCellA ($parent);
1435 $summary[$label] = implode ('<br>', $fmt_parents);
1436 }
1437 $children = getChildren ($info, 'object');
1438 foreach (groupBy ($children, 'objtype_id') as $objtype_id => $children_group)
1439 {
1440 uasort ($children_group, 'compare_name');
1441 $fmt_children = array();
1442 foreach ($children_group as $child)
1443 $fmt_children[] = mkCellA ($child);
1444 $summary["Contains " . mb_strtolower(decodeObjectType ($objtype_id))] = implode ('<br>', $fmt_children);
1445 }
1446 if ($info['has_problems'] == 'yes')
1447 $summary[] = array ('<tr><td colspan=2 class=msg_error>Has problems</td></tr>');
1448 foreach (getAttrValuesSorted ($object_id) as $record)
1449 if
1450 (
1451 $record['value'] != '' &&
1452 permitted (NULL, NULL, NULL, array (array ('tag' => '$attr_' . $record['id'])))
1453 )
1454 $summary['{sticker}' . $record['name']] = formatAttributeValue ($record, $info['objtype_id']);
1455 $summary[] = array (getOutputOf ('printTagTRs',
1456 $info,
1457 makeHref
1458 (
1459 array
1460 (
1461 'page'=>'depot',
1462 'tab'=>'default',
1463 'andor' => 'and',
1464 'cfe' => '{$typeid_' . $info['objtype_id'] . '}',
1465 )
1466 )."&"
1467 ));
1468 renderEntitySummary ($info, 'summary', $summary);
1469
1470 if ($info['comment'] != '')
1471 {
1472 startPortlet ('Comment');
1473 echo '<div class=commentblock>' . string_insert_hrefs ($info['comment']) . '</div>';
1474 finishPortlet ();
1475 }
1476
1477 $logrecords = getLogRecordsForObject ($_REQUEST['object_id']);
1478 if (count ($logrecords))
1479 {
1480 startPortlet ('log records');
1481 echo "<table cellspacing=0 cellpadding=5 align=center class=widetable width='100%'>";
1482 $order = 'odd';
1483 foreach ($logrecords as $row)
1484 {
1485 echo "<tr class=row_${order} valign=top>";
1486 echo '<td class=tdleft>' . $row['date'] . '<br>' . $row['user'] . '</td>';
1487 echo '<td class="logentry">' . string_insert_hrefs (htmlspecialchars ($row['content'], ENT_NOQUOTES)) . '</td>';
1488 echo '</tr>';
1489 $order = $nextorder[$order];
1490 }
1491 echo '</table>';
1492 finishPortlet();
1493 }
1494
1495 switchportInfoJS ($object_id); // load JS code to make portnames interactive
1496 renderFilesPortlet ('object', $object_id);
1497
1498 if (count ($info['ports']))
1499 {
1500 startPortlet ('ports and links');
1501 $hl_port_id = 0;
1502 if (isset ($_REQUEST['hl_port_id']))
1503 {
1504 genericAssertion ('hl_port_id', 'natural');
1505 $hl_port_id = $_REQUEST['hl_port_id'];
1506 addAutoScrollScript ("port-$hl_port_id");
1507 }
1508 echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>";
1509 callHook ('renderObjectPortHeaderRow');
1510 foreach ($info['ports'] as $port)
1511 callHook ('renderObjectPortRow', $port, ($hl_port_id == $port['id']));
1512 if (permitted (NULL, 'ports', 'set_reserve_comment'))
1513 addJSInternal ('js/inplace-edit.js');
1514 echo "</table><br>";
1515 finishPortlet();
1516 }
1517
1518 if (count ($info['ipv4']) + count ($info['ipv6']))
1519 {
1520 startPortlet ('IP addresses');
1521 echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>\n";
1522 if ('yes' == $ext_ipv4_view = getConfigVar ('EXT_IPV4_VIEW'))
1523 echo "<tr class=tdleft><th>OS interface</th><th>IP address</th><th>network</th><th>routed by</th><th>peers</th></tr>\n";
1524 else
1525 echo "<tr class=tdleft><th>OS interface</th><th>IP address</th><th>peers</th></tr>\n";
1526
1527 // group IP allocations by interface name instead of address family
1528 $allocs_by_iface = array();
1529 foreach (array ('ipv4', 'ipv6') as $ip_v)
1530 foreach ($info[$ip_v] as $ip_bin => $alloc)
1531 $allocs_by_iface[$alloc['osif']][$ip_bin] = $alloc;
1532
1533 // sort allocs array by portnames
1534 foreach (sortPortList ($allocs_by_iface) as $iface_name => $alloclist)
1535 {
1536 $is_first_row = TRUE;
1537 foreach ($alloclist as $alloc)
1538 {
1539 $rendered_alloc = callHook ('getRenderedAlloc', $object_id, $alloc);
1540 echo "<tr class='${rendered_alloc['tr_class']}' valign=top>";
1541
1542 // display iface name, same values are grouped into single cell
1543 if ($is_first_row)
1544 {
1545 $rowspan = count ($alloclist) > 1 ? 'rowspan="' . count ($alloclist) . '"' : '';
1546 echo "<td class=tdleft $rowspan>" . $iface_name . $rendered_alloc['td_name_suffix'] . "</td>";
1547 $is_first_row = FALSE;
1548 }
1549 echo $rendered_alloc['td_ip'];
1550 if ($ext_ipv4_view == 'yes')
1551 {
1552 echo $rendered_alloc['td_network'];
1553 echo $rendered_alloc['td_routed_by'];
1554 }
1555 echo $rendered_alloc['td_peers'];
1556
1557 echo "</tr>\n";
1558 }
1559 }
1560 echo "</table><br>\n";
1561 finishPortlet();
1562 }
1563
1564 $forwards = $info['nat4'];
1565 if (count ($forwards['in']) || count ($forwards['out']))
1566 {
1567 startPortlet('NATv4');
1568
1569 if (count($forwards['out']))
1570 {
1571
1572 echo "<h3>locally performed NAT</h3>";
1573
1574 echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center'>\n";
1575 echo "<tr><th>Proto</th><th>Match endpoint</th><th>Translate to</th><th>Target object</th><th>Rule comment</th></tr>\n";
1576
1577 foreach ($forwards['out'] as $pf)
1578 {
1579 $class = 'trerror';
1580 $osif = '';
1581 $localip_bin = ip_parse ($pf['localip']);
1582 if (array_key_exists ($localip_bin, $info['ipv4']))
1583 {
1584 $class = $info['ipv4'][$localip_bin]['addrinfo']['class'];
1585 $osif = $info['ipv4'][$localip_bin]['osif'] . ': ';
1586 }
1587 echo "<tr class='$class'>";
1588 echo "<td class=tdleft>${pf['proto']}</td><td class=tdleft>${osif}" . getRenderedIPPortPair ($pf['localip'], $pf['localport']) . "</td>";
1589 echo "<td class=tdleft>" . getRenderedIPPortPair ($pf['remoteip'], $pf['remoteport']) . "</td>";
1590 $address = getIPAddress (ip4_parse ($pf['remoteip']));
1591 echo "<td class='description tdleft'>";
1592 if (count ($address['allocs']))
1593 foreach($address['allocs'] as $bond)
1594 echo mkA ("${bond['object_name']}(${bond['name']})", 'object', $bond['object_id']) . ' ';
1595 elseif ($pf['remote_addr_name'] != '')
1596 echo '(' . $pf['remote_addr_name'] . ')';
1597 echo "</td><td class='description tdleft'>${pf['description']}</td></tr>";
1598 }
1599 echo "</table><br><br>";
1600 }
1601 if (count($forwards['in']))
1602 {
1603 echo "<h3>arriving NAT connections</h3>";
1604 echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center'>\n";
1605 echo "<tr><th>Matched endpoint</th><th>Source object</th><th>Translated to</th><th>Rule comment</th></tr>\n";
1606 foreach ($forwards['in'] as $pf)
1607 {
1608 echo "<tr>";
1609 echo "<td class=tdleft>${pf['proto']}/" . getRenderedIPPortPair ($pf['localip'], $pf['localport']) . "</td>";
1610 echo '<td class="description tdleft">' . mkA ($pf['object_name'], 'object', $pf['object_id']);
1611 echo "</td><td class=tdleft>" . getRenderedIPPortPair ($pf['remoteip'], $pf['remoteport']) . "</td>";
1612 echo "<td class='description tdleft'>${pf['description']}</td></tr>";
1613 }
1614 echo "</table><br><br>";
1615 }
1616 finishPortlet();
1617 }
1618
1619 renderSLBTriplets2 ($info);
1620 renderSLBTriplets ($info);
1621 echo "</td>\n";
1622
1623 // A conditional right column with the rackspace portlet only.
1624 if
1625 (
1626 ! in_array ($info['objtype_id'], $virtual_obj_types) &&
1627 count ($rack_ids = getResidentRackIDs ($object_id))
1628 )
1629 {
1630 echo '<td class=pcright>';
1631 startPortlet ('rackspace allocation');
1632 foreach ($rack_ids as $rack_id)
1633 renderRack ($rack_id, $object_id);
1634 echo '<br>';
1635 finishPortlet();
1636 echo '</td>';
1637 }
1638 echo "<tr></table>\n";
1639 }
1640
1641 function renderRackMultiSelect ($sname, $racks, $selected)
1642 {
1643 // Transform the given flat list into a list of groups, each representing a rack row.
1644 $rdata = array();
1645 foreach ($racks as $rack)
1646 {
1647 if ('' != $trail = getLocationTrail ($rack['location_id'], FALSE))
1648 $row_name = $trail . ' : ' . $rack['row_name'];
1649 else
1650 $row_name = $rack['row_name'];
1651 $rdata[$row_name][$rack['id']] = $rack['name'];
1652 }
1653 echo "<select name=${sname} multiple size=" . getConfigVar ('MAXSELSIZE') . " onchange='getElementsByName(\"updateObjectAllocation\")[0].submit()'>\n";
1654 $row_names = array_keys ($rdata);
1655 natsort ($row_names);
1656 foreach ($row_names as $optgroup)
1657 {
1658 echo "<optgroup label='${optgroup}'>";
1659 foreach ($rdata[$optgroup] as $rack_id => $rack_name)
1660 {
1661 echo "<option value=${rack_id}";
1662 if (!(array_search ($rack_id, $selected) === FALSE))
1663 echo ' selected';
1664 echo">${rack_name}</option>\n";
1665 }
1666 }
1667 echo "</select>\n";
1668 }
1669
1670 // This function renders a form for port edition.
1671 function renderPortsForObject ($object_id)
1672 {
1673 $prefs = getPortListPrefs();
1674 function printNewItemTR ($prefs)
1675 {
1676 printOpFormIntro ('addPort');
1677 echo "<tr><td>";
1678 printImageHREF ('add', 'add a port', TRUE);
1679 echo "</td><td class='tdleft'><input type=text size=16 name=port_name></td>\n";
1680 echo "<td><input type=text name=port_label></td><td>";
1681 printNiftySelect (getNewPortTypeOptions(), array ('name' => 'port_type_id'), $prefs['selected']);
1682 echo "<td><input type=text name=port_l2address size=17 maxlength=59></td>\n";
1683 echo "<td colspan=4>&nbsp;</td><td>";
1684 printImageHREF ('add', 'add a port', TRUE);
1685 echo "</td></tr></form>";
1686 }
1687
1688 function printBulkForm ($prefs)
1689 {
1690 echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>\n";
1691 echo "<tr><th>&nbsp;</th><th class=tdleft>Local name</th><th class=tdleft>Visible label</th><th class=tdleft>Interface</th><th class=tdleft>Start Number</th>";
1692 echo "<th class=tdleft>Count</th><th>&nbsp;</th></tr>\n";
1693 printOpFormIntro ('addBulkPorts');
1694 echo "<tr><td>";
1695 printImageHREF ('add', 'add ports', TRUE);
1696 echo "</td><td><input type=text size=8 name=port_name></td>\n";
1697 echo "<td><input type=text name=port_label></td><td>";
1698 printNiftySelect (getNewPortTypeOptions(), array ('name' => 'port_type_id'), $prefs['selected']);
1699 echo "<td><input type=text name=port_numbering_start size=3 maxlength=3></td>\n";
1700 echo "<td><input type=text name=port_numbering_count size=3 maxlength=3></td>\n";
1701 echo "<td>&nbsp;</td><td>";
1702 printImageHREF ('add', 'add ports', TRUE);
1703 echo "</td></tr></form>";
1704 echo "</table><br>\n";
1705 }
1706
1707 startPortlet ('Ports and interfaces');
1708 $object = spotEntity ('object', $object_id);
1709 amplifyCell ($object);
1710 if (getConfigVar ('ADDNEW_AT_TOP') == 'yes' && getConfigVar ('ENABLE_BULKPORT_FORM') == 'yes')
1711 printBulkForm ($prefs);
1712
1713 echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>\n";
1714 echo "<tr><th>&nbsp;</th><th class=tdleft>Local name</th><th class=tdleft>Visible label</th><th class=tdleft>Interface</th><th class=tdleft>L2 address</th>";
1715 echo "<th class=tdcenter colspan=2>Remote object and port</th><th>Cable ID</th><th class=tdcenter>(Un)link or (un)reserve</th><th>&nbsp;</th></tr>\n";
1716 if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
1717 printNewItemTR ($prefs);
1718
1719 // clear ports link
1720 echo getOpLink (array ('op'=>'deleteAll'), 'Clear port list', 'clear', '', 'need-confirmation');
1721
1722 // rename ports link
1723 $n_ports_to_rename = 0;
1724 foreach ($object['ports'] as $port)
1725 if ($port['name'] != shortenPortName ($port['name'], $object['id']))
1726 $n_ports_to_rename++;
1727 if ($n_ports_to_rename)
1728 echo '<p>' . getOpLink (array ('op'=>'renameAll'), "Auto-rename $n_ports_to_rename port(s)", 'recalc', 'Use RackTables naming convention for this device type') . '</p>';
1729
1730 if (isset ($_REQUEST['hl_port_id']))
1731 {
1732 genericAssertion ('hl_port_id', 'natural');
1733 $hl_port_id = intval ($_REQUEST['hl_port_id']);
1734 addAutoScrollScript ("port-$hl_port_id");
1735 }
1736 switchportInfoJS ($object_id); // load JS code to make portnames interactive
1737 foreach ($object['ports'] as $port)
1738 {
1739 // highlight port name with yellow if its name is not canonical
1740 $canon_pn = shortenPortName ($port['name'], $port['object_id']);
1741 $name_class = $canon_pn == $port['name'] ? '' : 'trwarning';
1742
1743 $tr_class = isset ($hl_port_id) && $hl_port_id == $port['id'] ? 'class="highlight"' : '';
1744 printOpFormIntro ('editPort', array ('port_id' => $port['id']));
1745 echo "<tr $tr_class><td><a name='port-${port['id']}' href='".makeHrefProcess(array('op'=>'delPort', 'port_id'=>$port['id']))."'>";
1746 printImageHREF ('delete', 'Unlink and Delete this port');
1747 echo "</a></td>\n";
1748 $a_class = isEthernetPort ($port) ? 'port-menu' : '';
1749 echo "<td class='tdleft $name_class' NOWRAP><input type=text name=name class='interactive-portname $a_class' value='${port['name']}' size=16></td>";
1750 echo "<td><input type=text name=label value='${port['label']}'></td>";
1751 echo '<td class=tdleft>';
1752 if ($port['iif_id'] != 1)
1753 echo '<label>' . $port['iif_name'] . ' ';
1754 $port_type_opts = array();
1755 if (! $port['linked'])
1756 $port_type_opts = getUnlinkedPortTypeOptions ($port['iif_id']);
1757 else
1758 foreach (getExistingPortTypeOptions ($port) as $oif_id => $opt_str)
1759 $port_type_opts[$port['iif_name']][$port['iif_id'] . '-' . $oif_id] = $opt_str;
1760 printNiftySelect ($port_type_opts, array ('name' => 'port_type_id'), $port['iif_id'] . '-' . $port['oif_id']);
1761 if ($port['iif_id'] != 1)
1762 echo '</label>';
1763 echo '</td>';
1764
1765 // 17 is the full notation length of a MAC address, 23 -- of a WWN address and 59 -- of an IPoIB address.
1766 echo "<td><input type=text name=l2address value='${port['l2address']}' size=17 maxlength=59></td>\n";
1767 if ($port['remote_object_id'])
1768 {
1769 $dname = formatObjectDisplayedName ($port['remote_object_name'], $port['remote_object_tid']);
1770 echo "<td class=tdleft>" .
1771 formatLoggedSpan ($port['last_log'], formatPortLink ($port['remote_object_id'], $dname, $port['remote_id'], NULL)) .
1772 "</td>";
1773 echo "<td class=tdleft> " . formatLoggedSpan ($port['last_log'], $port['remote_name'], 'underline') .
1774 "<input type=hidden name=reservation_comment value=''></td>";
1775 echo "<td><input type=text name=cable value='${port['cableid']}'></td>";
1776 echo "<td class=tdcenter>";
1777 echo getOpLink (array('op'=>'unlinkPort', 'port_id'=>$port['id'], ), '', 'cut', 'Unlink this port');
1778 echo "</td>";
1779 }
1780 elseif ($port['reservation_comment'] != '')
1781 {
1782 echo "<td>" . formatLoggedSpan ($port['last_log'], 'Reserved:', 'strong underline') . "</td>";
1783 echo "<td><input type=text name=reservation_comment value='${port['reservation_comment']}'></td>";
1784 echo "<td></td>";
1785 echo "<td class=tdcenter>";
1786 echo getOpLink (array('op'=>'useup', 'port_id'=>$port['id']), '', 'clear', 'Use up this port');
1787 echo "</td>";
1788 }
1789 else
1790 {
1791 $in_rack = getConfigVar ('NEAREST_RACKS_CHECKBOX');
1792 echo "<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td class=tdcenter>";
1793 echo getPopupLink ('portlist', array ('port' => $port['id'], 'in_rack' => ($in_rack == "yes" ? "on" : "")), 'findlink', 'plug', '', 'Link this port');
1794 echo " <input type=text name=reservation_comment></td>\n";
1795 }
1796 echo "<td>";
1797 printImageHREF ('save', 'Save changes', TRUE);
1798 echo "</td></form></tr>\n";
1799 }
1800 if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
1801 printNewItemTR ($prefs);
1802 echo "</table><br>\n";
1803 if (getConfigVar ('ADDNEW_AT_TOP') != 'yes' && getConfigVar ('ENABLE_BULKPORT_FORM') == 'yes')
1804 printBulkForm ($prefs);
1805 finishPortlet();
1806
1807 if (getConfigVar ('ENABLE_MULTIPORT_FORM') == 'yes' && permitted (NULL, NULL, 'addMultiPorts'))
1808 {
1809 startPortlet ('Add/update multiple ports');
1810 printOpFormIntro ('addMultiPorts');
1811 $formats = array
1812 (
1813 'ssv1' => 'SSV:<interface name> [<MAC address>]',
1814 );
1815 echo 'Format: ' . getSelect ($formats, array ('name' => 'format'), 'ssv1') . ' ';
1816 echo 'Default port type: ';
1817 printNiftySelect (getNewPortTypeOptions(), array ('name' => 'port_type'), $prefs['selected']);
1818 echo "<input type=submit value='Parse output'><br>\n";
1819 echo "<textarea name=input cols=100 rows=50></textarea><br>\n";
1820 echo '</form>';
1821 finishPortlet();
1822 }
1823 }
1824
1825 function renderIPForObject ($object_id)
1826 {
1827 function printNewItemTR ($default_type, $object_id)
1828 {
1829 global $aat;
1830
1831 includeJQueryUIJS();
1832 includeJQueryUICSS();
1833
1834 // Heredoc, not nowdoc!
1835 addJSText (<<<"JSEND"
1836 $(document).ready( function() {
1837 $('[name="bond_name"]').autocomplete({
1838 source: "?module=ajax&ac=autocomplete&realm=bond_name&object_id=$object_id",
1839 //minLength: 3,
1840 focus: function(event, ui) {
1841 if( ui.item.value == '' )
1842 event.preventDefault();
1843 },
1844 select: function(event, ui) {
1845 if( ui.item.value == '' )
1846 event.preventDefault();
1847 }
1848 });
1849 });
1850 JSEND
1851 ); // addJSText()
1852 printOpFormIntro ('add');
1853 echo "<tr><td>"; // left btn
1854 printImageHREF ('add', 'allocate', TRUE);
1855 echo "</td>";
1856 echo "<td class=tdleft><input type='text' size='10' name='bond_name'></td>\n"; // if-name
1857 echo "<td class=tdleft><input type=text name='ip'></td>\n"; // IP
1858 if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
1859 echo "<td colspan=2>&nbsp;</td>"; // network, routed by
1860 echo '<td>';
1861 printSelect ($aat, array ('name' => 'bond_type'), $default_type); // type
1862 echo "</td><td>&nbsp;</td><td>"; // misc
1863 printImageHREF ('add', 'allocate', TRUE); // right btn
1864 echo "</td></tr></form>";
1865 }
1866 global $aat;
1867
1868 startPortlet ('Allocations');
1869 echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'><tr>\n";
1870 echo '<th>&nbsp;</th>';
1871 echo '<th>OS interface</th>';
1872 echo '<th>IP address</th>';
1873 if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
1874 {
1875 echo '<th>network</th>';
1876 echo '<th>routed by</th>';
1877 }
1878 echo '<th>type</th>';
1879 echo '<th>misc</th>';
1880 echo '<th>&nbsp;</th>';
1881 echo '</tr>';
1882
1883 $alloc_list = ''; // most of the output is stored here
1884 $used_alloc_types = array();
1885 $ext_ipv4_view = getConfigVar ('EXT_IPV4_VIEW');
1886 foreach (getObjectIPAllocations ($object_id) as $alloc)
1887 {
1888 if (! isset ($used_alloc_types[$alloc['type']]))
1889 $used_alloc_types[$alloc['type']] = 0;
1890 $used_alloc_types[$alloc['type']]++;
1891
1892 $rendered_alloc = callHook ('getRenderedAlloc', $object_id, $alloc);
1893 $alloc_list .= getOutputOf ('printOpFormIntro', 'upd', array ('ip' => $alloc['addrinfo']['ip']));
1894 $alloc_list .= "<tr class='${rendered_alloc['tr_class']}' valign=top>";
1895
1896 $alloc_list .= "<td>" . getOpLink (array ('op' => 'del', 'ip' => $alloc['addrinfo']['ip']), '', 'delete', 'Delete this IP address') . "</td>";
1897 $alloc_list .= "<td class=tdleft><input type='text' name='bond_name' value='${alloc['osif']}' size=10>" . $rendered_alloc['td_name_suffix'] . "</td>";
1898 $alloc_list .= $rendered_alloc['td_ip'];
1899 if ($ext_ipv4_view == 'yes')
1900 {
1901 $alloc_list .= $rendered_alloc['td_network'];
1902 $alloc_list .= $rendered_alloc['td_routed_by'];
1903 }
1904 $alloc_list .= '<td>' . getSelect ($aat, array ('name' => 'bond_type'), $alloc['type']) . "</td>";
1905 $alloc_list .= $rendered_alloc['td_peers'];
1906 $alloc_list .= "<td>" .getImageHREF ('save', 'Save changes', TRUE) . "</td>";
1907
1908 $alloc_list .= "</form></tr>\n";
1909 }
1910 asort ($used_alloc_types, SORT_NUMERIC);
1911 $most_popular_type = ! count ($used_alloc_types) ? 'regular' : array_last (array_keys ($used_alloc_types));
1912
1913 if ($list_on_top = (getConfigVar ('ADDNEW_AT_TOP') != 'yes'))
1914 echo $alloc_list;
1915 printNewItemTR ($most_popular_type, $object_id);
1916 if (! $list_on_top)
1917 echo $alloc_list;
1918
1919 echo "</table><br>\n";
1920 finishPortlet();
1921 }
1922
1923 // This function is deprecated. Do not rely on its internals,
1924 // it will probably be removed in the next major relese.
1925 // Use new showError, showWarning, showSuccess functions.
1926 // Log array is stored in global $log_messages. Its format is simple: plain ordered array
1927 // with values having keys 'c' (both message code and severity) and 'a' (sprintf arguments array)
1928 function showMessageOrError ()
1929 {
1930 global $log_messages;
1931
1932 startSession();
1933 if (isset ($_SESSION['log']))
1934 {
1935 $log_messages = array_merge ($_SESSION['log'], $log_messages);
1936 unset ($_SESSION['log']);
1937 }
1938 session_commit();
1939
1940 if (empty ($log_messages))
1941 return;
1942 $msginfo = array
1943 (
1944 // records 0~99 with success messages
1945 0 => array ('code' => 'success', 'format' => '%s'),
1946 5 => array ('code' => 'success', 'format' => 'added record "%s" successfully'),
1947 6 => array ('code' => 'success', 'format' => 'updated record "%s" successfully'),
1948 7 => array ('code' => 'success', 'format' => 'deleted record "%s" successfully'),
1949 8 => array ('code' => 'success', 'format' => 'Port %s successfully linked with %s'),
1950 10 => array ('code' => 'success', 'format' => 'Added %u port(s), updated %u port(s), encountered %u error(s).'),
1951 21 => array ('code' => 'success', 'format' => 'Generation complete'),
1952 26 => array ('code' => 'success', 'format' => 'updated %u record(s) successfully'),
1953 37 => array ('code' => 'success', 'format' => 'added %u record(s) successfully'),
1954 38 => array ('code' => 'success', 'format' => 'removed %u record(s) successfully'),
1955 43 => array ('code' => 'success', 'format' => 'Saved successfully.'),
1956 44 => array ('code' => 'success', 'format' => '%s failure(s) and %s successfull change(s).'),
1957 48 => array ('code' => 'success', 'format' => 'added a record successfully'),
1958 49 => array ('code' => 'success', 'format' => 'deleted a record successfully'),
1959 51 => array ('code' => 'success', 'format' => 'updated a record successfully'),
1960 57 => array ('code' => 'success', 'format' => 'Reset complete'),
1961 58 => array ('code' => 'success', 'format' => '%u device(s) unmounted successfully'),
1962 63 => array ('code' => 'success', 'format' => '%u change request(s) have been processed'),
1963 67 => array ('code' => 'success', 'format' => "Tag rolling done, %u object(s) involved"),
1964 71 => array ('code' => 'success', 'format' => 'File "%s" was linked successfully'),
1965 72 => array ('code' => 'success', 'format' => 'File was unlinked successfully'),
1966 82 => array ('code' => 'success', 'format' => "Bulk port creation was successful. %u port(s) created, %u failed"),
1967 87 => array ('code' => 'success', 'format' => '802.1Q recalculate: %d port(s) changed on %d switch(es)'),
1968 // records 100~199 with fatal error messages
1969 100 => array ('code' => 'error', 'format' => '%s'),
1970 109 => array ('code' => 'error', 'format' => 'failed updating a record'),
1971 131 => array ('code' => 'error', 'format' => 'invalid format requested'),
1972 141 => array ('code' => 'error', 'format' => 'Encountered %u error(s), updated %u record(s)'),
1973 149 => array ('code' => 'error', 'format' => 'Turing test failed'),
1974 150 => array ('code' => 'error', 'format' => 'Can only change password under DB authentication.'),
1975 151 => array ('code' => 'error', 'format' => 'Old password doesn\'t match.'),
1976 152 => array ('code' => 'error', 'format' => 'New passwords don\'t match.'),
1977 154 => array ('code' => 'error', 'format' => "Verification error: %s"),
1978 155 => array ('code' => 'error', 'format' => 'Save failed.'),
1979 159 => array ('code' => 'error', 'format' => 'Permission denied moving port %s from VLAN%u to VLAN%u'),
1980 170 => array ('code' => 'error', 'format' => 'There is no network for IP address "%s"'),
1981 172 => array ('code' => 'error', 'format' => 'Malformed request'),
1982 179 => array ('code' => 'error', 'format' => 'Expired form has been declined.'),
1983 191 => array ('code' => 'error', 'format' => "deploy was blocked due to conflicting configuration versions"),
1984
1985 // records 200~299 with warnings
1986 200 => array ('code' => 'warning', 'format' => '%s'),
1987 201 => array ('code' => 'warning', 'format' => 'nothing happened...'),
1988 206 => array ('code' => 'warning', 'format' => '%s is not empty'),
1989 207 => array ('code' => 'warning', 'format' => 'File upload failed, error: %s'),
1990
1991 // records 300~399 with notices
1992 300 => array ('code' => 'neutral', 'format' => '%s'),
1993
1994 );
1995 // Handle the arguments. Is there any better way to do it?
1996 foreach ($log_messages as $record)
1997 {
1998 if (! isset ($record['c']) || ! isset ($msginfo[$record['c']]))
1999 {
2000 $prefix = isset ($record['c']) ? $record['c'] . ': ' : '';
2001 echo "<div class=msg_neutral>(${prefix}this message was lost)</div>";
2002 continue;
2003 }
2004 if (isset ($record['a']))
2005 switch (count ($record['a']))
2006 {
2007 case 1:
2008 $msgtext = sprintf
2009 (
2010 $msginfo[$record['c']]['format'],
2011 $record['a'][0]
2012 );
2013 break;
2014 case 2:
2015 $msgtext = sprintf
2016 (
2017 $msginfo[$record['c']]['format'],
2018 $record['a'][0],
2019 $record['a'][1]
2020 );
2021 break;
2022 case 3:
2023 $msgtext = sprintf
2024 (
2025 $msginfo[$record['c']]['format'],
2026 $record['a'][0],
2027 $record['a'][1],
2028 $record['a'][2]
2029 );
2030 break;
2031 case 4:
2032 default:
2033 $msgtext = sprintf
2034 (
2035 $msginfo[$record['c']]['format'],
2036 $record['a'][0],
2037 $record['a'][1],
2038 $record['a'][2],
2039 $record['a'][3]
2040 );
2041 break;
2042 }
2043 else
2044 $msgtext = $msginfo[$record['c']]['format'];
2045 echo '<div class=msg_' . $msginfo[$record['c']]['code'] . ">${msgtext}</div>";
2046 }
2047 $log_messages = array();
2048 }
2049
2050 // renders two tables: port link status and learned MAC list
2051 function renderPortsInfo($object_id)
2052 {
2053 $breed = detectDeviceBreed ($object_id);
2054 try
2055 {
2056 if (permitted (NULL, NULL, 'get_link_status'))
2057 {
2058 if (validBreedFunction ($breed, 'getportstatus'))
2059 $linkStatus = queryDevice ($object_id, 'getportstatus');
2060 }
2061 else
2062 showWarning ("You don't have permission to view ports link status");
2063
2064 if (permitted (NULL, NULL, 'get_mac_list'))
2065 {
2066 if (validBreedFunction ($breed, 'getmaclist'))
2067 $macList = sortPortList (queryDevice ($object_id, 'getmaclist'));
2068 }
2069 else
2070 showWarning ("You don't have permission to view learned MAC list");
2071 }
2072 catch (RTGatewayError $e)
2073 {
2074 showError ($e->getMessage());
2075 return;
2076 }
2077
2078 global $nextorder;
2079 echo "<table width='100%'><tr>";
2080 if (! empty ($linkStatus))
2081 {
2082 echo "<td valign='top' width='50%'>";
2083 startPortlet('Link status');
2084 echo "<table width='80%' class='widetable' cellspacing=0 cellpadding='5px' align='center'>";
2085 echo '<tr><th>Port</th><th>&nbsp;</th><th>Link status</th><th>Link info</th></tr>';
2086 $statusmap = array
2087 (
2088 'up' => 'link up',
2089 'down' => 'link down',
2090 'disabled' => 'link disabled',
2091 );
2092 $order = 'even';
2093 foreach ($linkStatus as $pn => $link)
2094 {
2095 echo "<tr class='row_$order'>";
2096 $order = $nextorder[$order];
2097 echo '<td>' . $pn . '</td>';
2098 echo '<td>' . getImageHREF (array_fetch ($statusmap, $link['status'], '16x16t')) . '</td>';
2099 echo '<td>' . $link['status'] . '</td>';
2100 $info = '';
2101 if (isset ($link['speed']))
2102 $info .= $link['speed'];
2103 if (isset ($link['duplex']))
2104 {
2105 if ($info != '')
2106 $info .= ', ';
2107 $info .= $link['duplex'];
2108 }
2109 echo '<td>' . $info . '</td>';
2110 echo '</tr>';
2111 }
2112 echo "</table></td>";
2113 finishPortlet();
2114 }
2115
2116 if (! empty ($macList))
2117 {
2118 echo "<td valign='top' width='50%'>";
2119 $rendered_macs = '';
2120 $mac_count = 0;
2121 $rendered_macs .= "<table width='80%' class='widetable' cellspacing=0 cellpadding='5px' align='center'>";
2122 $rendered_macs .= '<tr><th>MAC address</th><th>VLAN</th><th>Port</th></tr>';
2123 $order = 'even';
2124 foreach ($macList as $pn => $list)
2125 {
2126 $order = $nextorder[$order];
2127 foreach ($list as $item)
2128 {
2129 ++$mac_count;
2130 $rendered_macs .= "<tr class='row_$order'>";
2131 $rendered_macs .= '<td style="font-family: monospace">' . $item['mac'] . '</td>';
2132 $rendered_macs .= '<td>' . $item['vid'] . '</td>';
2133 $rendered_macs .= '<td>' . $pn . '</td>';
2134 $rendered_macs .= '</tr>';
2135 }
2136 }
2137 $rendered_macs .= "</table></td>";
2138
2139 startPortlet("Learned MACs ($mac_count)");
2140 echo $rendered_macs;
2141 finishPortlet();
2142 }
2143
2144 echo "</td></tr></table>";
2145 }
2146
2147 function addBulkSelectorJS ($element_id)
2148 {
2149 // Heredoc, not nowdoc!
2150 addJSText (<<<"ENDOFJAVASCRIPT"
2151 $(function () {
2152 $("#{$element_id} tbody").selectable({
2153 filter: 'td.atom',
2154 cancel: 'th,a',
2155 stop: function () {
2156 $(".ui-selected input:enabled", this).each(function () {
2157 this.checked = !this.checked
2158 });
2159 }
2160 });
2161 });
2162 ENDOFJAVASCRIPT
2163 ); // addJSText()
2164 }
2165
2166 // An object can be mounted onto free atoms only, that is, if any record for an atom
2167 // already exists in RackSpace, it cannot be used for mounting.
2168 function renderRackSpaceForObject ($object_id)
2169 {
2170 // Always process occupied racks plus racks chosen by user. First get racks with
2171 // already allocated rackspace...
2172 $workingRacksData = array();
2173 foreach (getResidentRackIDs ($object_id) as $rack_id)
2174 {
2175 $rackData = spotEntity ('rack', $rack_id);
2176 amplifyCell ($rackData);
2177 $workingRacksData[$rack_id] = $rackData;
2178 }
2179 // ...and then add those chosen by user (if any).
2180 if (isset($_REQUEST['rackmulti']))
2181 foreach ($_REQUEST['rackmulti'] as $cand_id)
2182 if (!isset ($workingRacksData[$cand_id]))
2183 {
2184 $rackData = spotEntity ('rack', $cand_id);
2185 amplifyCell ($rackData);
2186 $workingRacksData[$cand_id] = $rackData;
2187 }
2188
2189 // Get a list of rack ids that are parents of this object.
2190 $object = spotEntity ('object', $object_id);
2191 $parentRacks = reduceSubarraysToColumn (getParents ($object, 'rack'), 'id');
2192
2193 // Main layout starts.
2194 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0><tr>";
2195
2196 // Left portlet with rack list.
2197 echo "<td class=pcleft height='1%'>";
2198 startPortlet ('Racks');
2199 $allRacksData = listCells ('rack');
2200
2201 // filter rack list to match only racks having common tags with the object (reducing $allRacksData)
2202 if (! isset ($_REQUEST['show_all_racks']) && getConfigVar ('FILTER_RACKLIST_BY_TAGS') == 'yes')
2203 {
2204 $matching_racks = array();
2205 $matched_tags = array();
2206 foreach ($allRacksData as $rack)
2207 {
2208 $tag_chain = array_merge ($rack['etags'], $rack['itags']);
2209 foreach ($object['etags'] as $tag)
2210 if (tagOnChain ($tag, $tag_chain))
2211 {
2212 $matching_racks[$rack['id']] = $rack;
2213 $matched_tags[$tag['id']] = $tag;
2214 break;
2215 }
2216 }
2217 // add current object's racks even if they dont match filter
2218 foreach ($workingRacksData as $rack_id => $rack)
2219 if (! isset ($matching_racks[$rack_id]))
2220 $matching_racks[$rack_id] = $rack;
2221 // if matching racks found, and rack list is reduced, show 'show all' link
2222 if (count ($matching_racks) && count ($matching_racks) != count ($allRacksData))
2223 {
2224 $tmp = array();
2225 foreach ($matched_tags as $tag)
2226 $tmp[] = '{' . $tag['tag'] . '}';
2227 $filter_text = implode (' or ', $tmp);
2228 $href_show_all = trim($_SERVER['REQUEST_URI'], '&');
2229 $href_show_all .= htmlspecialchars('&show_all_racks=1');
2230 echo "(filtered by <span class='filter-text'>$filter_text</span>, <a href='$href_show_all'>show all</a>)<p>";
2231 $allRacksData = $matching_racks;
2232 }
2233 }
2234
2235 if (count ($allRacksData) <= getConfigVar ('RACK_PRESELECT_THRESHOLD'))
2236 foreach ($allRacksData as $rack)
2237 if (!array_key_exists ($rack['id'], $workingRacksData))
2238 {
2239 amplifyCell ($rack);
2240 $workingRacksData[$rack['id']] = $rack;
2241 }
2242 foreach (array_keys ($workingRacksData) as $rackId)
2243 applyObjectMountMask ($workingRacksData[$rackId], $object_id);
2244 printOpFormIntro ('updateObjectAllocation');
2245 renderRackMultiSelect ('rackmulti[]', $allRacksData, array_keys ($workingRacksData));
2246 echo "<br><br>";
2247 finishPortlet();
2248 echo "</td>";
2249
2250 // Middle portlet with comment and submit.
2251 echo "<td class=pcleft>";
2252 startPortlet ('Comment (for Rackspace History)');
2253 echo '<textarea name=comment rows=10 cols=40></textarea><br><br>';
2254 echo "<input type=submit value='Save' name=got_atoms>\n";
2255 echo "<br><br>";
2256 finishPortlet();
2257 echo "</td>";
2258
2259 // Right portlet with rendered racks. If this form submit is not final,
2260 // the former state of the grid needs to make it to the current form.
2261 echo "<td class=pcright rowspan=2 height='1%'>";
2262 startPortlet ('Working copy');
2263 includeJQueryUIJS();
2264 addJSInternal ('js/racktables.js');
2265 echo '<table border=0 cellspacing=10 align=center><tr>';
2266 foreach ($workingRacksData as $rack_id => $rackData)
2267 {
2268 $table_id = "selectableRack_{$rack_id}";
2269 addBulkSelectorJS ($table_id);
2270 $is_ro = !rackModificationPermitted ($rackData, 'updateObjectAllocation');
2271 // Order is important here: only original allocation is highlighted.
2272 highlightObject ($rackData, $object_id);
2273 markupAtomGrid ($rackData, 'T');
2274 // If an HTTP form has been processed, discard user input and show new database
2275 // contents.
2276 if (!$is_ro && isset ($_REQUEST['rackmulti'][0])) // is an update
2277 mergeGridFormToRack ($rackData);
2278 echo '<td valign=bottom>';
2279 echo '<center><h2 style="margin:0px">';
2280 echo mkA ($rackData['row_name'], 'row', $rackData['row_id']);
2281 echo ' : ';
2282 echo mkA ($rackData['name'], 'rack', $rackData['id']);
2283 echo '</h2>';
2284 $read_only_text = $is_ro ? '(read-only)' : '&nbsp;';
2285 echo "<p style='color: red; margin-top:0px'>${read_only_text}</p>\n";
2286 echo "<table class=rack id={$table_id} border=0 cellspacing=0 cellpadding=1>\n";
2287 echo "<tr><th width='10%'>&nbsp;</th>";
2288 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '0', ${rackData['height']})\">Front</a></th>";
2289 echo "<th width='50%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '1', ${rackData['height']})\">Interior</a></th>";
2290 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '2', ${rackData['height']})\">Back</a></th></tr>\n";
2291 renderAtomGrid ($rackData, $is_ro);
2292 echo "<tr><th width='10%'>&nbsp;</th>";
2293 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '0', ${rackData['height']})\">Front</a></th>";
2294 echo "<th width='50%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '1', ${rackData['height']})\">Interior</a></th>";
2295 echo "<th width='20%'><a href='javascript:;' onclick=\"toggleColumnOfAtoms('${rack_id}', '2', ${rackData['height']})\">Back</a></th></tr>\n";
2296 echo "</table>\n<br>\n";
2297 // Determine zero-u checkbox status.
2298 // If form has been submitted, use form data, otherwise use DB data.
2299 if (!$is_ro && isset($_REQUEST['op']))
2300 $checked = isset($_REQUEST['zerou_'.$rack_id]) ? 'checked' : '';
2301 else
2302 $checked = in_array($rack_id, $parentRacks) ? 'checked' : '';
2303 $disabled_text = $is_ro ? ' disabled' : '';
2304 echo "<label>Zero-U: <input type=checkbox ${checked} name=zerou_${rack_id}${disabled_text}></label>\n<br><br>\n";
2305 echo "<input type='button' onclick='uncheckAllAtoms({$rack_id}, {$rackData['height']});' value='Uncheck all'>\n";
2306 echo '</center></td>';
2307 }
2308 echo "</tr></table>";
2309 finishPortlet();
2310 echo "</td>\n";
2311
2312 echo "</form>\n";
2313 echo "</tr></table>\n";
2314 }
2315
2316 function renderMolecule ($mdata, $object_id)
2317 {
2318 // sort data out
2319 $rackpack = array();
2320 global $loclist;
2321 foreach ($mdata as $rua)
2322 {
2323 $rack_id = $rua['rack_id'];
2324 $unit_no = $rua['unit_no'];
2325 $atom = $rua['atom'];
2326 if (!isset ($rackpack[$rack_id]))
2327 {
2328 $rackData = spotEntity ('rack', $rack_id);
2329 amplifyCell ($rackData);
2330 for ($i = $rackData['height']; $i > 0; $i--)
2331 for ($locidx = 0; $locidx < 3; $locidx++)
2332 $rackData[$i][$locidx]['state'] = 'F';
2333 $rackpack[$rack_id] = $rackData;
2334 }
2335 $rackpack[$rack_id][$unit_no][$loclist[$atom]]['state'] = 'T';
2336 $rackpack[$rack_id][$unit_no][$loclist[$atom]]['object_id'] = $object_id;
2337 }
2338 // Now there are some racks to render.
2339 foreach ($rackpack as $rackData)
2340 {
2341 echo "<table class=molecule cellspacing=0>\n";
2342 echo "<caption>${rackData['name']}</caption>\n";
2343 echo "<tr><th width='10%'>&nbsp;</th><th width='20%'>Front</th><th width='50%'>Interior</th><th width='20%'>Back</th></tr>\n";
2344 $reverse = considerConfiguredConstraint ($rackData, 'REVERSED_RACKS_LISTSRC');
2345 for ($i = $rackData['height']; $i > 0; $i--)
2346 {
2347 echo '<tr><th>' . inverseRackUnit ($rackData['height'], $i, $reverse) . '</th>';
2348 for ($locidx = 0; $locidx < 3; $locidx++)
2349 {
2350 $state = $rackData[$i][$locidx]['state'];
2351 echo "<td class='atom state_${state}'>&nbsp;</td>\n";
2352 }
2353 echo "</tr>\n";
2354 }
2355 echo "</table>\n";
2356 }
2357 }
2358
2359 function renderDepot ()
2360 {
2361 global $pageno, $nextorder;
2362 $cellfilter = getCellFilter();
2363 $objects = array();
2364 $objects_count = getEntitiesCount ('object');
2365 $showobjecttype = (getConfigVar ('SHOW_OBJECTTYPE') == 'yes');
2366
2367 echo "<table border=0 class=objectview>\n";
2368 echo "<tr><td class=pcleft>";
2369
2370 if ($objects_count == 0)
2371 echo '<h2>No objects exist</h2>';
2372 // 1st attempt: do not fetch all objects if cellfilter is empty and rendering empty result is enabled
2373 elseif (! ($cellfilter['is_empty'] && renderEmptyResults ($cellfilter, 'objects', $objects_count)))
2374 {
2375 $objects = applyCellFilter ('object', $cellfilter);
2376 // 2nd attempt: do not render all fetched objects if rendering empty result is enabled
2377 if (! renderEmptyResults ($cellfilter, 'objects', count($objects)))
2378 {
2379 startPortlet ('Objects (' . count ($objects) . ')');
2380 echo '<br><br><table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
2381 echo '<tr><th>Common name</th>';
2382 if ($showobjecttype)
2383 echo '<th>Type</th>';
2384 echo '<th>Visible label</th><th>Asset tag</th><th>Row/Rack or Container</th></tr>';
2385 $order = 'odd';
2386 # gather IDs of all objects and fetch rackspace info in one pass
2387 $idlist = array();
2388 foreach ($objects as $obj)
2389 $idlist[] = $obj['id'];
2390 $mountinfo = getMountInfo ($idlist);
2391 $containerinfo = getContainerInfo ($idlist);
2392 foreach ($objects as $obj)
2393 {
2394 $problem = ($obj['has_problems'] == 'yes') ? 'has_problems' : '';
2395
2396 setEntityColors ($obj);
2397 $class = getCellClass ($obj, 'list_plain');
2398
2399 echo "<tr class='row_${order} tdleft ${problem}${class}' valign=top><td>" . mkA ("<strong>${obj['dname']}</strong>", 'object', $obj['id']);
2400 if (count ($obj['etags']))
2401 echo '<br><small>' . serializeTags ($obj['etags'], makeHref(array('page'=>$pageno, 'tab'=>'default')) . '&') . '</small>';
2402 echo '</td>';
2403 if ($showobjecttype)
2404 echo "<td>" . decodeObjectType ($obj['objtype_id']) . "</td>";
2405 echo "<td>${obj['label']}</td>";
2406 echo "<td>${obj['asset_no']}</td>";
2407 $places = array();
2408 if (array_key_exists ($obj['id'], $containerinfo))
2409 foreach ($containerinfo[$obj['id']] as $ci)
2410 $places[] = mkA ($ci['container_dname'], 'object', $ci['container_id']);
2411 if (array_key_exists ($obj['id'], $mountinfo))
2412 foreach ($mountinfo[$obj['id']] as $mi)
2413 $places[] = mkA ($mi['row_name'], 'row', $mi['row_id']) . '/' . mkA ($mi['rack_name'], 'rack', $mi['rack_id']);
2414 if (! count ($places))
2415 $places[] = 'Unmounted';
2416 echo "<td>" . implode (', ', $places) . '</td>';
2417 echo '</tr>';
2418 $order = $nextorder[$order];
2419 }
2420 echo '</table>';
2421 finishPortlet();
2422 }
2423 }
2424
2425 echo "</td><td class='pcright ${pageno}' width='25%'>";
2426
2427 renderCellFilterPortlet ($cellfilter, 'object', $objects);
2428 echo "</td></tr></table>\n";
2429 }
2430
2431 // This function returns TRUE if the result set is too big to be rendered, and no filter is set.
2432 // In this case it renders the describing message instead.
2433 function renderEmptyResults($cellfilter, $entities_name, $count = NULL)
2434 {
2435 if (!$cellfilter['is_empty'])
2436 return FALSE;
2437 if (isset ($_REQUEST['show_all_objects']))
2438 return FALSE;
2439 $max = getConfigVar('MAX_UNFILTERED_ENTITIES');
2440 if (0 == $max || $count <= $max)
2441 return FALSE;
2442
2443 $href_show_all = trim($_SERVER['REQUEST_URI'], '&');
2444 $href_show_all .= htmlspecialchars('&show_all_objects=1');
2445 $suffix = isset ($count) ? " ($count)" : '';
2446 // Heredoc, not nowdoc!
2447 echo <<<"END"
2448 <p>Please set a filter to display the corresponging $entities_name.
2449 <br><a href="$href_show_all">Show all $entities_name$suffix</a>
2450 END;
2451 return TRUE;
2452 }
2453
2454 // History viewer for history-enabled simple dictionaries.
2455 function renderObjectHistory ($object_id)
2456 {
2457 $order = 'odd';
2458 global $nextorder;
2459 echo '<table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
2460 echo '<tr><th>change time</th><th>author</th><th>name</th><th>visible label</th><th>asset no</th><th>has problems?</th><th>comment</th></tr>';
2461 $result = usePreparedSelectBlade
2462 (
2463 'SELECT ctime, user_name, name, label, asset_no, has_problems, comment FROM ObjectHistory WHERE id=? ORDER BY ctime',
2464 array ($object_id)
2465 );
2466 while ($row = $result->fetch (PDO::FETCH_NUM))
2467 {
2468 echo "<tr class=row_${order}><td>${row[0]}</td>";
2469 for ($i = 1; $i <= 6; $i++)
2470 echo "<td>" . $row[$i] . "</td>";
2471 echo "</tr>\n";
2472 $order = $nextorder[$order];
2473 }
2474 echo "</table><br>\n";
2475 }
2476
2477 function renderRackspaceHistory ()
2478 {
2479 global $nextorder, $pageno, $tabno;
2480 $order = 'odd';
2481 $history = getRackspaceHistory();
2482 // Show the last operation by default.
2483 if (isset ($_REQUEST['op_id']))
2484 $op_id = $_REQUEST['op_id'];
2485 elseif (isset ($history[0]['mo_id']))
2486 $op_id = $history[0]['mo_id'];
2487 else $op_id = NULL;
2488
2489 $omid = NULL;
2490 $nmid = NULL;
2491 $object_id = 1;
2492 if ($op_id)
2493 list ($omid, $nmid) = getOperationMolecules ($op_id);
2494
2495 // Main layout starts.
2496 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
2497
2498 // Left top portlet with old allocation.
2499 echo "<tr><td class=pcleft>";
2500 startPortlet ('Old allocation');
2501 if ($omid)
2502 {
2503 $oldMolecule = getMolecule ($omid);
2504 renderMolecule ($oldMolecule, $object_id);
2505 }
2506 else
2507 echo "nothing";
2508 finishPortlet();
2509
2510 echo '</td><td class=pcright>';
2511
2512 // Right top portlet with new allocation
2513 startPortlet ('New allocation');
2514 if ($nmid)
2515 {
2516 $newMolecule = getMolecule ($nmid);
2517 renderMolecule ($newMolecule, $object_id);
2518 }
2519 else
2520 echo "nothing";
2521 finishPortlet();
2522
2523 echo '</td></tr><tr><td colspan=2>';
2524
2525 // Bottom portlet with list
2526
2527 startPortlet ('Rackspace allocation history');
2528 echo "<table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>\n";
2529 echo "<tr><th>timestamp</th><th>author</th><th>object</th><th>comment</th></tr>\n";
2530 foreach ($history as $row)
2531 {
2532 if ($row['mo_id'] == $op_id)
2533 $class = 'hl';
2534 else
2535 $class = "row_${order}";
2536 echo "<tr class=${class}><td><a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno, 'op_id'=>$row['mo_id']))."'>${row['ctime']}</a></td>";
2537 echo "<td>${row['user_name']}</td><td>";
2538 renderCell (spotEntity ('object', $row['ro_id']));
2539 echo '</td><td>' . stringForTD ($row['comment'], 0) . '</td></tr>';
2540 $order = $nextorder[$order];
2541 }
2542 echo "</table>\n";
2543 finishPortlet();
2544
2545 echo '</td></tr></table>';
2546 }
2547
2548 function renderIPSpaceRecords ($tree, $baseurl, $target = 0, $level = 1)
2549 {
2550 $self = __FUNCTION__;
2551 $knight = (getConfigVar ('IPV4_ENABLE_KNIGHT') == 'yes');
2552 $display_routers = (getConfigVar ('IPV4_TREE_RTR_AS_CELL') != 'none');
2553
2554 // scroll page to the highlighted item
2555 if ($target && isset ($_REQUEST['hl_net']))
2556 addAutoScrollScript ("net-$target");
2557
2558 foreach ($tree as $item)
2559 {
2560 if (isset ($item['id']))
2561 {
2562 $decor = array ('indent' => $level);
2563 if ($item['symbol'] == 'node-collapsed')
2564 $decor['symbolurl'] = "${baseurl}&eid=${item['id']}&hl_net=1";
2565 elseif ($item['symbol'] == 'node-expanded')
2566 $decor['symbolurl'] = $baseurl . ($item['parent_id'] ? "&eid=${item['parent_id']}&hl_net=1" : '');
2567
2568 setEntityColors ($item);
2569 $class_context = ($target == $item['id'] && isset ($_REQUEST['hl_net'])) ? 'list_selected' : 'list_plain';
2570 $tr_class = getCellClass ($item, $class_context);
2571 // Use old-style highlighting for colourless networks.
2572 if ($class_context == 'list_selected' && $tr_class == '')
2573 {
2574 $decor['tdclass'] = ' highlight';
2575 $tr_class = $decor['tdclass'];
2576 }
2577
2578 echo "<tr valign=top class=\"$tr_class\">";
2579 printIPNetInfoTDs ($item, $decor);
2580
2581 // capacity and usage
2582 echo "<td class=tdcenter>";
2583 echo getRenderedIPNetCapacity ($item);
2584 echo "</td>";
2585
2586 if ($display_routers)
2587 printRoutersTD (findNetRouters ($item), getConfigVar ('IPV4_TREE_RTR_AS_CELL'));
2588 echo "</tr>";
2589 if ($item['symbol'] == 'node-expanded' || $item['symbol'] == 'node-expanded-static')
2590 $self ($item['kids'], $baseurl, $target, $level + 1);
2591 }
2592 elseif (getConfigVar ('IPV4_TREE_SHOW_UNALLOCATED') == 'yes')
2593 {
2594 // non-allocated (spare) IP range
2595 echo "<tr valign=top>";
2596 printIPNetInfoTDs ($item, array ('indent' => $level, 'knight' => $knight, 'tdclass' => 'sparenetwork'));
2597
2598 // capacity and usage
2599 echo "<td class=tdcenter>";
2600 echo getRenderedIPNetCapacity ($item);
2601 echo "</td>";
2602 if ($display_routers)
2603 echo "<td></td>";
2604 echo "</tr>";
2605 }
2606 }
2607 }
2608
2609 function renderIPSpace()
2610 {
2611 global $pageno, $tabno;
2612 $realm = ($pageno == 'ipv4space' ? 'ipv4net' : 'ipv6net');
2613 $cellfilter = getCellFilter();
2614
2615 // expand request can take either natural values or "ALL". Zero means no expanding.
2616 $eid = isset ($_REQUEST['eid']) ? $_REQUEST['eid'] : 0;
2617
2618 echo "<table border=0 class=objectview>\n";
2619 echo "<tr><td class=pcleft>";
2620
2621 $netlist = array();
2622 if (! ($cellfilter['is_empty'] && ! $eid && renderEmptyResults($cellfilter, 'IP nets', getEntitiesCount ($realm))))
2623 {
2624 $top = NULL;
2625 foreach (listCells ($realm) as $net)
2626 {
2627 if (isset ($top) && IPNetContains ($top, $net))
2628 ;
2629 elseif (! count ($cellfilter['expression']) || judgeCell ($net, $cellfilter['expression']))
2630 $top = $net;
2631 else
2632 continue;
2633 $netlist[$net['id']] = $net;
2634 }
2635 $netcount = count ($netlist);
2636 $tree = prepareIPTree ($netlist, $eid);
2637
2638 if (! renderEmptyResults($cellfilter, 'IP nets', count($tree)))
2639 {
2640 startPortlet ("networks (${netcount})");
2641 echo '<h4>';
2642 $all = "<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno, 'eid'=>'ALL')) .
2643 $cellfilter['urlextra'] . "'>expand&nbsp;all</a>";
2644 $none = "<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno, 'eid'=>'NONE')) .
2645 $cellfilter['urlextra'] . "'>collapse&nbsp;all</a>";
2646 $auto = "<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno)) .
2647 $cellfilter['urlextra'] . "'>auto-collapse</a>";
2648
2649 if ($eid === 0)
2650 echo 'auto-collapsing at threshold ' . getConfigVar ('TREE_THRESHOLD') . " ($all / $none)";
2651 elseif ($eid === 'ALL')
2652 echo "expanding all ($auto / $none)";
2653 elseif ($eid === 'NONE')
2654 echo "collapsing all ($all / $auto)";
2655 else
2656 {
2657 try
2658 {
2659 $netinfo = spotEntity ($realm, $eid);
2660 echo "expanding ${netinfo['ip']}/${netinfo['mask']} ($auto / $all / $none)";
2661 }
2662 catch (EntityNotFoundException $e)
2663 {
2664 // ignore invalid eid error
2665 }
2666 }
2667 echo "</h4><table class='widetable zebra' border=0 cellpadding=5 cellspacing=0 align='center'>\n";
2668 echo "<tr><th>prefix</th><th>name/tags</th><th>capacity</th>";
2669 if (getConfigVar ('IPV4_TREE_RTR_AS_CELL') != 'none')
2670 echo "<th>routed by</th>";
2671 echo "</tr>\n";
2672 $baseurl = makeHref(array('page'=>$pageno, 'tab'=>$tabno)) . $cellfilter['urlextra'];
2673 renderIPSpaceRecords ($tree, $baseurl, $eid);
2674 echo "</table>\n";
2675 finishPortlet();
2676 }
2677 }
2678
2679 echo "</td><td class='pcright ${pageno}'>";
2680 renderCellFilterPortlet ($cellfilter, $realm, $netlist);
2681 echo "</td></tr></table>\n";
2682 }
2683
2684 function renderIPSpaceEditor()
2685 {
2686 global $pageno;
2687 $realm = ($pageno == 'ipv4space' ? 'ipv4net' : 'ipv6net');
2688 $addrspaceList = listCells ($realm);
2689 startPortlet ('Manage existing (' . count ($addrspaceList) . ')');
2690 if (count ($addrspaceList))
2691 {
2692 echo "<table class='widetable' border=0 cellpadding=5 cellspacing=0 align='center'>\n";
2693 echo "<tr><th>&nbsp;</th><th>prefix</th><th>name</th><th>capacity</th></tr>";
2694 foreach ($addrspaceList as $netinfo)
2695 {
2696 echo "<tr valign=top><td>";
2697 if (! isIPNetworkEmpty ($netinfo))
2698 printImageHREF ('nodestroy', 'There are ' . count ($netinfo['addrlist']) . ' allocations inside');
2699 else
2700 echo getOpLink (array ('op' => 'del', 'id' => $netinfo['id']), '', 'destroy', 'Delete this prefix');
2701 echo '</td><td class=tdleft>' . mkCellA ($netinfo) . '</td>';
2702 echo '<td class=tdleft>' . stringForTD ($netinfo['name']);
2703 if (count ($netinfo['etags']))
2704 echo '<br><small>' . serializeTags ($netinfo['etags']) . '</small>';
2705 echo '</td><td>';
2706 echo getRenderedIPNetCapacity ($netinfo);
2707 echo '</tr>';
2708 }
2709 echo "</table>";
2710 finishPortlet();
2711 }
2712 }
2713
2714 function renderIPNewNetForm ()
2715 {
2716 global $pageno;
2717 if ($pageno == 'ipv6space')
2718 {
2719 $realm = 'ipv6net';
2720 $regexp = '^[a-fA-F0-9:]*:[a-fA-F0-9:\.]*/\d{1,3}$';
2721 }
2722 else
2723 {
2724 $realm = 'ipv4net';
2725 $regexp = '^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$';
2726 }
2727
2728 // IP prefix validator
2729 addJSInternal ('js/live_validation.js');
2730 $regexp = addslashes ($regexp);
2731 // Heredoc, not nowdoc!
2732 addJSText (<<<"END"
2733 $(document).ready(function () {
2734 $('form#add input[name="range"]').attr('match', '$regexp');
2735 Validate.init();
2736 });
2737 END
2738 ); // addJSText()
2739
2740 startPortlet ('Add new');
2741 printOpFormIntro ('add');
2742 echo '<table border=0 cellpadding=5 cellspacing=0 align=center>';
2743
2744 // inputs column
2745 $prefix_value = empty ($_REQUEST['set-prefix']) ? '' : $_REQUEST['set-prefix'];
2746 echo "<th class=tdright>Prefix:</th><td class=tdleft><input type=text name='range' size=36 class='live-validate' value='${prefix_value}'></td>";
2747 echo '<tr><th class=tdright>VLAN:</th><td class=tdleft>';
2748 echo getOptionTree ('vlan_ck', getAllVLANOptions(), array ('select_class' => 'vertical')) . '</td></tr>';
2749 echo "<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name='name' size='20'></td></tr>";
2750 echo '<tr><th class=tdright>Tags:</th><td class="tdleft">';
2751 printTagsPicker ();
2752 echo '</td></tr>';
2753 echo '<tr><td class=tdright><input type=checkbox name="is_connected" id="is_connected"></td>';
2754 echo '<th class=tdleft><label for="is_connected">reserve subnet-router anycast address</label></th></tr>';
2755 echo "<tr><td colspan=2>";
2756 printImageHREF ('CREATE', 'Add a new network', TRUE);
2757 echo '</td></tr>';
2758 echo "</table></form><br><br>\n";
2759 finishPortlet();
2760 }
2761
2762 function getRenderedIPNetBacktrace ($range)
2763 {
2764 if (getConfigVar ('EXT_IPV4_VIEW') != 'yes')
2765 return array();
2766
2767 $v = ($range['realm'] == 'ipv4net') ? 4 : 6;
2768 $space = "ipv${v}space"; // ipv4space, ipv6space
2769 $tag = "\$ip${v}netid_"; // $ip4netid_, $ip6netid_
2770
2771 $ret = array();
2772 // Build a backtrace from all parent networks.
2773 $clen = $range['mask'];
2774 $backtrace = array();
2775 $backtrace['&rarr;'] = $range;
2776 $key = '';
2777 while (NULL !== ($upperid = getIPAddressNetworkId ($range['ip_bin'], $clen)))
2778 {
2779 $upperinfo = spotEntity ($range['realm'], $upperid);
2780 $clen = $upperinfo['mask'];
2781 $key .= '&uarr;';
2782 $backtrace[$key] = $upperinfo;
2783 }
2784 foreach (array_reverse ($backtrace) as $arrow => $ainfo)
2785 {
2786 $link = '<a href="' . makeHref (array (
2787 'page' => $space,
2788 'tab' => 'default',
2789 'clear-cf' => '',
2790 'cfe' => '{' . $tag . $ainfo['id'] . '}',
2791 'hl_net' => 1,
2792 'eid' => $range['id'],
2793 )) . '" title="View IP tree with this net as root">' . $arrow . '</a>';
2794 $ret[] = array ($link, getOutputOf ('renderCell', $ainfo));
2795 }
2796 return $ret;
2797 }
2798
2799 function renderIPNetwork ($id)
2800 {
2801 global $pageno;
2802 $realm = $pageno; // 'ipv4net', 'ipv6net'
2803 $range = spotEntity ($realm, $id);
2804 loadIPAddrList ($range);
2805 setEntityColors ($range);
2806 echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
2807 echo "<tr><td colspan=2 align=center><h1>${range['ip']}/${range['mask']}</h1><h2>";
2808 echo htmlspecialchars ($range['name'], ENT_QUOTES, 'UTF-8') . "</h2></td></tr>\n";
2809
2810 echo "<tr><td class=pcleft width='50%'>";
2811
2812 // render summary portlet
2813 $summary = array();
2814 $summary['% used'] = getRenderedIPNetCapacity ($range);
2815 $summary = getRenderedIPNetBacktrace ($range) + $summary;
2816 if ($realm == 'ipv4net')
2817 {
2818 $summary[] = array ('Netmask:', ip4_format ($range['mask_bin']));
2819 $summary[] = array ('Netmask:', "0x" . strtoupper (implode ('', unpack ('H*', $range['mask_bin']))));
2820 $summary['Wildcard bits'] = ip4_format ( ~ $range['mask_bin']);
2821 }
2822
2823 $reuse_domain = considerConfiguredConstraint ($range, '8021Q_MULTILINK_LISTSRC');
2824 $domainclass = array();
2825 foreach (array_count_values (reduceSubarraysToColumn ($range['8021q'], 'domain_id')) as $domain_id => $vlan_count)
2826 $domainclass[$domain_id] = $vlan_count == 1 ? '' : ($reuse_domain ? '{trwarning}' : '{trerror}');
2827 foreach ($range['8021q'] as $item)
2828 $summary[] = array ($domainclass[$item['domain_id']] . 'VLAN:', formatVLANAsHyperlink (getVlanRow ($item['domain_id'] . '-' . $item['vlan_id'])));
2829 if (getConfigVar ('EXT_IPV4_VIEW') == 'yes' && count ($routers = findNetRouters ($range)))
2830 {
2831 $summary['Routed by'] = '';
2832 foreach ($routers as $rtr)
2833 $summary['Routed by'] .= getOutputOf ('renderRouterCell', $rtr['ip_bin'], $rtr['iface'], spotEntity ('object', $rtr['id']));
2834 }
2835 $summary['tags'] = '';
2836 renderEntitySummary ($range, 'summary', $summary);
2837
2838 if ($range['comment'] != '')
2839 {
2840 startPortlet ('Comment');
2841 echo '<div class=commentblock>' . string_insert_hrefs (htmlspecialchars ($range['comment'], ENT_QUOTES, 'UTF-8')) . '</div>';
2842 finishPortlet ();
2843 }
2844
2845 renderFilesPortlet ($realm, $id);
2846 echo "</td>\n";
2847
2848 echo "<td class=pcright>";
2849 startPortlet ('details');
2850 renderIPNetworkAddresses ($range);
2851 finishPortlet();
2852 echo "</td></tr></table>\n";
2853 }
2854
2855 // Used solely by renderSeparator
2856 function renderEmptyIPv6 ($ip_bin, $hl_ip)
2857 {
2858 $class = 'tdleft';
2859 if ($ip_bin === $hl_ip)
2860 $class .= ' highlight';
2861 $fmt = ip6_format ($ip_bin);
2862 echo "<tr class='$class'><td><a name='ip-$fmt' href='" . makeHref (array ('page' => 'ipaddress', 'ip' => $fmt)) . "'>" . $fmt;
2863 $editable = permitted ('ipaddress', 'properties', 'editAddress')
2864 ? 'editable'
2865 : '';
2866 echo "</a></td><td><span class='rsvtext $editable id-$fmt op-upd-ip-name'></span></td>";
2867 echo "<td><span class='rsvtext $editable id-$fmt op-upd-ip-comment'></span></td><td>&nbsp;</td></tr>\n";
2868 }
2869
2870 // Renders empty table line to shrink empty IPv6 address ranges.
2871 // If the range consists of single address, renders the address instead of empty line.
2872 // Renders address $hl_ip inside the range.
2873 // Used solely by renderIPv6NetworkAddresses
2874 function renderSeparator ($first, $last, $hl_ip)
2875 {
2876 $self = __FUNCTION__;
2877 if (strcmp ($first, $last) > 0)
2878 return;
2879 if ($first == $last)
2880 renderEmptyIPv6 ($first, $hl_ip);
2881 elseif (isset ($hl_ip) && strcmp ($hl_ip, $first) >= 0 && strcmp ($hl_ip, $last) <= 0)
2882 { // $hl_ip is inside the range $first - $last
2883 $self ($first, ip_prev ($hl_ip), $hl_ip);
2884 renderEmptyIPv6 ($hl_ip, $hl_ip);
2885 $self (ip_next ($hl_ip), $last, $hl_ip);
2886 }
2887 else
2888 echo "<tr><td colspan=4 class=tdleft></td></tr>\n";
2889 }
2890
2891 // calculates page number that contains given $ip (used by renderIPv6NetworkAddresses)
2892 function getPageNumOfIPv6 ($list, $ip_bin, $maxperpage)
2893 {
2894 if ($maxperpage <= 0 || count ($list) <= $maxperpage)
2895 return 0;
2896 $keys = array_keys ($list);
2897 for ($i = 1; $i <= count ($keys); $i++)
2898 if (strcmp ($keys[$i-1], $ip_bin) >= 0)
2899 return intval ($i / $maxperpage);
2900 return intval (count ($list) / $maxperpage);
2901 }
2902
2903 function renderIPNetworkAddresses ($range)
2904 {
2905 switch (strlen ($range['ip_bin']))
2906 {
2907 case 4: return renderIPv4NetworkAddresses ($range);
2908 case 16: return renderIPv6NetworkAddresses ($range);
2909 default: throw new InvalidArgException ("range['ip_bin']", $range['ip_bin']);
2910 }
2911 }
2912
2913 function renderIPv4NetworkPageLink ($rangeid, $page, $title)
2914 {
2915 global $pageno, $tabno;
2916 return "<a href='".makeHref (array ('page' => $pageno, 'tab' => $tabno, 'id' => $rangeid, 'pg' => $page)) . "' title='".$title."'>".$page."</a> ";
2917 }
2918
2919 function renderIPv4NetworkPagination ($range, $page, $numpages)
2920 {
2921 $rendered_pager = '';
2922 $startip = ip4_bin2int ($range['ip_bin']);
2923 $endip = ip4_bin2int (ip_last ($range));
2924 $rangeid = $range['id'];
2925 // Should make this configurable perhaps
2926 // How many pages before/after current page to show
2927 $prepostpagecount = 8;
2928 // Minimum pages where pagination does not happen
2929 $paginationat = 16; // 16 pages is a /20, 32 is a /19
2930 $maxperpage = getConfigVar ('IPV4_ADDRS_PER_PAGE');
2931 if ($numpages <= $paginationat)
2932 {
2933 // create original pagination
2934 for ($i = 0; $i < $numpages; $i++)
2935 if ($i == $page)
2936 $rendered_pager .= "<b>".$i."</b> ";
2937 else
2938 $rendered_pager .= renderIPv4NetworkPageLink($rangeid, $i, ip4_format(ip4_int2bin($startip + $i * $maxperpage)));
2939 }
2940 else // number of pages > page range, create ranged pagination
2941 {
2942 // page is within first subset
2943 if ($page - $prepostpagecount <= 1)
2944 {
2945 for ($i = 0; $i < $page; $i++)
2946 $rendered_pager .= renderIPv4NetworkPageLink($rangeid