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

Changes to the snapshot modal as discussed on 1/21/2019:

* When the profile is script based, we cannot update the profile code,
  so do not show any choices (radios), proceed as image only.

* Remove the "new profile" (3rd) option, it appears that no one at the
  meeting knew what that was about, so clearly mere users won't. Simplify!

* Rearrange the options (whole disk, account), together at the bottom.

* Swap the description and the image name inputs.

* Cleanup the help popovers and impl.
parent 7af67dbf
......@@ -2187,8 +2187,9 @@ $(function ()
* we present later depends on canclone/cansnap. But not allowed
* to clone a repo based profile.
*/
if (! ((window.APT_OPTIONS.canclone && !expinfo.repurl) ||
window.APT_OPTIONS.cansnap)) {
if (! (window.APT_OPTIONS.canclone_profile ||
window.APT_OPTIONS.canupdate_profile ||
window.APT_OPTIONS.cansnapshot)) {
return;
}
......@@ -2196,54 +2197,64 @@ $(function ()
$('button#snapshot_button').popover('hide');
DoSnapshotNode();
});
/*
* Various help buttons for snapshot choices.
*/
var visibleHelp = null;
$('#snapshot-help-button').click(function (event) {
event.preventDefault();
clearTimeout(snapshot_help_timer);
$('#snapshot-help-button').popover({
$('.snapshot-help-button').each(function () {
var target = $(this).find("span");
var which = target.data("which");
var content = null;
console.info("foo", this, which, target);
switch(which) {
case 'update-profile':
content = $('#snapshot-help-div').html();
break;
case 'copy-profile':
content = $('#clone-help-div').html();
break;
case 'image-only':
content = $('#imageonly-help-div').html();
break;
}
$(target).popover({
html: true,
content: $('#snapshot-help-div').html(),
content: content,
trigger: 'manual',
placement:'auto',
container:'body',
});
$('#snapshot-help-button').popover('show');
$('.snapshot-popover-close').on('click', function(event) {
$(this).click(function (event) {
event.preventDefault();
$('#snapshot-help-button').popover('destroy');
if (visibleHelp) {
var tmp = visibleHelp;
visibleHelp = null;
tmp.popover('hide');
// Clicked on the same one, hide it and leave.
if (tmp.data("which") == which) {
return;
}
}
$(target).popover('show');
visibleHelp = $(target);
// Bind the close button, sleazy internal stuff.
$(target).data('bs.popover').tip()
.find(".close").click(function (event) {
$(target).popover('hide');
$(this).off("click");
visibleHelp = null;
});
});
}).mouseenter(function (event) {
snapshot_help_timer = setTimeout(function() {
$('#snapshot-help-button').trigger("click");
},1000)
}).mouseleave(function(){
clearTimeout(snapshot_help_timer);
});
$('.clone-help-button').mouseenter(function (event) {
clone_help_timer = setTimeout(function() {
$(event.target).trigger("click");
},1000)
}).mouseleave(function(){
clearTimeout(clone_help_timer);
}).click(function (event) {
event.preventDefault();
clearTimeout(clone_help_timer);
var target = $('#clone-help-popover-div');
target.popover({
html: true,
content: $('#clone-help-div').html(),
trigger: 'manual',
placement:'auto',
container:'body',
});
target.popover('show');
$('.clone-popover-close').on('click', function(event) {
event.preventDefault();
target.popover('destroy');
});
});
$('#snapshot_modal input[type=radio]').on('change', function() {
switch($(this).val()) {
case 'update-profile':
......@@ -2251,16 +2262,15 @@ $(function ()
$('#snapshot-wholedisk-div').addClass("hidden");
break;
case 'copy-profile':
case 'new-profile':
$('#snapshot-name-div .new-profile').removeClass("hidden");
$('#snapshot-name-div .image-only').addClass("hidden");
$('#snapshot-name-div .copy-profile').removeClass("hidden");
$('#snapshot-name-div').removeClass("hidden");
if (wholedisk) {
$('#snapshot-wholedisk-div').removeClass("hidden");
}
break;
case 'image-only':
$('#snapshot-name-div .new-profile').addClass("hidden");
$('#snapshot-name-div .copy-profile').addClass("hidden");
$('#snapshot-name-div .image-only').removeClass("hidden");
$('#snapshot-name-div').removeClass("hidden");
if (wholedisk) {
......@@ -2271,23 +2281,30 @@ $(function ()
});
/*
* Not allowed to clone/snap repo based profiles, which means
* no choice at all, so hide everything except what name to
* use for the image.
* Hide choices in the snapshot modal per the flags.
*/
if (expinfo.repourl) {
$('#update-profile').closest(".radio").addClass("hidden");
$('#copy-profile').closest(".radio").addClass("hidden");
$('#new-profile').closest(".radio").addClass("hidden");
$('#image-only').prop("checked", true);
$('#image-only').trigger("change");
$('#image-only').closest(".radio").addClass("hidden");
$('#snapshot-radio-title').addClass("hidden");
if (isscript ||
(!window.APT_OPTIONS.canclone_profile &&
!window.APT_OPTIONS.canupdate_profile)) {
$('#snapshot-name-div .image-only').removeClass("hidden");
$('#snapshot-name-div').removeClass("hidden");
if (wholedisk) {
$('#snapshot-wholedisk-div').removeClass("hidden");
}
}
else if (!window.APT_OPTIONS.cansnap) {
$('#update-profile').closest(".radio").remove();
$('#copy-profile').prop("checked", true);
$('#copy-profile').trigger("change");
else {
if (!window.APT_OPTIONS.canclone_profile) {
$('#copy-profile-radio').remove();
}
else if (!window.APT_OPTIONS.canupdate_profile) {
$('#update-profile-radio').remove();
}
// As per the 'isscript' test above, one of these must be
// available, so make the other the default.
if (!window.APT_OPTIONS.canupdate_profile) {
$('#copy-profile').prop("checked", true);
$('#copy-profile').trigger("change");
}
}
}
......@@ -2300,8 +2317,9 @@ $(function ()
* we present later depends on canclone/cansnap. But not allowed
* to clone a repo based profile.
*/
if (! ((window.APT_OPTIONS.canclone && !expinfo.repourl) ||
window.APT_OPTIONS.cansnap)) {
if (! (window.APT_OPTIONS.canclone_profile ||
window.APT_OPTIONS.canupdate_profile ||
window.APT_OPTIONS.cansnapshot)) {
return;
}
$("#snapshot_button").removeClass("hidden");
......@@ -2341,11 +2359,11 @@ $(function ()
*/
if (Object.keys(imageablenodes).length == 1) {
/*
* If allowed to snapshot, then use the current profile name.
* If allowed to update image, then use the current profile name.
* Otherwise might as well let them choose the name.
*/
if (window.APT_OPTIONS.cansnap) {
$('#snapshot-name-div .image-only input')
if (window.APT_OPTIONS.cansnapshot) {
$('#snapshot-name-div input')
.val(expinfo.profile_name);
$('#snapshot-name-div .snapshot-name-warning')
.removeClass("hidden");
......@@ -2361,7 +2379,7 @@ $(function ()
.on("change", function (event) {
var node = $(this).val();
var name = expinfo.profile_name + "." + node;
$('#snapshot-name-div .image-only input').val(name);
$('#snapshot-name-div input').val(name);
$('#snapshot-name-div .snapshot-name-warning')
.removeClass("hidden");
});
......@@ -2395,7 +2413,7 @@ $(function ()
});
$('#confirm-update-systemimage').click(function() {
sup.HideModal('#confirm-update-systemimage-modal');
$('#snapshot_update_prepare_div').addClass("hidden");
$('#snapshot_update_prepare_option').addClass("hidden");
DoSnapshotNodeAux();
});
sup.ShowModal('#confirm-update-systemimage-modal',
......@@ -2432,14 +2450,17 @@ $(function ()
$('#snapshot_modal .choose-node-error').addClass("hidden");
// What does the user want to do?
var operation = $('#snapshot_modal input[type=radio]:checked').val();
var operation =
(isscript ? "image-only" :
$('#snapshot_modal input[type=radio]:checked').val());
var args = {"uuid" : uuid,
"node_id" : node_id,
"operation" : operation,
"update_prepare" : 0};
// Make sure we got an image/profile name.
if (operation == 'image-only') {
var name = $('#snapshot-name-div .image-only input').val();
var name = $('#snapshot-name-div input').val();
if (name == "") {
$('#snapshot-name-div .name-error')
.text("Please provide an image name");
......@@ -2450,7 +2471,7 @@ $(function ()
}
else if (operation == "copy-profile" ||
operation == "new-profile") {
var name = $('#snapshot-name-div .new-profile input').val();
var name = $('#snapshot-name-div input').val();
if (name == "") {
$('#snapshot-name-div .name-error')
.text("Please provide a profile name");
......@@ -2483,9 +2504,11 @@ $(function ()
$('#snapshot_modal').on('hidden.bs.modal', function (event) {
$(this).unbind(event);
$('button#snapshot_confirm').unbind("click.snapshot");
// Kill any popovers still showing.
$('#snapshot-help-button').popover('destroy');
$('#clone-help-popover-div').popover('destroy');
// Hide any popovers still showing.
$('.snapshot-help-button').each(function () {
var target = $(this).find("span");
target.popover('hide');
});
});
sup.ShowModal('#snapshot_modal');
......
<?php
#
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
# Copyright (c) 2000-2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -160,28 +160,34 @@ $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
$instance_status = $instance->status();
$creator_uid = $creator->uid();
$cansnapshot = ((isset($this_user) &&
$this_user->idx() == $creator->idx()) ||
ISADMIN() ? 1 : 0);
$canterminate = ((isset($this_user) &&
$instance->CanTerminate($this_user)) ||
ISADMIN() ? 1 : 0);
$cancopy_profile = 0;
$canclone_profile = 0;
$canupdate_profile = 0;
$isscript = 0;
if ($profile = Profile::Lookup($instance->profile_id(),
$instance->profile_version())) {
$cansnap = ((isset($this_user) &&
$this_user->idx() == $creator->idx() &&
$this_user->idx() == $profile->creator_idx()) ||
ISADMIN() ? 1 : 0);
$canclone = ((isset($this_user) &&
$profile->CanClone($this_user)) ||
ISADMIN() ? 1 : 0);
$canterminate = ((isset($this_user) &&
$instance->CanTerminate($this_user)) ||
ISADMIN() ? 1 : 0);
$isscript = ($profile->script() && $profile->script() != "" ? 1 : 0);
}
else {
$cansnap = 0;
$canclone = 0;
$canterminate = ((isset($this_user) &&
$instance->CanTerminate($this_user)) ||
ISADMIN() ? 1 : 0);
$isscript = 0;
#
# Not allowed to copy/clone/update a repo based profile.
#
if (!$profile->repourl()) {
$cancopy_profile = ((isset($this_user) &&
$profile->CanInstantiate($this_user)) ||
ISADMIN() ? 1 : 0);
$canclone_profile = ((isset($this_user) &&
$profile->CanClone($this_user)) ||
ISADMIN() ? 1 : 0);
$canupdate_profile = ((isset($this_user) &&
$this_user->idx() == $profile->creator_idx()) ||
ISADMIN() ? 1 : 0);
$isscript = ($profile->script() && $profile->script() != "" ? 1 : 0);
}
}
$registered = (isset($this_user) ? "true" : "false");
$snapping = 0;
......@@ -245,8 +251,10 @@ echo " window.APT_OPTIONS.registered = $registered;\n";
echo " window.APT_OPTIONS.isadmin = $isadmin;\n";
echo " window.APT_OPTIONS.isfadmin = $isfadmin;\n";
echo " window.APT_OPTIONS.isstud = $isstud;\n";
echo " window.APT_OPTIONS.cansnap = $cansnap;\n";
echo " window.APT_OPTIONS.canclone = $canclone;\n";
echo " window.APT_OPTIONS.cansnapshot = $cansnapshot;\n";
echo " window.APT_OPTIONS.canclone_profile = $canclone_profile;\n";
echo " window.APT_OPTIONS.canupdate_profile = $canupdate_profile;\n";
echo " window.APT_OPTIONS.cancopy_profile = $cancopy_profile;\n";
echo " window.APT_OPTIONS.canterminate = $canterminate;\n";
echo " window.APT_OPTIONS.wholedisk = $wholedisk;\n";
echo " window.APT_OPTIONS.snapping = $snapping;\n";
......@@ -269,6 +277,7 @@ echo "<script src='js/lib/nv.d3.js'></script>\n";
echo "<script src='js/lib/jquery-2.0.3.min.js'></script>\n";
echo "<script src='js/lib/jquery-ui.js'></script>\n";
echo "<script src='js/lib/codemirror-min.js'></script>\n";
echo "<script src='js/lib/filesize.min.js'></script>\n";
REQUIRE_UNDERSCORE();
REQUIRE_SUP();
......
......@@ -236,7 +236,7 @@ pre {
data-content="Create a disk image from one of your nodes.
Click for more info.">
Create Disk Image</button>
<% if (expinfo.profile_uuid) { %>
<% if (window.APT_OPTIONS.cancopy_profile) { %>
<a class='btn btn-xs btn-primary'
href='manage_profile.php?action=copy&uuid=<%= expinfo.profile_uuid %>'
id='copy_button' type=button
......@@ -244,11 +244,11 @@ pre {
data-delay='{"hide":1000, "show":500}'
data-html='true'
data-content="When you <em>copy</em> 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.">
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</a>
<% } %>
<% } %>
......@@ -759,109 +759,82 @@ class='fixedsize-panel with-3d-shadow with-transitions'>
<div class="text-danger hidden
snapshot-error choose-node-error"></div>
</center>
<div style="text-align: center;">
<div id='snapshot_update_prepare_div'
style="display: inline-block; width: 80%; margin-top: 10px;">
<b>Did you add any accounts or groups to your node?</b>
<input type=checkbox
id='snapshot_update_prepare' value=yes>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
data-delay='{"hide":500}'
data-content="Check this box if you installed any software
that added new
users or groups. For example, if you installed
<tt>mysql</tt>, a new <tt>mysqld</tt> user was
added, and you want to make sure that this
user account is retained in the new image.
If you are not sure, please
<em><b>ask us first</b></em> since
checking this box needlessly can have
negative side effects.">
<span class='glyphicon glyphicon-question-sign'
style='margin-bottom: 4px;'></span>
</a>
</div>
</div>
</div>
<% var imageonly = 0;
if (window.APT_OPTIONS.isscript ||
(!window.APT_OPTIONS.canclone_profile &&
!window.APT_OPTIONS.canupdate_profile)) { imageonly = 1; } %>
<div class="fhide step2" data-step="2">
<div style="text-align: center; margin-top: 10px;">
<% if (! imageonly) { %>
<div id="snapshot-radios"
style="text-align: center; margin-top: 10px;">
<div id="snapshot-radio-title">
<b>What would you like to do with this image?</b>
<br>
</div>
<div id="clone-help-popover-div"></div>
<div id="snapshot-help-popover-div"></div>
<div style="display: inline-block; text-align: left">
<div class="radio">
<div class="radio" id="update-profile-radio">
<label>
<input type="radio" name="whattodo"
id="update-profile" value="update-profile" checked>
Update the current profile to use it.
<a href='#' class='btn btn-xs' id='snapshot-help-button'>
<a href='#' class='btn btn-xs snapshot-help-button'>
<span class='glyphicon glyphicon-question-sign'
data-which="update-profile"
style='margin-bottom: 4px;'></span>
</a>
</label>
</div>
<div class="radio">
<div class="radio" id="copy-profile-radio">
<label>
<input type="radio" name="whattodo"
id="copy-profile" value="copy-profile">
Make a copy of the current profile, updated to use the new
image.
<a href='#' class='btn btn-xs clone-help-button'>
<a href='#' class='btn btn-xs snapshot-help-button'>
<span class='glyphicon glyphicon-question-sign'
data-which="copy-profile"
style='margin-bottom: 4px;'></span>
</a>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="whattodo"
id="new-profile" value="new-profile">
Create a brand new (empty) profile, using the new image.
<a href='#' class='btn btn-xs clone-help-button'>
<span class='glyphicon glyphicon-question-sign'
style='margin-bottom: 4px;'></span>
</a>
</label>
</div>
<div class="radio">
<div class="radio" id="image-only-radio">
<label>
<input type="radio" name="whattodo"
id="image-only" value="image-only">
Just create a disk image, I will decide what to do with it
later.
<a href='#' class='btn btn-xs snapshot-help-button'>
<span class='glyphicon glyphicon-question-sign'
data-which="image-only"
style='margin-bottom: 4px;'></span>
</a>
</label>
</div>
<div id='snapshot-description-div'>
<center>
<b>Please briefly describe your image:</b>
<br>
</center>
<textarea style="width: 100%;"
placeholder="Optional, but very handy when looking at a list of all your images"
rows=2 type='textarea'></textarea>
<div class="text-danger hidden
snapshot-error description-error"
style="text-align: center;">
</div>
</div>
</div>
</div>
<div id='snapshot-name-div' class='hidden'>
<% } %>
<div id='snapshot-name-div'
<% if (imageonly) { %>
style="margin-top: 10px;"
<% } %>
class='hidden'>
<center class="image-only hidden">
<b>Please tell us what name to use for your new image</b>
<br>
<input class="image-name"
placeholder="alphanumeric please" type="text">
<% if (imageonly) { %>
<a href='#' class='btn btn-xs snapshot-help-button'>
<span class='glyphicon glyphicon-question-sign'
data-which="image-only"
style='margin-bottom: 4px;'></span>
</a>
<% } %>
</center>
<center class="new-profile hidden">
<center class="copy-profile hidden">
<b>Please tell us what name to use for your new profile</b>
<br>
<input class="profile-name"
placeholder="alphanumeric please" type="text">
</center>
<center>
<input placeholder="alphanumeric please" type="text">
</center>
<div class="text-danger hidden snapshot-error name-error"
style="text-align: center;">
......@@ -870,12 +843,27 @@ class='fixedsize-panel with-3d-shadow with-transitions'>
style="text-align: center;">
</div>
</div>
<div style="text-align: center;">
<div id='snapshot-wholedisk-div' class="hidden"
style="display: inline-block; width: 80%; margin-top: 10px;">
<b>Create a whole disk image?</b>
</div>
<center id='snapshot-description-div'
style="margin-top: 10px;">
<b>Please briefly describe your image:</b>
<br>
<textarea style="width: 85%;"
placeholder="Optional, but very handy when looking at a list of all your images."
rows=2 type='textarea'></textarea>
<div class="text-danger hidden
snapshot-error description-error"
style="text-align: center;">
</center>
<div id="snapshot-options"
style="text-align: center; margin-top: 10px;">
<div style="display: inline-block; text-align: left">
<ul class="list-unstyled">
<li id='snapshot-wholedisk-div'
class="hidden">
<input type=checkbox
id='snapshot-wholedisk' value=yes>
<b>Create a whole disk image?</b>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
......@@ -890,8 +878,30 @@ class='fixedsize-panel with-3d-shadow with-transitions'>
do not know what this means!</em></b>">
<span class='glyphicon glyphicon-question-sign'
style='margin-bottom: 4px;'></span>
</a></li>
<li id='snapshot_update_prepare_option'>
<input type=checkbox
id='snapshot_update_prepare' value=yes>
<b>Did you add any accounts or groups to your node?</b>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
data-delay='{"hide":500}'
data-content="Check this box if you installed any software
that added new
users or groups. For example, if you installed
<tt>mysql</tt>, a new <tt>mysqld</tt> user was
added, and you want to make sure that this
user account is retained in the new image.
If you are not sure, please
<em><b>ask us first</b></em> since
checking this box needlessly can have
negative side effects.">
<span class='glyphicon glyphicon-question-sign'
style='margin-bottom: 4px;'></span>
</a>
</div>
</li>
</ul>
</div>
</div>
<p style="padding-top: 10px;">
......@@ -1241,12 +1251,9 @@ class='fixedsize-panel with-3d-shadow with-transitions'>
You can quickly convert a running experiment into
a <em>new</em> profile, capturing the disk contents of your
node and saving it away for later use by experiments based on
the new profile.
</p>
<p>
You can also create a new disk image, but start with an empty
(blank profile), and use the new image for the nodes in whatever
topology you create.
the new profile. The new profile is a copy of the current profile,
but with the node you selected to image, updated to boot the
new image.
</p>
<p>
The node in your experiment will be shutdown, and a disk image
......@@ -1281,6 +1288,39 @@ class='fixedsize-panel with-3d-shadow with-transitions'>
be shutdown, and a disk image captured. You will be able to
track the progress of the image capture as it proceeds.
</p>
<p>
When the imaging process is complete, we will update your
profile, changing the node you are imaging, to boot the new
image next time you start an experiment.
</p>
<p>
<em>The contents of your home directory is <b>NOT</b> saved</em>. You
should install your software and data files in standard locations like
<tt>/usr/local</tt> or <tt>/opt</tt>. The reason for this is so
that dependencies on specific user home directories do not
become embedded in your images.
</p>
</div>
</div>
</div>
<div class="hidden" id="imageonly-help-div">
<div class="panel panel-info">
<div class="panel-body">
<button type='button' class='close'>
&times;</button>
<br>
<p>
Create a disk image from the node selected, you can decide
later what profile(s) to use it in. This is a complete disk
copy of the <b>primary</b> partition on your node, everything
is saved. When the imaging process is complete we will tell
you the <em>URN</em> of your new image, to use in your profiles.
</p>
<p>
After you click to confirm, the node in your experiment will
be shutdown, and a disk image captured. You will be able to
track the progress of the image capture as it proceeds.
</p>
<p>
<em>The contents of your home directory is <b>NOT</b> saved</em>. You
should install your software and data files in standard locations like
......
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