All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

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

Allow admins to provide reason when deleting or denying a reservation.

See issue #276.
parent 5d952a85
......@@ -476,6 +476,20 @@ sub DoList()
#
sub DoDelete()
{
#
# We allow for admins to pass a reason (deny with cause).
#
my $optlist = "N:";
my $reason;
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"N"})) {
$reason = readfile($options{"N"});
chomp($reason);
}
usage()
if (@ARGV != 2);
my $pid = shift(@ARGV);
......@@ -493,10 +507,14 @@ sub DoDelete()
!$project->AccessCheck($this_user, TB_PROJECT_CREATEEXPT())) {
fatal("No permission to access reservation list for $project")
}
my $blob = { "idx" => $idx,
"project" => $project->urn()->asString()
};
if ($this_user->IsAdmin() && defined($reason) && $reason ne "") {
$blob->{'reason'} = $reason;
}
my $response =
APT_Geni::PortalRPC($authority, undef, "DeleteReservation",
{"idx" => $idx,
"project" => $project->urn()->asString()});
APT_Geni::PortalRPC($authority, undef, "DeleteReservation", $blob);
if (GeniResponse::IsError($response)) {
#
# All errors are fatal.
......
......@@ -1014,6 +1014,7 @@ sub ApproveReservation($)
sub DeleteReservation($)
{
my ($argref) = @_;
my $args = "";
my $project;
#
......@@ -1058,10 +1059,29 @@ sub DeleteReservation($)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if ($reservation->pid() ne $project->pid());
$reservation->Cancel() == 0
or GeniResponse->Create(GENIRESPONSE_ERROR);
# Write the reason to a tempfile to pass in. This will auto unlink.
my $fp;
if (exists($argref->{"reason"}) && $argref->{"reason"} ne "") {
my $reason = $argref->{"reason"};
if (!TBcheck_dbslot($reason, "default", "fulltext",
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
return GeniResponse->MalformedArgsResponse("Invalid reason")
}
$fp = File::Temp->new();
print $fp $reason;
$args = "-D $fp";
chmod(0755, "$fp");
}
GeniUtil::FlipToElabMan();
my $output = GeniUtil::ExecQuiet("$WAP $TB/sbin/reserve -c $idx $args");
if ($?) {
GeniUtil::FlipToGeniUser();
print STDERR "$output\n";
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef, $output);
}
GeniUtil::FlipToGeniUser();
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
......
......@@ -449,6 +449,7 @@ sub Start($$)
$method eq "Update" ||
$method eq "Reserve" ||
$method eq "ApproveReservation" ||
$method eq "DeleteReservation" ||
$method eq "DeleteSlice") {
$debug = 2;
$nostorelogs = 0;
......
......@@ -14,7 +14,7 @@ $(function ()
function initialize()
{
window.APT_OPTIONS.initialize(sup);
amlist = decodejson('#amlist-json');
amlist = decodejson('#amlist-json');
$('#oops_div').html(oopsString);
$('#waitwait_div').html(waitwaitString);
......@@ -63,6 +63,7 @@ $(function ()
"showproject" : true,
"showuser" : true,
"name" : name,
"isadmin" : window.ISADMIN,
});
html =
"<div class='row' id='" + name + "'>" +
......@@ -90,6 +91,18 @@ $(function ()
DeleteReservation($(this).closest('tr'));
return false;
});
if (window.ISADMIN) {
// Bind a deny handler.
$('#' + name + ' .deny-button').click(function() {
DenyReservation($(this).closest('tr'));
return false;
});
}
// This activates the tooltip subsystem.
$('[data-toggle="tooltip"]').tooltip({
delay: {"hide" : 250, "show" : 250},
placement: 'auto',
});
}
var xmlthing = sup.CallServerMethod(null, "reserve",
"ListReservations",
......@@ -139,6 +152,50 @@ $(function ()
sup.ShowModal("#confirm_modal");
}
/*
* Deny a reservation with cause. When complete, delete the table row.
*/
function DenyReservation(row) {
// This is what we are deleting.
var idx = $(row).attr('data-idx');
var pid = $(row).attr('data-pid');
var cluster = $(row).attr('data-cluster');
var table = $(row).closest("table");
// Callback for the delete request.
var callback = function (json) {
sup.HideModal('#waitwait-modal');
console.log("deny", json);
if (json.code) {
sup.SpitOops("oops", json.value);
return;
}
$(row).remove();
table.trigger('update');
};
// Bind the confirm button in the modal. Do the deletion.
$('#deny-modal #confirm-deny').click(function () {
sup.HideModal('#deny-modal', function () {
var reason = $('#deny-reason').val();
sup.ShowModal('#waitwait-modal');
var xmlthing = sup.CallServerMethod(null, "reserve",
"Delete",
{"idx" : idx,
"pid" : pid,
"cluster" : cluster,
"reason" : reason});
xmlthing.done(callback);
});
});
// Handler so we know the user closed the modal. We need to
// clear the confirm button handler.
$('#deny-modal').on('hidden.bs.modal', function (e) {
$('#deny-modal #confirm-deny').unbind("click");
$('#deny-modal').off('hidden.bs.modal');
})
sup.ShowModal("#deny-modal");
}
// Helper.
function decodejson(id) {
return JSON.parse(_.unescape($(id)[0].textContent));
......
......@@ -39,6 +39,10 @@ $(function ()
*/
if (editing) {
PopulateReservation();
$('#reserve-delete-button').click(function (e) {
e.preventDefault();
Delete();
});
}
}
......@@ -440,6 +444,8 @@ $(function ()
Approve();
});
}
// Need this in Delete().
window.PID = details.pid;
};
sup.ShowWaitWait();
var xmlthing = sup.CallServerMethod(null, "reserve",
......@@ -449,6 +455,44 @@ $(function ()
xmlthing.done(callback);
}
/*
* Delete a reservation
*/
function Delete()
{
var callback = function(json) {
sup.HideWaitWait();
if (json.code) {
sup.SpitOops("oops", json.value);
return;
}
window.location.replace("list-reservations.php");
};
// Bind the confirm button in the modal. Do the deletion.
$('#delete-reservation-modal #confirm-delete').click(function () {
sup.HideModal('#delete-reservation-modal', function () {
var reason = $('#delete-reason').val();
sup.ShowModal('#waitwait-modal');
var xmlthing = sup.CallServerMethod(null, "reserve",
"Delete",
{"cluster" : window.CLUSTER,
"idx" : window.IDX,
"pid" : window.PID,
"reason" : reason});
xmlthing.done(callback);
});
});
// Handler so we know the user closed the modal. We need to
// clear the confirm button handler.
$('#delete-reservation-modal').on('hidden.bs.modal', function (e) {
$('#delete-reservation-modal #confirm-delete').unbind("click");
$('#delete-reservation-modal').off('hidden.bs.modal');
})
sup.ShowModal("#delete-reservation-modal");
}
function HandleClusterChange(selected_cluster)
{
/*
......
......@@ -34,6 +34,7 @@ $page_title = "List Reservations";
RedirectSecure();
$this_user = CheckLoginOrRedirect();
$this_uid = $this_user->uid();
$isadmin = (ISADMIN() ? 1 : 0);
#
# Verify page arguments. Cluster is a domain that we turn into a URN.
......@@ -87,6 +88,10 @@ echo "<div id='oops_div'></div>
<div id='waitwait_div'></div>
<div id='confirm_div'></div>\n";
echo "<script type='text/javascript'>\n";
echo " window.ISADMIN = $isadmin;\n";
echo "</script>\n";
REQUIRE_UNDERSCORE();
REQUIRE_SUP();
REQUIRE_MOMENT();
......
......@@ -286,14 +286,37 @@ function Do_Delete()
return;
}
#
# Admins can add an optional cause when denying a reservation.
#
$opt = "";
if (ISADMIN()) {
if (isset($ajax_args["reason"])) {
$reason = $ajax_args["reason"];
if (!TBvalid_fulltext($reason)) {
SPITAJAX_ERROR(-1, "Illegal characters in reason");
return;
}
$reasonfile = tempnam("/tmp", "reason");
$fp = fopen($reasonfile, "w");
fwrite($fp, $reason);
fclose($fp);
chmod($reasonfile, 0666);
$opt = "-N $reasonfile";
}
}
$this_uid = $this_user->uid();
$pid = $project->pid();
$urn = $aggregate->urn();
$retval = SUEXEC($this_uid, $pid,
"webmanage_reservations -a '$urn' delete $pid $idx",
"webmanage_reservations -a '$urn' delete $opt $pid $idx",
SUEXEC_ACTION_CONTINUE);
if (isset($reasonfile)) {
unlink($reasonfile);
}
if ($retval) {
SPITAJAX_ERROR(-1, $suexec_output);
return;
......
......@@ -149,7 +149,7 @@ REQUIRE_SUP();
REQUIRE_MOMENT();
REQUIRE_APTFORMS();
AddTemplateList(array("reserve-request", "reserve-faq", "reservation-list",
"oops-modal", "waitwait-modal"));
"oops-modal", "waitwait-modal", "confirm-modal"));
SPITREQUIRE("js/reserve.js",
"<script src='js/lib/jquery.tablesorter.min.js'></script>\n".
"<script src='js/lib/jquery.tablesorter.widgets.min.js'></script>".
......
......@@ -48,15 +48,34 @@
data-cluster="<%- value.cluster %>"<% } %>>
<% if (showidx) { %>
<td><%- value.idx %>
<% if (isadmin) { %>
<a href="#" class="deny-button">
<span class='glyphicon glyphicon-thumbs-down pull-right'
style='color: red; margin-left: 3px;
margin-bottom: 4px;'
data-toggle='tooltip'
data-container="body"
data-trigger="hover"
title='Deny with reason'></span>
</a>
<% } %>
<a href="#" class="delete-button">
<span class='glyphicon glyphicon-remove pull-right'
style='color: red; margin-left: 3px;
margin-bottom: 4px;'></span>
margin-bottom: 4px;'
data-toggle='tooltip'
data-container="body"
data-trigger="hover"
title='Delete reservation'></span>
</a>
<a href="reserve.php?edit=1&cluster=<%- value.cluster %>&idx=<%- value.idx %>"
class="edit-button">
<span class='glyphicon glyphicon-pencil pull-right'
style='color: red; margin-bottom: 4px;'></span>
style='color: red; margin-bottom: 4px;'
data-toggle='tooltip'
data-container="body"
data-trigger="hover"
title='Edit reservation details'></span>
</a>
</td>
<% } %>
......@@ -96,3 +115,24 @@
</div>
</div>
</div>
<div id='deny-modal' class='modal fade'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-body'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button>
<center><h4>Confirm to Deny Reservation</h4>
Message to send to user
<div>
<textarea id='deny-reason'
class='form-control'
rows=5></textarea>
</div>
<br>
<button class='btn btn-danger btn-sm'
id='confirm-deny'>Confirm</a>
</center>
</div>
</div>
</div>
</div>
......@@ -280,6 +280,10 @@
id='reserve-submit-button'
type='submit' name='submit'>Check</button>
<% if (editing) { %>
<button class='btn btn-danger btn-sm pull-left'
style="margin-right: 10px"
id='reserve-delete-button'
type='submit' name='delete'>Delete</button>
<button class='hidden btn btn-primary btn-sm pull-right'
style="margin-right: 10px"
id='reserve-approve-button'
......@@ -397,5 +401,26 @@
</div>
</div>
</div>
<div id='delete-reservation-modal' class='modal fade'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-body'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button>
<center><h4>Confirm to Delete Reservation</h4>
<% if (window.ISADMIN) { %>
Message to send to user
<div>
<textarea id='delete-reason'
class='form-control'
rows=5></textarea>
</div>
<% } %>
<br>
<button class='btn btn-danger btn-sm'
id='confirm-delete'>Confirm</a>
</center>
</div>
</div>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment