nodecontrol_list.php3 17.4 KB
Newer Older
1
<?php
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6
# All rights reserved.
#
7
include("defs.php3");
8
include_once("node_defs.php");
9

10 11 12 13 14 15
#
# This page is used for both admin node control, and for mere user
# information purposes. Be careful about what you do outside of
# $isadmin tests.
# 

16 17 18
#
# Only known and logged in users can do this.
#
19 20 21
$this_user = CheckLoginOrDie();
$uid       = $this_user->uid();
$isadmin   = ISADMIN();
22 23

#
24
# Verify page arguments.
25 26 27
#
$optargs = OptionalPageArguments("target_user",	PAGEARG_USER,
				 "showtype",    PAGEARG_STRING,
28
				 "typefilter",  PAGEARG_STRING,
29 30 31 32
				 "bypid",       PAGEARG_STRING);

if (isset($target_user)) {
    if (! $target_user->AccessCheck($this_user, $TB_USERINFO_READINFO)) {
33 34 35 36
	USERERROR("You do not have permission to do this!", 1);
    }
    $target_uid  = $target_user->uid();
    $target_idx  = $target_user->uid_idx();
37 38
}
else {
39 40 41
    $target_uid  = $uid;
    $target_idx  = $this_user->uid_idx();
    $target_user = $this_user;
42
}
43

44 45 46 47 48
#
# Standard Testbed Header
#
PAGEHEADER("Node Control Center");

49
echo "<b>Tabular views: <a href='nodecontrol_list.php3?showtype=summary'>summary</a>,
50
               <a href='nodecontrol_list.php3?showtype=pcs'>pcs</a>,
51
               <a href='nodecontrol_list.php3?showtype=wireless'>
52
                                                        wireless</a>,";
53
if ($TBMAINSITE) {
54
    echo "     <a href='nodecontrol_list.php3?showtype=widearea&typefilter=pcpg,pcpg-i2'>protogeni</a>,";
55 56
}
echo "         <a href='nodecontrol_list.php3?showtype=widearea'>widearea</a>";
57 58

if ($isadmin) {
59 60
    echo    ", <a href='nodeutilization.php'>utilization</a>,
               <a href='nodecontrol_list.php3?showtype=virtnodes'>virtual</a>,
61 62 63
               <a href='nodecontrol_list.php3?showtype=physical'>physical</a>,
               <a href='nodecontrol_list.php3?showtype=all'>all</a>";
}
64
echo ".</b><br>\n";
65

66 67 68
echo "<b>Map views: <a href='floormap.php3'>wireless</a>";
if ($TBMAINSITE) {
    echo ", <a href='floormap.php3?feature=usrp'>
69 70 71
              GNU USRP</a>,
            <a href='floormap.php3?feature=usrp2'>
              USRP2</a> (software defined radio),
72 73 74 75
            <a href='robotmap.php3'>robot</a>";
}
echo ".</b><br>\n";

76
if (!isset($showtype)) {
77
    $showtype='summary';
78 79
}

Chad Barb's avatar
 
Chad Barb committed
80 81 82
$additionalVariables = "";
$additionalLeftJoin  = "";

