Commit 27740c3f authored by Leigh Stoller's avatar Leigh Stoller

Add a new drop down menu to the Portal Show Project page:

* Only for leaders and managers.

* Currently includes options to show project history and images.

* Add support for getting a list of all images in the project in all of
  the clusters (just like for a user) and allow leaders/managers to
  delete any image in the project.
parent d9480ed7
......@@ -39,10 +39,11 @@ sub usage()
print STDERR "Usage: manage_images [options] relocate ...\n";
exit(-1);
}
my $optlist = "dt:";
my $optlist = "dt:u:";
my $debug = 0;
my $webtask_id;
my $webtask;
my $target_user;
#
# Configure variables
......@@ -84,11 +85,14 @@ use GeniResponse;
use GeniCertificate;
use GeniCredential;
use GeniImage;
use Genixmlrpc;
use GeniXML;
use GeniUser;
use APT_Geni;
use APT_Profile;
use APT_Aggregate;
use APT_Instance;
use APT_Utility;
# Protos
sub fatal($);
......@@ -125,11 +129,19 @@ if (@ARGV < 1) {
my $action = shift(@ARGV);
# Need a real user.
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
if (defined($options{"u"})) {
$target_user = User->Lookup($options{"u"});
if (! defined($target_user)) {
fatal("No such user!");
}
}
else {
$target_user = User->ThisUser();
if (! defined($target_user)) {
fatal("You ($UID) do not exist!");
}
}
my $geniuser = GeniUser->CreateFromLocal($this_user);
my $geniuser = GeniUser->CreateFromLocal($target_user);
if ($action eq "list") {
exit(DoListImages());
......@@ -154,12 +166,12 @@ exit(1);
sub DoListImages()
{
my $usage = sub {
print STDERR "Usage: manage_images list [-a am_urn]\n";
print STDERR "Usage: manage_images list [-a am_urn] [-p pid]\n";
exit(-1);
};
my $optlist = "a:p";
my $optlist = "a:p:";
my $aggregate_urn = $MYURN;
my $withprofiles = 0;
my $project;
my $errmsg;
my %options = ();
if (! getopts($optlist, \%options)) {
......@@ -169,7 +181,14 @@ sub DoListImages()
$aggregate_urn = $options{"a"};
}
if (defined($options{"p"})) {
$withprofiles = 1;
$project = Project->Lookup($options{"p"});
if (!defined($project)) {
fatal("No such project");
}
if (! ($project->IsLeader($target_user) ||
$project->IsManager($target_user))) {
UserError("Not enough permissions to list images in project");
}
}
my $context = APT_Geni::GeniContext();
if (!defined($context)) {
......@@ -195,23 +214,32 @@ sub DoListImages()
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
}
my ($credential, $speaksfor) = APT_Geni::GenUserCredential($geniuser);
my $args = {};
my ($credential, $speaksfor);
if (defined($project)) {
($credential, $speaksfor) = APT_Geni::GenProjectCredential($project,
$geniuser);
$args->{"project_urn"} = $project->urn();
}
else {
($credential, $speaksfor) = APT_Geni::GenUserCredential($geniuser);
}
fatal("Could not generate credentials for user")
if (!defined($credential));
my $credentials = [$credential->asString()];
if (defined($speaksfor)) {
$credentials = [@$credentials, $speaksfor->asString()];
}
my $args = {
"credentials" => $credentials,
};
$args->{"credentials"} = $credentials;
my $response = Genixmlrpc::CallMethod($cmurl, undef, "ListImages", $args);
if ($response->code() != GENIRESPONSE_SUCCESS) {
print STDERR $response->error() . "\n";
ExitWithError($response);
}
#
# We get back a flat list, which can include mulitple versions of
# each image. Lets reorganize into multilevel hash structure
......@@ -247,6 +275,29 @@ sub DoListImages()
# Try and set a local project, but use remote pid otherwise.
$image->{'pid'} = $ospid;
# Convert creator and updater when getting list for project.
if (defined($project)) {
my $hrn = GeniHRN->new($image->{'creator_urn'});
my $geniuser = MapUserURN($hrn);
if (defined($geniuser)) {
$image->{'creator_uid'} = $geniuser->uid();
$image->{'creator_idx'} = $geniuser->idx();
}
else {
$image->{'creator_uid'} = $hrn->id();
}
if (exists($image->{'updater_urn'})) {
$hrn = GeniHRN->new($image->{'updater_urn'});
$geniuser = MapUserURN($hrn);
if (defined($geniuser)) {
$image->{'updater_uid'} = $geniuser->uid();
$image->{'updater_idx'} = $geniuser->idx();
}
else {
$image->{'updater_uid'} = $hrn->id();
}
}
}
if (!exists($ilist{$urn})) {
$ilist{$urn} = [];
}
......@@ -275,6 +326,7 @@ sub DoListImages()
#
if (exists($image0->{'project_urn'})) {
my $projhrn = GeniHRN->new($image0->{'project_urn'});
if ($projhrn->domain() eq $OURDOMAIN) {
my $project;
......@@ -290,12 +342,18 @@ sub DoListImages()
$ref->{'pid_idx'} = $project->pid_idx();
}
}
$ref->{"project_urn"} = $image0->{'project_urn'};
}
else {
# Remote pid, set above
$ref->{'pid'} = $image0->{'pid'};
}
$ref->{'imagename'} = $image0->{'imagename'};
if (defined($project)) {
$ref->{'creator_uid'} = $image0->{'creator_uid'};
$ref->{'creator_idx'} = $image0->{'creator_idx'}
if (exists($image0->{'creator_idx'}));
}
$ref->{'imagename'} = $image0->{'imagename'};
#
# Find profiles using the named image
......@@ -424,14 +482,16 @@ sub DoListImages()
sub DoDeleteImage()
{
my $usage = sub {
print STDERR "Usage: manage_images delete [-a am_urn] <image_urn>\n";
print STDERR "Usage: manage_images delete [-a am_urn] ".
"[-d profile -v versions] <image_urn>\n";
exit(-1);
};
my $optlist = "a:d:v:n";
my $optlist = "a:d:v:np:";
my $aggregate_urn = $MYURN;
my $impotent = 0;
my $profile;
my $errmsg;
my $project;
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
......@@ -439,6 +499,15 @@ sub DoDeleteImage()
if (defined($options{"a"})) {
$aggregate_urn = $options{"a"};
}
if (defined($options{"p"})) {
$project = Project->Lookup($options{"p"});
if (!defined($project)) {
fatal("No such project");
}
if (! ($target_user->IsAdmin() || $project->IsMember($target_user))) {
fatal("Not enough permission in project");
}
}
if (defined($options{"n"})) {
$impotent = 1;
}
......@@ -560,18 +629,29 @@ sub DoDeleteImage()
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
}
my ($credential, $speaksfor) = APT_Geni::GenUserCredential($geniuser);
my $args = {
"image_urn" => $image_urn,
};
my ($credential, $speaksfor);
if ($target_user->IsAdmin() ||
(defined($project) &&
($project->IsLeader($target_user) ||
$project->IsManager($target_user)))) {
($credential, $speaksfor) = APT_Geni::GenProjectCredential($project,
$geniuser);
}
else {
($credential, $speaksfor) = APT_Geni::GenUserCredential($geniuser);
}
fatal("Could not generate credentials for user")
if (!defined($credential));
my $credentials = [$credential->asString()];
if (defined($speaksfor)) {
$credentials = [@$credentials, $speaksfor->asString()];
}
my $args = {
"image_urn" => $image_urn,
"credentials" => $credentials,
};
$args->{"credentials"} = $credentials;
if ($impotent) {
$args->{"impotent"} = 1;
}
......@@ -732,7 +812,7 @@ sub DoRelocate()
}
}
else {
$user = $this_user;
$user = $target_user;
}
if (defined($options{"i"})) {
$imagename = $options{"i"};
......
<?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
#
......@@ -27,7 +27,8 @@ chdir("apt");
include_once("ajax-routines.ajax");
# We set this in CheckPageArgs
$target_user = null;
$target_user = null;
$target_project = null;
#
# Need to check the permission, since we allow admins to mess with
......@@ -35,35 +36,57 @@ $target_user = null;
#
function CheckPageArgs()
{
global $this_user, $target_user;
global $this_user, $target_user, $target_project;
global $ajax_args;
global $TB_USERINFO_READINFO;
if (!isset($ajax_args["uid"])) {
SPITAJAX_ERROR(-1, "Missing target uid");
return -1;
}
$uid = $ajax_args["uid"];
if (isset($ajax_args["uid"])) {
$uid = $ajax_args["uid"];
if (!TBvalid_uid($uid)) {
SPITAJAX_ERROR(-1, "Invalid target uid");
return -1;
}
$target_user = User::Lookup($uid);
if (!$target_user) {
sleep(2);
SPITAJAX_ERROR(-1, "Unknown target uid");
return -1;
}
if ($uid == $this_user->uid())
return 0;
if (!TBvalid_uid($uid)) {
SPITAJAX_ERROR(-1, "Invalid target uid");
return -1;
}
$target_user = User::Lookup($uid);
if (!$target_user) {
sleep(2);
SPITAJAX_ERROR(-1, "Unknown target uid");
return -1;
}
if ($uid == $this_user->uid())
return 0;
if (ISADMIN() || ISFOREIGN_ADMIN())
return 0;
if ($target_user->AccessCheck($this_user, $TB_USERINFO_READINFO))
return 0;
SPITAJAX_ERROR(1, "Not enough permission");
}
elseif (isset($ajax_args["pid"])) {
$pid = $ajax_args["pid"];
if (!ISADMIN() && !ISFOREIGN_ADMIN() &&
!$target_user->AccessCheck($this_user, $TB_USERINFO_READINFO)) {
SPITAJAX_ERROR(-1, "Not enough permission");
if (!TBvalid_pid($pid)) {
SPITAJAX_ERROR(-1, "Invalid target pid");
return -1;
}
$target_project = Project::Lookup($pid);
if (!$target_project) {
sleep(2);
SPITAJAX_ERROR(-1, "Unknown target pid");
return -1;
}
if (ISADMIN() || ISFOREIGN_ADMIN())
return 0;
if ($target_project->IsLeader($this_user) ||
$target_project->IsManager($this_user)) {
return 0;
}
SPITAJAX_ERROR(1, "Not enough permission");
}
else {
SPITAJAX_ERROR(-1, "Missing target uid/pid");
return -1;
}
return 0;
return 1;
}
#
......@@ -71,7 +94,7 @@ function CheckPageArgs()
#
function Do_ListImages()
{
global $this_user, $target_user;
global $this_user, $target_user, $target_project;
global $ajax_args;
global $TB_PROJECT_CREATEEXPT, $suexec_output;
......@@ -91,13 +114,20 @@ function Do_ListImages()
SPITAJAX_ERROR(-1, "No such cluster");
return;
}
$uid = $target_user->uid();
$popt = "";
if ($target_project) {
$uid = $this_user->uid();
$popt = "-p " . $target_project->pid();
}
else {
$uid = $target_user->uid();
}
$urn = $aggregate->urn();
$webtask = WebTask::CreateAnonymous();
$webtask_id = $webtask->task_id();
$retval = SUEXEC($uid, "nobody",
"webmanage_images -t $webtask_id list -a '$urn'",
"webmanage_images -t $webtask_id list -a '$urn' $popt",
SUEXEC_ACTION_IGNORE);
$webtask->Refresh();
......@@ -122,18 +152,19 @@ function Do_ListImages()
#
function Do_DeleteImage()
{
global $this_user, $target_user;
global $this_user;
global $ajax_args;
global $suexec_output;
global $TB_PROJECT_READINFO;
$pdarg = "";
if (CheckPageArgs()) {
return;
}
if (!isset($ajax_args["urn"]) || $ajax_args["urn"] == "") {
SPITAJAX_ERROR(-1, "Missing image urn");
return;
}
if (!TBvalid_URN($ajax_args["urn"])) {
SPITAJAX_ERROR(-1, "Invalid image urn");
return;
}
$image_urn = escapeshellarg($ajax_args["urn"]);
if (!isset($ajax_args["cluster"])) {
......@@ -149,6 +180,26 @@ function Do_DeleteImage()
SPITAJAX_ERROR(-1, "No such cluster");
return;
}
if (!isset($ajax_args["pid"]) || $ajax_args["pid"] == "") {
SPITAJAX_ERROR(-1, "Missing project");
return;
}
if (!TBvalid_pid($ajax_args["pid"])) {
SPITAJAX_ERROR(-1, "Invalid project");
return;
}
$project = Project::Lookup($ajax_args["pid"]);
if (!$project) {
SPITAJAX_ERROR(-1, "No such project");
return;
}
if (! (ISADMIN() ||
$project->AccessCheck($this_user, $TB_PROJECT_READINFO))) {
SPITAJAX_ERROR(-1, "Not enough permission in project");
return;
}
$pid = $project->pid();
if (isset($ajax_args["profile-delete"]) &&
$ajax_args["profile-delete"] != "") {
if (!preg_match("/^[-\w]+$/", $ajax_args["profile-delete"])) {
......@@ -170,14 +221,13 @@ function Do_DeleteImage()
$pdarg .= " -v " . implode(",", $ajax_args["profile-delete-versions"]);
}
$uid = $target_user->uid();
$aggurn = $aggregate->urn();
$webtask = WebTask::CreateAnonymous();
$webtask_id = $webtask->task_id();
$retval = SUEXEC($uid, "nobody",
$retval = SUEXEC($this_user->uid(), "nobody",
"webmanage_images -t $webtask_id ".
" delete -a '$aggurn' $pdarg $image_urn",
" delete -a '$aggurn' $pdarg -p $pid $image_urn",
SUEXEC_ACTION_IGNORE);
if ($retval) {
......
......@@ -104,8 +104,8 @@ $(function ()
// Generate the main template.
var html = listTemplate({
"images" : images,
"showproject" : false,
"showuser" : false,
"showproject" : window.TARGET_PROJECT === undefined,
"showuser" : window.TARGET_PROJECT !== undefined,
"name" : name,
"error" : error,
"showformat" : showformat,
......@@ -247,10 +247,16 @@ $(function ()
});
}
var args = {"cluster" : name};
if (window.TARGET_PROJECT !== undefined) {
args["pid"] = window.TARGET_PROJECT;
}
else {
args["uid"] = window.TARGET_USER;
}
var xmlthing = sup.CallServerMethod(null, "images",
"ListImages",
{"cluster" : name,
"uid" : window.TARGET_USER});
"ListImages", args);
xmlthing.done(callback);
});
}
......@@ -318,8 +324,8 @@ $(function ()
}
table.trigger('update');
};
var args = {"urn" : urn,
"uid" : window.TARGET_USER,
var args = {"urn" : urn,
"pid" : imagelist[cluster][index]["pid"],
"cluster" : cluster};
/*
* Look to see if this is a row with a profile in it, which
......
......@@ -25,6 +25,7 @@ $(function ()
emulablink : window.EMULAB_LINK,
isadmin : window.ISADMIN,
target_project : window.TARGET_PROJECT,
showmore : window.ISLEADER || window.ISMANAGER ? 1 : 0,
});
$('#main-body').html(html);
$('#waitwait_div').html(waitString);
......
<?php
#
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
# Copyright (c) 2000-2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -38,8 +38,9 @@ $this_uid = $this_user->uid();
#
# Verify page arguments. Cluster is a domain that we turn into a URN.
#
$optargs = OptionalPageArguments("cluster", PAGEARG_STRING,
"target_user", PAGEARG_USER);
$optargs = OptionalPageArguments("cluster", PAGEARG_STRING,
"target_user", PAGEARG_USER,
"target_project", PAGEARG_PROJECT);
SPITHEADER(1);
......@@ -49,6 +50,13 @@ if (isset($target_user)) {
SPITUSERERROR("Not enough permission to view this page!");
}
}
if (isset($target_project)) {
if (! ($target_project->IsLeader($this_user) ||
$target_project->IsManager($this_user) ||
ISADMIN() || ISFOREIGN_ADMIN())) {
SPITUSERERROR("Not enough permission to view this page!");
}
}
else {
$target_user = $this_user;
}
......@@ -102,7 +110,12 @@ echo "<div id='oops_div'></div>
<div id='confirm_div'></div>\n";
echo "<script type='text/javascript'>\n";
echo " window.TARGET_USER = '" . $target_user->uid() . "';\n";
if ($target_project) {
echo " window.TARGET_PROJECT = '" . $target_project->pid() . "';\n";
}
else {
echo " window.TARGET_USER = '" . $target_user->uid() . "';\n";
}
echo "</script>\n";
REQUIRE_UNDERSCORE();
......
<?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
#
......@@ -54,6 +54,7 @@ $canapprove = $project->AccessCheck($this_user, $TB_PROJECT_ADDUSER) ? 1 : 0;
$canbestow = $project->AccessCheck($this_user,
$TB_PROJECT_BESTOWGROUPROOT) ? 1 : 0;
$isleader = $project->IsLeader($this_user);
$ismanager = $project->IsManager($this_user);
echo "<link rel='stylesheet'
href='css/tablesorter.css'>\n";
......@@ -61,6 +62,7 @@ echo "<link rel='stylesheet'
echo "<script type='text/javascript'>\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.ISLEADER = $isleader;\n";
echo " window.ISMANAGER = $ismanager;\n";
echo " window.CANAPPROVE = $canapprove;\n";
echo " window.CANBESTOW = $canbestow;\n";
echo " window.EMULAB_LINK = '$emulablink';\n";
......
......@@ -38,7 +38,12 @@
<% } else { %>
<table class='tablesorter hidden' id='images-table-no-profiles'>
<caption>
<center>Images not used by any of your profiles
<center>
<% if (showuser && !showproject) { %>
Project images not used by any profiles
<% } else { %>
Images not used by any of your profiles
<% } %>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
......@@ -64,7 +69,12 @@
<thead>
<tr>
<th class="col-md-2">Image</th>
<th class="col-md-2">Project</th>
<% if (showproject) { %>
<th class="col-md-2">Project</th>
<% } %>
<% if (showuser) { %>
<th class="col-md-2">User</th>
<% } %>
<th class="col-md-2">Created</th>
<th class="col-md-3 sorter-false">Description</th>
<% if (showformat) { %>
......@@ -89,15 +99,29 @@
style='color: red; margin-left: 3px;'></span></a>
<% } %>
</td>
<% if (_.has(image, "pid_idx")) { %>
<td>
<a target="_blank"
href=show-project.php?pid=<%- image.pid_idx %>>
<%- image.pid %>
</a>
</td>
<% } else { %>
<td><%- image.pid %></td>
<% if (showproject) { %>
<% if (_.has(image, "pid_idx")) { %>
<td>
<a target="_blank"
href=show-project.php?pid=<%- image.pid_idx %>>
<%- image.pid %>
</a>
</td>
<% } else { %>
<td><%- image.pid %></td>
<% } %>
<% } %>
<% if (showuser) { %>
<% if (_.has(image, "creator_idx")) { %>
<td>
<a target="_blank"
href=show-user.php?user=<%- image.creator_idx %>>
<%- image.creator_uid %>
</a>
</td>
<% } else { %>
<td><%- image.creator_uid %></td>
<% } %>
<% } %>
<td class="format-date"><%- image.versions[0].created %></td>
<td><%- image.versions[0].description %></td>
......@@ -134,7 +158,25 @@
style='color: red; margin-left: 3px;'></span></a>
<% } %>
</td>
<td><%- image.pid %></td>
<% if (showproject) { %>
<td><%- image.pid %></td>
<% } %>
<% if (showuser) { %>
<% if (_.has(version, "updater_uid")) { %>
<% if (_.has(version, "updater_idx")) { %>
<td>
<a target="_blank"
href=show-user.php?user=<%- version.updater_idx %>>
<%- version.updater_uid %>
</a>
</td>
<% } else { %>
<td><%- version.updater_uid %></td>
<% } %>
<% } else { %>
<td><%- image.creator_uid %></td>
<% } %>
<% } %>
<td class="format-date"><%- version.created %></td>
<td><%- version.description %></td>
<% if (showformat) { %>
......@@ -153,7 +195,12 @@
</table>
<table class='tablesorter hidden' id='images-table-one-profile'>
<caption>
<center>Images used by only a single profile
<center>
<% if (showuser && !showproject) { %>
Project Images used by only a single profile
<% } else { %>
Images used by only a single profile
<% } %>
<a href='#' class='btn btn-xs'
data-toggle='popover'
data-html='true'
......@@ -187,7 +234,12 @@
<thead>
<tr>
<th class="col-md-2">Image</th>
<th class="col-md-2">Project</th>
<% if (showproject) { %>
<th class="col-md-2">Project</th>
<% } %>
<% if (showuser) { %>
<th class="col-md-2">User</th>
<% } %>
<th class="col-md-2">Created</th>
<th class="col-md-3 sorter-false">Description</th>
<% if (showformat) { %>
......@@ -215,15 +267,29 @@
<span class="glyphicon glyphicon-chevron-right"></span></a>
<%- image.imagename %>
</td>
<% if (_.has(image, "pid_idx")) { %>
<td>
<a target="_blank"
href=show-project.php?pid=<%- image.pid_idx %>>
<%- image.pid %>
</a>
</td>
<% } else { %>
<td><%- image.pid %></td>
<% if (showproject) { %>
<% if (_.has(image, "pid_idx")) { %>
<td>
<a target="_blank"
href=show-project.php?pid=<%- image.pid_idx %>>
<%- image.pid %>
</a>
</td>
<% } else { %>
<td><%- image.pid %></td>
<% } %>
<% } %>
<% if (showuser) { %>
<% if (_.has(image, "creator_idx")) { %>
<td>
<a target="_blank"
href=show-user.php?user=<%- image.creator_idx %>>
<%- image.creator_uid %>
</a>
</td>
<% } else { %>
<td><%- image.creator_uid %></td>
<% } %>
<% } %>
<td class="format-date"><%- image.versions[0].created %></td>
<td><%- image.versions[0].description %></td>
......@@ -260,7 +326,25 @@
<span class='glyphicon glyphicon-remove'
style='color: red; margin-left: 3px;'></span></a>
</td>
<td><%- image.pid %></td>
<% if (showproject) { %>
<td><%- image.pid %></td>
<% } %>
<% if (showuser) { %>