Commit 55486c89 authored by Jonathon Duerig's avatar Jonathon Duerig

Added the source for the flash demo. Build instructions and basic architecture...

Added the source for the flash demo. Build instructions and basic architecture are described in README.
parent 1625ef5b
The demo for the flash ProtoGENI client consists of three basic
parts.
test.fla is the base flash file which contains the basic structure and
art assets of the demo.
src/*.as is the set of classes which implement the demo
src/com/mattism/http/xmlrpc/* is the xmlrpc library which has been
modified to act as the network channel.
-----
Build
-----
The only way to build the flash demo is with the flash authoring tool. Start the flash authoring tool and open test.fla. Select File -> Publish.
test.swf will be created in the same directory as test.fla.
------------------
Basic Architecture
------------------
The demo is organized at a fundamental level as a set of menus only
one of which is active at any time. Main.as contains the basic
functionality to select and move between menus. MenuSliceSelect,
MenuSliceCreate, and MenuSliceDetail provide the implementations for
the three menus.
The canvas which is used to manage and modify a slice is
MenuSliceDetail. It delegates the work to three different objects: an
ActiveNodes object which manages the nodes and links between them, a
ComponentManager object which maintains the combo box and selection
list for nodes, and a Console which executes any xmlrpc calls needed
and update the console text box.
package
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.CapsStyle;
import flash.display.LineScaleMode;
import flash.text.TextField;
import flash.events.MouseEvent;
class ActiveNodes
{
public function ActiveNodes(newParent : DisplayObjectContainer,
newTrash : DisplayObject,
newDescription : TextField) : void
{
parent = newParent;
linkLayer = new Sprite();
parent.addChild(linkLayer);
nodeLayer = new Sprite();
parent.addChild(nodeLayer);
trash = newTrash;
description = newDescription;
nodes = new Array();
links = new Array();
dragging = null;
dragX = 0;
dragY = 0;
newLink = null;
selected = NO_SELECTION;
}
public function cleanup() : void
{
parent.stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveDrag);
parent.stage.removeEventListener(MouseEvent.MOUSE_UP, endDrag);
parent.stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveAddLink);
parent.stage.removeEventListener(MouseEvent.MOUSE_UP, endAddLink);
var i : int = 0;
for (i = 0; i < links.length; ++i)
{
links[i].cleanup();
}
for (i = 0; i < nodes.length; ++i)
{
nodes[i].cleanup();
}
nodes = null;
parent = null;
trash = null;
dragging = null;
if (newLink != null)
{
parent.removeChild(newLink);
newLink = null;
}
nodeLayer.parent.removeChild(nodeLayer);
nodeLayer = null;
linkLayer.parent.removeChild(linkLayer);
linkLayer = null;
}
public function addNode(name : String, uuid : String, cmIndex : int,
nodeIndex : int, cleanupMethod : Function,
x : int, y : int) : void
{
var newNode : Node = new Node(nodeLayer, name, uuid, cmIndex, nodeIndex,
cleanupMethod, nodes.length,
beginDragEvent, beginAddLink);
dragX = Node.CENTER_X;
dragY = Node.CENTER_Y;
newNode.move(x - dragX, y - dragY);
nodes.push(newNode);
beginDrag(newNode);
}
function removeNode(doomedNode : Node) : void
{
var nodeLinks : Array = doomedNode.getLinksCopy();
var i : int = 0;
for (; i < nodeLinks.length; ++i)
{
removeLink(nodeLinks[i]);
}
doomedNode.cleanup();
var index = nodes.indexOf(doomedNode);
if (index != -1)
{
// Remove node. O(nk) where n is the number of nodes and k is
// the incidence of this node.
nodes.splice(index, 1);
for (i = 0; i < nodes.length; ++i)
{
nodes[i].renumber(i);
}
if (selected > index)
{
--selected;
}
}
}
function beginDragEvent(event : MouseEvent) : void
{
dragX = event.localX;
dragY = event.localY;
beginDrag(nodes[event.target.number]);
changeSelect(event.target.number);
}
function beginDrag(clickedNode : Node) : void
{
dragging = clickedNode;
parent.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveDrag);
parent.stage.addEventListener(MouseEvent.MOUSE_UP, endDrag);
}
function moveDrag(event : MouseEvent) : void
{
dragging.move(event.stageX - dragX, event.stageY - dragY);
}
function endDrag(event : MouseEvent) : void
{
parent.stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveDrag);
parent.stage.removeEventListener(MouseEvent.MOUSE_UP, endDrag);
if (trash.hitTestPoint(event.stageX, event.stageY, false))
{
changeSelect(NO_SELECTION);
removeNode(dragging);
}
dragging = null;
}
function changeSelect(newIndex : int)
{
if (selected != NO_SELECTION)
{
nodes[selected].deselect();
}
if (newIndex != NO_SELECTION)
{
nodes[newIndex].select();
}
selected = newIndex;
updateSelectText();
}
function updateSelectText() : void
{
if (selected != NO_SELECTION)
{
description.htmlText = nodes[selected].getStatusText();
}
else
{
description.htmlText = "";
}
}
function beginAddLink(event : MouseEvent) : void
{
newLink = new Shape();
parent.addChild(newLink);
parent.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveAddLink);
parent.stage.addEventListener(MouseEvent.MOUSE_UP, endAddLink);
newLinkSource = nodes[event.target.number];
updateAddLink(event.stageX, event.stageY);
}
function moveAddLink(event : MouseEvent) : void
{
updateAddLink(event.stageX, event.stageY);
}
function updateAddLink(x : int, y : int) : void
{
var color = Link.INVALID_COLOR;
if (validLinkTo(x, y))
{
color = Link.VALID_COLOR;
}
newLink.graphics.clear();
newLink.graphics.lineStyle(Link.WIDTH, color, 1.0, true,
LineScaleMode.NORMAL, CapsStyle.ROUND);
newLink.graphics.moveTo(newLinkSource.centerX(),
newLinkSource.centerY());
newLink.graphics.lineTo(x, y);
}
function endAddLink(event : MouseEvent) : void
{
if (validLinkTo(event.stageX, event.stageY))
{
var dest = findNode(event.stageX, event.stageY);
addLink(newLinkSource, dest);
}
newLink.parent.removeChild(newLink);
newLink = null;
newLinkSource = null;
parent.stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveAddLink);
parent.stage.removeEventListener(MouseEvent.MOUSE_UP, endAddLink);
}
function findNode(x : int, y : int) : Node
{
var result : Node = null;
var i : int = 0;
for (; i < nodes.length; ++i)
{
if (nodes[i].contains(x, y))
{
result = nodes[i];
break;
}
}
return result;
}
function addLink(source : Node, dest : Node) : void
{
var newLink = new Link(linkLayer, links.length, source, dest,
removeLinkEvent);
links.push(newLink);
}
function removeLinkEvent(event : MouseEvent)
{
removeLink(links[event.target.number]);
}
function removeLink(doomedLink : Link)
{
doomedLink.cleanup();
var index : int = links.indexOf(doomedLink);
if (index != -1)
{
// Remove a node from the middle of the array. O(n)
links.splice(index, 1);
var i : int = 0;
for (; i < links.length; ++i)
{
links[i].renumber(i);
}
}
}
function validLinkTo(x : int, y : int)
{
var destNode = findNode(x, y);
return destNode != null
&& ! hasLink(newLinkSource, destNode)
&& newLinkSource != destNode;
}
function hasLink(source : Node, dest : Node) : Boolean
{
var result : Boolean = false;
var i : int = 0;
for (; i < links.length; ++i)
{
if (links[i].doesConnect(source, dest))
{
result = true;
break;
}
}
return result;
}
public function getXml(cmIndex : int) : XML
{
var result : XML = null;
var i : int = 0;
for (i = 0; i < nodes.length; ++i)
{
var currentNode : XML = nodes[i].getXml(cmIndex);
if (currentNode != null)
{
if (result == null)
{
result = <rspec xmlns="http://protogeni.net/resources/rspec/0.1" />;
}
result.appendChild(currentNode);
}
}
for (i = 0; i < links.length; ++i)
{
var currentLink : XML = links[i].getXml(cmIndex);
if (currentLink != null)
{
result.appendChild(currentLink);
}
}
return result;
}
public function changeState(cmIndex : int, newState : int) : void
{
var i : int = 0;
for (; i < nodes.length; ++i)
{
nodes[i].changeState(cmIndex, newState);
}
updateSelectText();
}
public function existsState(cmIndex : int, newState : int) : Boolean
{
var result : Boolean = false;
var i : int = 0;
for (; i < nodes.length && !result; ++i)
{
result = result || nodes[i].isState(cmIndex, newState);
}
return result;
}
var nodes : Array;
var links : Array;
var parent : DisplayObjectContainer;
var linkLayer : DisplayObjectContainer;
var nodeLayer : DisplayObjectContainer;
var trash : DisplayObject;
var description : TextField;
var dragging : Node;
var dragX : int;
var dragY : int;
var newLink : Shape;
var newLinkSource : Node;
var selected : int;
static var NO_SELECTION : int = -1;
public static var PLANNED : int = 0;
public static var PENDING : int = 1;
public static var CREATED : int = 2;
public static var BOOTED : int = 3;
public static var FAILED : int = 4;
public static var statusText = new Array("Planned",
"Pending",
"Created",
"Booted",
"Failed");
}
}
package
{
import flash.display.MovieClip;
public class AddLinkClip extends MovieClip
{
public function AddLinkClip() : void
{
number = 0;
}
public var number : int;
}
}
package
{
// This object populates and takes events from both 'select', a
// combo-box used to specify the component manager and 'list', a
// listbox with all of the nodes managed by the component manager.
import fl.controls.ComboBox;
import fl.controls.List;
import fl.events.ListEvent;
import flash.events.Event;
class ComponentManager
{
public function ComponentManager(newSelect : ComboBox,
newList : List,
newNodes : ActiveNodes) : void
{
select = newSelect;
list = newList;
list.allowMultipleSelection = true;
listStatus = new ListStatusClip();
list.addChild(listStatus);
listStatus.alpha = 0.3;
nodes = newNodes;
names = new Array();
uuids = new Array();
used = new Array();
tickets = new Array();
states = new Array();
select.removeAll();
var i : int = 0;
for (; i < cmNames.length; ++i)
{
select.addItem(new ListItem(cmNames[i], null));
names.push(new Array());
uuids.push(new Array());
used.push(new Array());
tickets.push(null);
states.push(LOADING);
// populateNodes(i, cmResults[i]);
}
select.selectedIndex = 0;
states[0] = NORMAL;
list.addEventListener(ListEvent.ITEM_CLICK, clickItem);
list.addEventListener(Event.CHANGE, changeItem);
select.addEventListener(Event.CHANGE, changeComponent);
updateList();
}
public function cleanup() : void
{
list.removeEventListener(ListEvent.ITEM_CLICK, clickItem);
list.removeEventListener(Event.CHANGE, changeItem);
select.removeEventListener(Event.CHANGE, changeComponent);
listStatus.parent.removeChild(listStatus);
}
public function getTicket(index : int) : String
{
return tickets[index];
}
public function setTicket(index : int, value : String) : void
{
tickets[index] = value;
}
public function getUrl(index : int) : String
{
return cmUrls[index];
}
public function getCmCount() : int
{
return cmNames.length;
}
public function removeNode(cmIndex : int, nodeIndex : int) : void
{
var target : int = used[cmIndex].indexOf(nodeIndex);
if (target != -1)
{
used[cmIndex].splice(target, 1);
}
if (cmIndex == select.selectedIndex)
{
list.selectedIndices = used[select.selectedIndex].slice();
}
}
function clickItem(event : ListEvent) : void
{
if (used[select.selectedIndex].indexOf(event.index) == -1)
{
nodes.addNode(names[select.selectedIndex][event.index],
uuids[select.selectedIndex][event.index],
select.selectedIndex,
event.index,
removeNode,
select.stage.mouseX,
select.stage.mouseY);
used[select.selectedIndex].push(event.index);
}
list.selectedIndices = used[select.selectedIndex].slice();
}
function changeItem(event : Event) : void
{
list.selectedIndices = used[select.selectedIndex].slice();
}
function changeComponent(event : Event) : void
{
updateList();
}
function updateList() : void
{
var current = select.selectedIndex;
list.removeAll();
list.clearSelection();
var i : int = 0;
for (; i < names[current].length; ++i)
{
list.addItem(new ListItem(names[current][i], "NodeNone"));
}
list.selectedIndices = used[current].slice();
if (states[current] == NORMAL)
{
listStatus.visible = false;
}
else if (states[current] == LOADING)
{
listStatus.visible = true;
listStatus.text.text = "Loading";
listStatus.text.backgroundColor = 0x00ff00;
}
else
{
listStatus.visible = true;
listStatus.text.text = "Failed";
listStatus.text.backgroundColor = 0xff0000;
}
}
public function populateNodes(index : int, str : String) : void
{
if (str != null)
{
var rspec : XML = XML(str);
var nodeName : QName = new QName(rspec.namespace(), "node");
for each (var element in rspec.elements(nodeName))
{
if (element.attribute("uuid") != null
&& element.attribute("uuid") != "")
{
names[index].push(element.attribute("name"));
uuids[index].push(element.attribute("uuid"));
}
}
}
states[index] = NORMAL;
updateList();
}
public function failResources(index : int)
{
states[index] = FAILED;
updateList();
}
var select : ComboBox;
var list : List;
var listStatus : ListStatusClip;
var nodes : ActiveNodes;
var names : Array;
var uuids : Array;
var used : Array;
var tickets : Array;
var states : Array;
var NORMAL = 0;
var LOADING = 1;
var FAILED = 2;
public static var cmNames : Array = new Array("", "ProtoGENI", "Emulab");
static var cmUrls : Array =
new Array(null,
"https://myboss.myelab.testbed.emulab.net:443/protogeni/xmlrpc",
"https://boss.emulab.net:443/protogeni/xmlrpc/");
static var cmResults : Array =
new Array(null,
"<rspec xmlns=\"http://protogeni.net/resources/rspec/0.1\"> "+
" <node uuid=\"de9803c2-773e-102b-8eb4-001143e453fe\" " +
" hrn=\"geni1\" " +
" virtualization_type=\"emulab-vnode\"> " +
" </node>" +
" <node uuid=\"de995217-773e-102b-8eb4-001143e453fe\" " +
" hrn=\"geni-other\" " +
" virtualization_type=\"emulab-vnode\"> " +
" </node>" +
"</rspec>",