83 84 85 86 87 88 89
if (! strcmp($showtype, "summary")) {
    # Separate query below.
    $role   = "";
    $clause = "";
    $view   = "Free Node Summary";
}
elseif (! strcmp($showtype, "all")) {
90 91 92
    $role   = "(role='testnode' or role='virtnode')";
    $clause = "";
    $view   = "All";
93 94
}
elseif (! strcmp($showtype, "pcs")) {
95 96 97
    $role   = "(role='testnode')";
    $clause = "and (nt.class='pc')";
    $view   = "PCs";
98
}
99 100 101 102
elseif (! strcmp($showtype, "sharks")) {
    $role   = "(role='testnode')";
    $clause = "and (nt.class='shark')";
    $view   = "Sharks";
103 104
}
elseif (! strcmp($showtype, "virtnodes")) {
105 106 107 108
    $role   = "(role='virtnode')";
    $clause = "";
    $view   = "Virtual Nodes";
}
109 110 111 112 113
elseif (! strcmp($showtype, "physical")) {
    $role   = "";
    $clause = "(nt.isvirtnode=0)";
    $view   = "Physical Nodes";
}
114 115 116
elseif (! strcmp($showtype, "widearea")) {
    $role   = "(role='testnode')";
    $clause = "and (nt.isremotenode=1)";
Chad Barb's avatar
 
Chad Barb committed
117 118 119 120 121 122 123

    $additionalVariables = ",".
			   "wani.machine_type,".
			   "REPLACE(CONCAT_WS(', ',".
			   "wani.city,wani.state,wani.country,wani.zip), ".
		 	   "'USA, ','')".
			   "AS location, ".
124
	 		   "wani.connect_type, ".
125
			   "wani.hostname, " .
126 127 128
                           "wani.site, ".
	 		   "wani.latitude, ".
			   "wani.longitude";
Chad Barb's avatar
 
Chad Barb committed
129 130 131
    $additionalLeftJoin = "LEFT JOIN widearea_nodeinfo AS wani ".
			  "ON n.node_id=wani.node_id";

132
    $view   = "Widearea";
133
}
134 135 136 137 138 139 140 141 142
elseif (! strcmp($showtype, "wireless")) {
    $role   = "(role='testnode')";
    $clause = "and (loc.node_id is not null)";

    $additionalLeftJoin = "LEFT JOIN location_info AS loc ".
			  "ON n.node_id=loc.node_id";

    $view   = "Wireless";
}
143 144 145 146 147
elseif (preg_match("/^[-\w]+$/", $showtype)) {
    $role   = "(role='testnode')";
    $clause = "and (nt.type='$showtype')";
    $view   = "only <a href=shownodetype.php3?node_type=$showtype>$showtype</a>";
}
148
else {
149 150 151
    $role   = "(role='testnode')";
    $clause = "and (nt.class='pc')";
    $view   = "PCs";
152
}
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

# If adding an additional type filter list, do that...
if (isset($typefilter)) {
    $types = explode(",",$typefilter);
    $typeclause = "and nt.type in (";
    foreach ($types as $t) {
	# Sanitize.
	if (!preg_match("/^[-\w]+$/", $t)) {
	    PAGEARGERROR("Invalid characters in typefilter argument '$t'.");
	}
	$typeclause .= "'$t',";
    }
    $typeclause = rtrim($typeclause,",");
    $typeclause .= ")";
    $clause .= " $typeclause";
}

170 171 172 173 174
# If admin or widearea, show the vname too. 
$showvnames = 0;
if ($isadmin || !strcmp($showtype, "widearea")) {
    $showvnames = 1;
}
175

