Commit 776dc702 authored by Leigh B Stoller's avatar Leigh B Stoller
Browse files

Add profile editing, deletion, and quick link to instantiate.

parent c9f7c976
...@@ -232,5 +232,45 @@ sub Stringify($) ...@@ -232,5 +232,45 @@ sub Stringify($)
return "[Profile: $pid,$name]"; return "[Profile: $pid,$name]";
} }
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $idx = $self->idx();
my $query = "update apt_profiles set ".
join(",", map("$_=" . DBQuoteSpecial($argref->{$_}), keys(%{$argref})));
$query .= " where idx='$idx'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
sub Delete($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $idx = $self->idx();
DBQueryWarn("delete from apt_profiles where idx='$idx'") or
return -1;
return 0;
}
# _Always_ make sure that this 1 is at the end of the file... # _Always_ make sure that this 1 is at the end of the file...
1; 1;
...@@ -33,13 +33,15 @@ use CGI; ...@@ -33,13 +33,15 @@ use CGI;
# #
sub usage() sub usage()
{ {
print("Usage: manage_profile [-v] <xmlfile>\n"); print("Usage: manage_profile [-u] <xmlfile>\n");
print("Usage: manage_profile -r profile\n"); print("Usage: manage_profile -r profile\n");
exit(-1); exit(-1);
} }
my $optlist = "dv"; my $optlist = "dur";
my $debug = 0; my $debug = 0;
my $verify = 0; # Check data and return status only. my $verify = 0; # Check data and return status only.
my $update = 0;
my $delete = 0;
my $skipadmin = 0; my $skipadmin = 0;
# #
...@@ -73,6 +75,7 @@ use APT_Profile; ...@@ -73,6 +75,7 @@ use APT_Profile;
# Protos # Protos
sub fatal($); sub fatal($);
sub UserError(); sub UserError();
sub DeleteProfile($);
# #
# Parse command arguments. Once we return from getopts, all that should be # Parse command arguments. Once we return from getopts, all that should be
...@@ -88,6 +91,9 @@ if (defined($options{"d"})) { ...@@ -88,6 +91,9 @@ if (defined($options{"d"})) {
if (defined($options{"v"})) { if (defined($options{"v"})) {
$verify = 1; $verify = 1;
} }
if (defined($options{"u"})) {
$update = 1;
}
if (@ARGV != 1) { if (@ARGV != 1) {
usage(); usage();
} }
...@@ -102,7 +108,7 @@ if (! defined($this_user)) { ...@@ -102,7 +108,7 @@ if (! defined($this_user)) {
# Remove profile. # Remove profile.
if (defined($options{"r"})) { if (defined($options{"r"})) {
exit(DeleteProfile()); exit(DeleteProfile($ARGV[0]));
} }
my $xmlfile = shift(@ARGV); my $xmlfile = shift(@ARGV);
...@@ -112,6 +118,7 @@ my $xmlfile = shift(@ARGV); ...@@ -112,6 +118,7 @@ my $xmlfile = shift(@ARGV);
my $SLOT_OPTIONAL = 0x1; # The field is not required. my $SLOT_OPTIONAL = 0x1; # The field is not required.
my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null. my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null.
my $SLOT_ADMINONLY = 0x4; # Only admins can set this field. my $SLOT_ADMINONLY = 0x4; # Only admins can set this field.
my $SLOT_UPDATE = 0x8; # Allowed to update.
# #
# XXX We should encode all of this in the DB so that we can generate the # XXX We should encode all of this in the DB so that we can generate the
# forms on the fly, as well as this checking code. # forms on the fly, as well as this checking code.
...@@ -121,9 +128,9 @@ my %xmlfields = ...@@ -121,9 +128,9 @@ my %xmlfields =
("profile_name" => ["name", $SLOT_REQUIRED], ("profile_name" => ["name", $SLOT_REQUIRED],
"profile_pid" => ["pid", $SLOT_REQUIRED], "profile_pid" => ["pid", $SLOT_REQUIRED],
"profile_creator" => ["creator", $SLOT_OPTIONAL], "profile_creator" => ["creator", $SLOT_OPTIONAL],
"profile_description" => ["description", $SLOT_REQUIRED], "profile_description" => ["description", $SLOT_REQUIRED|$SLOT_UPDATE],
"profile_public" => ["public", $SLOT_OPTIONAL], "profile_public" => ["public", $SLOT_OPTIONAL|$SLOT_UPDATE],
"rspec" => ["rspec", $SLOT_REQUIRED], "rspec" => ["rspec", $SLOT_REQUIRED|$SLOT_UPDATE],
); );
# #
...@@ -161,6 +168,7 @@ UserError() ...@@ -161,6 +168,7 @@ UserError()
# We build up an array of arguments to create. # We build up an array of arguments to create.
# #
my %new_args = (); my %new_args = ();
my %update_args = ();
foreach $key (keys(%{ $xmlparse->{'attribute'} })) { foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'}; my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
...@@ -206,6 +214,8 @@ foreach $key (keys(%{ $xmlparse->{'attribute'} })) { ...@@ -206,6 +214,8 @@ foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
next; next;
} }
$new_args{$dbslot} = $value; $new_args{$dbslot} = $value;
$update_args{$dbslot} = $value
if ($update && ($required & $SLOT_UPDATE));
} }
UserError() UserError()
if (keys(%errors)); if (keys(%errors));
...@@ -222,14 +232,31 @@ elsif (!$project->AccessCheck($this_user, TB_PROJECT_MAKEIMAGEID())) { ...@@ -222,14 +232,31 @@ elsif (!$project->AccessCheck($this_user, TB_PROJECT_MAKEIMAGEID())) {
$errors{"profile_pid"} = "Not enough permission in this project"; $errors{"profile_pid"} = "Not enough permission in this project";
} }
my $usererror; my $profile = APT_Profile->Lookup($new_args{"pid"}, $new_args{"name"});
my $profile = APT_Profile->Create($project, $this_user, \%new_args, \$usererror);
if (!defined($profile)) { if ($update) {
if (defined($usererror)) { if (!defined($profile)) {
$errors{"profile_name"} = $usererror; $errors{"profile_name"} = "No such profile exists";
UserError(); UserError();
} }
fatal("Could not create new profile"); $profile->Update(\%update_args) == 0 or
fatal("Could not update profile record");
}
else {
my $usererror;
if (defined($profile)) {
$errors{"profile_name"} = "Already in use";
UserError();
}
$profile =
APT_Profile->Create($project, $this_user, \%new_args, \$usererror);
if (!defined($profile)) {
if (defined($usererror)) {
$errors{"profile_name"} = $usererror;
UserError();
}
fatal("Could not create new profile");
}
} }
exit(0); exit(0);
...@@ -269,3 +296,18 @@ sub escapeshellarg($) ...@@ -269,3 +296,18 @@ sub escapeshellarg($)
$str =~ s/[^[:alnum:]]/\\$&/g; $str =~ s/[^[:alnum:]]/\\$&/g;
return $str; return $str;
} }
#
# Delete a profile.
#
sub DeleteProfile($)
{
my ($name) = @_;
my $profile = APT_Profile->Lookup($name);
if (!defined($profile)) {
fatal("No such profile exists");
}
$profile->Delete() == 0 or
fatal("Could not delete profile");
return 0;
}
...@@ -10,12 +10,14 @@ window.APT_OPTIONS.config = function () ...@@ -10,12 +10,14 @@ window.APT_OPTIONS.config = function ()
'formhelpers': 'formhelpers/js/bootstrap-formhelpers', 'formhelpers': 'formhelpers/js/bootstrap-formhelpers',
'dateformat': 'js/lib/date.format', 'dateformat': 'js/lib/date.format',
'd3': 'js/lib/d3.v3', 'd3': 'js/lib/d3.v3',
'filestyle': 'js/lib/filestyle',
}, },
shim: { shim: {
'bootstrap': { deps: ['jquery'] }, 'bootstrap': { deps: ['jquery'] },
'formhelpers': { deps: ['bootstrap']}, 'formhelpers': { deps: ['bootstrap']},
'dateformat': { exports: 'dateFormat' }, 'dateformat': { exports: 'dateFormat' },
'd3': { exports: 'd3' } 'd3': { exports: 'd3' },
'filestyle': { deps: ['bootstrap']},
}, },
}); });
} }
......
...@@ -2,7 +2,7 @@ window.APT_OPTIONS.config(); ...@@ -2,7 +2,7 @@ window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup', require(['jquery', 'js/quickvm_sup',
// jQuery modules // jQuery modules
'bootstrap', 'formhelpers'], 'bootstrap'],
function ($, sup) function ($, sup)
{ {
'use strict'; 'use strict';
...@@ -10,6 +10,12 @@ function ($, sup) ...@@ -10,6 +10,12 @@ function ($, sup)
function initialize() function initialize()
{ {
window.APT_OPTIONS.initialize(sup); window.APT_OPTIONS.initialize(sup);
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
});
$('body').show();
} }
$(document).ready(initialize); $(document).ready(initialize);
......
window.APT_OPTIONS.config(); window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup', require(['jquery', 'js/quickvm_sup',
// jQuery modules // jQuery modules
'bootstrap', 'formhelpers'], 'bootstrap','filestyle'],
function ($, sup) function ($, sup)
{ {
'use strict'; 'use strict';
...@@ -16,7 +16,7 @@ function ($, sup) ...@@ -16,7 +16,7 @@ function ($, sup)
reader.onload = function(event) { reader.onload = function(event) {
var content = event.target.result; var content = event.target.result;
sup.ShowUploadedRspec(content); ShowRspecContent(content);
}; };
reader.readAsText(this.files[0]); reader.readAsText(this.files[0]);
}); });
...@@ -24,6 +24,29 @@ function ($, sup) ...@@ -24,6 +24,29 @@ function ($, sup)
catch (e) { catch (e) {
alert(e); alert(e);
} }
$('#showtopo_modal_button').click(function (event) {
event.preventDefault();
// The rspec is taken from the text area.
ShowRspecContent($('#profile_rspec').val());
});
}
//
// Show the rspec text in the modal.
//
function ShowRspecContent(content)
{
var xmlDoc = $.parseXML(content);
var xml = $(xmlDoc);
var topo = sup.ConvertManifestToJSON(null, xml);
console.info(topo);
sup.ShowModal("#quickvm_topomodal");
sup.maketopmap("#showtopo_div",
($("#showtopo_dialog").outerWidth()) - 90,
300, topo);
} }
$(document).ready(initialize); $(document).ready(initialize);
......
window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup',
// jQuery modules
'bootstrap'],
function ($, sup)
{
'use strict';
function initialize()
{
window.APT_OPTIONS.initialize(sup);
sup.UpdateProfileSelection($('#profile_name li[value = ' +
window.PROFILE + ']'));
$('#quickvm_topomodal').on('hidden.bs.modal', function() {
sup.ShowProfileList($('.current'))
});
$('button#profile').click(function (event) {
event.preventDefault();
sup.ShowModal('#quickvm_topomodal');
});
$('li.profile-item').click(function (event) {
event.preventDefault();
sup.ShowProfileList(event.target);
});
$('button#showtopo_select').click(function (event) {
event.preventDefault();
sup.UpdateProfileSelection($('.selected'));
sup.HideModal('#quickvm_topomodal');
});
}
$(document).ready(initialize);
});
...@@ -30,6 +30,7 @@ function ($, sup) ...@@ -30,6 +30,7 @@ function ($, sup)
$('button#showtopo_select').click(function (event) { $('button#showtopo_select').click(function (event) {
event.preventDefault(); event.preventDefault();
sup.UpdateProfileSelection($('.selected')); sup.UpdateProfileSelection($('.selected'));
sup.HideModal('#quickvm_topomodal');
}); });
} }
......
...@@ -2,7 +2,7 @@ window.APT_OPTIONS.config(); ...@@ -2,7 +2,7 @@ window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup', require(['jquery', 'js/quickvm_sup',
// jQuery modules // jQuery modules
'bootstrap', 'formhelpers'], 'bootstrap'],
function ($, sup) function ($, sup)
{ {
'use strict'; 'use strict';
......
...@@ -142,38 +142,39 @@ function ShowTopo(uuid) ...@@ -142,38 +142,39 @@ function ShowTopo(uuid)
function UpdateProfileSelection(selectedElement) function UpdateProfileSelection(selectedElement)
{ {
console.log(selectedElement); var profile_name = $(selectedElement).text();
var profile = $(selectedElement).text(); var profile_value = $(selectedElement).attr('value');
$('#selected_profile').attr('value', profile); $('#selected_profile').attr('value', profile_value);
$('#selected_profile_text').html("" + profile); $('#selected_profile_text').html("" + profile_name);
if (!$(selectedElement).hasClass('current')) if (!$(selectedElement).hasClass('current')) {
{ $('#profile_name li').each(function() {
$('#profile_name li').each(function() { $(this).removeClass('current');
$(this).removeClass('current'); });
}); $(selectedElement).addClass('current');
}
$(selectedElement).addClass('current'); ShowProfileList(selectedElement);
}
ShowProfileList(selectedElement);
} }
function ShowProfileList(selectedElement) function ShowProfileList(selectedElement)
{ {
var profile = $(selectedElement).attr('value'); var profile = $(selectedElement).attr('value');
if (!$(selectedElement).hasClass('selected'))
{
$('#profile_name li').each(function() {
$(this).removeClass('selected');
});
$(selectedElement).addClass('selected'); if (!$(selectedElement).hasClass('selected')) {
} $('#profile_name li').each(function() {
$(this).removeClass('selected');
});
$(selectedElement).addClass('selected');
}
var callback = function(json) { var callback = function(json) {
console.info(json.value); console.info(json.value);
if (json.code) {
alert("Could not get profile: " + json.value);
return;
}
var xmlDoc = $.parseXML(json.value.rspec); var xmlDoc = $.parseXML(json.value.rspec);
var xml = $(xmlDoc); var xml = $(xmlDoc);
var topo = ConvertManifestToJSON(profile, xml); var topo = ConvertManifestToJSON(profile, xml);
...@@ -185,9 +186,9 @@ function ShowProfileList(selectedElement) ...@@ -185,9 +186,9 @@ function ShowProfileList(selectedElement)
maketopmap("#showtopo_div", maketopmap("#showtopo_div",
($("#showtopo_div").outerWidth()), ($("#showtopo_div").outerWidth()),
300, topo); 300, topo);
} }
var $xmlthing = CallMethod("getprofile", null, 0, profile); var $xmlthing = CallMethod("getprofile", null, 0, profile);
$xmlthing.done(callback); $xmlthing.done(callback);
} }
function ShowProfile(direction) function ShowProfile(direction)
...@@ -979,12 +980,15 @@ return { ...@@ -979,12 +980,15 @@ return {
RequestExtension: RequestExtension, RequestExtension: RequestExtension,
resetForm: resetForm, resetForm: resetForm,
ShowModal: ShowModal, ShowModal: ShowModal,
HideModal: HideModal,
ShowProfileList: ShowProfileList, ShowProfileList: ShowProfileList,
StartSSH: StartSSH, StartSSH: StartSSH,
Terminate: Terminate, Terminate: Terminate,
UpdateProfileSelection: UpdateProfileSelection, UpdateProfileSelection: UpdateProfileSelection,
ShowUploadedRspec: ShowUploadedRspec, ShowUploadedRspec: ShowUploadedRspec,
LoginByModal: LoginByModal, LoginByModal: LoginByModal,
Logout: Logout Logout: Logout,
ConvertManifestToJSON: ConvertManifestToJSON,
maketopmap: maketopmap,
}; };
}); });
...@@ -25,6 +25,7 @@ chdir(".."); ...@@ -25,6 +25,7 @@ chdir("..");
include("defs.php3"); include("defs.php3");
chdir("apt"); chdir("apt");
include("quickvm_sup.php"); include("quickvm_sup.php");
$page_title = "Login";
# #
# Verify page arguments. # Verify page arguments.
......
...@@ -25,6 +25,7 @@ chdir(".."); ...@@ -25,6 +25,7 @@ chdir("..");
include("defs.php3"); include("defs.php3");
chdir("apt"); chdir("apt");
include("quickvm_sup.php"); include("quickvm_sup.php");
$page_title = "Manage Profile";
# #
# Get current user. # Get current user.
...@@ -35,6 +36,8 @@ $this_user = CheckLogin($check_status); ...@@ -35,6 +36,8 @@ $this_user = CheckLogin($check_status);
# Verify page arguments. # Verify page arguments.
# #
$optargs = OptionalPageArguments("create", PAGEARG_STRING, $optargs = OptionalPageArguments("create", PAGEARG_STRING,
"action", PAGEARG_STRING,
"idx", PAGEARG_INTEGER,
"formfields", PAGEARG_ARRAY); "formfields", PAGEARG_ARRAY);
# #
...@@ -42,8 +45,19 @@ $optargs = OptionalPageArguments("create", PAGEARG_STRING, ...@@ -42,8 +45,19 @@ $optargs = OptionalPageArguments("create", PAGEARG_STRING,
# #
function SPITFORM($formfields, $errors) function SPITFORM($formfields, $errors)
{ {
global $this_user, $projlist; global $this_user, $projlist, $action, $idx;
$editing = 0;
if ($action == "edit") {
$button_label = "Modify";
$title = "Modify Profile";
$editing = 1;
}
else {
$button_label = "Create";
$title = "Create Profile";
}
# XSS prevention. # XSS prevention.
while (list ($key, $val) = each ($formfields)) { while (list ($key, $val) = each ($formfields)) {
$formfields[$key] = CleanString($val); $formfields[$key] = CleanString($val);
...@@ -64,7 +78,8 @@ function SPITFORM($formfields, $errors) ...@@ -64,7 +78,8 @@ function SPITFORM($formfields, $errors)
echo " <label for='$field' ". echo " <label for='$field' ".
" class='col-sm-3 control-label'>$label "; " class='col-sm-3 control-label'>$label ";
if ($help) { if ($help) {
echo "<a href='#' data-toggle='tooltip' title='$help'>". echo "<a href='#' class='btn btn-xs'
data-toggle='popover' data-content='$help'>".
"<span c