Commit 1e5b0c88 authored by Leigh B Stoller's avatar Leigh B 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, ...@@ -32,6 +32,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
var statusTemplate = _.template(statusString); var statusTemplate = _.template(statusString);
var terminateTemplate = _.template(terminateString); var terminateTemplate = _.template(terminateString);
var lastStatus = ""; var lastStatus = "";
var lockout = 0;
var lockdown = 0; var lockdown = 0;
var lockdown_code = ""; var lockdown_code = "";
var EMULAB_NS = "http://www.protogeni.net/resources/rspec/ext/emulab/1"; var EMULAB_NS = "http://www.protogeni.net/resources/rspec/ext/emulab/1";
...@@ -47,6 +48,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -47,6 +48,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
dossh = window.APT_OPTIONS.dossh; dossh = window.APT_OPTIONS.dossh;
extend = window.APT_OPTIONS.extend || null; extend = window.APT_OPTIONS.extend || null;
profile_uuid = window.APT_OPTIONS.profileUUID; profile_uuid = window.APT_OPTIONS.profileUUID;
lockout = window.APT_OPTIONS.lockout;
lockdown = window.APT_OPTIONS.lockdown; lockdown = window.APT_OPTIONS.lockdown;
lockdown_code= uuid.substr(2, 5); lockdown_code= uuid.substr(2, 5);
var instanceStatus = window.APT_OPTIONS.instanceStatus; var instanceStatus = window.APT_OPTIONS.instanceStatus;
...@@ -65,11 +67,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -65,11 +67,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sliceURN: window.APT_OPTIONS.sliceURN, sliceURN: window.APT_OPTIONS.sliceURN,
sliceExpires: window.APT_OPTIONS.sliceExpires, sliceExpires: window.APT_OPTIONS.sliceExpires,
sliceExpiresText: window.APT_OPTIONS.sliceExpiresText, sliceExpiresText: window.APT_OPTIONS.sliceExpiresText,
sliceCreated: window.APT_OPTIONS.sliceCreated,
creatorUid: window.APT_OPTIONS.creatorUid, creatorUid: window.APT_OPTIONS.creatorUid,
creatorEmail: window.APT_OPTIONS.creatorEmail, creatorEmail: window.APT_OPTIONS.creatorEmail,
registered: window.APT_OPTIONS.registered, registered: window.APT_OPTIONS.registered,
isadmin: window.APT_OPTIONS.isadmin, isadmin: window.APT_OPTIONS.isadmin,
errorURL: errorURL, errorURL: errorURL,
lockout: lockout,
lockdown: lockdown, lockdown: lockdown,
lockdown_code: lockdown_code, lockdown_code: lockdown_code,
// The status panel starts out collapsed. // The status panel starts out collapsed.
...@@ -84,6 +88,14 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -84,6 +88,14 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
$('#oneonly_div').html(oneonlyString); $('#oneonly_div').html(oneonlyString);
$('#approval_div').html(approvalString); $('#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. // Look at initial status to determine if we show the progress bar.
// //
...@@ -155,13 +167,6 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -155,13 +167,6 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
'&snapuuid=' + uuid); '&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 we got a publicURL, set the href and show the button.
if (window.APT_OPTIONS.publicURL) { if (window.APT_OPTIONS.publicURL) {
ShowSliverInfo(window.APT_OPTIONS.publicURL); ShowSliverInfo(window.APT_OPTIONS.publicURL);
...@@ -217,6 +222,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -217,6 +222,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
clearTimeout(popover_timer); clearTimeout(popover_timer);
}).click(function(){ }).click(function(){
clearTimeout(popover_timer); clearTimeout(popover_timer);
DoSnapshotNode();
}); });
// Terminate an experiment. // Terminate an experiment.
...@@ -256,6 +262,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -256,6 +262,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
xmlthing.done(callback); xmlthing.done(callback);
}); });
// lockout change event handler.
$('#lockout_checkbox').change(function() {
DoLockout($(this).is(":checked"));
});
/* /*
* Attach an event handler to the profile status collapse. * Attach an event handler to the profile status collapse.
* We want to change the text inside the collapsed view * We want to change the text inside the collapsed view
...@@ -514,7 +525,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -514,7 +525,7 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
// Reformat in local time and show the user. // Reformat in local time and show the user.
var local_date = new Date(when); 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. // Countdown also based on local time.
target_date = local_date.getTime(); target_date = local_date.getTime();
...@@ -599,12 +610,31 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -599,12 +610,31 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
sup.SpitOops("oops", message); sup.SpitOops("oops", message);
return; return;
} }
$("#quickvm_expires").html(moment(json.value).calendar()); $("#quickvm_expires").html(moment(json.value).format('lll'));
// Reset the countdown clock. // Reset the countdown clock.
StartCountdownClock.reset = json.value; 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 // Request a refresh from the backend cluster, to see if the sliverstatus
// has changed. // has changed.
...@@ -884,6 +914,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -884,6 +914,13 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
} }
DoReload(client_id); 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'); $('#context').contextmenu('destroy');
} }
}) })
...@@ -912,11 +949,12 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -912,11 +949,12 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
" data-toggle='dropdown'> " + " data-toggle='dropdown'> " +
" <span class='glyphicon glyphicon-cog'></span> " + " <span class='glyphicon glyphicon-cog'></span> " +
" </button> " + " </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='shell'>Shell</a></li> " +
" <li><a href='#' name='console'>Console</a></li> " + " <li><a href='#' name='console'>Console</a></li> " +
" <li><a href='#' name='reboot'>Reboot</a></li> " + " <li><a href='#' name='reboot'>Reboot</a></li> " +
" <li><a href='#' name='reload'>Reload</a></li> " + " <li><a href='#' name='reload'>Reload</a></li> " +
" <li class=hidden><a href='#' name='snapshot'>Snapshot</a></li> " +
" </ul>" + " </ul>" +
" </div>" + " </div>" +
" </td>" + " </td>" +
...@@ -1063,6 +1101,20 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -1063,6 +1101,20 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
return false; 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++; nodecount++;
}); });
...@@ -1165,8 +1217,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -1165,8 +1217,11 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
// //
// Request to start a snapshot. This assumes a single node of course. // 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'); sup.ShowModal('#waitwait-modal');
var callback = function(json) { var callback = function(json) {
...@@ -1180,13 +1235,56 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal, ...@@ -1180,13 +1235,56 @@ function (_, sup, moment, marked, UriTemplate, ShowImagingModal,
} }
ShowProgressModal(); ShowProgressModal();
} }
var xmlthing = sup.CallServerMethod(ajaxurl, var xmlthing =
"status", sup.CallServerMethod(ajaxurl, "status", "SnapShot",
"SnapShot", {"uuid" : uuid,
{"uuid" : uuid}); "node_id" : node_id,
"update_profile" : update_profile});
xmlthing.done(callback); 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 // 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 // the ssh tab with a panel in it, and then call StartSSH above
......
...@@ -303,10 +303,20 @@ function Do_RequestExtension() ...@@ -303,10 +303,20 @@ function Do_RequestExtension()
else { else {
$diff = $expires_time - time(); $diff = $expires_time - time();
$cdiff = time() - $created_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. # After a month, all extension requests require admin approval.
# #
if ($cdiff > (3600 * 24 * $autoextend_maxage)) { elseif ($cdiff > (3600 * 24 * $autoextend_maxage)) {
$needapproval = 1; $needapproval = 1;
$granted = 2; $granted = 2;
$message = "because it was started more then ". $message = "because it was started more then ".
...@@ -369,7 +379,9 @@ function Do_RequestExtension() ...@@ -369,7 +379,9 @@ function Do_RequestExtension()
} }
# Refresh. # Refresh.
$slice = GeniSlice::Lookup("sa", $instance->slice_uuid()); $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); SPITAJAX_RESPONSE($new_expires);
...@@ -378,7 +390,9 @@ function Do_RequestExtension() ...@@ -378,7 +390,9 @@ function Do_RequestExtension()
"A request to extend your experiment was made and ". "A request to extend your experiment was made and ".
"granted.\n". "granted.\n".
"Your reason was:\n\n". $reason . "\n\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", "$APTBASE/status.php?uuid=$uuid\n",
"From: $APTMAIL\n" . "From: $APTMAIL\n" .
"BCC: $TBMAIL_OPS"); "BCC: $TBMAIL_OPS");
...@@ -420,9 +434,10 @@ function needAdminApproval($wanted, $granted, $reason, $message) ...@@ -420,9 +434,10 @@ function needAdminApproval($wanted, $granted, $reason, $message)
# Refresh. # Refresh.
$slice = GeniSlice::Lookup("sa", $instance->slice_uuid()); $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)); 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, $instance->SendEmail($APTMAIL,
"Experiment Extension Request: $uuid", "Experiment Extension Request: $uuid",
...@@ -434,7 +449,7 @@ function needAdminApproval($wanted, $granted, $reason, $message) ...@@ -434,7 +449,7 @@ function needAdminApproval($wanted, $granted, $reason, $message)
$reason . "\n\n". $reason . "\n\n".
"This experiment was started on $created\n". "This experiment was started on $created\n".
"Granting the request would set the expiration to $new_expires\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", "\n\n". $url . "\n\n",
"From: " . $creator->email()); "From: " . $creator->email());
...@@ -443,8 +458,8 @@ function needAdminApproval($wanted, $granted, $reason, $message) ...@@ -443,8 +458,8 @@ function needAdminApproval($wanted, $granted, $reason, $message)
# XXX # XXX
SPITAJAX_ERROR(2, "Your request requires admininstrator approval". SPITAJAX_ERROR(2, "Your request requires admininstrator approval".
($message ? " because $message" : "") . ". " . ($message ? " because $message" : "") . ". " .
"You will receive email when your ". "You will receive email if/when your ".
"request is granted. Thanks!"); "request is granted (or denied). Thanks!");
return; return;
} }
...@@ -540,13 +555,28 @@ function Do_Snapshot() ...@@ -540,13 +555,28 @@ function Do_Snapshot()
SPITAJAX_ERROR(1, "Not your profile to change. Clone first!"); SPITAJAX_ERROR(1, "Not your profile to change. Clone first!");
return; 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. # Call out to the backend.
# #
$webtask_id = WebTask::GenerateID(); $webtask_id = WebTask::GenerateID();
$retval = SUEXEC($this_user->uid(), "nobody", $retval = SUEXEC($this_user->uid(), "nobody",
"webmanage_instance -t $webtask_id -- snapshot $uuid", "webmanage_instance -t $webtask_id -- ".
" snapshot $optargs $uuid",
SUEXEC_ACTION_IGNORE); SUEXEC_ACTION_IGNORE);
$webtask = WebTask::Lookup($webtask_id); $webtask = WebTask::Lookup($webtask_id);
...@@ -768,6 +798,43 @@ function Do_Reload() ...@@ -768,6 +798,43 @@ function Do_Reload()
Do_RebootOrReload("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: # Local Variables:
# mode:php # mode:php
# End: # End:
......
...@@ -139,11 +139,13 @@ if ($slice) { ...@@ -139,11 +139,13 @@ if ($slice) {
$slice_urn = $slice->urn(); $slice_urn = $slice->urn();
$slice_expires = DateStringGMT($slice->expires()); $slice_expires = DateStringGMT($slice->expires());
$slice_expires_text = gmdate("m-d\TH:i\Z", strtotime($slice->expires())); $slice_expires_text = gmdate("m-d\TH:i\Z", strtotime($slice->expires()));
$slice_created = DateStringGMT($instance->created());
} }
else { else {
$slice_urn = ""; $slice_urn = "";
$slice_expires = ""; $slice_expires = "";
$slice_expires_text = ""; $slice_expires_text = "";
$slice_created = "";
} }
$registered = (isset($this_user) ? "true" : "false"); $registered = (isset($this_user) ? "true" : "false");
$snapping = 0; $snapping = 0;
...@@ -153,6 +155,7 @@ $lockdown = ($instance->admin_lockdown() || ...@@ -153,6 +155,7 @@ $lockdown = ($instance->admin_lockdown() ||
$instance->user_lockdown() ? 1 : 0); $instance->user_lockdown() ? 1 : 0);
$extension_reason= ($instance->extension_reason() ? $extension_reason= ($instance->extension_reason() ?
CleanString($instance->extension_reason()) : ""); CleanString($instance->extension_reason()) : "");
$lockout = $instance->extension_lockout();
# #
# We give ssh to the creator (real user or guest user). # We give ssh to the creator (real user or guest user).
...@@ -193,6 +196,7 @@ echo " window.APT_OPTIONS.profilePublic = " . $profile_public . ";\n"; ...@@ -193,6 +196,7 @@ echo " window.APT_OPTIONS.profilePublic = " . $profile_public . ";\n";
echo " window.APT_OPTIONS.sliceURN = '" . $slice_urn . "';\n"; echo " window.APT_OPTIONS.sliceURN = '" . $slice_urn . "';\n";
echo " window.APT_OPTIONS.sliceExpires = '" . $slice_expires . "';\n"; echo " window.APT_OPTIONS.sliceExpires = '" . $slice_expires . "';\n";
echo " window.APT_OPTIONS.sliceExpiresText = '" . $slice_expires_text . "';\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.creatorUid = '" . $creator_uid . "';\n";
echo " window.APT_OPTIONS.creatorEmail = '" . $creator_email . "';\n"; echo " window.APT_OPTIONS.creatorEmail = '" . $creator_email . "';\n";
echo " window.APT_OPTIONS.registered = $registered;\n"; echo " window.APT_OPTIONS.registered = $registered;\n";
...@@ -204,6 +208,7 @@ echo " window.APT_OPTIONS.oneonly = $oneonly;\n"; ...@@ -204,6 +208,7 @@ echo " window.APT_OPTIONS.oneonly = $oneonly;\n";
echo " window.APT_OPTIONS.dossh = $dossh;\n"; echo " window.APT_OPTIONS.dossh = $dossh;\n";
echo " window.APT_OPTIONS.publicURL = $public_url;\n"; echo " window.APT_OPTIONS.publicURL = $public_url;\n";
echo " window.APT_OPTIONS.lockdown = $lockdown;\n"; echo " window.APT_OPTIONS.lockdown = $lockdown;\n";
echo " window.APT_OPTIONS.lockout = $lockout;\n";
echo " window.APT_OPTIONS.AJAXURL = 'server-ajax.php';\n"; echo " window.APT_OPTIONS.AJAXURL = 'server-ajax.php';\n";
if (isset($extend) && $extend != "") { if (isset($extend) && $extend != "") {
echo " window.APT_OPTIONS.extend = $extend;\n"; echo " window.APT_OPTIONS.extend = $extend;\n";
......
...@@ -51,11 +51,16 @@ ...@@ -51,11 +51,16 @@
<%- profileName %></a></td> <%- profileName %></a></td>
</tr> </tr>
<% } %> <% } %>
<tr>
<td class='border-none'>Created:</td>
<td class='border-none format-date'><%- sliceCreated %></td>
</tr>
<tr> <tr>
<td class='border-none'>Expires:</td> <td class='border-none'>Expires:</td>
<td class='border-none'> <td class='border-none'>
<span id='instance_expiration'> <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 id='quickvm_countdown'></span>)
</span> </span>
<% if (isadmin && lockdown) { %> <% if (isadmin && lockdown) { %>
...@@ -72,14 +77,20 @@ ...@@ -72,14 +77,20 @@
type='button'>Sliver type='button'>Sliver
</a> </a>
</div> </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'> <div class='pull-right'>
<% if (registered) { %> <% if (registered) { %>
<button class='btn btn-xs btn-primary hidden' disabled <button class='btn btn-xs btn-primary hidden' disabled
id='clone_button' type=button> id='clone_button' type=button>
Clone</button> Clone</button>
<button class='btn btn-xs btn-primary hidden' disabled <button class='btn btn-xs btn-primary hidden' disabled
id='snapshot_button' type=button id='snapshot_button' type=button>
data-toggle='modal' data-target='#snapshot_modal'>
Snapshot</button> Snapshot</button>
<% if (profileUUID && profileUUID != "") { %> <% if (profileUUID && profileUUID != "") { %>
<a class='btn btn-xs btn-primary' <a class='btn btn-xs btn-primary'
...@@ -223,14 +234,22 @@ ...@@ -223,14 +234,22 @@
<div class='modal-dialog'> <div class='modal-dialog'>
<div class='modal-content'> <div class='modal-content'>
<div class='modal-header'> <div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button>
<center><h3>Confirm to Snapshot</h3> <center><h3>Confirm to Snapshot</h3>
</div> </div>
<div class='modal-body'> <div class='modal-body'>
Performing a snapshot will create a new disk image and <div id='snapshot_update_profile_div' class='hidden'>