176 177 178 179 180 181 182 183
#
# Summary info very different.
# 
if (! strcmp($showtype, "summary")) {
    # Get permissions table so as not to show nodes the user is not allowed
    # to see.
    $perms = array();
    
184
    if (!$isadmin || isset($bypid)) {
185 186 187 188 189 190
	$query_result =
	    DBQueryFatal("select type from nodetypeXpid_permissions");

	while ($row = mysql_fetch_array($query_result)) {
	    $perms{$row[0]} = 0;
	}
191 192 193 194 195 196

	$pidclause = "";
	if (isset($bypid)) {
	    if ($bypid == "" || !TBvalid_pid($bypid)) {
		PAGEARGERROR("Invalid characters in 'bypid' argument!");
	    }
197
	    if (! ($target_project = Project::Lookup($bypid))) {
198 199
		PAGEARGERROR("No such project '$bypid'!");
	    }
200 201
	    if (!$target_project->AccessCheck($this_user,
					      $TB_PROJECT_READINFO)){
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
		USERERROR("You are not a member of project '$bypid!", 1);
	    }
	    $pidclause = "and g.pid='$bypid'";
	}
	if ($isadmin) {
	    $query_result =
		DBQueryFatal("select distinct type ".
			     "  from nodetypeXpid_permissions ".
			     "where pid='$bypid'");
	}
	else {
	    $query_result =
		DBQueryFatal("select distinct type from group_membership as g ".
			     "left join nodetypeXpid_permissions as p ".
			     "     on g.pid=p.pid ".
217
			     "where uid_idx='$target_idx' $pidclause");
218
	}
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
	
	while ($row = mysql_fetch_array($query_result)) {
	    $perms{$row[0]} = 1;
	}
    }
    
    # Get totals by type.
    $query_result =
	DBQueryFatal("select n.type,count(*) from nodes as n ".
		     "left join node_types as nt on n.type=nt.type ".
		     "where (role='testnode') and ".
		     "      (nt.class!='shark' and nt.class!='pcRemote' ".
		     "      and nt.class!='pcplabphys') ".
		     "group BY n.type");

234 235
    $alltotal  = 0;
    $allfree   = 0;
Timothy Stack's avatar
 
Timothy Stack committed
236
    $allunknown = 0;
237
    $totals    = array();
Timothy Stack's avatar
 
Timothy Stack committed
238 239
    $freecounts = array();
    $unknowncounts = array();
240 241 242 243 244 245 246

    while ($row = mysql_fetch_array($query_result)) {
	$type  = $row[0];
	$count = $row[1];

	$totals[$type]    = $count;
	$freecounts[$type] = 0;
Timothy Stack's avatar
 
Timothy Stack committed
247
	$unknowncounts[$type] = 0;
248 249
    }

250 251
    # Get free totals by type.  Note we also check that the physical node
    # is free, see note on non-summary query for why.
252
    $query_result =
Timothy Stack's avatar
 
Timothy Stack committed
253
	DBQueryFatal("select n.eventstate,n.type,count(*) from nodes as n ".
254
		     "left join nodes as np on np.node_id=n.phys_nodeid ".
255 256
		     "left join node_types as nt on n.type=nt.type ".
		     "left join reserved as r on r.node_id=n.node_id ".
257 258
		     "left join reserved as rp on rp.node_id=n.phys_nodeid ".
		     "where (n.role='testnode') and ".
259 260
		     "      (nt.class!='shark' and nt.class!='pcRemote' ".
		     "      and nt.class!='pcplabphys') ".
261 262
		     "      and r.pid is null and rp.pid is null ".
		     "      and n.reserved_pid is null and np.reserved_pid is null ".
Timothy Stack's avatar
 
Timothy Stack committed
263
		     "group BY n.eventstate,n.type");
264 265

    while ($row = mysql_fetch_array($query_result)) {
Timothy Stack's avatar
 
Timothy Stack committed
266 267 268 269 270 271 272 273 274 275 276 277
	$type  = $row[1];
	$count = $row[2];
        # XXX Yeah, I'm a doofus and can't figure out how to do this in SQL.
	if (($row[0] == TBDB_NODESTATE_ISUP) ||
	    ($row[0] == TBDB_NODESTATE_PXEWAIT) ||
	    ($row[0] == TBDB_NODESTATE_ALWAYSUP) ||
	    ($row[0] == TBDB_NODESTATE_POWEROFF)) {
	    $freecounts[$type] += $count;
	}
	else {
	    $unknowncounts[$type] += $count;
	}
278 279
    }

280
    $projlist = $target_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
281 282 283 284 285 286 287 288 289 290 291 292
    if (count($projlist) > 1) {
	echo "<b>By Project Permission: ";
	while (list($project) = each($projlist)) {
	    echo "<a href='nodecontrol_list.php3?".
		"showtype=summary&bypid=$project'>$project</a>,\n";
	}
	echo "<a href='nodecontrol_list.php3?showtype=summary'>".
	    "combined membership</a>.\n";
	echo "</b><br>\n";
    }

    echo "<br><center>
293
          <b>Free Node Summary</b>
294 295 296 297 298
          <br>\n";
    if (isset($bypid)) {
	echo "($bypid)<br><br>\n";
    }
    echo "<table>
299 300 301 302 303 304 305 306 307 308 309 310
          <tr>
             <th>Type</th>
             <th align=center>Free<br>Nodes</th>
             <th align=center>Total<br>Nodes</th>
          </tr>\n";

    foreach($totals as $key => $value) {
	$freecount = $freecounts[$key];

	# Check perm entry.
	if (isset($perms[$key]) && !$perms[$key])
	    continue;
311 312

	$allfree   += $freecount;
Timothy Stack's avatar
 
Timothy Stack committed
313
	$allunknown += $unknowncounts[$key];
314
	$alltotal  += $value;
Timothy Stack's avatar
 
Timothy Stack committed
315 316 317 318 319

	if ($unknowncounts[$key])
	    $ast = "*";
	else
	    $ast = "";
320
	
321 322 323 324 325 326
	echo "<tr>\n";
	if ($isadmin)
	    echo "<td><a href=editnodetype.php3?node_type=$key>\n";
	else
	    echo "<td><a href=shownodetype.php3?node_type=$key>\n";
	echo "           $key</a></td>
Timothy Stack's avatar
 
Timothy Stack committed
327
              <td align=center>${freecount}${ast}</td>
328 329 330
              <td align=center>$value</td>
              </tr>\n";
    }
331 332 333 334 335 336
    echo "<tr></tr>\n";
    echo "<tr>
            <td><b>Totals</b></td>
              <td align=center>$allfree</td>
              <td align=center>$alltotal</td>
              </tr>\n";
Timothy Stack's avatar
 
Timothy Stack committed
337

338 339
    if ($isadmin) {
	# Give admins the option to create a new type
340 341 342 343
	echo "<tr></tr>\n";
	echo "<th colspan=3 align=center>
                <a href=editnodetype.php3?new_type=1>Create a new type</a>
              </th>\n";
344
    }
345
    echo "</table>\n";
Timothy Stack's avatar
 
Timothy Stack committed
346 347 348 349
    if ($allunknown > 0) {
	    echo "<br><font size=-1><b>*</b> - Some nodes ($allunknown) are ".
		    "free, but currently in an unallocatable state.</font>";
    }
350 351 352 353
    PAGEFOOTER();
    exit();
}

