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

Add support for disabling profile (versions). Mostly a bunch of little UI

changes and some checks to prevent a user from instantiating a disabled
version.
parent 32c3d934
......@@ -196,6 +196,8 @@ my %xmlfields =
"profile_shared" => ["shared", $SLOT_OPTIONAL|$SLOT_UPDATE],
"profile_topdog" => ["topdog", $SLOT_OPTIONAL|
$SLOT_UPDATE|$SLOT_ADMINONLY],
"profile_disabled" => ["disabled", $SLOT_OPTIONAL|
$SLOT_UPDATE|$SLOT_ADMINONLY],
"rspec" => ["rspec", $SLOT_REQUIRED|$SLOT_UPDATE],
"script" => ["script", $SLOT_OPTIONAL|$SLOT_UPDATE],
);
......@@ -403,6 +405,9 @@ if ($update) {
if (!defined($profile)) {
fatal("Could not create new version of the profile");
}
# Tell the web interface we created a new version.
$webtask->newProfile($profile->uuid())
if (defined($webtask));
}
$profile->UpdateVersion({"rspec" => $update_args{"rspec"}})
if (exists($update_args{"rspec"}));
......@@ -421,6 +426,14 @@ if ($update) {
$profile->UpdateMetaData(\%update_args) == 0 or
fatal("Could not update profile record");
#
# Disabled is version specific right now.
#
if ($this_user->IsAdmin() &&
exists($update_args{"disabled"})) {
$profile->UpdateVersion({"disabled" => $update_args{"disabled"}});
}
# Bump the modtime.
$profile->MarkModified();
}
......
......@@ -179,6 +179,10 @@ if (isset($profile)) {
$profile_array[$profile->uuid()] = $profile->name();
$profilename = $profile->name();
}
if ($profile->isDisabled()) {
SPITUSERERROR("This profile is disabled!");
exit();
}
}
else {
#
......@@ -209,7 +213,8 @@ else {
" v.profileid=p.profileid and ".
" v.version=p.version ".
"$joinclause ".
"where locked is null and ($whereclause) ".
"where locked is null and p.disabled=0 and ".
" v.disabled=0 and ($whereclause) ".
"order by p.topdog desc");
while ($row = mysql_fetch_array($query_result)) {
$profile_array[$row["uuid"]] = $row["name"];
......@@ -235,6 +240,10 @@ else {
SPITUSERERROR("No permission to use profile: $default");
exit();
}
if ($obj->isDisabled()) {
SPITUSERERROR("This profile is disabled!");
exit();
}
$profile_array[$obj->uuid()] = $obj->name();
$profile_default = $obj->uuid();
}
......@@ -304,9 +313,11 @@ function SPITFORM($formfields, $newuser, $errors)
$showpicker = (isset($profile) ? 0 : 1);
if (isset($profilename)) {
$profilename = "'$profilename'";
$profilevers = $profile->version();
}
else {
$profilename = "null";
$profilevers = "null";
}
SPITHEADER(1);
......@@ -368,6 +379,7 @@ function SPITFORM($formfields, $newuser, $errors)
echo "<script type='text/javascript'>\n";
echo " window.PROFILE = '" . $formfields["profile"] . "';\n";
echo " window.PROFILENAME= $profilename;\n";
echo " window.PROFILEVERS= $profilevers;\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.SHOWABOUT = $showabout;\n";
echo " window.NOPPRSPEC = $nopprspec;\n";
......
......@@ -5,9 +5,11 @@ require(window.APT_OPTIONS.configObject,
'js/lib/text!template/aboutapt.html',
'js/lib/text!template/aboutcloudlab.html',
'js/lib/text!template/waitwait-modal.html',
'js/lib/text!template/rspectextview-modal.html',
'formhelpers', 'filestyle', 'marked', 'jacks', 'jquery-steps'],
function (_, Constraints, sup, ppstart, JacksEditor, wt,
instantiateString, aboutaptString, aboutcloudString, waitwaitString)
instantiateString, aboutaptString, aboutcloudString,
waitwaitString, rspecviewString)
{
'use strict';
......@@ -17,7 +19,8 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
var profilelist = null;
var amdefault = null;
var selected_uuid = null;
var selected_rspec = null;
var selected_rspec = null;
var selected_version = null;
var ispprofile = 0;
var webonly = 0;
var isadmin = 0;
......@@ -76,6 +79,7 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
registered: registered,
profilename: window.PROFILENAME,
profileuuid: window.PROFILEUUID,
profilevers: window.PROFILEVERS,
showpicker: showpicker,
cancopy: window.CANCOPY,
clustername: (window.ISCLOUD ? "CloudLab" : "APT"),
......@@ -92,6 +96,7 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
});
$('#waitwait_div').html(waitwaitString);
$('#rspecview_div').html(rspecviewString);
// The about panel.
if (window.SHOWABOUT) {
$('#about_div').html(window.ISCLOUD ?
......@@ -185,6 +190,26 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
return false;
});
$('#show_xml_modal_button').click(function (event) {
//
// Show the XML source in the modal. This is used when we
// have a script, and the XML was generated. We show the
// XML, but it is not intended to be edited.
//
$('#rspec_modal_editbuttons').addClass("hidden");
$('#rspec_modal_viewbuttons').removeClass("hidden");
$('#modal_profile_rspec_textarea').val(selected_rspec);
$('#modal_profile_rspec_textarea').prop("readonly", true);
$('#modal_profile_rspec_div').addClass("hidden");
$('#modal_profile_rspec_textarea').removeClass("hidden");
$('#rspec_modal').modal({'backdrop':'static','keyboard':false});
$('#rspec_modal').modal('show');
});
$('#close_rspec_modal_button').click(function (event) {
$('#rspec_modal').modal('hide');
$('#modal_profile_rspec_textarea').val("");
});
// Profile picker search box.
var profile_picker_timeout = null;
......@@ -727,7 +752,8 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
$(selectedElement).addClass('selected');
}
var continuation = function(rspec, description, name, amdefault, ispp) {
var continuation = function(rspec, description, name, version,
amdefault, ispp) {
$('#showtopo_title').html("<h3>" + name + "</h3>");
$('#showtopo_description').html(description);
sup.maketopmap('#showtopo_div', rspec, false, !multisite);
......@@ -739,7 +765,8 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
function ShowProfileSelectionInline(selectedElement, root, selectionPane) {
editor = new JacksEditor(root, true, true,
selectionPane, true, !multisite);
var continuation = function(rspec, description, name, amdefault, ispp) {
var continuation = function(rspec, description, name, version,
amdefault, ispp) {
if (rspec)
{
editor.show(rspec);
......@@ -761,16 +788,19 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
$('#selected_profile').attr('value', profile_value);
$('#selected_profile_text').html("" + profile_name);
var continuation = function(rspec, description, name, amdef, ispp) {
var continuation = function(rspec, description, name, version,
amdef, ispp) {
$('#showtopo_title').html("<h3>" + name + "</h3>");
$('#showtopo_description').html(description);
$('#selected_profile_description').html(description);
$('#finalize_profile_name').val(name);
$('#finalize_profile_name').text(name);
$('#finalize_profile_version').text(version);
ispprofile = ispp;
selected_uuid = profile_value;
selected_rspec = rspec;
amdefault = amdef;
selected_rspec = rspec;
selected_version = version;
amdefault = amdef;
CreateAggregateSelectors(rspec);
......@@ -815,7 +845,8 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
description = "Hmm, no description for this profile";
}
continuation(json.value.rspec, description,
json.value.name, json.value.amdefault,
json.value.name, json.value.version,
json.value.amdefault,
json.value.ispprofile);
}
var $xmlthing = sup.CallServerMethod(ajaxurl,
......
......@@ -99,6 +99,8 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
button_label: window.BUTTONLABEL,
version_uuid: window.VERSION_UUID,
profile_uuid: window.PROFILE_UUID,
latest_uuid: window.LATEST_UUID,
latest_version: window.LATEST_VERSION,
candelete: window.CANDELETE,
canmodify: window.CANMODIFY,
canpublish: window.CANPUBLISH,
......@@ -109,6 +111,7 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
snapuuid: (window.SNAPUUID || null),
general_error: (errors.error || ''),
iscloud: window.ISCLOUD,
disabled: window.DISABLED,
versions: versions,
withpublishing: window.WITHPUBLISHING,
});
......@@ -349,6 +352,7 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, ppstart,
$('#profile_who_registered').change(function() { ProfileModified(); });
$('#profile_who_private').change(function() { ProfileModified(); });
$('#profile_topdog').change(function() { ProfileModified(); });
$('#profile_disabled').change(function() { ProfileModified(); });
/*
* A double click handler that will render the instructions
......
......@@ -51,6 +51,7 @@ function (_, sup, moment, ppstart,
history: window.HISTORY,
isadmin: window.ISADMIN,
canedit: window.CANEDIT,
disabled: window.DISABLED,
withpublishing: window.WITHPUBLISHING,
});
$('#page-body').html(show_html);
......
......@@ -69,8 +69,11 @@ function SPITFORM($formfields, $errors)
$isadmin = (ISADMIN() ? 1 : 0);
$multisite = 1;
$cloning = 0;
$disabled = 0;
$version_uuid = "null";
$profile_uuid = "null";
$latest_uuid = "null";
$latest_version = "null";
if ($action == "edit") {
$button_label = "Save";
......@@ -83,12 +86,16 @@ function SPITFORM($formfields, $errors)
$canpublish = ($profile->CanPublish() ? 1 : 0);
$activity = ($profile->HasActivity() ? 1 : 0);
$ispp = ($profile->isParameterized() ? 1 : 0);
$disabled = ($profile->isDisabled() ? 1 : 0);
if ($canmodify) {
$title = "Modify Profile";
}
else {
$title = "View Profile";
}
$latest_profile = Profile::Lookup($profile->profile_uuid());
$latest_uuid = "'" . $latest_profile->uuid() . "'";
$latest_version = $latest_profile->version();
}
else {
# New page action is now create, not copy or clone.
......@@ -166,6 +173,8 @@ function SPITFORM($formfields, $errors)
echo " window.VIEWING = $viewing;\n";
echo " window.VERSION_UUID = $version_uuid;\n";
echo " window.PROFILE_UUID = $profile_uuid;\n";
echo " window.LATEST_UUID = $latest_uuid;\n";
echo " window.LATEST_VERSION = $latest_version;\n";
echo " window.UPDATED = $notifyupdate;\n";
echo " window.SNAPPING = $notifyclone;\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
......@@ -173,6 +182,7 @@ function SPITFORM($formfields, $errors)
echo " window.CANDELETE= $candelete;\n";
echo " window.CANMODIFY= $canmodify;\n";
echo " window.CANPUBLISH= $canpublish;\n";
echo " window.DISABLED= $disabled;\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.MULTISITE = $multisite;\n";
echo " window.HISTORY = $history;\n";
......@@ -356,6 +366,8 @@ if (! isset($create)) {
($profile->ispublic() ? "public" : "private"));
$defaults["profile_topdog"] =
($profile->topdog() ? "checked" : "");
$defaults["profile_disabled"] =
($profile->isDisabled() ? "checked" : "");
# Warm fuzzy message.
if (isset($_SESSION["notifyupdate"])) {
......@@ -559,6 +571,15 @@ else {
fwrite($fp, "0");
}
fwrite($fp, "</value></attribute>\n");
fwrite($fp, "<attribute name='profile_disabled'><value>");
if (isset($formfields["profile_disabled"]) &&
$formfields["profile_disabled"] == "checked") {
fwrite($fp, "1");
}
else {
fwrite($fp, "0");
}
fwrite($fp, "</value></attribute>\n");
}
fwrite($fp, "</profile>\n");
fclose($fp);
......@@ -577,9 +598,9 @@ if ($action == "edit") {
}
else {
$command .= " create -t $webtask_id ";
}
if (isset($snapuuid)) {
$command .= " -s " . escapeshellarg($snapuuid);
if (isset($snapuuid)) {
$command .= " -s " . escapeshellarg($snapuuid);
}
}
$command .= " $xmlname";
......@@ -612,15 +633,25 @@ if (count($errors)) {
return;
}
#
# Need the index to pass back through. But when its an edit operation,
# we have to let the backend tell us it created a new version, since
# we want to return to that.
#
if ($action == "edit") {
if ($webtask->TaskValue("newProfile")) {
$profile = Profile::Lookup($webtask->TaskValue("newProfile"));
}
}
else {
$profile = Profile::LookupByName($project, $formfields["profile_name"]);
}
# Done with this, unless doing a snapshot (needed for imaging status).
if (!isset($snapuuid)) {
$webtask->Delete();
}
#
# Need the index to pass back through.
#
$profile = Profile::LookupByName($project, $formfields["profile_name"]);
if ($profile) {
$uuid = $profile->uuid();
}
......
......@@ -46,7 +46,8 @@ class Profile
# version with the uuid.
#
$query_result =
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid, ".
" i.disabled as profile_disabled ".
" from apt_profiles as i ".
"left join apt_profile_versions as v on ".
" v.profileid=i.profileid and ".
......@@ -55,7 +56,8 @@ class Profile
if (!$query_result || !mysql_num_rows($query_result)) {
$query_result =
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid, ".
" i.disabled as profile_disabled ".
" from apt_profile_versions as v ".
"left join apt_profiles as i on ".
" v.profileid=i.profileid ".
......@@ -65,7 +67,8 @@ class Profile
}
elseif (is_null($version)) {
$query_result =
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid, ".
" i.disabled as profile_disabled ".
" from apt_profiles as i ".
"left join apt_profile_versions as v on ".
" v.profileid=i.profileid and ".
......@@ -75,7 +78,8 @@ class Profile
else {
$safe_version = addslashes($version);
$query_result =
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid, ".
" i.disabled as profile_disabled ".
" from apt_profile_versions as v ".
"left join apt_profiles as i on ".
" i.profileid=v.profileid ".
......@@ -117,6 +121,8 @@ class Profile
function locked() { return $this->field('status'); }
function status() { return $this->field('locked'); }
function topdog() { return $this->field('topdog'); }
function disabled() { return $this->field('disabled'); }
function profile_disabled() { return $this->field('profile_disabled'); }
function parent_profileid() { return $this->field('parent_profileid'); }
function parent_version() { return $this->field('parent_version'); }
......@@ -128,6 +134,10 @@ class Profile
function isParameterized() {
return ($this->paramdefs() != "" ? 1 : 0);
}
# A profile is disabled if version is disabled or entire profile is disabled
function isDisabled() {
return ($this->disabled() || $this->profile_disabled());
}
# Hmm, how does one cause an error in a php constructor?
function IsValid() {
......
......@@ -61,6 +61,7 @@ $version_uuid = $profile->uuid();
$ispp = ($profile->isParameterized() ? 1 : 0);
$history = ($profile->HasHistory() ? 1 : 0);
$canedit = ($profile->CanEdit($this_user) ? 1 : 0);
$disabled = ($profile->isDisabled() ? 1 : 0);
$defaults = array();
$defaults["profile_name"] = $profile->name();
......@@ -111,6 +112,7 @@ echo " window.VERSION_UUID = '$version_uuid';\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.CANEDIT = $canedit;\n";
echo " window.DISABLED = $disabled;\n";
echo " window.HISTORY = $history;\n";
echo " window.ISPPPROFILE = $ispp;\n";
echo " window.WITHPUBLISHING = $WITHPUBLISHING;\n";
......
......@@ -227,6 +227,30 @@
name='fully_bound' value="0">
<div id='finalize_container'
class='col-lg-8 col-md-8 col-sm-8 col-xs-12'>
<div class='panel panel-default' style="margin-bottom: 5px">
<div class='panel-body'
style="padding-top: 5px; padding-bottom: 0px;">
<table class='table table-condensed nospaceafter border-none'
style="font-size: 14px; font-family: Arial,sans-serif;">
<tr>
<td style="padding: 0px;">
<span style="font-weight: bolder;">Profile:</span>
<span id='finalize_profile_name'><%= profilename %></span>
</td>
<td style="padding: 0px;">
<span style="font-weight: bolder;">Version:</span>
<span id='finalize_profile_version'><%= profilevers %>
</span>
</td>
<td style="padding: 0px;">
<button class='btn btn-primary btn-xs'
type='button'
id="show_xml_modal_button">
Source</button></td>
</tr>
</table>
</div>
</div>
<div class='panel panel-default'>
<div class='panel-heading'>
Please review the selections below and then click Finish.
......@@ -239,18 +263,6 @@
</font>
</div>
<div id='finalize_options'>
<div class='form-horizontal'>
<div class='form-group'>
<label class='col-sm-4'
style='text-align: right;'>Profile:</label>
<div class='col-sm-6'>
<input id='finalize_profile_name'
class='form-control'
readonly
value='<%= profilename %>'>
</div>
</div>
</div>
<% if (registered) { %>
<!-- Optional experiment name -->
<div id='name_selector'
......@@ -427,6 +439,7 @@
</div>
</div>
<div id='waitwait_div'></div>
<div id='rspecview_div'></div>
<div id='ppviewmodal_div'></div>
<div id='ppmodal_div'></div>
<div id='instantiate_div'></div>
......
......@@ -23,7 +23,13 @@
<% } else { %>
<%- formfields.profile_version %>
<% } %>
</td></tr>
<% if (version_uuid != latest_uuid) { %>
(Latest:
<a href='manage_profile.php?action=edit&uuid=<%= latest_uuid %>'>
<%- latest_version %></a>)
<% } %>
</td>
</tr>
<tr>
<td>Project:</td>
<td><%- formfields.profile_pid %></td>
......@@ -48,6 +54,23 @@
<% } %>
</tr>
<% } %>
<% if (disabled) { %>
<tr>
<td>Disabled:</td><td><span class="text-danger">Yes</span>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
data-delay='{"hide":500}'
data-content="This profile has been disabled by an
administrator. You may be able to instantiate
another version of this profile, see the
version info above.">
<span class='glyphicon glyphicon-question-sign'
style='margin-bottom: 4px;'></span>
</a>
</td>
</tr>
<% } %>
</table>
<% if (activity) { %>
<a class='btn btn-info btn-xs pull-left'
......@@ -343,7 +366,21 @@
data-key='profile_topdog'
id='profile_topdog' value='checked'
type='checkbox'>Put this profile at the top of
the list.
the list.
</label>
</div>
</div>
</div>
<div class='row'>
<div class='col-sm-10 col-sm-offset-2'>
<div class='checkbox format-me' data-key='profile_disabled'
data-compact='yep'>
<label>
<input name=formfields[profile_disabled]
<%- formfields.profile_disabled %>
data-key='profile_disabled'
id='profile_disabled' value='checked'
type='checkbox'>Disable this version.
</label>
</div>
</div>
......@@ -376,83 +413,85 @@
</button>
<% } %>
<% if (viewing) { %>
<a class='btn btn-primary btn-xs pull-right' disabled
id='profile_instantiate_button'
style='margin-right: 10px;'
type='submit' name='create'>Instantiate
</a>
<% if (!iscloud) { %>
<span class='pull-right'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="When you instantiate as a guest, you get
<% if (!disabled) { %>
<a class='btn btn-primary btn-xs pull-right' disabled
id='profile_instantiate_button'
style='margin-right: 10px;'
type='submit' name='create'>Instantiate
</a>
<% if (!iscloud) { %>
<span class='pull-right'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="When you instantiate as a guest, you get
to see exactly how another user will
experience your profile. This allows you to
better debug your profile for other users">
<button class='btn btn-success btn-xs' disabled
id='guest_instantiate_button'
style='margin-right: 10px;'
data-toggle='modal'
data-target='#guest_instantiate_modal'
type='button'>Instantiate as Guest
</button>
</span>
<% } %>
<a class='btn btn-primary btn-xs pull-left'
id='profile_copy_button'
style='margin-right: 10px;'
type='button'
href='manage_profile.php?action=copy&uuid=<%= version_uuid %>'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="When you <em>copy</em> (instead of
<button class='btn btn-success btn-xs' disabled
id='guest_instantiate_button'
style='margin-right: 10px;'
data-toggle='modal'
data-target='#guest_instantiate_modal'
type='button'>Instantiate as Guest
</button>
</span>
<% } %>
<a class='btn btn-primary btn-xs pull-left'
id='profile_copy_button'
style='margin-right: 10px;'
type='button'
href='manage_profile.php?action=copy&uuid=<%= version_uuid %>'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="When you <em>copy</em> (instead of
clone), 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>
<span class='pull-left'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="Share your profile with other users by
sending them a link to instantiate it.
Click for more info.">
<button class='btn btn-primary btn-xs'
id='profile_share_button'
style='margin-right: 10px;'
type='button'>
Share
</button>
</span>
<% if (candelete) { %>
<button class='btn btn-danger btn-xs pull-left' disabled
id='profile_delete_button'
style='margin-right: 10px;'
data-toggle='modal' data-target='#delete_modal'
type='button' name='delete'>Delete
</button>
<% } %>
<% if (withpublishing && canpublish) { %>
<span class='pull-right'
data-toggle='popover'
data-delay='{"hide":1500, "show":250}'
data-html='true'
data-content="Publishing is like creating
a <em>checkpoint</em>. Click for more info.">
<button class='btn btn-success btn-xs' disabled
id='profile_publish_button'
style='margin-right: 10px;'