Commit e7c54c4a authored by Leigh Stoller's avatar Leigh 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