354 355
#
# Suck out info for all the nodes.
356 357 358 359 360 361
#
# If a node is free we check to make sure that that the physical node
# is also.  This is based on the assumption that if a physical node is
# not available, neither is the node, such as the case with netpga2.
# This may not be true for virtual nodes, such as PlanetLab slices,
# but virtual nodes are allocated on demand, and thus are never free.
362
# 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
363
$query_result =
364
    DBQueryFatal("select distinct n.node_id,n.phys_nodeid,n.type,ns.status, ".
365 366 367 368 369
		 "   n.def_boot_osid, ".
		 "   if(r.pid is not null,r.pid,rp.pid) as pid, ".
	         "   if(r.pid is not null,r.eid,rp.eid) as eid, ".
		 "   nt.class, ".
	 	 "   if(r.pid is not null,r.vname,rp.vname) as vname ".
Chad Barb's avatar
 
Chad Barb committed
370 371
		 "$additionalVariables ".
		 "from nodes as n ".
372
		 "left join node_types as nt on n.type=nt.type ".
373
		 "left join node_status as ns on n.node_id=ns.node_id ".
374
		 "left join reserved as r on n.node_id=r.node_id ".
375
		 "left join reserved as rp on n.phys_nodeid=rp.node_id ".
Chad Barb's avatar
 
Chad Barb committed
376
		 "$additionalLeftJoin ".
377 378
		 "where $role $clause ".
		 "ORDER BY priority");
379 380 381 382

if (mysql_num_rows($query_result) == 0) {
    echo "<center>Oops, no nodes to show you!</center>";
    PAGEFOOTER();
383
    exit();
384
}
385

386
#
387
# First count up free nodes as well as status counts.
388
#
389 390 391 392 393
$num_free = 0;
$num_up   = 0;
$num_pd   = 0;
$num_down = 0;
$num_unk  = 0;
394
$freetypes= array();
395

396
while ($row = mysql_fetch_array($query_result)) {
397 398 399
    $pid                = $row["pid"];
    $status             = $row["status"];
    $type               = $row["type"];
400

401 402 403
    if (! isset($freetypes[$type])) {
	$freetypes[$type] = 0;
    }
404 405 406
    if (!$pid) {
	$num_free++;
	$freetypes[$type]++;
407
	continue;
408
    }
409 410 411 412 413 414 415 416 417 418 419 420 421 422
    switch ($status) {
    case "up":
	$num_up++;
	break;
    case "possibly down":
    case "unpingable":
	$num_pd++;
	break;
    case "down":
	$num_down++;
	break;
    default:
	$num_unk++;
	break;
423 424
    }
}
425
$num_total = ($num_free + $num_up + $num_down + $num_pd + $num_unk);
426 427
mysql_data_seek($query_result, 0);

