Commit 0d09773b authored by Leigh Stoller's avatar Leigh Stoller

Checkpoint various changes.

* Various UI tweaks for profile versioning.

* Roll out profile versioning for all users.

* Disable/Hide publishing for now.

* Move profile/version URLs into a modal that is invoked by a new Share
  button, that explains things a little better.

* Unify profile permissions between APT/Cloudlab. Users now see just two
  choices; project or anyone, where anyone includes guest users in the APT
  interface, for now.

* Get rid of "List on the front page" checkbox, all public profiles will be
  listed, but red-dot can still set that bit.

* Return the publicURL dynamically in the status blob, and set/show the
  sliver info button as soon as we get it.

* Console password support; if the aggregate returns the console password,
  add an item to the context menu to show it.

* Other stuff.
parent 7ea1e119
......@@ -451,6 +451,34 @@ sub WarnExpiring($$)
#
# Ask aggregate for the console URL for a node.
#
sub ConsoleInfo($$)
{
my ($self, $sliver_urn) = @_;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->GetGeniUser();
my $slice = $self->GetGeniSlice();
my $context = APT_Geni::GeniContext();
return undef
if (! (defined($geniuser) && defined($authority) &&
defined($slice) && defined($context)));
my ($slice_credential, $speaksfor_credential) =
APT_Geni::GenCredentials($slice, $geniuser);
return undef
if (! (defined($speaksfor_credential) &&
defined($slice_credential)));
my $args = {
"slice_urn" => $slice->urn(),
"sliver_urn" => $sliver_urn,
"credentials" => [$slice_credential->asString(),
$speaksfor_credential->asString()],
};
my $cmurl = $authority->url();
$cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree);
return Genixmlrpc::CallMethod($cmurl, $context, "ConsoleInfo", $args);
}
sub ConsoleURL($$)
{
my ($self, $sliver_urn) = @_;
......
......@@ -547,23 +547,47 @@ sub DoConsole()
if (!defined($sliver_urn)) {
fatal("Could not find node '$node_id' in manifest");
}
my $response = $instance->ConsoleURL($sliver_urn);
my $response = $instance->ConsoleInfo($sliver_urn);
if (!defined($response)) {
fatal("RPC Error calling ConsoleURL");
fatal("RPC Error calling ConsoleInfo");
}
if ($response->code() != GENIRESPONSE_SUCCESS) {
$response = $instance->ConsoleURL($sliver_urn);
if (!defined($response)) {
fatal("RPC Error calling ConsoleURL");
}
if ($response->code() != GENIRESPONSE_SUCCESS) {
fatal("Could not get console info");
}
}
my $url;
my $pswd;
if (ref($response->value())) {
$url = $response->value()->{'url'};
$pswd = $response->value()->{'password'}
if (exists($response->value()->{'password'}));
}
else {
$url = $response->value();
}
if (defined($webtask)) {
$webtask->output($response->output());
$webtask->code($response->code());
$webtask->value($response->value())
if (! $response->code());
if ($response->code()) {
$webtask->output($response->output());
}
else {
$webtask->url($url);
$webtask->password($pswd) if (defined($pswd));
}
$webtask->Exited($response->code());
exit(0);
exit($response->code());
}
# For command line operation too.
if ($response->code()) {
fatal($response->output());
}
print $response->value() . "\n";
print $url . "\n";
print $pswd . "\n" if (defined($pswd));
exit(0);
}
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2005-2014 University of Utah and the Flux Group.
# Copyright (c) 2005-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -1636,6 +1636,14 @@ sub bootdisk_unit($;$) {
return NodeTypeInfo($self)->bootdisk_unit($stuff);
}
sub root_password($) {
my ($self) = @_;
my $val = undef;
NodeAttribute($self, "root_password", \$val);
return $val;
}
#
# And these are the less common attributes, but still common enough to
# warrant shortcuts.
......
......@@ -404,3 +404,7 @@ blockquote #selected_profile_description {
cursor: default;
}
/* The default margins leave no space at all! */
.popover-content {
margin: 0px 5px 5px 5px;
}
......@@ -148,7 +148,7 @@ if (isset($profile)) {
}
#
# Must be public or belong to user.
# Must be public or pass the permission test for the user.
#
if (! ($obj->ispublic() ||
(isset($this_user) && $obj->CanInstantiate($this_user)))) {
......@@ -400,8 +400,16 @@ function SPITFORM($formfields, $newuser, $errors)
if (isset($this_user)) {
echo "<button class='btn btn-default btn-sm pull-left'
type='button' id='profile_copy_button'
style='margin-right: 10px;'>
Copy Profile
style='margin-right: 10px;'
data-toggle='popover'
data-delay='{hide:1500, show:500}'
data-html='true'
data-content='When you copy a profile
you are creating a new profile that
uses the same source code and metadata (description,
instructions) as the original profile, but without
creating a new disk image. Instead, the new profile uses
whatever images the original profile uses.'>Copy Profile
</button>";
echo "<button class='btn btn-default btn-sm pull-left'
type='button' id='profile_show_button'>
......
......@@ -42,6 +42,12 @@ function (_, Constraints, sup, ppstart, aboutaptString, aboutcloudString, waitwa
$('#about_div').html(window.ISCLOUD ?
aboutcloudString : aboutaptString);
}
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
placement: 'auto',
container: 'body',
});
if (window.APT_OPTIONS.isNewUser) {
$('#verify_modal_submit').click(function (event) {
......
......@@ -10,12 +10,14 @@ require(window.APT_OPTIONS.configObject,
'js/lib/text!template/guest-instantiate.html',
'js/lib/text!template/publish-modal.html',
'js/lib/text!template/instantiate-modal.html',
'js/lib/text!template/share-modal.html',
// jQuery modules
'filestyle','marked','jquery-ui','jquery-grid'],
function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
manageString, waitwaitString,
rendererString, showtopoString, oopsString, rspectextviewString,
guestInstantiateString, publishString, instantiateString)
guestInstantiateString, publishString, instantiateString,
shareString)
{
'use strict';
var profile_uuid = null;
......@@ -38,6 +40,7 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
var oopsTemplate = _.template(oopsString);
var guestInstTemplate = _.template(guestInstantiateString);
var InstTemplate = _.template(instantiateString);
var shareTemplate = _.template(shareString);
var stepsInitialized = false;
function initialize()
......@@ -52,6 +55,11 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
var fields = JSON.parse(_.unescape($('#form-json')[0].textContent));
var errors = JSON.parse(_.unescape($('#error-json')[0].textContent));
var projlist = JSON.parse(_.unescape($('#projects-json')[0].textContent));
var versions = null;
if (window.VIEWING) {
versions =
JSON.parse(_.unescape($('#versions-json')[0].textContent));
}
amlist = JSON.parse(_.unescape($('#amlist-json')[0].textContent));
// Notice if we have an rspec in the formfields, to start from.
......@@ -96,6 +104,9 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
manual: window.MANUAL,
snapuuid: (window.SNAPUUID || null),
general_error: (errors.error || ''),
iscloud: window.ISCLOUD,
versions: versions,
withpublishing: window.WITHPUBLISHING,
});
manage_html = formatter(manage_html, errors).html();
$('#manage-body').html(manage_html);
......@@ -117,6 +128,7 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
$('#instantiate_div').html(instantiate_html);
var rspectext_html = rspectextTemplate({});
$('#rspectext_div').html(rspectext_html);
$('#share_div').html(shareTemplate({formfields: fields}))
//
// Fix for filestyle problem; not a real class I guess, it
......@@ -132,7 +144,8 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
container: 'body'
placement: 'auto',
container: 'body',
});
// Format dates with moment before display.
$('.format-date').each(function() {
......
......@@ -13,7 +13,10 @@ function (_, sup, profileString)
ajaxurl = window.AJAXURL;
var profiles = JSON.parse(_.unescape($('#profiles-json')[0].textContent));
var profile_html = profileTemplate({profiles: profiles});
var profile_html =
profileTemplate({profiles: profiles,
withpublishing: window.WITHPUBLISHING});
$('#history-body').html(profile_html);
console.info(profiles);
......
......@@ -48,6 +48,7 @@ function (_, sup, moment, ppstart,
profile_uuid: profile_uuid,
history: window.HISTORY,
isadmin: window.ISADMIN,
withpublishing: window.WITHPUBLISHING,
});
$('#page-body').html(show_html);
......
......@@ -62,7 +62,6 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sliceExpiresText: window.APT_OPTIONS.sliceExpiresText,
creatorUid: window.APT_OPTIONS.creatorUid,
creatorEmail: window.APT_OPTIONS.creatorEmail,
publicURL: window.APT_OPTIONS.publicURL,
registered: window.APT_OPTIONS.registered,
isadmin: window.APT_OPTIONS.isadmin,
errorURL: errorURL,
......@@ -156,6 +155,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
StartSnapshot();
});
// If we got a publicURL, set the href and show the button.
if (window.APT_OPTIONS.publicURL) {
ShowSliverInfo(window.APT_OPTIONS.publicURL);
}
//
// Attach a hover popover to explain what Clone means. We need
// the hover action delayed by our own code, since we want to
......@@ -308,6 +312,10 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
ShowTopo(uuid);
StatusWatchCallBack.active = 1;
}
// Ditto the publicURL.
if (_.has(json.value, "publicURL")) {
ShowSliverInfo(json.value.publicURL);
}
if (status == 'provisioned') {
$("#status_progress_bar").width("66%");
......@@ -763,6 +771,9 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
var hostportList = {};
// Remember passwords to show user later.
var nodePasswords = {};
//
// Show a context menu over nodes in the topo viewer.
//
......@@ -797,9 +808,18 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
}
DoReboot(client_id);
}
else if ($(e.target).text() == "Console Pswd") {
ShowConsolePassword(client_id);
}
$('#context').contextmenu('destroy');
}
})
if (_.has(nodePasswords, client_id)) {
$('#context_menu_pswd').removeClass("hidden");
}
else {
$('#context_menu_pswd').addClass("hidden");
}
$('#context').contextmenu('show', event);
}
......@@ -828,6 +848,8 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
" <ul class='dropdown-menu' role='menu'> " +
" <li><a href='#' name='shell'>Shell</a></li> " +
" <li><a href='#' name='console'>Console</a></li> " +
" <li class='hidden' name='console_menu_pswd'> " +
" <a href='#' name='console_pswd'>Console Pswd</a></li> " +
" <li><a href='#' name='reboot'>Reboot</a></li> " +
" </ul>" +
" </div>" +
......@@ -934,7 +956,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
//
// Now a handler for the console action.
//
if (coninfo.length && !isguest) {
if (coninfo.length) {
// Attach handler to the menu button.
$('#listview-row-' + node + ' [name=console]')
.click(function (e) {
......@@ -947,6 +969,17 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
NewConsoleTab(node);
return false;
});
$('#listview-row-' + node + ' [name=console_pswd]')
.click(function (e) {
e.preventDefault();
if (isguest) {
alert("Only registered users can access " +
"the console");
return false;
}
ShowConsolePassword(node);
return false;
});
}
//
// And a handler for the reboot action.
......@@ -1038,7 +1071,6 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
result[key] = host;
}
});
console.log(result);
return result;
}
......@@ -1103,8 +1135,14 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sup.SpitOops("oops", "Could not start console: " + json.value);
return;
}
var url = json.value + '&noclose=1';
var url = json.value.url + '&noclose=1';
if (_.has(json.value, "password")) {
nodePasswords[client_id] = json.value.password;
$('#listview-row-' + client_id +
' [name=console_menu_pswd]').removeClass("hidden");
}
//
// Need to create the tab before we can create the topo, since
// we need to know the dimensions of the tab.
......@@ -1238,5 +1276,18 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
}
}
function ShowSliverInfo(url)
{
$("#sliverinfo_button").attr("href", url);
$("#sliverinfo_button").removeClass("hidden");
}
function ShowConsolePassword(client_id)
{
$('#console_password_clientid').val(client_id);
$('#console_password_input').val(nodePasswords[client_id]);
sup.ShowModal('#console_password_modal');
}
$(document).ready(initialize);
});
......@@ -58,6 +58,7 @@ function SPITFORM($formfields, $errors)
{
global $this_user, $projlist, $action, $profile, $DEFAULT_AGGREGATE;
global $notifyupdate, $notifyclone, $snapuuid, $am_array, $ISCLOUD;
global $version_array, $WITHPUBLISHING;
$viewing = 0;
$candelete = 0;
$canmodify = 0;
......@@ -137,6 +138,12 @@ function SPITFORM($formfields, $errors)
echo "<script type='text/plain' id='projects-json'>\n";
echo htmlentities(json_encode($plist));
echo "</script>\n";
if ($viewing) {
echo "<script type='text/plain' id='versions-json'>\n";
echo json_encode($version_array);
echo "</script>\n";
}
echo "<link rel='stylesheet'
href='css/jquery-ui-1.10.4.custom.min.css'>\n";
......@@ -164,6 +171,7 @@ function SPITFORM($formfields, $errors)
echo " window.AMDEFAULT= '$amdefault';\n";
echo " window.BUTTONLABEL = '$button_label';\n";
echo " window.ISPPPROFILE = $ispp;\n";
echo " window.WITHPUBLISHING = $WITHPUBLISHING;\n";
if ($ISCLOUD) {
echo " window.ISCLOUD = true;";
} else {
......@@ -254,7 +262,7 @@ if (! isset($create)) {
# New page action is now create, not copy or clone.
$action = "create";
$defaults["profile_rspec"] = $profile->rspec();
$defaults["profile_who"] = "shared";
$defaults["profile_who"] = "private";
if ($profile->script() && $profile->script() != "") {
$defaults["profile_script"] = $profile->script();
}
......@@ -307,6 +315,53 @@ if (! isset($create)) {
if ($webtask && ! $webtask->exited()) {
$notifyclone = 1;
}
#
# Spit out the version history.
#
$version_array = array();
$profileid = $profile->profileid();
$query_result =
DBQueryFatal("select v.*,DATE(v.created) as created ".
" from apt_profile_versions as v ".
"where v.profileid='$profileid' ".
"order by v.created desc");
while ($row = mysql_fetch_array($query_result)) {
$uuid = $row["uuid"];
$version = $row["version"];
$pversion= $row["parent_version"];
$created = $row["created"];
$published = $row["published"];
$rspec = $row["rspec"];
$desc = '';
$obj = array();
if ($version == 0) {
$pversion = " ";
}
if (!$published) {
$published = " ";
}
else {
$published = date("Y-m-d", strtotime($published));
}
$parsed_xml = simplexml_load_string($rspec);
if ($parsed_xml &&
$parsed_xml->rspec_tour &&
$parsed_xml->rspec_tour->description) {
$desc = (string) $parsed_xml->rspec_tour->description;
}
$obj["uuid"] = $uuid;
$obj["version"] = $version;
$obj["description"] = $desc;
$obj["created"] = $created;
$obj["published"] = $published;
$obj["parent_version"] = $pversion;
$version_array[] = $obj;
}
}
}
else {
......@@ -316,7 +371,7 @@ if (! isset($create)) {
reset($projlist);
$defaults["profile_pid"] = $project;
}
$defaults["profile_who"] = "shared";
$defaults["profile_who"] = "private";
}
SPITFORM($defaults, $errors);
return;
......@@ -467,15 +522,27 @@ else {
"</value>");
fwrite($fp, "</attribute>\n");
}
fwrite($fp, "<attribute name='profile_listed'><value>");
if (isset($formfields["profile_listed"]) &&
$formfields["profile_listed"] == "checked") {
fwrite($fp, "1");
}
else {
fwrite($fp, "0");
#
# When the profile is created we mark it listed=public if a mere
# user. Mere users cannot change the value later. Admin users can
# always set/change the value.
#
if ($action != "edit" || ISADMIN()) {
fwrite($fp, "<attribute name='profile_listed'><value>");
if (ISADMIN()) {
if (isset($formfields["profile_listed"]) &&
$formfields["profile_listed"] == "checked") {
fwrite($fp, "1");
}
else {
fwrite($fp, "0");
}
}
elseif ($action != "edit") {
fwrite($fp, ($who == "public" ? "1" : "0"));
}
fwrite($fp, "</value></attribute>\n");
}
fwrite($fp, "</value></attribute>\n");
fwrite($fp, "<attribute name='profile_shared'><value>" .
($who == "shared" ? 1 : 0) . "</value></attribute>\n");
fwrite($fp, "<attribute name='profile_public'><value>" .
......
......@@ -138,9 +138,9 @@ while ($row = mysql_fetch_array($query_result)) {
if ($public)
$privacy = "Public";
elseif ($shared)
$privacy = "Shared";
$privacy = "Site";
else
$privacy = "Private";
$privacy = "Project";
$parsed_xml = simplexml_load_string($rspec);
if ($parsed_xml &&
......
<?php
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2000-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -99,6 +99,7 @@ echo "<div id='history-body'></div>\n";
echo "<script type='text/javascript'>\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.WITHPUBLISHING = $WITHPUBLISHING;\n";
echo "</script>\n";
echo "<script type='text/plain' id='profiles-json'>\n";
echo json_encode($profiles);
......
......@@ -401,6 +401,9 @@ class Profile
function CanView($user) {
return $this->CanInstantiate($user);
}
function CanClone($user) {
return $this->CanInstantiate($user);
}
function CanDelete($user) {
# Want to know if the project is APT or Cloud/Emulab. APT projects
# may not delete profiles (yet).
......
......@@ -38,6 +38,7 @@ $GOOGLEUA = 'UA-45161989-1';
# See tbauth.php3
$CHANGEPSWD_PAGE= "changepswd.php";
$MAXGUESTINSTANCES = 10;
$WITHPUBLISHING = 0;
#
# Global flag to disable accounts. We do this on some pages which
......
......@@ -111,6 +111,7 @@ echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.HISTORY = $history;\n";
echo " window.ISPPPROFILE = $ispp;\n";
echo " window.WITHPUBLISHING = $WITHPUBLISHING;\n";
echo "</script>\n";
echo "<script src='js/lib/codemirror-min.js'></script>\n";
......
......@@ -89,6 +89,10 @@ function Do_GetInstanceStatus()
$blob["status"] = $instance->status();
# Indicate that we have a manifest and so we can view it.
$blob["havemanifest"] = ($instance->manifest() ? 1 : 0);
# Provide PublicURL so that we can show the sliver info button
if ($instance->public_url()) {
$blob["publicURL"] = $instance->public_url();
}
$webtask = WebTask::LookupByObject($instance->uuid());
if ($webtask) {
......@@ -377,7 +381,13 @@ function Do_ConsoleURL()
$webtask = WebTask::Lookup($webtask_id);
if ($retval == 0) {
SPITAJAX_RESPONSE($webtask->TaskValue("value"));
$taskdata = $webtask->TaskData();
$blob = array();
$blob["url"] = $taskdata["url"];
if (isset($taskdata["password"])) {
$blob["password"] = $taskdata["password"];
}
SPITAJAX_RESPONSE($blob);
$webtask->Delete();
return;
}
......
<?php
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2000-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -120,8 +120,8 @@ if ($instance->profile_id()) {
$this_user->idx() == $creator->idx() &&
$this_user->idx() == $profile->creator_idx()) ||
ISADMIN() ? 1 : 0);
$canclone = (($profile->published() && isset($this_user) &&
$this_user->idx() == $creator->idx()) ||
$canclone = ((isset($this_user) &&
$profile->CanClone($this_user)) ||
ISADMIN() ? 1 : 0);
$public_url = ($instance->public_url() ?
"'" . $instance->public_url() . "'" : "null");
......
......@@ -37,47 +37,26 @@
<td class='format-date' style='word-wrap:break-word;'>
<%- formfields.profile_created %></td>
</tr>
<% if (withpublishing) { %>
<tr>
<td>Published:</td>
<% if (formfields.profile_published != "") { %>
<td id='profile_published' class='format-date'>
<%- formfields.profile_published %></td>
<% } else { %>
<td class='text-danger'>Not Published!</td>
<% } %>
</tr>
<tr>
<td>Version&nbspURL:</td>
<td><input href='<%- formfields.profile_version_url %>'
onClick="this.select();"
readonly
data-toggle="popover" data-html='true'
data-content="Anyone with this URL can instantiate this
<b>version</b> of your profile."
data-triger='hover'
value='<%- formfields.profile_version_url %>'>
</td>
</tr>
<tr>
<td>Profile&nbspURL:</td>
<td><input href='<%- formfields.profile_profile_url %>'
onClick="this.select();"
readonly
data-toggle="popover" data-html='true'
data-content="This URL instantiates the
most recently <b>published</b> version of your profile."
data-triger='hover'
value='<%- formfields.profile_profile_url %>'>
</td>
</tr>
<% } %>
</table>
<% if (history) { %>
<a class='btn btn-info btn-xs pull-left'
id='profile_history_button'
style='margin-right: 10px; font-size: 12px'
href='profile-history.php?uuid=<%= profile_uuid %>'
type='button'>Version History
</a>
<% } %>
<% if (activity) { %>
<a class='btn btn-info btn-xs pull-left'
id='profile_activity_button'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="View a history of when your profile was
instantiated."
style='margin-right: 10px; font-size: 12px'
href='profile-activity.php?uuid=<%= profile_uuid %>'
type='button'>Activity
......@@ -206,7 +185,8 @@
type='button'
style='margin-right: 10px;'
id='show_source_modal_button'>
Source</button>
<% if (viewing && canmodify) { %>Edit <% } %>Source
</button>
<button class='btn btn-primary btn-xs hidden'
type='button'
style='margin-right: 10px;'
......@@ -283,6 +263,7 @@
</div>
<!-- Public listing checkbox -->
<% if (isadmin) { %>
<div class='row'>
<div class='col-sm-10 col-sm-offset-2'>
<div class='checkbox format-me' data-compact='yep'>
......@@ -297,6 +278,7 @@
</div>
</div>
</div>
<% } %>
<!-- Permission checkboxes. -->
<div class='row'>
......@@ -308,6 +290,7 @@
<div class='row'>
<div class='col-sm-9 col-sm-offset-3'>
<div class='format-me' data-key='profile_who'>
<% if (1) { %>
<div class='radio'>
<label>
<input type='radio' name='formfields[profile_who]'
......@@ -315,9 +298,15 @@
<% if (formfields.profile_who == "public") {
%>checked<% } %>
value='public'>
<em>Anyone</em> on the internet (guest users)
<% if (iscloud) { %>
<em>Anyone</em>
<% } else { %>
<em>Anyone</em> on the internet
<% } %>
</label>
</div>
<% } %>
<% if (0) { %>
<div class='radio'>