Commit 636cea07 authored by Leigh B Stoller's avatar Leigh B Stoller

Couple of small reservation system changes:

1. Show the reason on the listing page, as popover in the last
   column. Makes it easier to approve directly from the listing page if
   you can see the reason.

2. Add optional approval message to pass along to the user.
parent 3d6d4aca
...@@ -643,9 +643,10 @@ sub DoDelete() ...@@ -643,9 +643,10 @@ sub DoDelete()
# #
sub DoApprove() sub DoApprove()
{ {
my $optlist = "p:"; my $optlist = "p:F:";
my $portal; my $portal;
my $errmsg; my $errmsg;
my $message;
my %options = (); my %options = ();
if (! getopts($optlist, \%options)) { if (! getopts($optlist, \%options)) {
...@@ -654,6 +655,10 @@ sub DoApprove() ...@@ -654,6 +655,10 @@ sub DoApprove()
if (defined($options{"p"})) { if (defined($options{"p"})) {
$portal = $options{"p"} $portal = $options{"p"}
} }
if (defined($options{"F"})) {
$message = readfile($options{"F"});
chomp($message);
}
usage() usage()
if (@ARGV != 1 || !defined($portal)); if (@ARGV != 1 || !defined($portal));
my $uuid = shift(@ARGV); my $uuid = shift(@ARGV);
...@@ -666,7 +671,6 @@ sub DoApprove() ...@@ -666,7 +671,6 @@ sub DoApprove()
if (!defined($brand)) { if (!defined($brand)) {
fatal("Bad branding"); fatal("Bad branding");
} }
if (!$this_user->IsAdmin()) { if (!$this_user->IsAdmin()) {
fatal("No permission to approve reservations") fatal("No permission to approve reservations")
} }
...@@ -674,11 +678,14 @@ sub DoApprove() ...@@ -674,11 +678,14 @@ sub DoApprove()
if ($aggregate->CheckStatus(\$errmsg, 1)) { if ($aggregate->CheckStatus(\$errmsg, 1)) {
UserError($errmsg); UserError($errmsg);
} }
my $blob = {"uuid" => $uuid};
if (defined($message) && $message ne "") {
$blob->{'message'} = $message;
}
# PortalRPC will use the root context in this case, which is # PortalRPC will use the root context in this case, which is
# essentially saying the caller is an admin. # essentially saying the caller is an admin.
my $response = my $response =
APT_Geni::PortalRPC($authority, undef, "ApproveReservation", APT_Geni::PortalRPC($authority, undef, "ApproveReservation", $blob);
{"uuid" => $uuid});
if (GeniResponse::IsError($response)) { if (GeniResponse::IsError($response)) {
ExitWithError($response); ExitWithError($response);
} }
...@@ -687,7 +694,7 @@ sub DoApprove() ...@@ -687,7 +694,7 @@ sub DoApprove()
# #
# Schedule an announcement for the user. # Schedule an announcement for the user.
# #
my $blob = $response->value(); $blob = $response->value();
if (exists($blob->{'user'})) { if (exists($blob->{'user'})) {
my $geniuser = GeniUser->Lookup($blob->{'user'}, 1); my $geniuser = GeniUser->Lookup($blob->{'user'}, 1);
if (defined($geniuser) && $geniuser->IsLocal()) { if (defined($geniuser) && $geniuser->IsLocal()) {
......
...@@ -927,7 +927,7 @@ sub Reserve($) ...@@ -927,7 +927,7 @@ sub Reserve($)
chmod(0755, "$fp"); chmod(0755, "$fp");
} }
my $command = ($asadmin ? "$WAP " : "") . "$TB/sbin/reserve $args"; my $command = ($asadmin ? "$WAP " : "") . "$TB/sbin/reserve $args";
print STDERR "$command\n"; #print STDERR "$command\n";
if ($asadmin) { if ($asadmin) {
GeniUtil::FlipToElabMan(); GeniUtil::FlipToElabMan();
...@@ -980,7 +980,7 @@ sub Reserve($) ...@@ -980,7 +980,7 @@ sub Reserve($)
"uuid" => $reservation->uuid(), "uuid" => $reservation->uuid(),
"approved" => $approved, "approved" => $approved,
}; };
print STDERR Dumper($blob); #print STDERR Dumper($blob);
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob); return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
} }
...@@ -1174,10 +1174,20 @@ sub ApproveReservation($) ...@@ -1174,10 +1174,20 @@ sub ApproveReservation($)
my $reservation = Reservation->Lookup($target); my $reservation = Reservation->Lookup($target);
return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED) return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED)
if (!defined($reservation)); if (!defined($reservation));
my $idx = $reservation->idx(); my $idx = $reservation->idx();
my $args = "-a -m $idx";
# Write the message to a tempfile to pass in. This will auto unlink.
my $fp;
if (exists($argref->{"message"}) && $argref->{"message"} ne "") {
$fp = File::Temp->new();
print $fp $argref->{"message"};
$args = "-F $fp $args";
chmod(0644, "$fp");
}
GeniUtil::FlipToElabMan(); GeniUtil::FlipToElabMan();
my $output = GeniUtil::ExecQuiet("$WAP $TB/sbin/reserve -a -m $idx"); my $output = GeniUtil::ExecQuiet("$WAP $TB/sbin/reserve $args");
if ($?) { if ($?) {
GeniUtil::FlipToGeniUser(); GeniUtil::FlipToGeniUser();
......
...@@ -26,6 +26,7 @@ use English; ...@@ -26,6 +26,7 @@ use English;
use Getopt::Std; use Getopt::Std;
use Date::Parse; use Date::Parse;
use POSIX; use POSIX;
use Text::Wrap;
# #
# Configure variables # Configure variables
...@@ -77,10 +78,11 @@ sub usage() ...@@ -77,10 +78,11 @@ sub usage()
print STDERR " -A Supply file containing admin-only notes about reservation\n"; print STDERR " -A Supply file containing admin-only notes about reservation\n";
print STDERR " -N Supply file containing user notes justifying reservation\n"; print STDERR " -N Supply file containing user notes justifying reservation\n";
print STDERR " -D Supply file containing reason why reservation was denied\n"; print STDERR " -D Supply file containing reason why reservation was denied\n";
print STDERR " -F Supply file containing messsage to accompany approval\n";
exit( -1 ); exit( -1 );
} }
my $optlist = "ac:de:fhilm:npqs:t:uA:CD:N:S:U:T:E:Oy"; my $optlist = "ac:de:fhilm:npqs:t:uA:CD:N:S:U:T:E:OyF:";
my $debug = 0; my $debug = 0;
my $info = 0; my $info = 0;
my $list = 0; my $list = 0;
...@@ -95,6 +97,7 @@ my $endtime = time + 24 * 60 * 60; # default to ending tomorrow ...@@ -95,6 +97,7 @@ my $endtime = time + 24 * 60 * 60; # default to ending tomorrow
my $notes = undef; my $notes = undef;
my $adminnotes = undef; my $adminnotes = undef;
my $denynotes = undef; my $denynotes = undef;
my $approvenotes = undef;
my $approve = 0; my $approve = 0;
my $pending = 0; my $pending = 0;
my $tidy = 0; my $tidy = 0;
...@@ -252,6 +255,9 @@ if (defined($options{"A"})) { ...@@ -252,6 +255,9 @@ if (defined($options{"A"})) {
if (defined($options{"D"})) { if (defined($options{"D"})) {
$denynotes = readfile( $options{"D"} ); $denynotes = readfile( $options{"D"} );
} }
if (defined($options{"F"})) {
$approvenotes = readfile( $options{"F"} );
}
if (defined($options{"U"})) { if (defined($options{"U"})) {
fatal( "-U option requires administrator privileges" ) unless( $admin ); fatal( "-U option requires administrator privileges" ) unless( $admin );
$target_user = User->Lookup($options{"U"}); $target_user = User->Lookup($options{"U"});
...@@ -640,6 +646,8 @@ while( 1 ) { ...@@ -640,6 +646,8 @@ while( 1 ) {
my $s = convert( $starttime ); my $s = convert( $starttime );
my $e = convert( $endtime ); my $e = convert( $endtime );
if( $approve ) { if( $approve ) {
$Text::Wrap::columns = 60;
# The reservation is approved -- presumably it is either newly # The reservation is approved -- presumably it is either newly
# approved or edited since first approval. E-mail the user # approved or edited since first approval. E-mail the user
# unconditionally, since it's probably good for them to hear # unconditionally, since it's probably good for them to hear
...@@ -650,6 +658,11 @@ while( 1 ) { ...@@ -650,6 +658,11 @@ while( 1 ) {
"starting at $s and ending at\n" . "starting at $s and ending at\n" .
"$e, has been approved.\n" . "$e, has been approved.\n" .
"\n" . "\n" .
($approvenotes ?
"*****************************************************\n".
wrap("", "", "$approvenotes\n") .
"*****************************************************\n\n"
: "") .
"If you do not intend to use these resources, please\n" . "If you do not intend to use these resources, please\n" .
"cancel this reservation as soon as possible, since\n" . "cancel this reservation as soon as possible, since\n" .
"the nodes are currently unavailable to other users for\n" . "the nodes are currently unavailable to other users for\n" .
......
...@@ -219,6 +219,11 @@ $(function () ...@@ -219,6 +219,11 @@ $(function ()
delay: {"hide" : 250, "show" : 250}, delay: {"hide" : 250, "show" : 250},
placement: 'auto', placement: 'auto',
}); });
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
placement: 'auto',
container: 'body',
});
} }
var xmlthing = sup.CallServerMethod(null, "reserve", var xmlthing = sup.CallServerMethod(null, "reserve",
"ListReservations", "ListReservations",
...@@ -337,10 +342,12 @@ $(function () ...@@ -337,10 +342,12 @@ $(function ()
// Bind the confirm button in the modal. Do the approval. // Bind the confirm button in the modal. Do the approval.
$('#approve-modal #confirm-approve').click(function () { $('#approve-modal #confirm-approve').click(function () {
sup.HideModal('#approve-modal', function () { sup.HideModal('#approve-modal', function () {
var message = $('#approve-modal .user-message').val().trim();
sup.ShowModal('#waitwait-modal'); sup.ShowModal('#waitwait-modal');
var xmlthing = sup.CallServerMethod(null, "reserve", var xmlthing = sup.CallServerMethod(null, "reserve",
"Approve", "Approve",
{"uuid" : uuid, {"uuid" : uuid,
"message" : message,
"cluster" : cluster}); "cluster" : cluster});
xmlthing.done(callback); xmlthing.done(callback);
}); });
......
...@@ -456,20 +456,34 @@ $(function () ...@@ -456,20 +456,34 @@ $(function ()
*/ */
function Approve() function Approve()
{ {
var callback = function(json) { var callback = function (json) {
sup.HideWaitWait(); sup.HideModal('#waitwait-modal');
if (json.code) { if (json.code) {
sup.SpitOops("oops", json.value); sup.SpitOops("oops", json.value);
return; return;
} }
window.location.reload(true); window.location.reload(true);
}; };
sup.ShowWaitWait(); // Bind the confirm button in the modal. Do the approval.
var xmlthing = sup.CallServerMethod(null, "reserve", $('#approve-modal #confirm-approve').click(function () {
"Approve", sup.HideModal('#approve-modal', function () {
{"cluster" : window.CLUSTER, var message = $('#approve-modal .user-message').val().trim();
"uuid" : window.UUID}); sup.ShowModal('#waitwait-modal');
xmlthing.done(callback); var xmlthing = sup.CallServerMethod(null, "reserve",
"Approve",
{"cluster" : window.CLUSTER,
"uuid" : window.UUID,
"message" : message});
xmlthing.done(callback);
});
});
// Handler so we know the user closed the modal. We need to
// clear the confirm button handler.
$('#approve-modal').on('hidden.bs.modal', function (e) {
$('#approve-modal #confirm-approve').unbind("click");
$('#approve-modal').off('hidden.bs.modal');
})
sup.ShowModal("#approve-modal");
} }
function PopulateReservation() function PopulateReservation()
......
...@@ -482,6 +482,22 @@ function Do_Approve() ...@@ -482,6 +482,22 @@ function Do_Approve()
SPITAJAX_ERROR(-1, "Only administrators can approve reservations"); SPITAJAX_ERROR(-1, "Only administrators can approve reservations");
return; return;
} }
$opt = "";
if (isset($ajax_args["message"])) {
$message = $ajax_args["message"];
if (!TBvalid_fulltext($message)) {
SPITAJAX_ERROR(-1, "Illegal characters in message");
return;
}
$messagefile = tempnam("/tmp", "message");
$fp = fopen($messagefile, "w");
fwrite($fp, $message);
fclose($fp);
chmod($messagefile, 0666);
$opt = "-F $messagefile";
}
$webtask = WebTask::CreateAnonymous(); $webtask = WebTask::CreateAnonymous();
$this_uid = $this_user->uid(); $this_uid = $this_user->uid();
$urn = $aggregate->urn(); $urn = $aggregate->urn();
...@@ -489,9 +505,11 @@ function Do_Approve() ...@@ -489,9 +505,11 @@ function Do_Approve()
$retval = SUEXEC($this_uid, "nobody", $retval = SUEXEC($this_uid, "nobody",
"webmanage_reservations -a '$urn' ". "webmanage_reservations -a '$urn' ".
"-t " . $webtask->task_id() . "-t " . $webtask->task_id() .
" approve -p $PORTAL_GENESIS $uuid", " approve -p $PORTAL_GENESIS $opt $uuid",
SUEXEC_ACTION_IGNORE); SUEXEC_ACTION_IGNORE);
if (isset($messagefile)) {
unlink($messagefile);
}
if ($retval) { if ($retval) {
$webtask->Refresh(); $webtask->Refresh();
if (!$webtask->exited() || $retval < 0) { if (!$webtask->exited() || $retval < 0) {
...@@ -505,6 +523,7 @@ function Do_Approve() ...@@ -505,6 +523,7 @@ function Do_Approve()
$webtask->Delete(); $webtask->Delete();
return; return;
} }
$webtask->Delete(); $webtask->Delete();
SPITAJAX_RESPONSE("list-reservations.php"); SPITAJAX_RESPONSE("list-reservations.php");
} }
...@@ -681,6 +700,7 @@ function Do_ListReservations() ...@@ -681,6 +700,7 @@ function Do_ListReservations()
$blob["created"] = $details['created']; $blob["created"] = $details['created'];
$blob["start"] = $details['start']; $blob["start"] = $details['start'];
$blob["end"] = $details['end']; $blob["end"] = $details['end'];
$blob["notes"] = $details['notes'];
$blob["cluster"] = $aggregate->nickname(); $blob["cluster"] = $aggregate->nickname();
$rlist[$uuid] = $blob; $rlist[$uuid] = $blob;
} }
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
.panel-heading-list { .panel-heading-list {
padding: 2px; padding: 2px;
} }
.tablesorter-green th.sorter-false .tablesorter-header-inner {
padding-left: 4px;
}
</style> </style>
<div> <div>
<div class='panel panel-default'> <div class='panel panel-default'>
...@@ -60,6 +63,7 @@ ...@@ -60,6 +63,7 @@
<th>Starts</th> <th>Starts</th>
<th>Ends</th> <th>Ends</th>
<th>Status</th> <th>Status</th>
<th class="sorter-false">Notes</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -204,6 +208,17 @@ ...@@ -204,6 +208,17 @@
<% } %> <% } %>
</span> </span>
</td> </td>
<td align="center">
<a href="#"
data-toggle='popover'
data-html='true'
data-trigger='hover'
data-title="Reservation Notes"
data-content="<div class='form-control'
style='height: auto;'><%- value.notes %></div>">
<span>&#8230;</span>
</a>
</td>
</tr> </tr>
<% }); %> <% }); %>
</tbody> </tbody>
...@@ -240,6 +255,11 @@ ...@@ -240,6 +255,11 @@
<button type='button' class='close' data-dismiss='modal' <button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button> aria-hidden='true'>&times;</button>
<center><h4>Confirm to Approve Reservation</h4> <center><h4>Confirm to Approve Reservation</h4>
Additional text to send with approval (no text is okay):
<div>
<textarea class='form-control user-message'
rows=5></textarea>
</div>
<br> <br>
<button class='btn btn-danger btn-sm' <button class='btn btn-danger btn-sm'
id='confirm-approve'>Confirm</a> id='confirm-approve'>Confirm</a>
...@@ -274,7 +294,7 @@ ...@@ -274,7 +294,7 @@
<button type='button' class='close' data-dismiss='modal' <button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button> aria-hidden='true'>&times;</button>
<center><h4>Confirm to Send Warning Message</h4> <center><h4>Confirm to Send Warning Message</h4>
Additional text to send with warning (no text is okay):</span> Additional text to send with warning (no text is okay):
<div> <div>
<textarea class='form-control user-message' <textarea class='form-control user-message'
rows=5></textarea> rows=5></textarea>
......
...@@ -505,4 +505,23 @@ ...@@ -505,4 +505,23 @@
</div> </div>
</div> </div>
</div> </div>
<div id='approve-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 Approve Reservation</h4>
Additional text to send with approval (no text is okay):
<div>
<textarea class='form-control user-message'
rows=5></textarea>
</div>
<br>
<button class='btn btn-danger btn-sm'
id='confirm-approve'>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