428
if (! strcmp($showtype, "widearea")) {
429
    echo "<a href='$WIKIDOCURL/widearea'>
430 431 432 433
             Widearea Usage Notes</a>\n";
}

echo "<br><center><b>
434
       View: $view\n";
435 436 437

if (! strcmp($showtype, "widearea")) {
    echo "<br>
438
          <a href=widearea_nodeinfo.php3>(Widearea Link Metrics)</a><br>
Leigh B. Stoller's avatar
Leigh B. Stoller committed
439
          <a href=plabmetrics.php3>(PlanetLab Node Metrics)</a>\n";
440 441 442
}

echo "</b></center><br>\n";
443

444 445 446
SUBPAGESTART();

echo "<table>
447 448 449
       <tr><td align=right>
           <img src='/autostatus-icons/greenball.gif' alt=up>
           <b>Up</b></td>
450 451
           <td align=left>$num_up</td>
       </tr>
452 453 454
       <tr><td align=right nowrap>
           <img src='/autostatus-icons/yellowball.gif' alt='possibly down'>
           <b>Possibly Down</b></td>
455 456
           <td align=left>$num_pd</td>
       </tr>
457 458 459
       <tr><td align=right>
           <img src='/autostatus-icons/blueball.gif' alt=unknown>
           <b>Unknown</b></td>
460 461
           <td align=left>$num_unk</td>
       </tr>
462 463 464
       <tr><td align=right>
           <img src='/autostatus-icons/redball.gif' alt=down>
           <b>Down</b></td>
465 466
           <td align=left>$num_down</td>
       </tr>
467 468 469
       <tr><td align=right>
           <img src='/autostatus-icons/whiteball.gif' alt=free>
           <b>Free</b></td>
470 471
           <td align=left>$num_free</td>
       </tr>
472 473 474
       <tr><td align=right><b>Total</b></td>
           <td align=left>$num_total</td>
       </tr>
475 476 477 478 479
       <tr><td colspan=2 nowrap align=center>
               <b>Free Subtotals</b></td></tr>\n";

foreach($freetypes as $key => $value) {
    echo "<tr>
480
           <td align=right><a href=shownodetype.php3?node_type=$key>
481
                           $key</a></td>
482 483 484 485
           <td align=left>$value</td>
          </tr>\n";
}
echo "</table>\n";
486 487
SUBMENUEND_2B();

488
echo "<table border=2 cellpadding=2 cellspacing=2 id='nodelist'>\n";
489

490
echo "<thead class='sort'>";
491
echo "<tr>
492 493 494 495 496 497 498
          <th align=center>ID</th>\n";

if ($showvnames) {
    echo "<th align=center>Name</th>\n";
}

echo "    <th align=center>Type (Class)</th>
499
          <th align=center class='sorttable_nosort'>Up?</th>\n";
500 501

if ($isadmin) {
Chad Barb's avatar
 
Chad Barb committed
502 503 504
    echo "<th align=center>PID</th>
          <th align=center>EID</th>
          <th align=center>Default<br>OSID</th>\n";
505
}
506 507
elseif (strcmp($showtype, "widearea")) {
    # Widearea nodes are always "free"
Chad Barb's avatar
 
Chad Barb committed
508
    echo "<th align=center>Free?</th>\n";
Chad Barb's avatar
 
Chad Barb committed
509 510 511
}

if (!strcmp($showtype, "widearea")) {
512
    echo "<th align=center>Site</th>
Chad Barb's avatar
 
Chad Barb committed
513
	  <th align=center>Connection</th>
514 515 516
	  <th align=center>Location</th>
	  <th align=center>Latitude</th>
	  <th align=center>Longitude</th>";
Chad Barb's avatar
 
Chad Barb committed
517 518
}
    
519
echo "</tr></thead>\n";
520

