Commit 3e1e712b authored by Leigh Stoller's avatar Leigh Stoller

More cleanup of ajax code, convert status page to a template

and new ajax handling.
parent 791a2d49
......@@ -271,6 +271,9 @@ body {
.text-danger {
color: red;
}
.border-none {
border: none;
}
/*
.panel-body {
......@@ -348,3 +351,8 @@ body {
blockquote #selected_profile_description {
font-size: 14px;
}
.popover
{
min-width: 350px ! important;
}
<?php
#
# Copyright (c) 2000-2014 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_once("webtask.php");
chdir("apt");
include_once("profile_defs.php");
#
# Return info about specific profile.
#
function Do_GetProfile()
{
global $this_user;
global $ajax_args;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$uuid = $ajax_args["uuid"];
if (!IsValidUUID($uuid)) {
SPITAJAX_ERROR(1, "Not a valid UUID: $uuid");
return;
}
$profile = Profile::Lookup($uuid);
if (!$profile) {
SPITAJAX_ERROR(1, "No such profile $uuid");
return;
}
#
# We need permission checks on this path.
#
if (! $profile->ispublic()) {
if (! isset($this_user)) {
SPITAJAX_ERROR(1, "You must be logged in to access profile");
return;
}
if (!(ISADMIN() ||
$this_user->uid_idx() == $profile->creator_idx() ||
($profile->IsPrivate() &&
$profile->GetProject()->IsMember($this_user, $approved) &&
$approved))) {
SPITAJAX_ERROR(1, "Not enough permission to access profile");
return;
}
}
SPITAJAX_RESPONSE(array('rspec' => $profile->rspec(),
'name' => $profile->name(),
'idx' => $profile->idx(),
'description' => $profile->description()));
}
# Local Variables:
# mode:php
# End:
?>
......@@ -46,39 +46,7 @@ $optargs = OptionalPageArguments("create", PAGEARG_STRING,
"stuffing", PAGEARG_STRING,
"verify", PAGEARG_STRING,
"project", PAGEARG_PROJECT,
"formfields", PAGEARG_ARRAY,
"ajax_request", PAGEARG_BOOLEAN,
"ajax_method", PAGEARG_STRING,
"ajax_argument", PAGEARG_STRING);
#
# Deal with ajax requests.
#
if (isset($ajax_request)) {
if ($ajax_method == "getprofile") {
#
# We require the UUID on this path, until proper permission
# checks are done; too easy to guess an index.
#
if (!IsValidUUID($ajax_argument)) {
SPITAJAX_ERROR(1, "Not a valid UUID: $ajax_argument");
exit();
}
$obj = Profile::Lookup($ajax_argument);
if (!$obj) {
SPITAJAX_ERROR(1, "No such profile $ajax_argument");
exit();
}
#
# Need permission checks here.
#
SPITAJAX_RESPONSE(array('rspec' => $obj->rspec(),
'name' => $obj->name(),
'description' => $obj->description()));
}
exit();
}
"formfields", PAGEARG_ARRAY);
$profile_default = "OneVM";
$profile_array = array();
......@@ -361,6 +329,7 @@ function SPITFORM($formfields, $newuser, $errors)
echo "<script type='text/javascript'>\n";
echo " window.PROFILE = '" . $formfields["profile"] . "';\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
if ($newuser) {
echo "window.APT_OPTIONS.isNewUser = true;\n";
}
......
......@@ -16,6 +16,7 @@ window.APT_OPTIONS.config = function ()
'marked': 'js/lib/marked',
'moment': 'js/lib/moment',
'underscore': 'js/lib/underscore-min',
'filesize': 'js/lib/filesize.min',
'jacks': 'https://www.emulab.net/protogeni/jacks-stable/js/jacks'
},
shim: {
......@@ -28,7 +29,8 @@ window.APT_OPTIONS.config = function ()
'tablesorter': { },
'tablesorterwidgets': { deps: ['tablesorter'] },
'marked' : { exports: 'marked' },
'underscore': { exports: '_' }
'underscore': { exports: '_' },
'filesize' : { exports: 'filesize' },
},
});
};
......
......@@ -8,10 +8,12 @@ function (_, sup)
var jacksInstance;
var jacksUpdate;
var ajaxurl;
function initialize()
{
window.APT_OPTIONS.initialize();
ajaxurl = window.AJAXURL;
if (window.APT_OPTIONS.isNewUser) {
$('#verify_modal_submit').click(function (event) {
......@@ -143,7 +145,9 @@ function (_, sup)
[{ rspec: json.value.rspec }]);
}
}
var $xmlthing = sup.CallMethod("getprofile", null, 0, profile);
var $xmlthing = sup.CallServerMethod(ajaxurl,
"instantiate", "GetProfile",
{"uuid" : profile});
$xmlthing.done(callback);
}
......
/*
2013 Jason Mulligan
@license BSD-3 <https://raw.github.com/avoidwork/filesize.js/master/LICENSE>
@link http://filesizejs.com
@module filesize
@version 2.0.0
*/
(function(r){function f(f,c){var b="",g=!1,l=6,a,h,q,d,m,n,p,e,k;if(isNaN(f))throw Error("Invalid arguments");c=c||{};h=!0===c.bits;e=!0===c.unix;a=void 0!==c.base?c.base:e?2:10;m=void 0!==c.round?c.round:e?1:2;k=void 0!==c.spacer?c.spacer:e?"":" ";d=Number(f);(q=0>d)&&(d=-d);if(0===d)b=e?"0":"0"+k+"B";else for(p=s[a][h?"bits":"bytes"];l--;)if(n=p[l][1],a=p[l][0],d>=n){t.test(a)&&(g=!0,m=0);b=(d/n).toFixed(m);!g&&e?(h&&u.test(a)&&(a=a.toLowerCase()),a=a.charAt(0),g=v.exec(b),h||"k"!==a||(a="K"),null!==
g&&void 0!==g[1]&&w.test(g[1])&&(b=parseInt(b,x)),b+=k+a):e||(b+=k+a);break}q&&(b="-"+b);return b}var u=/b$/,t=/^B$/,x=10,v=/\.(.*)/,w=/^0$/,s={2:{bits:[["B",1],["kb",128],["Mb",131072],["Gb",134217728],["Tb",137438953472],["Pb",0x800000000000]],bytes:[["B",1],["kB",1024],["MB",1048576],["GB",1073741824],["TB",1099511627776],["PB",0x4000000000000]]},10:{bits:[["B",1],["kb",125],["Mb",125E3],["Gb",125E6],["Tb",125E9],["Pb",125E12]],bytes:[["B",1],["kB",1E3],["MB",1E6],["GB",1E9],["TB",1E12],["PB",
1E15]]}};"undefined"!==typeof exports?module.exports=f:"function"===typeof define?define(function(){return f}):r.filesize=f})(this);
//@ sourceMappingURL=filesize.map
window.APT_OPTIONS.config();
require(['underscore', 'js/quickvm_sup',
require(['underscore', 'js/quickvm_sup', 'filesize',
'js/lib/text!template/manage-profile.html',
'js/lib/text!template/waitwait-modal.html',
'js/lib/text!template/imaging-modal.html',
......@@ -9,7 +9,7 @@ require(['underscore', 'js/quickvm_sup',
'js/lib/text!template/rspectextview-modal.html',
// jQuery modules
'filestyle','marked','jquery-ui','jquery-grid'],
function (_, sup,
function (_, sup, filesize,
manageString, waitwaitString, imagingString,
rendererString, showtopoString, rspectextviewString)
{
......@@ -269,6 +269,8 @@ require(['underscore', 'js/quickvm_sup',
label_text = label_text +
"<a href='#' class='btn btn-xs' " +
" data-toggle='popover' " +
" data-html='true' " +
" data-delay='{\"hide\":1000}' " +
" data-content='" + item.dataset['help'] + "'>" +
"<span class='glyphicon glyphicon-question-sign'>" +
" </span></a>";
......@@ -522,7 +524,6 @@ require(['underscore', 'js/quickvm_sup',
// Ask the server for information to populate the imaging modal.
//
var callback = function(json) {
console.info(json);
var value = json.value;
if (json.code) {
......@@ -553,7 +554,11 @@ require(['underscore', 'js/quickvm_sup',
if (! _.has(value, "node_status")) {
value["node_status"] = "unknown";
}
if (! _.has(value, "image_size")) {
if (_.has(value, "image_size")) {
// We get KB to avoid overflow along the way.
value["image_size"] = filesize(value["image_size"] * 1024);
}
else {
value["image_size"] = "unknown";
}
$('#imaging_modal_node_status').html(value["node_status"]);
......@@ -612,7 +617,7 @@ require(['underscore', 'js/quickvm_sup',
var $xmlthing = sup.CallServerMethod(ajaxurl,
"manage_profile",
"SnapShotStatus",
"CloneStatus",
{"uuid" : uuid});
$xmlthing.done(callback);
}
......
window.APT_OPTIONS.config();
require(['js/quickvm_sup', 'moment',
require(['underscore', 'js/quickvm_sup', 'moment',
'js/lib/text!template/status.html',
'js/lib/text!template/waitwait-modal.html',
'js/lib/text!template/oops-modal.html',
'js/lib/text!template/register-modal.html',
'js/lib/text!template/terminate-modal.html',
'js/lib/text!template/extend-modal.html',
'js/lib/text!template/clone-help.html',
'tablesorter', 'tablesorterwidgets'],
function (sup, moment)
function (_, sup, moment, statusString, waitwaitString, oopsString,
registerString, terminateString, extendString, cloneHelpString)
{
'use strict';
var CurrentTopo = null;
var nodecount = 0;
var ajaxurl = null;
var statusTemplate = _.template(statusString);
var waitwaitTemplate = _.template(waitwaitString);
var oopsTemplate = _.template(oopsString);
var registerTemplate = _.template(registerString);
var terminateTemplate = _.template(terminateString);
var extendTemplate = _.template(extendString);
function initialize()
{
window.APT_OPTIONS.initialize(sup);
ajaxurl = window.APT_OPTIONS.AJAXURL;
// Generate the templates.
var template_args = {
uuid: window.APT_OPTIONS.UUID,
profileName: window.APT_OPTIONS.profileName,
sliceURN: window.APT_OPTIONS.sliceURN,
sliceExpires: window.APT_OPTIONS.sliceExpires,
sliceExpiresText: window.APT_OPTIONS.sliceExpiresText,
creatorUid: window.APT_OPTIONS.creatorUid,
creatorEmail: window.APT_OPTIONS.creatorEmail,
registered: window.APT_OPTIONS.registered,
};
var status_html = statusTemplate(template_args);
$('#status-body').html(status_html);
var waitwait_html = waitwaitTemplate(template_args);
$('#waitwait_div').html(waitwait_html);
var oops_html = oopsTemplate(template_args);
$('#oops_div').html(oops_html);
var register_html = registerTemplate(template_args);
$('#register_div').html(register_html);
var extend_html = extendTemplate(template_args);
$('#extend_div').html(extend_html);
var terminate_html = terminateTemplate(template_args);
$('#terminate_div').html(terminate_html);
//
// Look at initial status to determine if we show the progress bar.
//
var spinwidth = 0;
var instanceStatus = window.APT_OPTIONS.instanceStatus;
if (instanceStatus == "created") {
spinwidth = "33";
}
else if (instanceStatus == "provisioned") {
spinwidth = "66";
}
if (spinwidth) {
$("#status_progress_bar").width(spinwidth + "%");
$('#status_progress_outerdiv').removeClass("hidden");
}
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
......@@ -48,6 +102,41 @@ function (sup, moment)
win.focus();
});
// Handler for the Clone button.
$('button#clone_button').click(function (event) {
event.preventDefault();
window.location.replace('manage_profile.php?action=clone' +
'&snapuuid=' + window.APT_OPTIONS.uuid);
});
//
// Attach a hover popover to explain what Clone means. We need
// the hover action delayed by our own code, since we want to
// use a manual trigger to close the popover, or else the user
// will not have enough time to read the content.
//
var popover_timer;
$("button#clone_button").mouseenter(function(){
popover_timer = setTimeout(function() {
$('button#clone_button').popover({
html: true,
content: cloneHelpString +
'<span id=clone_popover_close class=close>' +
'&times;</span>',
trigger: 'manual',
placement:'left',
container:'body',
});
$('button#clone_button').popover('show');
$('#clone_popover_close').on('click', function(e) {
$('button#clone_button').popover('hide');
});
},1000)
}).mouseleave(function(){
clearTimeout(popover_timer);
});
$('button#request-extension').click(function (event) {
event.preventDefault();
RequestExtension(window.APT_OPTIONS.uuid);
......@@ -64,7 +153,7 @@ function (sup, moment)
// This is considered the home page, for now.
window.location.replace('instantiate.php');
}
sup.ShowModal("#waitwait");
sup.ShowModal("#waitwait-modal");
var xmlthing = sup.CallServerMethod(ajaxurl,
"status",
......@@ -119,18 +208,18 @@ function (sup, moment)
var statustext = "Please wait while we get your experiment ready";
if (status == 'provisioned') {
$("#quickvm_progress_bar").width("66%");
$("#status_progress_bar").width("66%");
status_html = "booting";
}
else if (status == 'ready') {
bgtype = "bg-success";
statustext = "Your experiment is ready!";
status_html = "<font color=green>ready</font>";
if ($("#quickvm_progress").length) {
$("#quickvm_progress").removeClass("progress-striped");
$("#quickvm_progress").removeClass("active");
$("#quickvm_progress").addClass("progress-bar-success");
$("#quickvm_progress_bar").width("100%");
if ($("#status_progress_div").length) {
$("#status_progress_div").removeClass("progress-striped");
$("#status_progress_div").removeClass("active");
$("#status_progress_div").addClass("progress-bar-success");
$("#status_progress_bar").width("100%");
}
if (! StatusWatchCallBack.active) {
ShowTopo(uuid);
......@@ -144,11 +233,11 @@ function (sup, moment)
statustext = "Something went wrong, sorry! " +
"We've been notified.";
status_html = "<font color=red>failed</font>";
if ($("#quickvm_progress").length) {
$("#quickvm_progress").removeClass("progress-striped");
$("#quickvm_progress").removeClass("active");
$("#quickvm_progress").addClass("progress-bar-danger");
$("#quickvm_progress_bar").width("100%");
if ($("#status_progress_div").length) {
$("#status_progress_div").removeClass("progress-striped");
$("#status_progress_div").removeClass("active");
$("#status_progress_div").addClass("progress-bar-danger");
$("#status_progress_bar").width("100%");
}
DisableButtons();
EnableButton("terminate");
......@@ -186,13 +275,13 @@ function (sup, moment)
{
EnableButton("terminate");
EnableButton("extend");
EnableButton("snapshot");
EnableButton("clone");
}
function DisableButtons()
{
DisableButton("terminate");
DisableButton("extend");
DisableButton("snapshot");
DisableButton("clone");
}
function EnableButton(button)
{
......@@ -208,8 +297,8 @@ function (sup, moment)
button = "#terminate_button";
else if (button == "extend")
button = "#extend_button";
else if (button == "snapshot" && nodecount == 1)
button = "#snapshot_button";
else if (button == "clone" && nodecount == 1)
button = "#clone_button";
else
return;
......@@ -360,7 +449,7 @@ function (sup, moment)
return;
}
var callback = function(json) {
sup.HideModal("#waitwait");
sup.HideModal("#waitwait-modal");
// console.info(json.value);
var message;
......@@ -382,7 +471,7 @@ function (sup, moment)
StartCountdownClock.reset = json.value;
}
sup.HideModal('#extend_modal');
sup.ShowModal("#waitwait");
sup.ShowModal("#waitwait-modal");
var xmlthing = sup.CallServerMethod(ajaxurl,
"status",
"RequestExtension",
......@@ -608,15 +697,15 @@ function (sup, moment)
ReDrawTopoMap();
$("#showtopo_container").removeClass("invisible");
// If a single node, show the snapshot button. Only
// If a single node, show the clone button. Only
// single node experiments can do this.
if (nodecount == 1) {
$("#snapshot_button").removeClass("invisible");
EnableButton("snapshot");
$("#clone_button").removeClass("invisible");
EnableButton("clone");
}
// And start up ssh for single node topologies.
if (nodecount == 1 && nodehostport) {
if (nodecount == 1 && nodehostport && 0) {
NewSSHTab(nodehostport, nodename);
}
}
......
......@@ -21,22 +21,15 @@
#
# }}}
#
# Local Variables:
# mode:php
# End:
chdir("..");
include_once("webtask.php");
chdir("apt");
include_once("profile_defs.php");
# Logged in users only. This will not return if it fails.
CheckLoginForAjax();
#
# Return snapshot status.
# Return clone status.
#
function Do_SnapShotStatus()
function Do_CloneStatus()
{
global $this_user;
global $ajax_args;
......@@ -69,10 +62,24 @@ function Do_SnapShotStatus()
$blob["exited"] = $webtask->exited();
$blob["exitcode"] = $webtask->exitcode();
}
$blob["image_size"] = $taskdata["image_size"];
#
# Size is in KB to avoid bigint problems. But kill the KB.
#
if (isset($taskdata["image_size"])) {
if (preg_match("/^(\d+)KB$/", $taskdata["image_size"], $matches)) {
$taskdata["image_size"] = $matches[1];
}
$blob["image_size"] = $taskdata["image_size"];
}
else {
$blob["image_size"] = 0;
}
$blob["node_status"] = $taskdata["rawstate"];
$blob["image_status"] = $taskdata["image_status"];
SPITAJAX_RESPONSE($blob);
}
# Local Variables:
# mode:php
# End:
?>
......@@ -32,7 +32,7 @@ include("instance_defs.php");
include_once("../session.php");
$page_title = "Manage Profile";
$notifyupdate = 0;
$notifysnapshot = 0;
$notifyclone = 0;
#
# Get current user.
......@@ -66,7 +66,7 @@ $this_idx = $this_user->uid_idx();
function SPITFORM($formfields, $errors)
{
global $this_user, $projlist, $action;
global $notifyupdate, $notifysnapshot, $snapuuid;
global $notifyupdate, $notifyclone, $snapuuid;
$editing = 0;
if ($action == "edit") {
......@@ -120,7 +120,7 @@ function SPITFORM($formfields, $errors)
echo " window.EDITING = " . ($editing ? 1 : 0) . ";\n";
echo " window.UUID = " . (isset($uuid) ? "'$uuid'" : "null") . ";\n";
echo " window.UPDATED = $notifyupdate;\n";
echo " window.SNAPPING = $notifysnapshot;\n";
echo " window.SNAPPING = $notifyclone;\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.ACTION = '$action';\n";
echo " window.TITLE = '$title';\n";
......@@ -159,14 +159,14 @@ if (! isset($create)) {
"You do not appear to be a member of any projects in which ".
"you have permission to create new profiles";
}
if ($action == "edit" || $action == "delete" || $action == "snapshot") {
if ($action == "snapshot") {
if ($action == "edit" || $action == "delete" || $action == "clone") {
if ($action == "clone") {
if (! (isset($snapuuid) && IsValidUUID($snapuuid))) {
$errors["error"] = "No experiment specified for snapshot!";
$errors["error"] = "No experiment specified for clone!";
}
$instance = Instance::Lookup($snapuuid);
if (!$instance) {
SPITUSERERROR("No such instance to snapshot!");
SPITUSERERROR("No such instance to clone!");
}
else if ($this_idx != $instance->creator_idx() && !ISADMIN()) {
SPITUSERERROR("Not enough permission!");
......@@ -235,12 +235,12 @@ if (! isset($create)) {
#
# See if we have a task running in the background
# for this profile. At the moment it can only be a
# snapshot task. If there is one, we have to tell
# the js code to show the status of the snapshot.
# clone task. If there is one, we have to tell
# the js code to show the status of the clone.
#
$webtask = WebTask::LookupByObject($profile->uuid());
if ($webtask && ! $webtask->exited()) {
$notifysnapshot = 1;
$notifyclone = 1;
}
}
}
......@@ -344,13 +344,13 @@ else {
#
# Sanity check the snapuuid argument.
#
if (isset($action) && $action == "snapshot") {
if (isset($action) && $action == "clone") {
if (!isset($snapuuid) || $snapuuid == "" || !IsValidUUID($snapuuid)) {
$errors["error"] = "Invalid experiment specified for snapshot!";
$errors["error"] = "Invalid experiment specified for clone!";
}
$instance = Instance::Lookup($snapuuid);
if (!$instance) {
$errors["error"] = "No such experiment to snapshot!";
$errors["error"] = "No such experiment to clone!";
}
else if ($this_idx != $instance->creator_idx() && !ISADMIN()) {
$errors["error"] = "Not enough permission!";
......
......@@ -21,18 +21,11 @@
#
# }}}
#
# Local Variables:
# mode:php
# End:
chdir("..");
include_once("webtask.php");
chdir("apt");
include_once("profile_defs.php");
# Logged in users only. This will not return if it fails.
CheckLoginForAjax();
#
# Return info about specific profile.
#
......@@ -61,4 +54,7 @@ function Do_GetProfile()
'idx' => $profile->idx(),
'description' => $profile->description()));
}
# Local Variables:
# mode:php
# End:
?>
......@@ -84,6 +84,11 @@ class Profile
function rspec() { return $this->field('rspec'); }
function locked() { return $this->field('status'); }
function status() { return $this->field('locked'); }
# Private means only in the same project.
function IsPrivate() {
return !($this->ispublic() || $this->shared());
}
# Hmm, how does one cause an error in a php constructor?
function IsValid() {
......
......@@ -31,14 +31,22 @@ include("quickvm_sup.php");
#
$routing = array("myprofiles" =>
array("file" => "myprofiles.ajax",
"guest" => false,
"methods" => array("GetProfile" =>
"Do_GetProfile")),
"instantiate" =>
array("file" => "instantiate.ajax",
"guest" => true,
"methods" => array("GetProfile" =>
"Do_GetProfile")),
"manage_profile" =>
array("file" => "manage_profile.ajax",
"methods" => array("SnapShotStatus" =>
"Do_SnapShotStatus")),
"guest" => false,
"methods" => array("CloneStatus" =>
"Do_CloneStatus")),
"status" =>
array("file" => "status.ajax",
"guest" => true,
"methods" => array("GetInstanceStatus" =>
"Do_GetInstanceStatus",
"TerminateInstance" =>
......@@ -71,7 +79,7 @@ $this_user = CheckLogin($check_status);
# way to let guest users pass through when allowed, without
# duplicating the code in each file.
#
function CheckLoginForAjax($guestokay = 0)
function CheckLoginForAjax($guestokay = false)
{
global $this_user, $check_status;
......@@ -118,7 +126,7 @@ if (! array_key_exists($ajax_method, $routing[$ajax_route]["methods"])) {
SPITAJAX_ERROR(1, "Invalid method: $ajax_route,$ajax_method");
exit(1);
}
CheckLoginForAjax($routing[$ajax_route]["guest"]);
include($routing[$ajax_route]["file"]);
call_user_func($routing[$ajax_route]["methods"][$ajax_method]);
......
......@@ -21,19 +21,12 @@
#
# }}}
#
# Local Variables:
# mode:php
# End:
#
chdir("..");
include_once("geni_defs.php");
chdir("apt");
include_once("profile_defs.php");
include_once("instance_defs.php");
# Guest users okay. This will not return if it fails.
CheckLoginForAjax(1);
# Set these globals below.
$instance = null;
$creator = null;
......@@ -210,5 +203,7 @@ function Do_RequestExtension()
SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
}
}