Commit fe8cc493 authored by Leigh B Stoller's avatar Leigh B Stoller

Image handling changes:

1. The primary change is to the Create Image modal; we now allow users
   to optionally specify a description for the image. This needed to be
   plumbed through all the way to the GeniCM CreateImage() API. Since
   the modal is getting kinda overloaded, I rearranged things a bit and
   changed the argument checking and error handling. I think this is the
   limit of what we want to do on this modal, need a better UI in the
   future.

2. Of course, if we let users set descriptions, lets show them on the
   image listing page. While I was there, I made the list look more like
   the classic image list; show the image name and project, and put the
   URN in a tooltip, since in general the URN is noisy to look at.

3. And while I was messing with the image listing, I noticed that we
   were not deleting profiles like we said we would. The problem is that
   when we form the image list, we know the profile versions that can be
   deleted, but when the user actually clicks to delete, I was trying to
   regen that decision, but without asking the cluster for the info
   again. So instead, just pass through the version list from the web
   UI.
parent ef7f6eef
...@@ -2776,10 +2776,11 @@ sub ConsoleURL($$) ...@@ -2776,10 +2776,11 @@ sub ConsoleURL($$)
# #
# Create an Image, # Create an Image,
# #
sub CreateImage($$$$;$$$$$) sub CreateImage($$$$;$$$$$$)
{ {
my ($self, $sliver_urn, $imagename, $update_prepare, my ($self, $sliver_urn, $imagename, $update_prepare,
$copyback_uuid, $bsname, $nosnapshot, $mustnotexist, $wholedisk) = @_; $copyback_uuid, $bsname, $nosnapshot,
$mustnotexist, $wholedisk, $description) = @_;
my $authority = $self->GetGeniAuthority(); my $authority = $self->GetGeniAuthority();
my $geniuser = $self->instance()->GetGeniUser(); my $geniuser = $self->instance()->GetGeniUser();
my $slice = $self->instance()->GetGeniSlice(); my $slice = $self->instance()->GetGeniSlice();
...@@ -2818,6 +2819,8 @@ sub CreateImage($$$$;$$$$$) ...@@ -2818,6 +2819,8 @@ sub CreateImage($$$$;$$$$$)
if ($mustnotexist); if ($mustnotexist);
$args->{'wholedisk'} = 1 $args->{'wholedisk'} = 1
if ($wholedisk); if ($wholedisk);
$args->{'description'} = $description
if (defined($description));
my $cmurl = $authority->url(); my $cmurl = $authority->url();
$cmurl = devurl($cmurl) if ($usemydevtree); $cmurl = devurl($cmurl) if ($usemydevtree);
......
...@@ -221,6 +221,8 @@ sub DoListImages() ...@@ -221,6 +221,8 @@ sub DoListImages()
if (!defined($authority)); if (!defined($authority));
# URN without the version. # URN without the version.
$urn = GeniHRN::GenerateImage($auth,$ospid,$os,undef); $urn = GeniHRN::GenerateImage($auth,$ospid,$os,undef);
# Put it into the object so that PHP/JS code can find it easy.
$image->{'imagename'} = $os;
# Default to version zero, for old sites not reporting version. # Default to version zero, for old sites not reporting version.
my $vers = (defined($osvers) ? $osvers : 0); my $vers = (defined($osvers) ? $osvers : 0);
...@@ -258,9 +260,16 @@ sub DoListImages() ...@@ -258,9 +260,16 @@ sub DoListImages()
# #
if (exists($image0->{'project_urn'})) { if (exists($image0->{'project_urn'})) {
my $projhrn = GeniHRN->new($image0->{'project_urn'}); my $projhrn = GeniHRN->new($image0->{'project_urn'});
if ($projhrn->domain() eq $OURDOMAIN && if ($projhrn->domain() eq $OURDOMAIN) {
defined($projhrn->project())) { my $project;
my $project = Project->Lookup($projhrn->project());
if (defined($projhrn->project())) {
$project = Project->Lookup($projhrn->project());
}
else {
# Backwards compat; we did not always send project urns.
$project = Project->Lookup($image0->{'pid'});
}
if (defined($project)) { if (defined($project)) {
$ref->{'pid'} = $project->pid(); $ref->{'pid'} = $project->pid();
$ref->{'pid_idx'} = $project->pid_idx(); $ref->{'pid_idx'} = $project->pid_idx();
...@@ -271,6 +280,7 @@ sub DoListImages() ...@@ -271,6 +280,7 @@ sub DoListImages()
# Remote pid, set above # Remote pid, set above
$ref->{'pid'} = $image0->{'pid'}; $ref->{'pid'} = $image0->{'pid'};
} }
$ref->{'imagename'} = $image0->{'imagename'};
# #
# Find profiles using the named image # Find profiles using the named image
...@@ -322,11 +332,18 @@ sub DoListImages() ...@@ -322,11 +332,18 @@ sub DoListImages()
while (@versions) { while (@versions) {
my $image = shift(@versions); my $image = shift(@versions);
my $urn = $image->{'urn'}; my $urn = $image->{'urn'};
my $hrn = GeniHRN->new($urn);
my @using = (); my @using = ();
$image->{'using'} = []; $image->{'using'} = [];
$image->{'candelete'} = 0; $image->{'candelete'} = 0;
$image->{'deleted'} = 0; $image->{'deleted'} = 0;
my (undef, undef, undef, $osvers) = $hrn->ParseImage();
# Default to version zero, for old sites not reporting version.
my $vers = (defined($osvers) ? $osvers : 0);
# Put it into the object so that PHP/JS code can find it easy.
$image->{'version'} = int($vers);
next next
if (APT_Profile::ImageInfo::FindProfilesUsing($urn, \@using)); if (APT_Profile::ImageInfo::FindProfilesUsing($urn, \@using));
...@@ -395,7 +412,7 @@ sub DoDeleteImage() ...@@ -395,7 +412,7 @@ sub DoDeleteImage()
print STDERR "Usage: manage_images delete [-a am_urn] <image_urn>\n"; print STDERR "Usage: manage_images delete [-a am_urn] <image_urn>\n";
exit(-1); exit(-1);
}; };
my $optlist = "a:d:n"; my $optlist = "a:d:v:n";
my $aggregate_urn = $MYURN; my $aggregate_urn = $MYURN;
my $impotent = 0; my $impotent = 0;
my $profile; my $profile;
...@@ -422,64 +439,24 @@ sub DoDeleteImage() ...@@ -422,64 +439,24 @@ sub DoDeleteImage()
if ($profile->isLocked()) { if ($profile->isLocked()) {
fatal("Profile is locked down, cannot be deleted"); fatal("Profile is locked down, cannot be deleted");
} }
# #
# This argument says; delete any version of the specified # The caller tells us what versions of the image to delete,
# profile, that reference the image being deleted. So we
# have to go through every version of the profile and check
# to see if its using this image. For any of those versions,
# we try to delete it.
# #
if (!exists($options{"v"})) {
fatal("Missing version number list");
}
if ($options{"v"} !~ /^[\d,]+$/) {
fatal("Version number list should be comma separated integers");
}
my @todelete = (); my @todelete = ();
foreach my $version ($profile->AllVersions()) { foreach my $versnum (split(",", $options{"v"})) {
my $usingimage = 0; my $version = APT_Profile->Lookup($profile->profileid(), $versnum);
my $conflict; next
if (!defined($version));
#
# Check image references for this version. We want to
# know if there are any other images associated with this
# version beside the one we are trying to delete. If so,
# we cannot delete the profile version since that will
# result in another image getting deleted.
#
my %irefs = %{ $version->images() };
foreach my $client_id (keys(%irefs)) {
my $imageinfo = $irefs{$client_id};
# We do not ever care about system images.
next
if ($imageinfo->ospid() eq "emulab-ops");
# The image we are trying to delete is okay
if ($imageinfo->image() eq $image_urn) {
$usingimage = 1;
next;
}
my $snapname = $profile->name() . "." . $client_id;
if ($imageinfo->os() eq $profile->name() ||
$imageinfo->os() eq $snapname) {
$conflict = $imageinfo;
}
}
if ($usingimage && $conflict) {
my $mesg =
"Version " . $version->version() . " of the " .
$version->name() . " profile has another ".
"image that would be deleted as well: ".
$conflict->image() . ". ".
"You will need to go to the profile page and delete ".
"that profile version before you can delete this image.";
if ($webtask) { if ($version->isLocked()) {
$webtask->reason("conflict");
$webtask->profile($version->uuid());
$webtask->image($conflict->image());
}
UserError($mesg);
}
if ($usingimage && $version->isLocked()) {
my $mesg = my $mesg =
"Version " . $version->version() . " of the " . "Version " . $version->version() . " of the " .
$version->name() . " profile is locked down, ". $version->name() . " profile is locked down, ".
...@@ -491,11 +468,42 @@ sub DoDeleteImage() ...@@ -491,11 +468,42 @@ sub DoDeleteImage()
} }
UserError($mesg); UserError($mesg);
} }
if ($usingimage) {
push(@todelete, $version); #
print "Would delete version " . $version->version() . # Check image references for this version. We want to
" of profile " . $profile->name() . "\n"; # know if there are any other images associated with this
# version beside the one we are trying to delete. If so,
# we cannot delete the profile version since that will
# result in another image getting deleted.
#
my %irefs = %{ $version->images() };
if (keys(%irefs) > 1) {
foreach my $client_id (keys(%irefs)) {
my $imageinfo = $irefs{$client_id};
# We do not ever care about system images.
next
if ($imageinfo->ospid() eq "emulab-ops");
my $mesg =
"Version " . $version->version() . " of the " .
$version->name() . " profile is using multiple ".
"images. As a safety measure, we require that you ".
"delete or edit that profile before you can delete ".
"this image.";
if ($webtask) {
$webtask->reason("conflict");
$webtask->profile($version->uuid());
$webtask->image($imageinfo->image());
}
UserError($mesg);
}
} }
print "Would delete version " . $version->version() .
" of profile " . $profile->name() . "\n";
push(@todelete, $version);
} }
foreach my $version (@todelete) { foreach my $version (@todelete) {
my $vers = $version->version(); my $vers = $version->version();
......
...@@ -299,6 +299,7 @@ sub DoSnapshot() ...@@ -299,6 +299,7 @@ sub DoSnapshot()
my $old_status = $instance->status(); my $old_status = $instance->status();
my $node_id; my $node_id;
my $imagename; my $imagename;
my $description;
my $cloneprofile; my $cloneprofile;
my $update_profile; my $update_profile;
my $copyback_uuid; my $copyback_uuid;
...@@ -312,7 +313,7 @@ sub DoSnapshot() ...@@ -312,7 +313,7 @@ sub DoSnapshot()
my $usetracker = 0; my $usetracker = 0;
my $operation = "image-only"; # Default to just snapshot. my $operation = "image-only"; # Default to just snapshot.
my $optlist = "n:i:u:Uc:O:Sse"; my $optlist = "n:i:u:Uc:O:SseD:";
my %options = (); my %options = ();
if (! getopts($optlist, \%options)) { if (! getopts($optlist, \%options)) {
usage(); usage();
...@@ -335,6 +336,9 @@ sub DoSnapshot() ...@@ -335,6 +336,9 @@ sub DoSnapshot()
if (defined($options{"U"})) { if (defined($options{"U"})) {
$update_prepare = 1; $update_prepare = 1;
} }
if (defined($options{"D"})) {
$description = ReadFile($options{"D"});
}
if (defined($options{"s"})) { if (defined($options{"s"})) {
$nosnapshot = 1; $nosnapshot = 1;
} }
...@@ -604,12 +608,14 @@ sub DoSnapshot() ...@@ -604,12 +608,14 @@ sub DoSnapshot()
my $response = my $response =
$aggregate->CreateImage($sliver_urn, $imagename, $aggregate->CreateImage($sliver_urn, $imagename,
$update_prepare, $copyback_uuid, $update_prepare, $copyback_uuid,
undef, $nosnapshot, $mustnotexist, $wholedisk); undef, $nosnapshot, $mustnotexist, $wholedisk,
$description);
if ($response->code() != GENIRESPONSE_SUCCESS) { if ($response->code() != GENIRESPONSE_SUCCESS) {
$errcode = $response->code(); $errcode = $response->code();
($exitcode,$errmsg) = ResponseErrorMessage($aggregate, $response); ($exitcode,$errmsg) = ResponseErrorMessage($aggregate, $response);
# Important to tell web user about these. # Important to tell web user about these.
if ($response->code() == GENIRESPONSE_NOSPACE || if ($response->code() == GENIRESPONSE_NOSPACE ||
$response->code() == GENIRESPONSE_FORBIDDEN ||
$response->code() == GENIRESPONSE_ALREADYEXISTS) { $response->code() == GENIRESPONSE_ALREADYEXISTS) {
$exitcode = 1; $exitcode = 1;
} }
...@@ -933,7 +939,7 @@ sub DoSnapshot() ...@@ -933,7 +939,7 @@ sub DoSnapshot()
$slice->UnLock() $slice->UnLock()
if ($needunlock); if ($needunlock);
exit($errcode); exit($exitcode);
} }
sub DoImageTrackerStuff($$$$$$$) sub DoImageTrackerStuff($$$$$$$)
......
...@@ -156,6 +156,18 @@ function Do_DeleteImage() ...@@ -156,6 +156,18 @@ function Do_DeleteImage()
return; return;
} }
$pdarg = "-d " . escapeshellarg($ajax_args["profile-delete"]); $pdarg = "-d " . escapeshellarg($ajax_args["profile-delete"]);
if (!isset($ajax_args["profile-delete-versions"])) {
SPITAJAX_ERROR(-1, "Missing profile version list for deletion");
return;
}
foreach ($ajax_args["profile-delete-versions"] as $vers) {
if (!preg_match("/^\d+$/", $vers)) {
SPITAJAX_ERROR(1, "Illegal characters in version number");
return;
}
}
$pdarg .= " -v " . implode(",", $ajax_args["profile-delete-versions"]);
} }
$uid = $target_user->uid(); $uid = $target_user->uid();
...@@ -167,6 +179,7 @@ function Do_DeleteImage() ...@@ -167,6 +179,7 @@ function Do_DeleteImage()
"webmanage_images -t $webtask_id ". "webmanage_images -t $webtask_id ".
" delete -a '$aggurn' $pdarg $image_urn", " delete -a '$aggurn' $pdarg $image_urn",
SUEXEC_ACTION_IGNORE); SUEXEC_ACTION_IGNORE);
if ($retval) { if ($retval) {
$webtask->Refresh(); $webtask->Refresh();
if (!$webtask->exited() || $retval < 0) { if (!$webtask->exited() || $retval < 0) {
......
...@@ -19,6 +19,7 @@ $(function () ...@@ -19,6 +19,7 @@ $(function ()
{ {
window.APT_OPTIONS.initialize(sup); window.APT_OPTIONS.initialize(sup);
amlist = decodejson('#amlist-json'); amlist = decodejson('#amlist-json');
window.IMLIST = imagelist;
$('#oops_div').html(oopsString); $('#oops_div').html(oopsString);
$('#waitwait_div').html(waitwaitString); $('#waitwait_div').html(waitwaitString);
...@@ -297,22 +298,33 @@ $(function () ...@@ -297,22 +298,33 @@ $(function ()
* should be deleted along with the image. Pass that along, * should be deleted along with the image. Pass that along,
* the backend is going to check anyway. * the backend is going to check anyway.
*/ */
var profiles = null;
if ($(row).find("td.delete-profile").length) { if ($(row).find("td.delete-profile").length) {
var uuid = $(row).find("td.delete-profile").attr('data-uuid'); var uuid = $(row).find("td.delete-profile").attr('data-uuid');
args["profile-delete"] = uuid; args["profile-delete"] = uuid;
} args["profile-delete-versions"] = [];
/* /*
* The confirm modal is a template in case we need to warn * The confirm modal is a template in case we need to warn
* about profiles that will be deleted. Need to find that * about profiles that will be deleted. Need to find that
* list in the saved data structure. * list in the saved data structure.
*/ */
var profiles = null;
if ($(row).find("td.delete-profile").length) {
_.each(imagelist[cluster], function(image, index) { _.each(imagelist[cluster], function(image, index) {
_.each(image.versions, function(version, index) { _.each(image.versions, function(version, index) {
if (version.urn == urn) { if (version.urn == urn) {
profiles = version.using; profiles = version.using;
/*
* Add the version list to the args.
*/
_.each(profiles, function(profile, i) {
_.each(profile.versions, function(version, j) {
args["profile-delete-versions"]
.push(version.version);
});
});
// Just one profile can be deleted.
return;
} }
}); });
}); });
...@@ -364,6 +376,7 @@ $(function () ...@@ -364,6 +376,7 @@ $(function ()
$('#confirm-delete-image-modal #confirm-delete-image') $('#confirm-delete-image-modal #confirm-delete-image')
.click(function () { .click(function () {
sup.HideModal('#confirm-delete-image-modal'); sup.HideModal('#confirm-delete-image-modal');
sup.ShowWaitWait('It takes a moment to delete an image; ' + sup.ShowWaitWait('It takes a moment to delete an image; ' +
'patience please'); 'patience please');
......
...@@ -48,6 +48,7 @@ $(function () ...@@ -48,6 +48,7 @@ $(function ()
var EMULAB_OPS = "emulab-ops"; var EMULAB_OPS = "emulab-ops";
var EMULAB_NS = "http://www.protogeni.net/resources/rspec/ext/emulab/1"; var EMULAB_NS = "http://www.protogeni.net/resources/rspec/ext/emulab/1";
var GENIRESPONSE_REFUSED = 7; var GENIRESPONSE_REFUSED = 7;
var GENIRESPONSE_ALREADYEXISTS = 17;
var GENIRESPONSE_INSUFFICIENT_NODES = 26; var GENIRESPONSE_INSUFFICIENT_NODES = 26;
var MAXJACKSNODES = 200; var MAXJACKSNODES = 200;
...@@ -159,7 +160,7 @@ $(function () ...@@ -159,7 +160,7 @@ $(function ()
// This activates the popover subsystem. // This activates the popover subsystem.
$('[data-toggle="popover"]').popover({ $('[data-toggle="popover"]').popover({
trigger: 'hover', trigger: 'hover',
placement: 'top', placement: 'auto',
}); });
$('[data-toggle="tooltip"]').tooltip({ $('[data-toggle="tooltip"]').tooltip({
placement: 'top', placement: 'top',
...@@ -2168,28 +2169,11 @@ $(function () ...@@ -2168,28 +2169,11 @@ $(function ()
$('#snapshot_modal .choose-node').removeClass("hidden"); $('#snapshot_modal .choose-node').removeClass("hidden");
} }
// Project list for copy/new profile.
if (projlist && projlist.length) {
var html = "";
_.each(projlist, function(name) {
html = html +
"<option value='" + name + "'>" + name + "</option>";
});
$('#snapshot_modal .choose-project-div select').append(html);
if (projlist.length == 1) {
// No need to show it, just select the project for later.
$('#snapshot_modal .choose-project-div select')
.val(projlist[0]);
}
}
$('#snapshot_modal input[type=radio]').on('change', function() { $('#snapshot_modal input[type=radio]').on('change', function() {
switch($(this).val()) { switch($(this).val()) {
case 'update-profile': case 'update-profile':
$('#snapshot-name-div').addClass("hidden"); $('#snapshot-name-div').addClass("hidden");
$('#snapshot-wholedisk-div').addClass("hidden"); $('#snapshot-wholedisk-div').addClass("hidden");
$('#snapshot_modal .choose-project-div').addClass("hidden");
break; break;
case 'copy-profile': case 'copy-profile':
case 'new-profile': case 'new-profile':
...@@ -2199,10 +2183,6 @@ $(function () ...@@ -2199,10 +2183,6 @@ $(function ()
if (wholedisk) { if (wholedisk) {
$('#snapshot-wholedisk-div').removeClass("hidden"); $('#snapshot-wholedisk-div').removeClass("hidden");
} }
if (0 && projlist.length > 1) {
$('#snapshot_modal .choose-project-div')
.removeClass("hidden");
}
break; break;
case 'image-only': case 'image-only':
$('#snapshot-name-div .new-profile').addClass("hidden"); $('#snapshot-name-div .new-profile').addClass("hidden");
...@@ -2211,7 +2191,6 @@ $(function () ...@@ -2211,7 +2191,6 @@ $(function ()
if (wholedisk) { if (wholedisk) {
$('#snapshot-wholedisk-div').removeClass("hidden"); $('#snapshot-wholedisk-div').removeClass("hidden");
} }
$('#snapshot_modal .choose-project-div').addClass("hidden");
break; break;
} }
}); });
...@@ -2278,11 +2257,8 @@ $(function () ...@@ -2278,11 +2257,8 @@ $(function ()
alert("Experiment is not ready yet, snapshot not allowed"); alert("Experiment is not ready yet, snapshot not allowed");
return; return;
} }
// Clear previous errors' // Clear previous errors
$('#snapshot_modal .choose-node-error').addClass("hidden"); $('#snapshot_modal .snapshot-error').addClass("hidden");
$('#snapshot_modal .name-error').addClass("hidden");
$('#snapshot_modal .inuse-error').addClass("hidden");
$('#snapshot_modal .project-error').addClass("hidden");
// Default to unchecked any time we show the modal. // Default to unchecked any time we show the modal.
//$('#snapshot_update_prepare').prop("checked", false); //$('#snapshot_update_prepare').prop("checked", false);
...@@ -2315,15 +2291,20 @@ $(function () ...@@ -2315,15 +2291,20 @@ $(function ()
function DoSnapshotNodeAux() function DoSnapshotNodeAux()
{ {
var node_id; var node_id;
sup.ShowModal('#snapshot_modal');
// Handler for the Snapshot confirm button. // Handler for the Snapshot confirm button.
$('button#snapshot_confirm').bind("click.snapshot", function (event) { $('button#snapshot_confirm').bind("click.snapshot", function (event) {
event.preventDefault(); event.preventDefault();
// Clear previous errors
$('#snapshot_modal .snapshot-error').addClass("hidden");
// Make sure node is selected (one node, it is forced selection). // Make sure node is selected (one node, it is forced selection).
node_id = $('#snapshot_modal .choose-node select ' + node_id = $('#snapshot_modal .choose-node select ' +
'option:selected').val(); 'option:selected').val();
if (node_id === undefined || node_id === '') { if (node_id === undefined || node_id === '') {
$('#snapshot_modal .choose-node-error')
.text("Please choose a node");
$('#snapshot_modal .choose-node-error').removeClass("hidden"); $('#snapshot_modal .choose-node-error').removeClass("hidden");
return; return;
} }
...@@ -2331,13 +2312,6 @@ $(function () ...@@ -2331,13 +2312,6 @@ $(function ()
// What does the user want to do? // What does the user want to do?
var operation = $('#snapshot_modal input[type=radio]:checked').val(); var operation = $('#snapshot_modal input[type=radio]:checked').val();
if (operation == 'copy-profile-no' ||
operation == 'new-profile-no') {
var action = operation == 'copy-profile' ? "clone" : "create";
window.location.replace('manage_profile.php?action=' + action +
'&snapuuid=' + uuid +
'&snapnode_id=' + node_id);
}
var args = {"uuid" : uuid, var args = {"uuid" : uuid,
"node_id" : node_id, "node_id" : node_id,
"operation" : operation, "operation" : operation,
...@@ -2346,6 +2320,8 @@ $(function () ...@@ -2346,6 +2320,8 @@ $(function ()
if (operation == 'image-only') { if (operation == 'image-only') {
var name = $('#snapshot-name-div .image-only input').val(); var name = $('#snapshot-name-div .image-only input').val();
if (name == "") { if (name == "") {
$('#snapshot-name-div .name-error')
.text("Please provide an image name");
$('#snapshot-name-div .name-error').removeClass("hidden"); $('#snapshot-name-div .name-error').removeClass("hidden");
return; return;
} }
...@@ -2355,6 +2331,8 @@ $(function () ...@@ -2355,6 +2331,8 @@ $(function ()
operation == "new-profile") { operation == "new-profile") {
var name = $('#snapshot-name-div .new-profile input').val(); var name = $('#snapshot-name-div .new-profile input').val();
if (name == "") { if (name == "") {
$('#snapshot-name-div .name-error')
.text("Please provide a profile name");
$('#snapshot-name-div .name-error').removeClass("hidden"); $('#snapshot-name-div .name-error').removeClass("hidden");
return; return;
} }
...@@ -2369,7 +2347,9 @@ $(function () ...@@ -2369,7 +2347,9 @@ $(function ()
operation == "new-profile" || operation == "image-only")) { operation == "new-profile" || operation == "image-only")) {
args["wholedisk"] = 1; args["wholedisk"] = 1;
} }
$('button#snapshot_confirm').unbind("click.snapshot"); args["description"] =
$.trim($('#snapshot-description-div textarea').val());
if (operation == "copy-profile" || operation == "new-profile") { if (operation == "copy-profile" || operation == "new-profile") {
NewProfile(args); NewProfile(args);
} }
...@@ -2386,22 +2366,20 @@ $(function () ...@@ -2386,22 +2366,20 @@ $(function ()
$('#snapshot-help-button').popover('destroy'); $('#snapshot-help-button').popover('destroy');
$('#clone-help-popover-div').popover('destroy'); $('#clone-help-popover-div').popover('destroy');
}); });
sup.ShowModal('#snapshot_modal');
} }
function StartSnapshot(args) function StartSnapshot(args)
{ {
sup.HideModal('#snapshot_modal');
sup.ShowWaitWait("Starting image capture, " +
"this can take a minute. Patience please.");
var callback = function(json) {