Commit 776dc702 authored by Leigh Stoller's avatar Leigh Stoller

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

parent c9f7c976
......@@ -232,5 +232,45 @@ sub Stringify($)
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...
1;
......@@ -33,13 +33,15 @@ use CGI;
#
sub usage()
{
print("Usage: manage_profile [-v] <xmlfile>\n");
print("Usage: manage_profile [-u] <xmlfile>\n");
print("Usage: manage_profile -r profile\n");
exit(-1);
}
my $optlist = "dv";
my $optlist = "dur";
my $debug = 0;
my $verify = 0; # Check data and return status only.
my $update = 0;
my $delete = 0;
my $skipadmin = 0;
#
......@@ -73,6 +75,7 @@ use APT_Profile;
# Protos
sub fatal($);
sub UserError();
sub DeleteProfile($);
#
# Parse command arguments. Once we return from getopts, all that should be
......@@ -88,6 +91,9 @@ if (defined($options{"d"})) {
if (defined($options{"v"})) {
$verify = 1;
}
if (defined($options{"u"})) {
$update = 1;
}
if (@ARGV != 1) {
usage();
}
......@@ -102,7 +108,7 @@ if (! defined($this_user)) {
# Remove profile.
if (defined($options{"r"})) {
exit(DeleteProfile());
exit(DeleteProfile($ARGV[0]));
}
my $xmlfile = shift(@ARGV);
......@@ -112,6 +118,7 @@ my $xmlfile = shift(@ARGV);
my $SLOT_OPTIONAL = 0x1; # The field is not required.
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_UPDATE = 0x8; # Allowed to update.
#
# 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.
......@@ -121,9 +128,9 @@ my %xmlfields =
("profile_name" => ["name", $SLOT_REQUIRED],
"profile_pid" => ["pid", $SLOT_REQUIRED],
"profile_creator" => ["creator", $SLOT_OPTIONAL],
"profile_description" => ["description", $SLOT_REQUIRED],
"profile_public" => ["public", $SLOT_OPTIONAL],
"rspec" => ["rspec", $SLOT_REQUIRED],
"profile_description" => ["description", $SLOT_REQUIRED|$SLOT_UPDATE],
"profile_public" => ["public", $SLOT_OPTIONAL|$SLOT_UPDATE],
"rspec" => ["rspec", $SLOT_REQUIRED|$SLOT_UPDATE],
);
#
......@@ -161,6 +168,7 @@ UserError()
# We build up an array of arguments to create.
#
my %new_args = ();
my %update_args = ();
foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
......@@ -206,6 +214,8 @@ foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
next;
}
$new_args{$dbslot} = $value;
$update_args{$dbslot} = $value
if ($update && ($required & $SLOT_UPDATE));
}
UserError()
if (keys(%errors));
......@@ -222,14 +232,31 @@ elsif (!$project->AccessCheck($this_user, TB_PROJECT_MAKEIMAGEID())) {
$errors{"profile_pid"} = "Not enough permission in this project";
}
my $usererror;
my $profile = APT_Profile->Create($project, $this_user, \%new_args, \$usererror);
if (!defined($profile)) {
if (defined($usererror)) {
$errors{"profile_name"} = $usererror;
my $profile = APT_Profile->Lookup($new_args{"pid"}, $new_args{"name"});
if ($update) {
if (!defined($profile)) {
$errors{"profile_name"} = "No such profile exists";
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);
......@@ -269,3 +296,18 @@ sub escapeshellarg($)
$str =~ s/[^[:alnum:]]/\\$&/g;
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 ()
'formhelpers': 'formhelpers/js/bootstrap-formhelpers',
'dateformat': 'js/lib/date.format',
'd3': 'js/lib/d3.v3',
'filestyle': 'js/lib/filestyle',
},
shim: {
'bootstrap': { deps: ['jquery'] },
'formhelpers': { deps: ['bootstrap']},
'dateformat': { exports: 'dateFormat' },
'd3': { exports: 'd3' }
'd3': { exports: 'd3' },
'filestyle': { deps: ['bootstrap']},
},
});
}
......
......@@ -2,7 +2,7 @@ window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup',
// jQuery modules
'bootstrap', 'formhelpers'],
'bootstrap'],
function ($, sup)
{
'use strict';
......@@ -10,6 +10,12 @@ function ($, sup)
function initialize()
{
window.APT_OPTIONS.initialize(sup);
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
});
$('body').show();
}
$(document).ready(initialize);
......
window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup',
require(['jquery', 'js/quickvm_sup',
// jQuery modules
'bootstrap', 'formhelpers'],
'bootstrap','filestyle'],
function ($, sup)
{
'use strict';
......@@ -16,7 +16,7 @@ function ($, sup)
reader.onload = function(event) {
var content = event.target.result;
sup.ShowUploadedRspec(content);
ShowRspecContent(content);
};
reader.readAsText(this.files[0]);
});
......@@ -24,6 +24,29 @@ function ($, sup)
catch (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);
......
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)
$('button#showtopo_select').click(function (event) {
event.preventDefault();
sup.UpdateProfileSelection($('.selected'));
sup.HideModal('#quickvm_topomodal');
});
}
......
......@@ -2,7 +2,7 @@ window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup',
// jQuery modules
'bootstrap', 'formhelpers'],
'bootstrap'],
function ($, sup)
{
'use strict';
......
......@@ -142,38 +142,39 @@ function ShowTopo(uuid)
function UpdateProfileSelection(selectedElement)
{
console.log(selectedElement);
var profile = $(selectedElement).text();
$('#selected_profile').attr('value', profile);
$('#selected_profile_text').html("" + profile);
if (!$(selectedElement).hasClass('current'))
{
$('#profile_name li').each(function() {
$(this).removeClass('current');
});
$(selectedElement).addClass('current');
}
ShowProfileList(selectedElement);
var profile_name = $(selectedElement).text();
var profile_value = $(selectedElement).attr('value');
$('#selected_profile').attr('value', profile_value);
$('#selected_profile_text').html("" + profile_name);
if (!$(selectedElement).hasClass('current')) {
$('#profile_name li').each(function() {
$(this).removeClass('current');
});
$(selectedElement).addClass('current');
}
ShowProfileList(selectedElement);
}
function ShowProfileList(selectedElement)
{
var profile = $(selectedElement).attr('value');
if (!$(selectedElement).hasClass('selected'))
{
$('#profile_name li').each(function() {
$(this).removeClass('selected');
});
var profile = $(selectedElement).attr('value');
$(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);
if (json.code) {
alert("Could not get profile: " + json.value);
return;
}
var xmlDoc = $.parseXML(json.value.rspec);
var xml = $(xmlDoc);
var topo = ConvertManifestToJSON(profile, xml);
......@@ -185,9 +186,9 @@ function ShowProfileList(selectedElement)
maketopmap("#showtopo_div",
($("#showtopo_div").outerWidth()),
300, topo);
}
var $xmlthing = CallMethod("getprofile", null, 0, profile);
$xmlthing.done(callback);
}
var $xmlthing = CallMethod("getprofile", null, 0, profile);
$xmlthing.done(callback);
}
function ShowProfile(direction)
......@@ -979,12 +980,15 @@ return {
RequestExtension: RequestExtension,
resetForm: resetForm,
ShowModal: ShowModal,
HideModal: HideModal,
ShowProfileList: ShowProfileList,
StartSSH: StartSSH,
Terminate: Terminate,
UpdateProfileSelection: UpdateProfileSelection,
ShowUploadedRspec: ShowUploadedRspec,
LoginByModal: LoginByModal,
Logout: Logout
Logout: Logout,
ConvertManifestToJSON: ConvertManifestToJSON,
maketopmap: maketopmap,
};
});
......@@ -25,6 +25,7 @@ chdir("..");
include("defs.php3");
chdir("apt");
include("quickvm_sup.php");
$page_title = "Login";
#
# Verify page arguments.
......
This diff is collapsed.
<?php
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
chdir("..");
include("defs.php3");
chdir("apt");
include("quickvm_sup.php");
$page_title = "My Profiles";
#
# Verify page arguments.
#
$optargs = OptionalPageArguments("target_user", PAGEARG_USER,
"ajax_request", PAGEARG_BOOLEAN,
"ajax_method", PAGEARG_STRING,
"ajax_argument", PAGEARG_STRING);
#
# Get current user.
#
RedirectSecure();
$this_user = CheckLogin($check_status);
if (!$this_user) {
if (isset($ajax_request)) {
SPITAJAX_ERROR(1, "You are not logged in anymore");
exit();
}
RedirectLoginPage();
exit();
}
#
# Deal with ajax requests.
#
if (isset($ajax_request)) {
if ($ajax_method == "getprofile") {
$profile_idx = addslashes($ajax_argument);
#
# XXX This query effectively allows a user to look at another
# users profile, by cheating the ajax interface. Not a big
# deal yet, but something to worry about right now.
#
$query_result =
DBQueryWarn("select * from apt_profiles ".
"where idx='$profile_idx'");
if (!$query_result || !mysql_num_rows($query_result)) {
SPITAJAX_ERROR(1, "No such profile $profile_idx!");
exit();
}
$row = mysql_fetch_array($query_result);
SPITAJAX_RESPONSE(array('rspec' => $row['rspec'],
'name' => $row['name'],
'idx' => $row['idx'],
'description' => $row['description']));
}
exit();
}
SPITHEADER(1);
if (isset($target_user) && !$this_user->SameUser($target_user)) {
if (!ISADMIN()) {
SPITUSERERROR("You do not have permission to view ".
"target user's profiles");
SPITFOOTER();
exit();
}
}
else {
$target_user = $this_user;
}
$target_idx = $target_user->uid_idx();
$query_result =
DBQueryFatal("select * from apt_profiles ".
"where creator_idx=$target_idx");
if (mysql_num_rows($query_result) == 0) {
echo "<b>No profiles to show you. Maybe you want to ".
"<a href='manage_profile.php'>create one?</a></b>\n";
SPITFOOTER();
exit();
}
$profile_array = array();
$profile_default = null;
while ($row = mysql_fetch_array($query_result)) {
$profile_array[$row["idx"]] = $row["name"];
if (!$profile_default) {
$profile_default = $row["idx"];
}
}
echo "<div class='row'>
<div class='col-lg-6 col-lg-offset-3
col-md-6 col-md-offset-3
col-sm-8 col-sm-offset-2
col-xs-12 col-xs-offset-0'>\n";
echo " <div class='panel panel-default'>
<div class='panel-heading'>
<h3 class='panel-title'>
Your Profiles</h3>
</div>
<div class='panel-body'>
<form id='quickvm_create_profile_form'
role='form'
method='get' action='manage_profile.php'>
<input type='hidden' name='action' value='edit'/>
<div id='profile_well' class='form-group well well-md'>
<span id='selected_profile_text' class='pull-left'>
</span>
<input id='selected_profile' type='hidden' name='idx'/>
<button id='profile' class='btn btn-primary btn-xs pull-right'
type='button' name='profile_button'>
Select a Profile</button>\n";
echo " </div>
<button class='btn btn-primary btn-xs pull-right'
type='submit' name='submit'>Go</button>
</form>
</div>
</div>
</div>
</div>\n";
SpitTopologyViewModal("quickvm_topomodal", $profile_array);
echo "<script type='text/javascript'>\n";
echo "window.PROFILE = '$profile_default';\n";
echo "</script>\n";
echo "<script src='js/lib/require.js' data-main='js/myprofiles'></script>\n";
SPITFOOTER();
?>
......@@ -27,6 +27,7 @@ include_once("osinfo_defs.php");
include_once("geni_defs.php");
chdir("apt");
include("quickvm_sup.php");
$page_title = "QuickVM Create";
$dblink = GetDBLink("sa");
#
......@@ -45,6 +46,7 @@ $optargs = OptionalPageArguments("create", PAGEARG_STRING,
"stuffing", PAGEARG_STRING,
"verify", PAGEARG_STRING,
"sshkey", PAGEARG_STRING,
"profile", PAGEARG_INTEGER,
"ajax_request", PAGEARG_BOOLEAN,
"ajax_method", PAGEARG_STRING,
"ajax_argument", PAGEARG_STRING);
......@@ -81,13 +83,19 @@ $profile_default = "ThreeVMs";
$profile_array = array();
$query_result =
DBQueryFatal("select * from apt_profiles where public=1");
DBQueryFatal("select * from apt_profiles ".
"where public=1 " .
($this_user ? "or creator_idx=" . $this_user->uid_idx() : ""));
while ($row = mysql_fetch_array($query_result)) {
$profile_array[$row["idx"]] = $row["name"];
if ($row["pid"] == $TBOPSPID && $row["name"] == $profile_default) {
$profile_default = $row["idx"];
}
}
# URL specified profile to use.
if (isset($profile) && array_key_exists($profile, $profile_array)) {
$profile_default = $profile;
}
function SPITFORM($username, $email, $sshkey, $profile, $newuser, $errors)
{
......@@ -222,73 +230,20 @@ function SPITFORM($username, $email, $sshkey, $profile, $newuser, $errors)
}
echo "</form>\n";
echo "<!-- This is the topology view modal -->
<div id='quickvm_topomodal' class='modal fade'>
<div class='modal-dialog' id='showtopo_dialog'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>
&times;</button>
<h3>Select a Profile</h3>
</div>
<div class='modal-body'>
<!-- This topo diagram goes inside this div -->
<div class='panel panel-default'
id='showtopo_container'>
<div class='form-group pull-left'>
<ul class='list-group' id='profile_name'
name='profile'
>\n";
while (list ($id, $title) = each ($profile_array)) {
$selected = "";
if ($profile_value == $id)
$selected = "selected";
echo "<li class='list-group-item profile-item' $selected value='$id'>$title </li>\n";
}
SpitTopologyViewModal("quickvm_topomodal", $profile_array);
echo "</ul>
<label
style='color: red'
for='profile'>$profile_error</label>
</div>
<div class='pull-right'>
<span id='showtopo_title'></span>
<div class='panel-body'>
<div id='showtopo_div'></div>
<span class='pull-left' id='showtopo_description'></span>
</div>
</div>
</div>
<div id='showtopo_buttons' class='pull-right'>
<button id='showtopo_select'
class='btn btn-primary btn-sm'
type='submit' name='select'>
Select Profile</button>
<button type='button' class='btn btn-default btn-sm'
data-dismiss='modal' aria-hidden='true'>
Cancel</button>
</div>
</div>
</div>
</div>
</div>\n";
echo "<script type='text/javascript'>\n";
if (isset($profile) && $profile != "") {
echo "<script type='text/javascript'>\n";
if (isset($profile) && $profile != "") {
echo "window.PROFILE = '$profile_value';\n";
}
else {
}
else {
echo "window.PROFILE = '$profile_default';\n";
}
if ($newuser) {
echo "window.APT_OPTIONS.isNewUser = true;\n";
}
echo "</script>\n";
echo "<script src='js/lib/require.js' data-main='js/quickvm'></script>";
}
if ($newuser) {
echo "window.APT_OPTIONS.isNewUser = true;\n";
}
echo "</script>\n";
echo "<script src='js/lib/require.js' data-main='js/quickvm'></script>";
}
if (!isset($create)) {
......
......@@ -27,6 +27,7 @@ include_once("osinfo_defs.php");
include_once("geni_defs.php");
chdir("apt");
include("quickvm_sup.php");
$page_title = "QuickVM Status";
$ajax_request = 0;
#
......
......@@ -48,7 +48,11 @@ function SPITHEADER($thinheader = 0)
{
global $TBMAINSITE;
global $login_user, $login_status;
global $disable_accounts;
global $disable_accounts, $page_title;
$title = "AptLab";
if (isset($page_title)) {
$title .= " - $page_title";
}
$height = ($thinheader ? 150 : 250);
......@@ -62,7 +66,7 @@ function SPITHEADER($thinheader = 0)
echo "<html>
<head>
<title>AptLab</title>
<title>$title</title>
<link rel='stylesheet' href='bootstrap/css/bootstrap.css'>
<link rel='stylesheet' href='quickvm.css'>
<script src='js/common.js'></script>
......@@ -101,7 +105,16 @@ function SPITHEADER($thinheader = 0)
<ul class='nav navbar-nav navbar-left'>
<li><a h