Commit 1e5b0c88 authored by Leigh Stoller's avatar Leigh Stoller

Several changes:

* Add a "lockout" checkbox to the red-dot instance view. When checked it
  prevents all future extension requests (no pad time granted either). This
  is an active checkbox; backend ajax call when you check/uncheck.

* Add Created to the instance view panel.

* Most of the snapshot node changes, but hidden for now until I get more
  testing done. This includes the modal and ajax changes, but not the
  backend changes (still in my devel tree).

* Minor changes to date formatting.

* More details in the extension email.
parent 7390f752
......@@ -32,6 +32,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
var statusTemplate = _.template(statusString);
var terminateTemplate = _.template(terminateString);
var lastStatus = "";
var lockout = 0;
var lockdown = 0;
var lockdown_code = "";
var EMULAB_NS = "http://www.protogeni.net/resources/rspec/ext/emulab/1";
......@@ -47,6 +48,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
dossh = window.APT_OPTIONS.dossh;
extend = window.APT_OPTIONS.extend || null;
profile_uuid = window.APT_OPTIONS.profileUUID;
lockout = window.APT_OPTIONS.lockout;
lockdown = window.APT_OPTIONS.lockdown;
lockdown_code= uuid.substr(2, 5);
var instanceStatus = window.APT_OPTIONS.instanceStatus;
......@@ -65,11 +67,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sliceURN: window.APT_OPTIONS.sliceURN,
sliceExpires: window.APT_OPTIONS.sliceExpires,
sliceExpiresText: window.APT_OPTIONS.sliceExpiresText,
sliceCreated: window.APT_OPTIONS.sliceCreated,
creatorUid: window.APT_OPTIONS.creatorUid,
creatorEmail: window.APT_OPTIONS.creatorEmail,
registered: window.APT_OPTIONS.registered,
isadmin: window.APT_OPTIONS.isadmin,
errorURL: errorURL,
lockout: lockout,
lockdown: lockdown,
lockdown_code: lockdown_code,
// The status panel starts out collapsed.
......@@ -84,6 +88,14 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
$('#oneonly_div').html(oneonlyString);
$('#approval_div').html(approvalString);
// Format dates with moment before display.
$('.format-date').each(function() {
var date = $.trim($(this).html());
if (date != "") {
$(this).html(moment($(this).html()).format("lll"));
}
});
//
// Look at initial status to determine if we show the progress bar.
//
......@@ -155,13 +167,6 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
'&snapuuid=' + uuid);
});
// Handler for the Snapshot confirm button.
$('button#snapshot_confirm').click(function (event) {
event.preventDefault();
sup.HideModal('#snapshot_modal');
StartSnapshot();
});
// If we got a publicURL, set the href and show the button.
if (window.APT_OPTIONS.publicURL) {
ShowSliverInfo(window.APT_OPTIONS.publicURL);
......@@ -217,6 +222,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
clearTimeout(popover_timer);
}).click(function(){
clearTimeout(popover_timer);
DoSnapshotNode();
});
// Terminate an experiment.
......@@ -256,6 +262,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
xmlthing.done(callback);
});
// lockout change event handler.
$('#lockout_checkbox').change(function() {
DoLockout($(this).is(":checked"));
});
/*
* Attach an event handler to the profile status collapse.
* We want to change the text inside the collapsed view
......@@ -514,7 +525,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
// Reformat in local time and show the user.
var local_date = new Date(when);
$("#quickvm_expires").html(moment(when).calendar());
$("#quickvm_expires").html(moment(when).format('lll'));
// Countdown also based on local time.
target_date = local_date.getTime();
......@@ -599,12 +610,31 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sup.SpitOops("oops", message);
return;
}
$("#quickvm_expires").html(moment(json.value).calendar());
$("#quickvm_expires").html(moment(json.value).format('lll'));
// Reset the countdown clock.
StartCountdownClock.reset = json.value;
}
//
// Request lockout set/clear.
//
function DoLockout(lockout)
{
lockout = (lockout ? 1 : 0);
var callback = function(json) {
if (json.code) {
alert("Failed to change lockout: " + json.value);
return;
}
}
var xmlthing = sup.CallServerMethod(ajaxurl, "status", "Lockout",
{"uuid" : uuid,
"lockout" : lockout});
xmlthing.done(callback);
}
//
// Request a refresh from the backend cluster, to see if the sliverstatus
// has changed.
......@@ -884,6 +914,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
}
DoReload(client_id);
}
else if ($(e.target).text() == "Snapshot") {
if (isguest) {
alert("Only registered users can snapshot nodes");
return;
}
DoSnapshotNode(client_id);
}
$('#context').contextmenu('destroy');
}
})
......@@ -912,11 +949,12 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
" data-toggle='dropdown'> " +
" <span class='glyphicon glyphicon-cog'></span> " +
" </button> " +
" <ul class='dropdown-menu' role='menu'> " +
" <ul class='dropdown-menu text-left' role='menu'> " +
" <li><a href='#' name='shell'>Shell</a></li> " +
" <li><a href='#' name='console'>Console</a></li> " +
" <li><a href='#' name='reboot'>Reboot</a></li> " +
" <li><a href='#' name='reload'>Reload</a></li> " +
" <li class=hidden><a href='#' name='snapshot'>Snapshot</a></li> " +
" </ul>" +
" </div>" +
" </td>" +
......@@ -1063,6 +1101,20 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
return false;
});
//
// And a handler for the snapshot action.
//
$('#listview-row-' + node + ' [name=snapshot]')
.click(function (e) {
e.preventDefault();
if (isguest) {
alert("Only registered users can snapshot nodes");
return false;
}
DoSnapshotNode(node);
return false;
});
nodecount++;
});
......@@ -1165,8 +1217,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
//
// Request to start a snapshot. This assumes a single node of course.
//
function StartSnapshot()
function StartSnapshot(node_id, update_profile)
{
if (node_id === undefined) {
node_id = "";
}
sup.ShowModal('#waitwait-modal');
var callback = function(json) {
......@@ -1180,13 +1235,56 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
}
ShowProgressModal();
}
var xmlthing = sup.CallServerMethod(ajaxurl,
"status",
"SnapShot",
{"uuid" : uuid});
var xmlthing =
sup.CallServerMethod(ajaxurl, "status", "SnapShot",
{"uuid" : uuid,
"node_id" : node_id,
"update_profile" : update_profile});
xmlthing.done(callback);
}
//
// This is for snapshot of a single node profile, or a specific
// node in a multi-node profile.
//
function DoSnapshotNode(node_id)
{
// Default to update unless checkbox says otherwise.
var update_profile = 1;
//
// Snapshot specific node from the context menu. We give the
// the user some extra options in confirm modal.
//
if (node_id) {
// Default to checked any time we show the modal.
$('#snapshot_update_profile').prop("checked", true);
$('#snapshot_update_profile_div').removeClass("hidden");
}
else {
$('#snapshot_update_profile_div').addClass("hidden");
}
sup.ShowModal('#snapshot_modal');
// Handler for the Snapshot confirm button.
$('button#snapshot_confirm').bind("click.snapshot", function (event) {
event.preventDefault();
$('button#snapshot_confirm').unbind("click.snapshot");
if (node_id && $('#snapshot_update_profile').is(':checked')) {
update_profile = 1;
}
sup.HideModal('#snapshot_modal');
StartSnapshot(node_id, update_profile);
});
// Handler for hide modal to unbind the click handler.
$('#snapshot_modal').on('hidden.bs.modal', function (event) {
$(this).unbind(event);
$('button#snapshot_confirm').unbind("click.snapshot");
});
}
//
// User clicked on a node, so we want to create a tab to hold
// the ssh tab with a panel in it, and then call StartSSH above
......
......@@ -303,10 +303,20 @@ function Do_RequestExtension()
else {
$diff = $expires_time - time();
$cdiff = time() - $created_time;
#
# If admin lockout, we are refusing any more free time.
#
if ($instance->extension_lockout()) {
$needapproval = 1;
$granted = 0;
$message = "because you are not allowed any more ".
"free extensions";
}
#
# After a month, all extension requests require admin approval.
#
if ($cdiff > (3600 * 24 * $autoextend_maxage)) {
elseif ($cdiff > (3600 * 24 * $autoextend_maxage)) {
$needapproval = 1;
$granted = 2;
$message = "because it was started more then ".
......@@ -369,7 +379,9 @@ function Do_RequestExtension()
}
# Refresh.
$slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
$new_expires = gmdate("Y-m-d\TH:i:s\Z", strtotime($slice->expires()));
$new_expires = date("Y-m-d H:i:s T", strtotime($slice->expires()));
$created = date("Y-m-d H:i:s T", strtotime($instance->created()));
list($cluster) = Instance::ParseURN($instance->aggregate_urn());
SPITAJAX_RESPONSE($new_expires);
......@@ -378,7 +390,9 @@ function Do_RequestExtension()
"A request to extend your experiment was made and ".
"granted.\n".
"Your reason was:\n\n". $reason . "\n\n".
"Your experiment will now expire at $new_expires.\n\n\n".
"Your experiment was started on $created\n".
"Your experiment will now expire at $new_expires\n".
"It is running on $cluster\n\n\n".
"$APTBASE/status.php?uuid=$uuid\n",
"From: $APTMAIL\n" .
"BCC: $TBMAIL_OPS");
......@@ -420,9 +434,10 @@ function needAdminApproval($wanted, $granted, $reason, $message)
# Refresh.
$slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
$new_expires = date("Y-m-d H:i:s",
$new_expires = date("Y-m-d H:i:s T",
strtotime($slice->expires()) + ($howlong * 3600 * 24));
$created = date("Y-m-d H:i:s", strtotime($instance->created()));
$created = date("Y-m-d H:i:s T", strtotime($instance->created()));
list($cluster) = Instance::ParseURN($instance->aggregate_urn());
$instance->SendEmail($APTMAIL,
"Experiment Extension Request: $uuid",
......@@ -434,7 +449,7 @@ function needAdminApproval($wanted, $granted, $reason, $message)
$reason . "\n\n".
"This experiment was started on $created\n".
"Granting the request would set the expiration to $new_expires\n".
"It is running on ". $instance->aggregate_urn() . "\n".
"It is running on $cluster\n".
"\n\n". $url . "\n\n",
"From: " . $creator->email());
......@@ -443,8 +458,8 @@ function needAdminApproval($wanted, $granted, $reason, $message)
# XXX
SPITAJAX_ERROR(2, "Your request requires admininstrator approval".
($message ? " because $message" : "") . ". " .
"You will receive email when your ".
"request is granted. Thanks!");
"You will receive email if/when your ".
"request is granted (or denied). Thanks!");
return;
}
......@@ -540,13 +555,28 @@ function Do_Snapshot()
SPITAJAX_ERROR(1, "Not your profile to change. Clone first!");
return;
}
$optargs = "";
if (isset($ajax_args["node_id"])) {
$node_id = $ajax_args["node_id"];
if (!TBvalid_vnode_id($node_id)) {
SPITAJAX_ERROR(1, "Bad node id");
return;
}
$optargs .= " -s $node_id ";
if (isset($ajax_args["update_profile"]) &&
$ajax_args["update_profile"]) {
$optargs .= " -u all ";
}
}
#
# Call out to the backend.
#
$webtask_id = WebTask::GenerateID();
$retval = SUEXEC($this_user->uid(), "nobody",
"webmanage_instance -t $webtask_id -- snapshot $uuid",
"webmanage_instance -t $webtask_id -- ".
" snapshot $optargs $uuid",
SUEXEC_ACTION_IGNORE);
$webtask = WebTask::Lookup($webtask_id);
......@@ -768,6 +798,43 @@ function Do_Reload()
Do_RebootOrReload("reload");
}
#
# Set or clear the lockout flag
#
function Do_Lockout()
{
global $this_user;
global $ajax_args;
$this_idx = $this_user->uid_idx();
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
if (!isset($ajax_args["lockout"])) {
SPITAJAX_ERROR(1, "Missing lockout value");
return;
}
$uuid = $ajax_args["uuid"];
$instance = Instance::Lookup($uuid);
if (!$instance) {
SPITAJAX_ERROR(1, "Unknown instance uuid");
return;
}
if (!ISADMIN()) {
SPITAJAX_ERROR(1, "Not enough permission.");
return;
}
$lockout = ($ajax_args["lockout"] ? 1 : 0);
if (!DBQueryWarn("update apt_instances set extension_adminonly='$lockout' ".
"where uuid='$uuid'")) {
SPITAJAX_ERROR(1, "Database failure.");
return;
}
SPITAJAX_RESPONSE("Success");
}
# Local Variables:
# mode:php
# End:
......
......@@ -139,11 +139,13 @@ if ($slice) {
$slice_urn = $slice->urn();
$slice_expires = DateStringGMT($slice->expires());
$slice_expires_text = gmdate("m-d\TH:i\Z", strtotime($slice->expires()));
$slice_created = DateStringGMT($instance->created());
}
else {
$slice_urn = "";
$slice_expires = "";
$slice_expires_text = "";
$slice_created = "";
}
$registered = (isset($this_user) ? "true" : "false");
$snapping = 0;
......@@ -153,6 +155,7 @@ $lockdown = ($instance->admin_lockdown() ||
$instance->user_lockdown() ? 1 : 0);
$extension_reason= ($instance->extension_reason() ?
CleanString($instance->extension_reason()) : "");
$lockout = $instance->extension_lockout();
#
# We give ssh to the creator (real user or guest user).
......@@ -193,6 +196,7 @@ echo " window.APT_OPTIONS.profilePublic = " . $profile_public . ";\n";
echo " window.APT_OPTIONS.sliceURN = '" . $slice_urn . "';\n";
echo " window.APT_OPTIONS.sliceExpires = '" . $slice_expires . "';\n";
echo " window.APT_OPTIONS.sliceExpiresText = '" . $slice_expires_text . "';\n";
echo " window.APT_OPTIONS.sliceCreated = '" . $slice_created . "';\n";
echo " window.APT_OPTIONS.creatorUid = '" . $creator_uid . "';\n";
echo " window.APT_OPTIONS.creatorEmail = '" . $creator_email . "';\n";
echo " window.APT_OPTIONS.registered = $registered;\n";
......@@ -204,6 +208,7 @@ echo " window.APT_OPTIONS.oneonly = $oneonly;\n";
echo " window.APT_OPTIONS.dossh = $dossh;\n";
echo " window.APT_OPTIONS.publicURL = $public_url;\n";
echo " window.APT_OPTIONS.lockdown = $lockdown;\n";
echo " window.APT_OPTIONS.lockout = $lockout;\n";
echo " window.APT_OPTIONS.AJAXURL = 'server-ajax.php';\n";
if (isset($extend) && $extend != "") {
echo " window.APT_OPTIONS.extend = $extend;\n";
......
......@@ -51,11 +51,16 @@
<%- profileName %></a></td>
</tr>
<% } %>
<tr>
<td class='border-none'>Created:</td>
<td class='border-none format-date'><%- sliceCreated %></td>
</tr>
<tr>
<td class='border-none'>Expires:</td>
<td class='border-none'>
<span id='instance_expiration'>
<span id='quickvm_expires'><%- sliceExpiresText %></span>
<span id='quickvm_expires' class='format-date'>
<%- sliceExpires %></span>
(<span id='quickvm_countdown'></span>)
</span>
<% if (isadmin && lockdown) { %>
......@@ -72,14 +77,20 @@
type='button'>Sliver
</a>
</div>
<% if (isadmin) { %>
<div class='pull-left'>
<label class="checkbox-inline" style='margin-right: 10px;'>
<input type="checkbox" id="lockout_checkbox"
<% if (lockout) { %>checked<% } %> >Locked</label>
</div>
<% } %>
<div class='pull-right'>
<% if (registered) { %>
<button class='btn btn-xs btn-primary hidden' disabled
id='clone_button' type=button>
Clone</button>
<button class='btn btn-xs btn-primary hidden' disabled
id='snapshot_button' type=button
data-toggle='modal' data-target='#snapshot_modal'>
id='snapshot_button' type=button>
Snapshot</button>
<% if (profileUUID && profileUUID != "") { %>
<a class='btn btn-xs btn-primary'
......@@ -223,14 +234,22 @@
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button>
<center><h3>Confirm to Snapshot</h3>
</div>
<div class='modal-body'>
Performing a snapshot will create a new disk image and
modify the profile. This is okay, but please confirm.
<div id='snapshot_update_profile_div' class='hidden'>
<center>
<b>Update Profile?</b>
<br>
<input type=checkbox
id='snapshot_update_profile' checked value=yes>
</center>
Check this box if you want us to update your profile to use the
new disk image. All nodes running the same image will be
updated. If you uncheck the box, you will need to modify the
profile source code yourself.
<br>
</div>
<div id='wholedisk_div' class='hidden'>
<br>
<center>
......@@ -241,11 +260,16 @@
need to do this if you put data into one of the unused
partitions on the local disk. <b><em>Do not check this box if you
do not know what this means!</em></b>
<br>
</div>
<br>
<button class='btn btn-danger btn-sm align-center' type='button'
id='snapshot_confirm'>Confirm</button></center>
<center>
<button style='margin-right: 20px;'
class='btn btn-primary btn-sm'
data-dismiss='modal' aria-hidden='true'>
Cancel</button>
<button class='btn btn-danger btn-sm'; type='button'
id='snapshot_confirm'>
Confirm</button>
</center>
</div>
</div>
</div>
......@@ -257,6 +281,7 @@
<li><a tabindex="-1">Console</a></li>
<li><a tabindex="-1">Reboot</a></li>
<li><a tabindex="-1">Reload</a></li>
<li class=hidden><a tabindex="-1">Snapshot</a></li>
</ul>
</div>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment