Commit 834eed95 authored by Leigh Stoller's avatar Leigh Stoller

Convert the node history page to portal interface.

parent e749c564
#!/usr/bin/perl -w
#
# Copyright (c) 2005-2015 University of Utah and the Flux Group.
# Copyright (c) 2005-2015, 2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -159,7 +159,11 @@ if (defined($options{"s"})) {
$summary = 1;
}
if (defined($options{"d"})) {
$datetime = timelocal(strptime($options{"d"}));
$datetime = $options{"d"};
if ($datetime !~ /^\d+$/) {
$datetime = timelocal(strptime($datetime));
}
$summary = 0;
}
if (defined($options{"x"})) {
......@@ -473,10 +477,10 @@ while (my %row = $query_result->fetchhash()) {
[ $node, $opideid, $oidx, $ouid, $ostamp, $elapsed, $ohistory_id ]);
}
if (!$datelimit) {
if (! ($datelimit || $startrecord)) {
# Include the current state of nodes in a final record
my $stamp = time();
for $node (keys(%nodeinfo)) {
for $node (sort(keys(%nodeinfo))) {
my ($opideid, $oidx, $ostamp, $ouid, $ohistory_id)= @{$nodeinfo{$node}};
my $elapsed = $stamp - $ostamp;
push(@rawrecords,
......
......@@ -157,6 +157,8 @@ class Instance
function openstack_utilization() {
return $this->field('openstack_utilization');
}
# Convenience
function isActive() { return 1; }
function IsAPT() {
return preg_match('/aptlab/', $this->servername());
}
......@@ -952,6 +954,8 @@ class InstanceHistory
function script() { return $this->field('script'); }
function params() { return $this->field('params'); }
function manifest() { return $this->field('manifest'); }
# Convenience
function isActive() { return 0; }
function IsAPT() {
return preg_match('/aptlab/', $this->servername());
}
......@@ -989,6 +993,20 @@ class InstanceHistory
$row = mysql_fetch_array($query_result);
return InstanceHistory::Lookup($row[0]);
}
function SliceToUUID($slice_uuid)
{
$safe_uuid = addslashes($slice_uuid);
$query_result =
DBQueryWarn("select uuid from apt_instance_history ".
"where slice_uuid='$safe_uuid'");
if (!$query_result || !mysql_num_rows($query_result)) {
return null;
}
$row = mysql_fetch_array($query_result);
return $row[0];
}
#
# Permission check; does user have permission to view instance.
#
......
$(function ()
{
'use strict';
var templates = APT_OPTIONS.fetchTemplateList(['show-nodehistory',
'nodehistory-list',
'oops-modal',
'waitwait-modal']);
var mainTemplate = _.template(templates['show-nodehistory']);
var listTemplate = _.template(templates['nodehistory-list']);
var reverse = true;
var alloconly = true;
var min = null; // Lowest record index on the page
var max = null; // Highest record index on the page
function initialize()
{
window.APT_OPTIONS.initialize(sup);
var html = mainTemplate({});
$('#main-body').html(html);
// Now we can do this.
$('#oops_div').html(templates['oops-modal']);
$('#waitwait_div').html(templates['waitwait-modal']);
if (window.TARGET !== undefined) {
$('#main-body .fornode')
.html("for " + window.TARGET)
.removeClass("hidden");
}
$('.next-button').click(function (event) {
event.preventDefault();
LoadHistory("next");
});
$('#allocated-only, #reverse-order').change(function (event) {
ChangeMode(event.target);
});
$('#start-date-button').click(function (event) {
StartAtDate();
});
$('#start-search-button').click(function (event) {
Search();
});
LoadHistory(null);
}
// Use "null" for direction to keep from changing the page.
function LoadHistory(direction, args)
{
var callback = function(json) {
console.log(json);
if (json.code) {
console.log("Could not get history data: " + json.value);
return;
}
// Remember bounds on new page, for nex/prev buttons
min = json.value.min;
max = json.value.max;
if (_.has(args, "TARGET")) {
window.TARGET = args["TARGET"];
$('#main-body .fornode')
.html("for " + window.TARGET)
.removeClass("hidden");
}
RenderHistory(json.value.entries);
};
$('#nodehistory-table-div').addClass("hidden");
$('#main-body .control-buttons').addClass("hidden");
$('#main-body .spinning').removeClass("hidden");
if (args === undefined) {
args = {};
}
args["reverse"] = (reverse ? 1 : 0);
args["alloconly"] = (alloconly ? 1 : 0);
if (min != null && max != null) {
args["min"] = min;
args["max"] = max;
if (direction != null) {
args["direction"] = direction;
}
}
// Add this unless we are searching.
if (!_.has(args, "TARGET") && window.TARGET !== undefined) {
args["TARGET"] = window.TARGET;
}
console.info(args);
var xmlthing = sup.CallServerMethod(null, "node", "GetHistory", args);
xmlthing.done(callback);
}
function RenderHistory(history)
{
var html = listTemplate({"history" : history,
"shownodeid" : (window.TARGET !== undefined
? false : true),
});
$('#main-body .spinning').addClass("hidden");
$('#nodehistory-table-div').removeClass("hidden");
$('#nodehistory-table-div').html(html);
$('#main-body .control-buttons').removeClass("hidden");
// Format dates with moment before display.
$('.format-date').each(function() {
var date = $.trim($(this).html());
if (date != "") {
$(this).html(moment($(this).html())
.format("MM/DD/YY h:mm A"));
}
});
$('[data-toggle="popover"]').popover({
trigger: 'hover',
placement: 'auto',
html: true,
});
}
// Change mode (checkboxes).
function ChangeMode(target)
{
var id = $(target).attr("id");
var checked = $(target).is(':checked');
if (id == "allocated-only") {
alloconly = checked;
}
else if (id == "reverse-order") {
reverse = checked;
}
else {
console.info("ChangeMode oops: " + id);
return;
}
LoadHistory(null);
}
// Change search to start at a date.
function StartAtDate()
{
var val = $('#start-date').val();
console.info("StartAtDate: ", val);
if (val == "") {
return;
}
var when = moment(val);
if (! (when && when.isValid())) {
alert("Not a valid date");
return;
}
// Clear these, we will get new bounds.
min = max = null;
LoadHistory(null, {"startdate" : when.unix()});
}
// Search for a specific node or IP. We respect the date is there one.
function Search()
{
var val = $('#search-node').val();
if (val == "") {
return;
}
var args = {"TARGET" : val};
// Pass through date.
if ($('#start-date').val() != "") {
var when = moment($('#start-date').val());
if (! (when && when.isValid())) {
alert("Not a valid date");
return;
}
args["startdate"] = when.unix();
// Clear these, we will get new bounds.
min = max = null;
}
LoadHistory(null, args);
}
$(document).ready(initialize);
});
......@@ -359,6 +359,125 @@ function Do_SaveLogEntry()
SPITAJAX_RESPONSE(true);
}
#
# Get the log entries for a node.
#
function Do_GetHistory()
{
global $this_user, $ajax_args;
$node_id = null;
if (! (ISADMIN() || OPSGUY())) {
SPITAJAX_ERROR(-1, "Not enough permission");
return -1;
}
$node_id = null;
$record = null;
$count = 500;
$showall = 1;
$reverse = 1;
$date = null;
$IP = null;
$mac = null;
$node_opt = "";
$asdata = true;
if (isset($ajax_args["TARGET"])) {
if (TBvalid_IP($ajax_args["TARGET"])) {
$IP = $ajax_args["TARGET"];
}
elseif (TBvalid_node_id($ajax_args["TARGET"])) {
$node_id = $ajax_args["TARGET"];
}
else {
SPITAJAX_ERROR(-1, "Not a valid node ID or IP");
return -1;
}
}
if (isset($ajax_args["reverse"])) {
$reverse = ($ajax_args["reverse"] ? 1 : 0);
}
if (isset($ajax_args["alloconly"])) {
$showall = ($ajax_args["alloconly"] ? 0 : 1);
}
#
# Look to see if we are moving forwards or backwards after initial
# page load.
#
if (isset($ajax_args["min"]) && isset($ajax_args["max"]) &&
isset($ajax_args["direction"])) {
if ($ajax_args["direction"] == "next") {
if ($reverse) {
if (!TBvalid_integer($ajax_args["min"])) {
SPITAJAX_ERROR(-1, "Not a valid integer");
return;
}
$record = intval($ajax_args["min"]);
}
else {
if (!TBvalid_integer($ajax_args["max"])) {
SPITAJAX_ERROR(-1, "Not a valid integer");
return;
}
$record = intval($ajax_args["max"]);
}
}
}
elseif (isset($ajax_args["startdate"])) {
$date = $ajax_args["startdate"];
if (!TBvalid_integer($ajax_args["startdate"])) {
SPITAJAX_ERROR(-1, "Not a valid unix time");
return;
}
if (is_numeric($date)) {
$date = intval($date);
}
}
# Check if also searching for node or IP.
$results = ShowNodeHistory($node_id, $record, $count, $showall, $reverse,
$date, $IP, $mac, $node_opt, $asdata);
#
# Post process to add a few things for nicer display
#
foreach ($results["entries"] as &$info) {
#
# Let see if a portal experiment so we can link directly to it
# instead of classic experiment page.
#
if ($info["pid"] && array_key_exists("slice_uuid", $info)) {
#
# See if a current or historical apt instance
#
$instance = Instance::LookupBySlice($info["slice_uuid"]);
if ($instance) {
$info["isportal"] = true;
$info["isrunning"] = true;
$info["instance_uuid"] = $instance->uuid();
}
else {
$uuid = InstanceHistory::SliceToUUID($info["slice_uuid"]);
if ($uuid) {
$info["isportal"] = true;
$info["isrunning"] = false;
$info["instance_uuid"] = $uuid;
}
else {
$info["isportal"] = false;
}
}
}
else {
$info["isportal"] = false;
}
}
SPITAJAX_RESPONSE($results);
}
# Local Variables:
# mode:php
# End:
......
......@@ -427,7 +427,9 @@ $routing = array("geni-login" =>
"GetLog" =>
"Do_GetLog",
"SaveLogEntry" =>
"Do_SaveLogEntry")),
"Do_SaveLogEntry",
"GetHistory" =>
"Do_GetHistory")),
"vlan" =>
array("file" => "vlan.ajax",
"guest" => false,
......
<?php
#
# Copyright (c) 2000-2019 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");
include("node_defs.php");
chdir("apt");
include("quickvm_sup.php");
# Must be after quickvm_sup.php since it changes the auth domain.
$page_title = "Show Node History";
#
# Get current user.
#
RedirectSecure();
$this_user = CheckLoginOrRedirect();
$this_idx = $this_user->uid_idx();
$isadmin = (ISADMIN() ? "true" : "false");
#
# Verify page arguments.
#
$reqargs = OptionalPageArguments("node_id", PAGEARG_STRING,
"IP", PAGEARG_STRING);
if (! ($isadmin || OPSGUY())) {
SPITUSERERROR("Not enough permission!");
}
SPITHEADER(1);
# Place to hang the toplevel template.
echo "<div class=row>
<div id='main-body'></div>
</div>\n";
echo "<script type='text/javascript'>\n";
if (isset($node_id)) {
echo " window.TARGET = '$node_id';\n";
}
elseif (isset($IP)) {
echo " window.TARGET = '$IP';\n";
}
echo " window.ISADMIN = $isadmin;\n";
echo "</script>\n";
REQUIRE_UNDERSCORE();
REQUIRE_SUP();
REQUIRE_MOMENT();
SPITREQUIRE("js/show-nodehistory.js");
AddTemplateList(array("show-nodehistory", "nodehistory-list",
"oops-modal", "waitwait-modal"));
SPITFOOTER();
?>
<table id="history-table"
class='table table-condensed table-bordered table-striped'>
<thead>
<% if (shownodeid) { %>
<th>Node ID</th>
<% } %>
<th>PID</th>
<th>EID</th>
<th><img src="images/slice.png"></th>
<th>User</th>
<th>Allocated</th>
<th>Released</th>
<th>Duration</th>
</thead>
<tbody>
<% _.each(history, function (entry) { %>
<tr>
<% if (shownodeid) { %>
<td><a href="show-node.php?node_id=<%- entry.node_id %>"
target="_blank"><%- entry.node_id %></a></td>
<% } %>
<% if (!entry.pid) { %>
<td></td>
<td></td>
<td></td>
<td></td>
<% } else { %>
<td><a href="show-project.php?pid=<%- entry.pid_idx %>"
target="_blank"><%- entry.pid %></a></td>
<td>
<% if (entry.isportal) { %>
<% if (entry.isrunning) { %>
<a href="status.php?uuid=<%- entry.instance_uuid %>"
target="_blank"><%- entry.eid %></a>
<% } else { %>
<a href="memlane.php?uuid=<%- entry.instance_uuid %>"
target="_blank"><%- entry.eid %></a>
<% } %>
<% } else { %>
<% if (entry.running) { %>
<a href="../showexp.php3?experiment=<%- entry.eid_idx %>"
target="_blank"><%- entry.eid %></a>
<% } else { %>
<a href="../showexpstats.php3?record=<%- entry.eid_idx %>"
target="_blank"><%- entry.eid %></a>
<% } %>
<% } %>
</td>
<td>
<% if (_.has(entry, "slice_uuid")) { %>
<a href="../genihistory.php?slice_uuid=<%- entry.slice_uuid %>"
target="_blank"><span class='glyphicon glyphicon-link'
style='margin-bottom: 4px;'></span></a>
<% } %>
</td>
<td><a href="user-dashboard.php?user=<%- entry.uid %>"
target="_blank"><%- entry.uid %></a></td>
<% } %>
<td style='white-space: nowrap;'
class="format-date"><%- entry.allocated %></td>
<td style='white-space: nowrap;'
class="format-date"><%- entry.released %></td>
<td style='white-space: nowrap;'>
<%- entry.duration_string %></td>
</tr>
<% }); %>
</tbody>
</table>
......@@ -84,6 +84,12 @@
data-toggle='tooltip'
title="Node message log"
type='button'>Admin Log</a>
<a href="show-nodehistory.php?node_id=<%= fields.node_id %>"
class='btn btn-primary btn-xs pull-right'
style='margin-right: 10px; margin-top: -4px;'
data-toggle='tooltip'
title="Node History"
type='button'>History</a>
<% } %>
<h3 class='panel-title'>Node <%- fields.node_id %></h3>
</div>
......
<style>
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
.table-condensed > tfoot > tr > th,
.table-condensed > thead > tr > td,
.table-condensed > tbody > tr > td,
.table-condensed > tfoot > tr > td {
font-size: small;
}
.panel-body > table {
margin-bottom: 0px;
}
</style>
<div class='col-xs-12'>
<div>
<div class='panel panel-default'>
<div class='panel-heading text-center'>
<h3 class='panel-title'>Node History
<span class="hidden fornode"></span></h3>
</div>
<div class='panel-body'>
<center class="spinning">
<img src='images/spinner.gif'/>
</center>
<div class="control-buttons hidden"
style="margin-bottom: 10px;">
<div class="pull-left">
<span style="margin-right: 10px;">
<input type="checkbox" id="allocated-only" checked> Allocated only
</span>
<span style="margin-right: 10px;">
<input type="checkbox" id="reverse-order" checked> Reverse order
</span>
<span>
<input class='search' type='search' id="start-date"
style='width: 80px; margin-right: 5px;'
placeholder='MM/DD/YY'>
<a href='#' id="start-date-button">
<span class='glyphicon glyphicon-search'></span></a>
</span>
</div>
<div class="pull-right">
<span>
<input class='search' type='search' id="search-node"
style='width: 120px; margin-right: 5px;'
placeholder='Node ID or IP'>
<a href='#' id="start-search-button">
<span class='glyphicon glyphicon-search'></span></a>
</span>
</div>
<div class="text-center" style="margin-right: 325px;">
<button class='btn btn-primary btn-sm next-button'
type='button'>Next</button>
</div>
</div>
<div id="nodehistory-table-div"></div>
<div class="control-buttons hidden">
<center>
<button class='btn btn-primary btn-sm next-button'
type='button'>Next</button>
</center>
</div>
</div>
</div>
</div>
</div>
<div id='oops_div'></div>
<div id='waitwait_div'></div>
This diff is collapsed.
......@@ -42,7 +42,7 @@ $optargs = OptionalPageArguments("classic", PAGEARG_BOOLEAN);
$node_id = $node->node_id();
if (!$classic) {
header("Location: apt/show-node.php?node_id=$node_id");
header("Location: portal/show-node.php?node_id=$node_id");
return;
}
......
<?php
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2000-2014, 2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -48,7 +48,17 @@ $optargs = OptionalPageArguments("showall", PAGEARG_BOOLEAN,
"mac", PAGEARG_STRING,
# To allow for pcvm search, since they are
# transient and will not map to a node.
"node_id", PAGEARG_STRING);
"node_id", PAGEARG_STRING,
"classic", PAGEARG_BOOLEAN);
if (!$classic) {
$url = "portal/show-nodehistory.php";
if (isset($node_id)) {
$url .= "?node_id=$node_id";
}
header("Location: $url");
return;
}
#
# Standard Testbed Header
......
......@@ -39,7 +39,7 @@ $optargs = OptionalPageArguments("classic", PAGEARG_BOOLEAN);
$node_id = $node->node_id();
if (!$classic) {
header("Location: apt/show-nodelog.php?node_id=$node_id");
header("Location: portal/show-nodelog.php?node_id=$node_id");
return;
}
......
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