521
while ($row = mysql_fetch_array($query_result)) {
522 523 524
    $node_id            = $row["node_id"]; 
    $phys_nodeid        = $row["phys_nodeid"]; 
    $type               = $row["type"];
525
    $class              = $row["class"];
526 527 528 529 530
    $def_boot_osid      = $row["def_boot_osid"];
    $pid                = $row["pid"];
    $eid                = $row["eid"];
    $vname              = $row["vname"];
    $status             = $row["status"];
531

Chad Barb's avatar
 
Chad Barb committed
532
    if (!strcmp($showtype, "widearea")) {	
533 534 535 536 537
	$site         = $row["site"];
	$machine_type = $row["machine_type"];
	$location     = $row["location"];
	$connect_type = $row["connect_type"];
	$vname        = $row["hostname"];
538 539
	$latitude     = $row["latitude"];
	$longitude    = $row["longitude"];
Chad Barb's avatar
 
Chad Barb committed
540 541
    } 

542
    echo "<tr>";
543

544
    # Admins get a link to expand the node.
545 546
    if ($isadmin ||
	(OPSGUY() && (!$pid || $pid == $TBOPSPID))) {
547 548 549 550
	echo "<td><A href='shownode.php3?node_id=$node_id'>$node_id</a> " .
	    (!strcmp($node_id, $phys_nodeid) ? "" :
	     "(<A href='shownode.php3?node_id=$phys_nodeid'>$phys_nodeid</a>)")
	    . "</td>\n";
551 552
    }
    else {
553 554 555
	echo "<td>$node_id " .
  	      (!strcmp($node_id, $phys_nodeid) ? "" : "($phys_nodeid)") .
	      "</td>\n";
556
    }
557 558 559 560 561 562 563

    if ($showvnames) {
	if ($vname)
	    echo "<td>$vname</td>\n";
	else
	    echo "<td>--</td>\n";
    }
564
    
565 566
    echo "   <td>$type ($class)</td>\n";

567 568 569 570
    if (!$pid)
	echo "<td align=center>
                  <img src='/autostatus-icons/whiteball.gif' alt=free></td>\n";
    elseif (!$status)
571 572 573
	echo "<td align=center>
                  <img src='/autostatus-icons/blueball.gif' alt=unk></td>\n";
    elseif ($status == "up")
574 575
	echo "<td align=center>
                  <img src='/autostatus-icons/greenball.gif' alt=up></td>\n";
576
    elseif ($status == "down")
577 578
	echo "<td align=center>
                  <img src='/autostatus-icons/redball.gif' alt=down></td>\n";
579 580 581
    else
	echo "<td align=center>
                  <img src='/autostatus-icons/yellowball.gif' alt=unk></td>\n";
582 583 584 585

    # Admins get pid/eid/vname, but mere users yes/no.
    if ($isadmin) {
	if ($pid) {
Mike Hibler's avatar
Mike Hibler committed
586 587
	    echo "<td><a href=showproject.php3?pid=$pid>$pid</a></td>
                  <td><a href=showexp.php3?pid=$pid&eid=$eid>$eid</a></td>\n";
588 589 590 591 592
	}
	else {
	    echo "<td>--</td>
   	          <td>--</td>\n";
	}
593 594 595
	if ($def_boot_osid &&
	    ($osinfo = OSinfo::Lookup($def_boot_osid))) {
	    $osname = $osinfo->osname();
596
	    echo "<td>$osname</td>\n";
597
	}
598 599 600
	else
	    echo "<td>&nbsp</td>\n";
    }
601
    elseif (strcmp($showtype, "widearea")) {
602
	if ($pid)
603
	    echo "<td>--</td>\n";
604 605 606
	else
	    echo "<td>Yes</td>\n";
    }
Chad Barb's avatar
 
Chad Barb committed
607 608

    if (!strcmp($showtype, "widearea")) {	
609
	echo "<td>$site</td>
Chad Barb's avatar
 
Chad Barb committed
610
	      <td>$connect_type</td>
611 612 613
	      <td><font size='-1'>$location</font></td>
	      <td><font size='-1'>$latitude</font></td>
	      <td><font size='-1'>$longitude</font></td>\n";
Chad Barb's avatar
 
Chad Barb committed
614
    }
615
    
616
    echo "</tr>\n";
617 618 619
}

echo "</table>\n";
620 621 622
echo "<script type='text/javascript' language='javascript'>
         sorttable.makeSortable(getObjbyName('nodelist'));
      </script>\n";
623
SUBPAGEEND();
624 625 626 627 628

#
# Standard Testbed Footer
# 
PAGEFOOTER();
629 630 631
?>