Commit 6e9457c0 authored by Leigh B Stoller's avatar Leigh B Stoller
Browse files

Cleaning up the ajax server code into per page modules, rather

then scattering around. Makes the php pages a lot cleaner, and
reduces code duplication. Its an approach, lets see how it goes.
parent 161415f3
window.APT_OPTIONS.config();
require(['jquery', 'js/quickvm_sup',
require(['jquery', 'underscore', 'js/quickvm_sup',
'js/lib/text!template/manage-profile.html',
'js/lib/text!template/waitwait-modal.html',
'js/lib/text!template/imaging-modal.html',
'js/lib/text!template/renderer-modal.html',
'js/lib/text!template/showtopo-modal.html',
'js/lib/text!template/rspectextview-modal.html',
// jQuery modules
'bootstrap','filestyle','marked','jquery-ui','jquery-grid'],
function ($, sup)
function ($, _, sup,
manageString, waitwaitString, imagingString,
rendererString, showtopoString, rspectextviewString)
{
'use strict';
var editing = 0;
var uuid = null;
var snapping= 0;
var gotrspec = 0;
var ajaxurl = "";
var manageTemplate = _.template(manageString);
var waitwaitTemplate = _.template(waitwaitString);
var imagingTemplate = _.template(imagingString);
var rendererTemplate = _.template(rendererString);
var showtopoTemplate = _.template(showtopoString);
var rspectextTemplate = _.template(rspectextviewString);
function initialize()
{
window.APT_OPTIONS.initialize(sup);
editing = window.EDITING;
editing = window.EDITING;
snapping = window.SNAPPING;
uuid = window.UUID;
ajaxurl = window.AJAXURL;
var fields = JSON.parse(_.unescape($('#form-json')[0].textContent));
var errors = JSON.parse(_.unescape($('#error-json')[0].textContent));
var projlist = JSON.parse(_.unescape($('#projects-json')[0].textContent));
// Notice if we have an rspec in the formfields, to start from.
if (_.has(fields, "profile_rspec")) {
gotrspec = 1;
}
// Generate the templates.
var manage_html = manageTemplate({
formfields: fields,
projects: projlist,
title: window.TITLE,
notifyupdate: window.UPDATED,
editing: editing,
gotrspec: gotrspec,
action: window.ACTION,
button_label: window.BUTTONLABEL,
uuid: window.UUID,
snapuuid: (window.SNAPUUID || null),
general_error: (errors.error || ''),
});
manage_html = formatter(manage_html, errors).html();
$('#manage-body').html(manage_html);
var waitwait_html = waitwaitTemplate({});
$('#waitwait_div').html(waitwait_html);
var imaging_html = imagingTemplate({});
$('#imaging_div').html(imaging_html);
var showtopo_html = showtopoTemplate({});
$('#showtopomodal_div').html(showtopo_html);
var renderer_html = rendererTemplate({});
$('#renderer_div').html(renderer_html);
var rspectext_html = rspectextTemplate({});
$('#rspectext_div').html(rspectext_html);
//
// Fix for filestyle problem; not a real class I guess, it
// runs at page load, and so the filestyle'd button in the
// form is not as it should be.
//
$('#rspecfile').each(function() {
$(this).filestyle({input : false,
buttonText : $(this).attr('data-buttonText'),
classButton: $(this).attr('data-classButton')});
});
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
......@@ -74,20 +143,6 @@ function ($, sup)
$('#profile_rspec_textarea').css({"opacity":"0.2"});
$('#profile_rspec_textarea').animate({"opacity":"1.0"}, 1500);
});
if (0) {
// Enable the Modify button if the form changes.
$('#quickvm_create_profile_form :input').each(function() {
// Need to use keyup since an input does not "change"
// until focus is lost.
$(this).keyup(function() {
$('#profile_submit_button').prop('disabled', false);
});
});
// This one for the checkboxes.
$('#quickvm_create_profile_form').change(function() {
$('#profile_submit_button').prop('disabled', false);
});
}
// Auto select the URL if the user clicks in the box.
$('#profile_url').click(function() {
$(this).focus();
......@@ -117,7 +172,7 @@ function ($, sup)
event.preventDefault();
return false;
}
sup.ShowModal("#waitwait");
WaitWait();
return true;
});
......@@ -152,12 +207,12 @@ function ($, sup)
});
/*
* If editing, need to suck the description and instructions
* If we were given an rspec, suck the description and instructions
* out of the rspec and put them into the text boxes. But
* watch for some already in the description box, it is an old
* one and we want to use it if no description in the rspec.
*/
if (editing) {
if (gotrspec) {
var old_text = $('#profile_description').val();
if (old_text != "") {
ChangeHandlerAux("description");
......@@ -171,9 +226,72 @@ function ($, sup)
* Not editing, so disable the text boxes until we get
* an rspec via the file chooser.
*/
$('#profile_instructions').prop("disabled", true);
$('#profile_description').prop("disabled", true);
DisableButton('profile_instructions');
DisableButton('profile_description');
}
//
// If taking a disk image, throw up the modal that tracks progress.
//
if (snapping) {
DisableButtons();
ShowProgressModal();
}
else {
EnableButtons();
}
}
// Formatter for the form. This did not work out nicely at all!
function formatter(fieldString, errors)
{
var root = $(fieldString);
var list = root.find('.format-me');
list.each(function (index, item) {
if (item.dataset) {
var key = item.dataset['key'];
var margin = 15;
var colsize = 12;
if ($(item).attr('data-compact')) {
margin = 5;
}
var outerdiv = $("<div class='form-group' " +
" style='margin-bottom: " + margin +
"px;'></div>");
if ($(item).attr('data-label')) {
var label_text =
"<label for='" + key + "' " +
" class='col-sm-2 control-label'> " +
item.dataset['label'];
if ($(item).attr('data-help')) {
label_text = label_text +
"<a href='#' class='btn btn-xs' " +
" data-toggle='popover' " +
" data-content='" + item.dataset['help'] + "'>" +
"<span class='glyphicon glyphicon-question-sign'>" +
" </span></a>";
}
label_text = label_text + "</label>";
outerdiv.append($(label_text));
colsize = 10;
}
var innerdiv = $("<div class='col-sm-" + colsize + "'></div>");
innerdiv.html($(item).clone());
if (_.has(errors, key)) {
outerdiv.addClass('has-error');
innerdiv.append('<label class="control-label" ' +
'for="inputError">' +
_.escape(errors[key]) + '</label>');
}
outerdiv.append(innerdiv);
$(item).after(outerdiv);
$(item).remove();
}
});
return root;
}
/*
......@@ -392,5 +510,151 @@ function ($, sup)
300, topo, null);
}
//
// Progress Modal
//
var imaging_modal_display = true;
var imaging_modal_active = false;
function ShowProgressModal()
{
//
// Ask the server for information to populate the imaging modal.
//
var callback = function(json) {
console.info(json);
var value = json.value;
if (json.code) {
if (imaging_modal_active) {
sup.HideModal("#imaging-modal");
imaging_modal_active = false;
$('#imaging_modal').off('hidden.bs.modal');
}
EnableButton("profile_delete_button");
return;
}
if (! imaging_modal_active && imaging_modal_display) {
sup.ShowModal("#imaging-modal");
imaging_modal_active = true;
imaging_modal_display = false;
// Handler so we know the user closed the modal.
$('#imaging_modal').on('hidden.bs.modal', function (e) {
imaging_modal_active = false;
$('#imaging_modal').off('hidden.bs.modal');
})
}
//
// Fill in the details.
//
if (! _.has(value, "node_status")) {
value["node_status"] = "unknown";
}
if (! _.has(value, "image_size")) {
value["image_size"] = "unknown";
}
$('#imaging_modal_node_status').html(value["node_status"]);
$('#imaging_modal_image_size').html(value["image_size"]);
if (_.has(value, "image_status")) {
var status = value["image_status"];
if (status == "imaging") {
$('#tracker-imaging').removeClass('progtrckr-todo');
$('#tracker-imaging').addClass('progtrckr-done');
}
if (status == "finishing") {
$('#tracker-imaging').removeClass('progtrckr-todo');
$('#tracker-imaging').addClass('progtrckr-done');
$('#tracker-finishing').removeClass('progtrckr-todo');
$('#tracker-finishing').addClass('progtrckr-done');
}
else if (status == "ready") {
$('#tracker-imaging').removeClass('progtrckr-todo');
$('#tracker-imaging').addClass('progtrckr-done');
$('#tracker-finishing').removeClass('progtrckr-todo');
$('#tracker-finishing').addClass('progtrckr-done');
$('#tracker-ready').removeClass('progtrckr-todo');
$('#tracker-ready').addClass('progtrckr-done');
$('#imaging-spinner').addClass("invisible");
EnableButtons();
return;
}
else if (status == "failed") {
$('#tracker-imaging').removeClass('progtrckr-todo');
$('#tracker-imaging').addClass('progtrckr-done');
$('#tracker-finishing').removeClass('progtrckr-todo');
$('#tracker-finishing').addClass('progtrckr-done');
$('#tracker-ready').removeClass('progtrckr-todo');
$('#tracker-ready').addClass('progtrckr-done');
$('#tracker-ready').html("Failed");
$('#imaging-spinner').addClass("invisible");
EnableButton("profile_delete_button");
return;
}
}
//
// Done, we need to do something here if we exited before
// ready or failed above.
//
if (_.has(value, "exited")) {
$('#imaging-spinner').addClass("invisible");
EnableButtons();
return;
}
// And check again in a little bit.
setTimeout(function f() { ShowProgressModal() }, 5000);
}
var $xmlthing = sup.CallServerMethod(ajaxurl, "SnapShotStatus",
{"uuid" : uuid});
$xmlthing.done(callback);
}
//
// Show the waitwait modal.
//
function WaitWait()
{
sup.ShowModal('#waitwait-modal');
}
//
// Enable/Disable buttons.
//
function EnableButtons()
{
EnableButton("profile_delete_button");
EnableButton("profile_instantiate_button");
EnableButton("profile_submit_button");
}
function DisableButtons()
{
DisableButton("profile_delete_button");
DisableButton("profile_instantiate_button");
DisableButton("profile_submit_button");
}
function EnableButton(button)
{
ButtonState(button, 1);
}
function DisableButton(button)
{
ButtonState(button, 0);
}
function ButtonState(button, enable)
{
if (enable) {
$('#' + button).removeAttr("disabled");
}
else {
$('#' + button).attr("disabled", "disabled");
}
}
$(document).ready(initialize);
});
......@@ -5,10 +5,12 @@ require(['jquery', 'js/quickvm_sup',
function ($, sup)
{
'use strict';
var ajaxurl = null;
function initialize()
{
window.APT_OPTIONS.initialize(sup);
ajaxurl = window.AJAXURL;
var table = $(".tablesorter")
.tablesorter({
......@@ -65,7 +67,8 @@ function ($, sup)
($("#showtopo_nopicker").outerWidth() - 2),
300, topo, null);
};
var $xmlthing = sup.CallMethod("getprofile", null, 0, profile);
var $xmlthing = sup.CallServerMethod(ajaxurl, "GetProfile",
{"uuid" : profile});
$xmlthing.done(callback);
}
......
......@@ -35,6 +35,30 @@ function CallMethod(method, callback, uuid, arg)
});
}
function CallServerMethod(url, method, args)
{
if (url == null) {
url = 'https://' + window.location.host + '/apt/server-ajax.php';
}
return $.ajax({
// the URL for the request
url: url,
// the data to send (will be converted to a query string)
data: {
ajax_request: 1,
ajax_method: method,
ajax_args: args,
},
// whether this is a POST or GET request
type: "POST",
// the type of data we expect back
dataType : "json",
});
}
function maketopmap(divname, width, height, json, sshcallback)
{
var ismousedown = false;
......@@ -352,6 +376,7 @@ return {
ShowModal: ShowModal,
HideModal: HideModal,
CallMethod: CallMethod,
CallServerMethod: CallServerMethod,
ConvertManifestToJSON: ConvertManifestToJSON,
maketopmap: maketopmap,
SpitOops: SpitOops,
......
<?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/>.
#
# }}}
#
# Local Variables:
# mode:php
# End:
chdir("..");
include_once("webtask.php");
chdir("apt");
include_once("profile_defs.php");
#
# Return info about specific profile.
#
function Do_GetProfile()
{
global $this_idx;
global $ajax_args;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$profile = Profile::Lookup($ajax_args["uuid"]);
if (!$profile) {
SPITAJAX_ERROR(1, "Unknown profile uuid");
return;
}
if ($this_idx != $profile->creator_idx() && !ISADMIN()) {
SPITAJAX_ERROR(1, "Not enough permission");
return;
}
SPITAJAX_RESPONSE(array('rspec' => $profile->rspec(),
'name' => $profile->name(),
'idx' => $profile->idx(),
'description' => $profile->description()));
}
?>
......@@ -31,10 +31,7 @@ $page_title = "My Profiles";
# Verify page arguments.
#
$optargs = OptionalPageArguments("target_user", PAGEARG_USER,
"all", PAGEARG_BOOLEAN,
"ajax_request", PAGEARG_BOOLEAN,
"ajax_method", PAGEARG_STRING,
"ajax_argument", PAGEARG_STRING);
"all", PAGEARG_BOOLEAN);
#
# Get current user.
......@@ -42,10 +39,6 @@ $optargs = OptionalPageArguments("target_user", PAGEARG_USER,
RedirectSecure();
$this_user = CheckLogin($check_status);
if (!$this_user) {
if (isset($ajax_request)) {
SPITAJAX_ERROR(1, "You are not logged in anymore");
exit();
}
RedirectLoginPage();
exit();
}
......@@ -54,42 +47,13 @@ if (!isset($target_user)) {
}
if (!$this_user->SameUser($target_user)) {
if (!ISADMIN()) {
if (isset($ajax_request)) {
SPITAJAX_ERROR(1, "You do not have permission to do this");
}
else {
SPITUSERERROR("You do not have permission to view ".
"target user's profiles");
}
SPITUSERERROR("You do not have permission to view ".
"target user's profiles");
exit();
}
}
$target_idx = $target_user->uid_idx();
#
# Deal with ajax requests.
#
if (isset($ajax_request)) {
if ($ajax_method == "getprofile") {
$profile_idx = addslashes($ajax_argument);
$query_result =
DBQueryWarn("select * from apt_profiles ".
"where idx='$profile_idx'");
if (!$query_result || !mysql_num_rows($query_result)) {
SPITAJAX_ERROR(1, "No such profile $profile_idx!");
exit();
}
$row = mysql_fetch_array($query_result);
SPITAJAX_RESPONSE(array('rspec' => $row['rspec'],
'name' => $row['name'],
'idx' => $row['idx'],
'description' => $row['description']));
}
exit();
}
SPITHEADER(1);
echo "<link rel='stylesheet'
......@@ -215,6 +179,10 @@ echo "<!-- This is the topology view modal -->
</div>
</div>\n";
echo "<script type='text/javascript'>\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo "</script>\n";
echo "<script src='js/lib/require.js' data-main='js/myprofiles'></script>\n";
SPITFOOTER();
......
<?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("defs.php3");
chdir("apt");
include("quickvm_sup.php");
#
# Redefine this so we return XML instead of html for all errors.
#
$PAGEERROR_HANDLER = function($msg, $status_code = 0) {
if ($status_code == 0) {
$status_code = 1;
}
SPITAJAX_ERROR(1, $msg);
return;
};
#
# At this point, must always be a logged in user.
#
$this_user = CheckLogin($check_status);
if (!$this_user) {
SPITAJAX_ERROR(2, "Your login has timed out");
return;
}
$this_idx = $this_user->uid_idx();
#
# Verify page arguments.