Commit bc84ff27 authored by Jonathon Duerig's avatar Jonathon Duerig

Integrate Jacks with remainder of Apt pages. Fix jquery-ui paths to point to ../images.

parent 46d6377d
......@@ -130,13 +130,17 @@ body {
}
#showtopo_nopicker {
border: 1px solid #000;
height: 300px;
}
#showtopo_div {
height: 300px;
}
#showtopo_statuspage {
height: 300px;
}
@media (min-width: 970px) {
#showtopo_dialog {
......
......@@ -2,13 +2,11 @@
require(window.APT_OPTIONS.configObject,
['underscore', 'js/quickvm_sup', // jQuery modules
'formhelpers', 'filestyle', 'marked', 'jacks'],
'formhelpers', 'filestyle', 'marked'],
function (_, sup)
{
'use strict';
var jacksInstance;
var jacksUpdate;
var ajaxurl;
function initialize()
......@@ -120,31 +118,7 @@ function (_, sup)
$('#showtopo_description').html(description);
$('#selected_profile_description').html(description);
if (! jacksInstance)
{
jacksInstance = new window.Jacks({
mode: 'viewer',
source: 'rspec',
root: '#showtopo_div',
// size: { x: 643, y: 300 },
nodeSelect: false,
readyCallback: function (input, output) {
jacksUpdate = input;
jacksUpdate.trigger('change-topology',
[{ rspec: json.value.rspec }]);
},
show: {
rspec: false,
tour: false,
version: false
}
});
}
else if (jacksUpdate)
{
jacksUpdate.trigger('change-topology',
[{ rspec: json.value.rspec }]);
}
sup.maketopmap('#showtopo_div', json.value.rspec, null);
}
var $xmlthing = sup.CallServerMethod(ajaxurl,
"instantiate", "GetProfile",
......
......@@ -121,10 +121,7 @@ function (_, sup, filesize, ShowImagingModal,
$('#showtopo_modal_button').click(function (event) {
event.preventDefault();
// The rspec is taken from the text area.
var xmlDoc = $.parseXML($('#profile_rspec_textarea').val());
var xml = $(xmlDoc);
ShowRspecTopo(xml);
ShowRspecTopo($('#profile_rspec_textarea').val());
});
$('#expand_rspec_modal_button').click(function (event) {
$('#modal_profile_rspec_textarea').val(
......@@ -500,15 +497,8 @@ function (_, sup, filesize, ShowImagingModal,
//
function ShowRspecTopo(xml)
{
var topo = sup.ConvertManifestToJSON(null, xml);
console.info(topo);
sup.ShowModal("#quickvm_topomodal");
// Subtract -2 cause of the border.
sup.maketopmap("#showtopo_nopicker",
($("#showtopo_nopicker").outerWidth() - 2),
300, topo, null);
sup.maketopmap("#showtopo_nopicker", xml, null);
}
//
......
......@@ -59,16 +59,8 @@ function (sup)
alert("Failed to get rspec for topology viewer: " + json.value);
return;
}
var xmlDoc = $.parseXML(json.value.rspec);
var xml = $(xmlDoc);
var topo = sup.ConvertManifestToJSON(profile, xml);
sup.ShowModal("#quickvm_topomodal");
// Subtract -2 cause of the border.
sup.maketopmap("#showtopo_nopicker",
($("#showtopo_nopicker").outerWidth() - 2),
300, topo, null);
sup.maketopmap('#showtopo_nopicker', json.value.rspec, null);
};
var $xmlthing = sup.CallServerMethod(ajaxurl,
"myprofiles",
......
define(['d3', 'dateformat', 'marked'],
define(['d3', 'dateformat', 'marked', 'jacks'],
function (d3) {
function ShowModal(which)
......@@ -37,308 +37,46 @@ function CallServerMethod(url, route, method, args)
});
}
function maketopmap(divname, width, height, json, sshcallback)
{
var ismousedown = false;
var savedTrans;
var savedScale;
// Flag to distinguish between click and click/drag.
var isDragging = false;
var change_view = d3.behavior.zoom()
.scaleExtent([1,5])
.on("zoom", rescaleg);
var jacksInstance;
var jacksInput;
var jacksOutput;
function rescaleg(d, i, j) {
if (!ismousedown)
function maketopmap(divname, xml, sshcallback)
{
if (! jacksInstance)
{
jacksInstance = new window.Jacks({
mode: 'viewer',
source: 'rspec',
root: divname,
nodeSelect: false,
readyCallback: function (input, output) {
jacksInput = input;
jacksOutput = output;
jacksInput.trigger('change-topology',
[{ rspec: xml }]);
if (sshcallback)
{
trans=d3.event.translate;
scale=d3.event.scale;
tx = Math.min(0, Math.max(width * (1 - scale), trans[0]));
ty = Math.min(0, Math.max(height * (1 - scale), trans[1]));
change_view.translate([tx, ty]);
vis.attr("transform",
"translate(" + tx + "," + ty + ")"
+ " scale(" + scale + ")");
}
}
function mousedown()
{
$("#quickvm_topomodal").addClass("unselectable");
}
function mouseup()
{
$("#quickvm_topomodal").removeClass("unselectable");
}
$(divname).html("<div></div>");
var outer = d3.select(divname).append("svg:svg")
.attr("class", "topomap")
.style("visibility", "hidden")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all");
var vis = outer
.append('svg:g')
.on("dblclick.zoom", null)
.call(change_view)
.append('svg:g')
.on("mousedown", mousedown)
.on("mouseup", mouseup);
var rect = vis.append("svg:rect")
.attr("width", width)
.attr("height", height)
.style("fill-opacity", 0.0)
.style("stroke", "#000");
var topo = function(json) {
var force = self.force = d3.layout.force()
.nodes(json.nodes)
.links(json.links)
.distance(150)
.charge(-400)
.size([width, height])
.start();
var linkg = vis.selectAll("g.link")
.data(json.links)
.enter().append("svg:g");
var link = linkg.append("svg:line")
.attr("class", "linkline")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
var linklabel = linkg.append("svg:text")
.attr("class", "linktext")
.attr("x", function(d) { return (d.source.x + d.target.x) / 2 })
.attr("y", function(d) { return (d.source.y + d.target.y) / 2 })
.text(function(d) { return d.name });
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
function dragstart(d, i) {
// stops the force auto positioning before you start dragging
force.stop()
ismousedown = true;
savedTrans = change_view.translate();
savedScale = change_view.scale();
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
// this is the key to make it work together with updating
// both px,py,x,y on d !
tick(null);
}
function dragend(d, i) {
// of course set the node to fixed so the force doesn't
// include the node in its auto positioning stuff
d.fixed = true;
force.resume();
ismousedown = false;
change_view.translate(savedTrans);
change_view.scale(savedScale);
}
var nodeg = vis.selectAll("g.node")
.data(json.nodes)
.enter().append("svg:g")
.call(node_drag);
//
// The mouse events are to distinguish between click and drag.
// I found it with a Google search of course.
//
var node = nodeg.append("svg:rect")
.attr("class", "nodebox")
.on("mousedown", function(d) {
$(window).mousemove(function() {
isDragging = true;
$(window).unbind("mousemove");
});
})
.on("mouseup", function(d) {
var wasDragging = isDragging;
isDragging = false;
$(window).unbind("mousemove");
if (!wasDragging && sshcallback) { //was clicking
sshcallback(d.hostport, d.client_id);
jacksOutput.on('click-event', function (event) {
if (event.type === 'node')
{
sshcallback(event.ssh, event.client_id);
}
});
}
})
.attr("x", "-10px")
.attr("y", "-10px")
.attr("width", "20px")
.attr("height", "20px");
var nodelabel = nodeg.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 16)
.attr("dy", ".35em")
.text(function(d) { return d.name });
function tick(e) {
if (e && e.alpha < 0.05) {
outer.style("visibility", "visible")
force.stop();
return;
}
if (0) {
node.attr("x",
function(d) {
return d.x =
Math.max(10,
Math.min(width - 10, d.x));
})
.attr("y",
function(d) {
return d.y =
Math.max(10,
Math.min(height - 10, d.y));
});
},
show: {
rspec: false,
tour: false,
version: false
}
else {
nodeg.attr("transform", function(d) {
d.px = d.x = Math.max(12, Math.min(width - 12, d.x));
d.py = d.y = Math.max(12, Math.min(height - 12, d.y));
return "translate(" + d.x + "," + d.y + ")"; });
}
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
linklabel.attr("x", function(d) { return (d.source.x + d.target.x)
/ 2 })
.attr("y", function(d) { return (d.source.y + d.target.y)
/ 2 });
};
force.on("tick", tick);
}(json);
return topo;
}
// Avoid recalc of the layout if we already have seen it. Stash
// json here and return it if we have it.
var saved = new Object();
//
// Convert a manifest in XML to a JSON object of nodes and links.
//
function ConvertManifestToJSON(name, xml)
{
if (name && saved[name]) {
return saved[name];
}
var json = {
"nodes": [],
"links": [],
};
var interfaces = new Array();
var count = 0;
$(xml).find("node").each(function(){
var client_id = $(this).attr("client_id");
var jobj = {"name" : client_id};
$(this).find("interface").each(function() {
var interface_id = $(this).attr("client_id");
var interface = new Object();
interface.client_id = interface_id;
interface.node_id = client_id;
interface.node_index = count;
interfaces.push(interface);
});
var login = $(this).find("login");
if (login) {
var user = login.attr("username");
var host = login.attr("hostname");
var port = login.attr("port");
var sshurl = "ssh://" + user + "@" + host + ":" + port + "/";
jobj.client_id = client_id;
jobj.hostport = host + ":" + port;
jobj.sshurl = sshurl;
}
json.nodes[count] = jobj;
count++;
});
$(xml).find("link").each(function(){
var client_id = $(this).attr("client_id");
var link_type = $(this).find("link_type");
var ifacerefs = $(this).find("interface_ref");
if (ifacerefs.length < 2) {
console.info("Oops, not enough interfaces in " + client_id);
}
else if (ifacerefs.length > 2) {
console.info("Oops, too many interfaces in " + client_id);
}
else {
var source = ifacerefs[0];
var target = ifacerefs[1];
source = $(source);
target = $(target);
var source_ifname = source.attr("client_id");
var target_ifname = target.attr("client_id");
var source_name = null;
var target_name = null;
var source_index = null;
var target_index = null;
/*
* First we have map the client_ids to the node by
* searching all of the interfaces we put into the
* list above.
*
* Javascript does not do dictionaries. Too bad.
*/
for (i = 0; i < interfaces.length; i++) {
if (interfaces[i].client_id == source_ifname) {
source_name = interfaces[i].node_id;
source_index = interfaces[i].node_index;
}
if (interfaces[i].client_id == target_ifname) {
target_name = interfaces[i].node_id;
target_index = interfaces[i].node_index;
}
}
json.links.push({"name" : client_id,
"source" : source_index,
"target" : target_index,
"source_name" : source_name,
"target_name" : target_name,
});
}
});
if (name) {
saved[name] = json;
}
return json;
else if (jacksInput)
{
jacksInput.trigger('change-topology',
[{ rspec: xml }]);
}
}
// Spit out the oops modal.
......@@ -355,7 +93,6 @@ return {
ShowModal: ShowModal,
HideModal: HideModal,
CallServerMethod: CallServerMethod,
ConvertManifestToJSON: ConvertManifestToJSON,
maketopmap: maketopmap,
SpitOops: SpitOops,
};
......
require([window.APT_OPTIONS.configObject,
'underscore', 'js/quickvm_sup', 'moment', 'js/image',
require(window.APT_OPTIONS.configObject,
['underscore', 'js/quickvm_sup', 'moment', 'js/image',
'js/lib/text!template/status.html',
'js/lib/text!template/waitwait-modal.html',
'js/lib/text!template/oops-modal.html',
......@@ -15,7 +15,6 @@ function (_, sup, moment, ShowImagingModal,
cloneHelpString, snapshotHelpString)
{
'use strict';
var CurrentTopo = null;
var nodecount = 0;
var ajaxurl = null;
var uuid = null;
......@@ -279,7 +278,6 @@ function (_, sup, moment, ShowImagingModal,
}
if (! StatusWatchCallBack.active) {
ShowTopo(uuid);
StartResizeWatchdog()
StatusWatchCallBack.active = 1;
}
EnableButtons();
......@@ -370,41 +368,6 @@ function (_, sup, moment, ShowImagingModal,
}
}
//
// Install a window resize handler to redraw the topomap.
//
function StartResizeWatchdog()
{
var resizeTimer;
//
// This does the actual work, called from the timer.
//
function resizeFunction() {
// console.info("resizing topo");
// Must clear the div for the D3 library.
$("#showtopo_statuspage").html("<div></div>");
$("#showtopo_statuspage").removeClass("invisible");
ReDrawTopoMap();
}
//
// When we get (the first of a series) of resize events,
// we want to throw away the current topograph and set a
// timer that will run a little while later, to redraw
// it in the newly sized container. But, resize events might
// come pouring in as the user moves the moouse, so we just
// kill the old one each time, and eventually it will fire
// after the user stops dinking around.
//
$(window).resize(function() {
$("#showtopo_statuspage").addClass("invisible");
clearTimeout(resizeTimer);
resizeTimer = setTimeout(resizeFunction, 250);
});
}
//
// Found this with a Google search; countdown till the expiration time,
// updating the display. Watch for extension via the reset variable.
......@@ -669,6 +632,9 @@ function (_, sup, moment, ShowImagingModal,
"hostport" : hostport});
xmlthing.done(callback);
}
var hostportList = {};
//
// Show the topology inside the topo container. Called from the status
// watchdog and the resize wachdog. Replaces the current topo drawing.
......@@ -676,18 +642,14 @@ function (_, sup, moment, ShowImagingModal,
function ShowTopo(uuid)
{
var callback = function(json) {
// console.info(json.value);
var xmlDoc = $.parseXML(json.value);
var xml = $(xmlDoc);
var topo = sup.ConvertManifestToJSON(null, xml);
// console.info(json.value);
if ($("#manifest_textarea").length) {
$("#manifest_textarea").html(json.value);
$("#manifest_textarea").css("height", "300");
}
var xmlDoc = $.parseXML(json.value);
var xml = $(xmlDoc);
// Suck the instructions out of the tour and put them into
// the Usage area.
$(xml).find("rspec_tour").each(function() {
......@@ -728,6 +690,7 @@ function (_, sup, moment, ShowImagingModal,
// console.info(url);
var hostport = host + ":" + port;
hostportList[node] = hostport;
ssh = "<button class='btn btn-primary btn-sm' " +
" id='" + "sshbutton_" + node + "' " +
" type='button'>" +
......@@ -752,10 +715,11 @@ function (_, sup, moment, ShowImagingModal,
nodecount++;
});
// Stash this for resize watchdog redraw.
CurrentTopo = topo;
ReDrawTopoMap();
$("#showtopo_container").removeClass("invisible");
$('#quicktabs a[href="#profile"]').tab('show');
sup.maketopmap('#showtopo_statuspage', json.value, function(ssh, clientId) {
NewSSHTab(hostportList[clientId], clientId);
});
// If a single node, show the clone button. Only
// single node experiments can do this.
......@@ -778,19 +742,6 @@ function (_, sup, moment, ShowImagingModal,
xmlthing.done(callback);
}
function ReDrawTopoMap()
{
// Activate the "profile" tab or else the map has no size.
$('#quicktabs a[href="#profile"]').tab('show');
// Subtract -2 cause of the border.
sup.maketopmap("#showtopo_statuspage",
$("#showtopo_statuspage").outerWidth() - 2,
300, CurrentTopo,
// Callback for ssh.
function(arg1, arg2) { NewSSHTab(arg1, arg2); });
}
function ShowProgressModal()
{
ShowImagingModal(function()
......
......@@ -171,7 +171,7 @@ echo "<!-- This is the topology view modal -->
<div class='panel panel-default'
id='showtopo_container'>
<div class='panel-body'>
<div id='showtopo_nopicker'></div>
<div id='showtopo_nopicker' class='jacks'></div>
</div>
</div>
</div>
......
......@@ -13,7 +13,7 @@
<div class='panel panel-default'
id='showtopo_container'>
<div class='panel-body'>
<div id='showtopo_nopicker'></div>
<div id='showtopo_nopicker' class='jacks'></div>
</div>
</div>
</div>
......
......@@ -123,7 +123,7 @@
</ul>
<div id='quicktabs_content' class='tab-content'>
<div class='tab-pane active' id='profile'>
<div id='showtopo_statuspage'></div>
<div id='showtopo_statuspage' class='jacks'></div>
<small>Click on a node to open a shell on that node.
Click and drag to move things around.</small>
</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