Imports prototype from ansible-network-ui

The ansible-network-ui prototype project builds a standalone Network UI
outside of Tower as its own Django application. The original prototype
code is located here:
https://github.com/benthomasson/ansible-network-ui.

The prototype provides a virtual canvas that supports placing
networking devices onto 2D plane and connecting those devices together
with connections called links.  The point where the link connects
to the network device is called an interface.  The devices, interfaces,
and links may all have their respective names.  This models physical
networking devices is a simple fashion.

The prototype implements a pannable and zoomable 2D canvas in using SVG
elements and AngularJS directives.   This is done by adding event
listeners for mouse and keyboard events to an SVG element that fills the
entire browser window.

Mouse and keyboard events are handled in a processing pipeline where
the processing units are implemented as finite state machines that
provide deterministic behavior to the UI.

The finite state machines are built in a visual way that makes
the states and transitions clearly evident.  The visual tool for
building FSM is located here:
https://github.com/benthomasson/fsm-designer-svg.   This tool
is a fork of this project where the canvas is the same.  The elements
on the page are FSM states and the directional connections are called
transitions.   The bootstrapping of the FSM designer tool and
network-ui happen in parallel.  It was useful to try experiemental
code in FSM designer and then import it into network-ui.

The FSM designer tool provides a YAML description of the design
which can be used to generate skeleton code and check the implementation
against the design for discrepancies.

Events supported:

* Mouse click
* Mouse scroll-wheel
* Keyboard events
* Touch events

Interactions supported:

* Pan canvas by clicking-and-dragging on the background
* Zooming canvas by scrolling mousewheel
* Adding devices and links by using hotkeys
* Selecting devices, interaces, and links by clicking on their icon
* Editing labels on devices, interfaces, and links by double-clicking on
  their icon
* Moving devices around the canvas by clicking-and-dragging on their
  icon

Device types supported:

* router
* switch
* host
* racks

The database schema for the prototype is also developed with a visual
tool that makes the relationships in the snowflake schema for the models
quickly evident.  This tool makes it very easy to build queries across
multiple tables using Django's query builder.

See: https://github.com/benthomasson/db-designer-svg

The client and the server communicate asynchronously over a websocket.
This allows the UI to be very responsive to user interaction since
the full request/response cycle is not needed for every user
interaction.

The server provides persistence of the UI state in the database
using event handlers for events generated in the UI.  The UI
processes mouse and keyboard events, updates the UI, and
generates new types of events that are then sent to the server
to be persisted in the database.

UI elements are tracked by unique ids generated on the client
when an element is first created.  This allows the elements to
be correctly tracked before they are stored in the database.

The history of the UI is stored in the TopologyHistory model
which is useful for tracking which client made which change
and is useful for implementing undo/redo.

Each message is given a unique id per client and has
a known message type.  Message types are pre-populated
in the MessageType model using a database migration.

A History message containing all the change messages for a topology is
sent when the websocket is connected.  This allows for undo/redo work
across sessions.

This prototype provides a server-side test runner for driving
tests in the user interface.  Events are emitted on the server
to drive the UI.  Test code coverage is measured using the
istanbul library which produces instrumented client code.
Code coverage for the server is is measured by the coverage library.

The test code coverage for the Python code is 100%.
This commit is contained in:
Ben Thomasson
2017-07-26 15:38:15 -04:00
parent 1595947ae2
commit 48d801271c
105 changed files with 50273 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
.PHONY: all models admin
all: models admin
models:
jinja2 templates/models.pyt designs/models.yml > models.py
autopep8 -i models.py
admin:
jinja2 templates/admin.pyt designs/models.yml > admin.py
autopep8 -i admin.py
View File
+71
View File
@@ -0,0 +1,71 @@
from django.contrib import admin
from awx.network_ui.models import Device
from awx.network_ui.models import Link
from awx.network_ui.models import Topology
from awx.network_ui.models import Client
from awx.network_ui.models import TopologyHistory
from awx.network_ui.models import MessageType
from awx.network_ui.models import Interface
class DeviceAdmin(admin.ModelAdmin):
fields = ('topology', 'name', 'x', 'y', 'id', 'type',)
raw_id_fields = ('topology',)
admin.site.register(Device, DeviceAdmin)
class LinkAdmin(admin.ModelAdmin):
fields = ('from_device', 'to_device', 'from_interface', 'to_interface', 'id', 'name',)
raw_id_fields = ('from_device', 'to_device', 'from_interface', 'to_interface',)
admin.site.register(Link, LinkAdmin)
class TopologyAdmin(admin.ModelAdmin):
fields = ('name', 'scale', 'panX', 'panY',)
raw_id_fields = ()
admin.site.register(Topology, TopologyAdmin)
class ClientAdmin(admin.ModelAdmin):
fields = ()
raw_id_fields = ()
admin.site.register(Client, ClientAdmin)
class TopologyHistoryAdmin(admin.ModelAdmin):
fields = ('topology', 'client', 'message_type', 'message_id', 'message_data', 'undone',)
raw_id_fields = ('topology', 'client', 'message_type',)
admin.site.register(TopologyHistory, TopologyHistoryAdmin)
class MessageTypeAdmin(admin.ModelAdmin):
fields = ('name',)
raw_id_fields = ()
admin.site.register(MessageType, MessageTypeAdmin)
class InterfaceAdmin(admin.ModelAdmin):
fields = ('device', 'name', 'id',)
raw_id_fields = ('device',)
admin.site.register(Interface, InterfaceAdmin)
+1
View File
@@ -0,0 +1 @@
alter table prototype_device add constraint prototype_device_topology_id_unique unique (topology_id, id);
+779
View File
@@ -0,0 +1,779 @@
# In consumers.py
from channels import Group, Channel
from channels.sessions import channel_session
from awx.network_ui.models import Topology, Device, Link, Client, TopologyHistory, MessageType, Interface
from awx.network_ui.serializers import yaml_serialize_topology
import urlparse
from django.db.models import Q
from collections import defaultdict
from django.conf import settings
import math
import random
from awx.network_ui.utils import transform_dict
from pprint import pprint
import dpath.util
import json
import time
# Connected to websocket.connect
HISTORY_MESSAGE_IGNORE_TYPES = ['DeviceSelected',
'DeviceUnSelected',
'LinkSelected',
'LinkUnSelected',
'Undo',
'Redo',
'MouseEvent',
'MouseWheelEvent',
'KeyEvent']
SPACING = 200
RACK_SPACING = 50
settings.RECORDING = False
def circular_layout(topology_id):
n = Device.objects.filter(topology_id=topology_id).count()
r = 200
if n > 0:
arc_radians = 2 * math.pi / n
else:
arc_radians = 2 * math.pi
for i, device in enumerate(Device.objects.filter(topology_id=topology_id)):
device.x = math.cos(arc_radians * i + math.pi / 4) * r
device.y = math.sin(arc_radians * i + math.pi / 4) * r
device.save()
send_snapshot(Group("topology-%s" % topology_id), topology_id)
def v_distance(graph, grid, device):
d = 0
for edge in graph['edges'][device]:
d += math.sqrt(math.pow(device.x - edge.x, 2) + math.pow(device.y - edge.y, 2))
return d
def reduce_distance(graph, grid):
devices = graph['vertices']
def sum_distances():
distances = {x: v_distance(graph, grid, x) for x in grid.keys()}
return sum(distances.values())
total_distance = sum_distances()
for i in xrange(10000):
a = random.choice(devices)
b = random.choice(devices)
if a == b:
continue
else:
swap(grid, a, b)
place(grid, a)
place(grid, b)
new_total = sum_distances()
if new_total < total_distance:
print "New total", new_total
total_distance = new_total
a.save()
b.save()
else:
swap(grid, a, b)
place(grid, a)
place(grid, b)
def place(grid, device):
device.x = grid[device][1] * SPACING
device.y = grid[device][0] * SPACING
def swap(grid, a, b):
tmp = grid[a]
grid[a] = grid[b]
grid[b] = tmp
def grid_layout(topology_id):
n = Device.objects.filter(topology_id=topology_id).count()
cols = rows = int(math.ceil(math.sqrt(n)))
def device_seq_generator():
for d in Device.objects.filter(topology_id=topology_id):
yield d
device_seq = device_seq_generator()
grid = {}
graph = dict(vertices=[], edges=defaultdict(list))
links = Link.objects.filter(Q(from_device__topology_id=topology_id) |
Q(to_device__topology_id=topology_id))
for l in links:
graph['edges'][l.from_device].append(l.to_device)
graph['edges'][l.to_device].append(l.from_device)
for i in xrange(rows):
for j in xrange(cols):
try:
device = next(device_seq)
graph['vertices'].append(device)
grid[device] = (i, j)
place(grid, device)
device.save()
except StopIteration:
pass
reduce_distance(graph, grid)
send_snapshot(Group("topology-%s" % topology_id), topology_id)
def tier_layout(topology_id):
devices = list(Device.objects.filter(topology_id=topology_id))
device_map = {x.pk: x for x in devices}
links = Link.objects.filter(Q(from_device__topology_id=topology_id) |
Q(to_device__topology_id=topology_id))
def guess_role(devices):
for device in devices:
if getattr(device, "role", None):
continue
if device.type == "host":
device.role = "host"
continue
if device.type == "switch":
if 'leaf' in device.name.lower():
device.role = "leaf"
continue
if 'spine' in device.name.lower():
device.role = "spine"
continue
device.role = "unknown"
guess_role(devices)
edges = defaultdict(set)
racks = []
for l in links:
edges[device_map[l.from_device.pk]].add(device_map[l.to_device.pk])
edges[device_map[l.to_device.pk]].add(device_map[l.from_device.pk])
pprint(devices)
similar_connections = defaultdict(list)
for device, connections in edges.iteritems():
similar_connections[tuple(connections)].append(device)
pprint(dict(**similar_connections))
for connections, from_devices in similar_connections.iteritems():
if len(from_devices) > 0 and from_devices[0].role == "host":
racks.append(from_devices)
pprint(racks)
pprint(devices)
tiers = defaultdict(list)
for device in devices:
if getattr(device, 'tier', None):
pass
elif device.role == "leaf":
device.tier = 1
elif device.role == "spine":
device.tier = 2
elif device.role == "host":
device.tier = 0
else:
device.tier = 3
tiers[device.tier].append(device)
for rack in racks:
rack.sort(key=lambda x: x.name)
racks.sort(key=lambda x: x[0].name)
for tier in tiers.values():
tier.sort(key=lambda x: x.name)
pprint(tiers)
for device in devices:
print device, getattr(device, 'tier', None)
if getattr(device, 'tier', None) is None:
device.y = 0
device.x = 0
else:
device.y = SPACING * 3 - device.tier * SPACING
device.x = 0 - (len(tiers[device.tier]) * SPACING) / 2 + tiers[device.tier].index(device) * SPACING
device.save()
for j, rack in enumerate(racks):
x = 0 - (len(racks) * SPACING) / 2 + j * SPACING
for i, device in enumerate(rack):
device.x = x
device.y = SPACING * 3 + i * RACK_SPACING
device.save()
send_snapshot(Group("topology-%s" % topology_id), topology_id)
def parse_topology_id(data):
topology_id = data.get('topology_id', ['null'])
try:
topology_id = int(topology_id[0])
except ValueError:
topology_id = None
if not topology_id:
topology_id = None
return topology_id
# Persistence
class _Persistence(object):
def handle(self, message):
topology_id = message.get('topology')
assert topology_id is not None, "No topology_id"
client_id = message.get('client')
assert client_id is not None, "No client_id"
data = json.loads(message['text'])
if isinstance(data[1], list):
print "no sender"
return
if isinstance(data[1], dict) and client_id != data[1].get('sender'):
print "client_id mismatch expected:", client_id, "actual:", data[1].get('sender')
return
message_type = data[0]
message_value = data[1]
message_type_id = MessageType.objects.get_or_create(name=message_type)[0].pk
TopologyHistory(topology_id=topology_id,
client_id=client_id,
message_type_id=message_type_id,
message_id=data[1].get('message_id', 0),
message_data=message['text']).save()
handler = self.get_handler(message_type)
if handler is not None:
handler(message_value, topology_id, client_id)
else:
print "Unsupported message ", message_type
def get_handler(self, message_type):
return getattr(self, "on{0}".format(message_type), None)
def onDeviceCreate(self, device, topology_id, client_id):
device = transform_dict(dict(x='x',
y='y',
name='name',
type='type',
id='id'), device)
d, _ = Device.objects.get_or_create(topology_id=topology_id, id=device['id'], defaults=device)
d.x = device['x']
d.y = device['y']
d.type = device['type']
d.save()
def onDeviceDestroy(self, device, topology_id, client_id):
Device.objects.filter(topology_id=topology_id, id=device['id']).delete()
def onDeviceMove(self, device, topology_id, client_id):
Device.objects.filter(topology_id=topology_id, id=device['id']).update(x=device['x'], y=device['y'])
def onDeviceLabelEdit(self, device, topology_id, client_id):
Device.objects.filter(topology_id=topology_id, id=device['id']).update(name=device['name'])
def onInterfaceLabelEdit(self, interface, topology_id, client_id):
(Interface.objects
.filter(device__topology_id=topology_id,
id=interface['id'],
device__id=interface['device_id'])
.update(name=interface['name']))
def onLinkLabelEdit(self, link, topology_id, client_id):
Link.objects.filter(from_device__topology_id=topology_id, id=link['id']).update(name=link['name'])
def onInterfaceCreate(self, interface, topology_id, client_id):
Interface.objects.get_or_create(device_id=Device.objects.get(id=interface['device_id'],
topology_id=topology_id).pk,
id=interface['id'],
defaults=dict(name=interface['name']))
def onLinkCreate(self, link, topology_id, client_id):
device_map = dict(Device.objects
.filter(topology_id=topology_id, id__in=[link['from_device_id'], link['to_device_id']])
.values_list('id', 'pk'))
Link.objects.get_or_create(id=link['id'],
name=link['name'],
from_device_id=device_map[link['from_device_id']],
to_device_id=device_map[link['to_device_id']],
from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']],
id=link['from_interface_id']).pk,
to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']],
id=link['to_interface_id']).pk)
def onLinkDestroy(self, link, topology_id, client_id):
device_map = dict(Device.objects
.filter(topology_id=topology_id, id__in=[link['from_device_id'], link['to_device_id']])
.values_list('id', 'pk'))
Link.objects.filter(id=link['id'],
from_device_id=device_map[link['from_device_id']],
to_device_id=device_map[link['to_device_id']],
from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']],
id=link['from_interface_id']).pk,
to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']],
id=link['to_interface_id']).pk).delete()
def onDeviceSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onDeviceUnSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onLinkSelected(self, message_value, topology_id, client_id):
'Ignore LinkSelected messages'
pass
def onLinkUnSelected(self, message_value, topology_id, client_id):
'Ignore LinkSelected messages'
pass
def onUndo(self, message_value, topology_id, client_id):
undo_persistence.handle(message_value['original_message'], topology_id, client_id)
def onRedo(self, message_value, topology_id, client_id):
redo_persistence.handle(message_value['original_message'], topology_id, client_id)
def onMultipleMessage(self, message_value, topology_id, client_id):
for message in message_value['messages']:
handler = self.get_handler(message['msg_type'])
if handler is not None:
handler(message, topology_id, client_id)
else:
print "Unsupported message ", message['msg_type']
def onDeploy(self, message_value, topology_id, client_id):
Group("workers").send({"text": json.dumps(["Deploy", topology_id, yaml_serialize_topology(topology_id)])})
def onDestroy(self, message_value, topology_id, client_id):
Group("workers").send({"text": json.dumps(["Destroy", topology_id])})
def onDiscover(self, message_value, topology_id, client_id):
Group("workers").send({"text": json.dumps(["Discover", topology_id, yaml_serialize_topology(topology_id)])})
def onLayout(self, message_value, topology_id, client_id):
# circular_layout(topology_id)
# grid_layout(topology_id)
tier_layout(topology_id)
def onCoverageRequest(self, coverage, topology_id, client_id):
pass
def onCoverage(self, coverage, topology_id, client_id):
with open("coverage/coverage{0}.json".format(int(time.time())), "w") as f:
f.write(json.dumps(coverage['coverage']))
def onStartRecording(self, recording, topology_id, client_id):
settings.RECORDING = True
def onStopRecording(self, recording, topology_id, client_id):
settings.RECORDING = False
def write_event(self, event, topology_id, client_id):
if settings.RECORDING and event.get('save', True):
with open("recording.log", "a") as f:
f.write(json.dumps(event))
f.write("\n")
onViewPort = write_event
onMouseEvent = write_event
onTouchEvent = write_event
onMouseWheelEvent = write_event
onKeyEvent = write_event
persistence = _Persistence()
class _UndoPersistence(object):
def handle(self, message, topology_id, client_id):
message_type = message[0]
message_value = message[1]
TopologyHistory.objects.filter(topology_id=topology_id,
client_id=message_value['sender'],
message_id=message_value['message_id']).update(undone=True)
handler = getattr(self, "on{0}".format(message_type), None)
if handler is not None:
handler(message_value, topology_id, client_id)
else:
print "Unsupported undo message ", message_type
def onSnapshot(self, snapshot, topology_id, client_id):
pass
def onDeviceCreate(self, device, topology_id, client_id):
persistence.onDeviceDestroy(device, topology_id, client_id)
def onDeviceDestroy(self, device, topology_id, client_id):
inverted = device.copy()
inverted['type'] = device['previous_type']
inverted['name'] = device['previous_name']
inverted['x'] = device['previous_x']
inverted['y'] = device['previous_y']
persistence.onDeviceCreate(inverted, topology_id, client_id)
def onDeviceMove(self, device, topology_id, client_id):
inverted = device.copy()
inverted['x'] = device['previous_x']
inverted['y'] = device['previous_y']
persistence.onDeviceMove(inverted, topology_id, client_id)
def onDeviceLabelEdit(self, device, topology_id, client_id):
inverted = device.copy()
inverted['name'] = device['previous_name']
persistence.onDeviceLabelEdit(inverted, topology_id, client_id)
def onLinkCreate(self, link, topology_id, client_id):
persistence.onLinkDestroy(link, topology_id, client_id)
def onLinkDestroy(self, link, topology_id, client_id):
persistence.onLinkCreate(link, topology_id, client_id)
def onDeviceSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onDeviceUnSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onUndo(self, message_value, topology_id, client_id):
pass
undo_persistence = _UndoPersistence()
class _RedoPersistence(object):
def handle(self, message, topology_id, client_id):
message_type = message[0]
message_value = message[1]
TopologyHistory.objects.filter(topology_id=topology_id,
client_id=message_value['sender'],
message_id=message_value['message_id']).update(undone=False)
handler_name = "on{0}".format(message_type)
handler = getattr(self, handler_name, getattr(persistence, handler_name, None))
if handler is not None:
handler(message_value, topology_id, client_id)
else:
print "Unsupported redo message ", message_type
def onDeviceSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onDeviceUnSelected(self, message_value, topology_id, client_id):
'Ignore DeviceSelected messages'
pass
def onUndo(self, message_value, topology_id, client_id):
'Ignore Undo messages'
pass
def onRedo(self, message_value, topology_id, client_id):
'Ignore Redo messages'
pass
redo_persistence = _RedoPersistence()
class _Discovery(object):
def handle(self, message):
topology_id = message.get('topology')
data = json.loads(message['text'])
message_type = data[0]
message_value = data[1]
handler = self.get_handler(message_type)
if handler is not None:
handler(message_value, topology_id)
else:
print "Unsupported message ", message_type
def get_handler(self, message_type):
return getattr(self, "on{0}".format(message_type), None)
def onFacts(self, message, topology_id):
send_updates = False
print message['key']
name = message['key']
device, created = Device.objects.get_or_create(topology_id=topology_id,
name=name,
defaults=dict(x=0,
y=0,
type="switch",
id=0))
if created:
device.id = device.pk
device.save()
send_updates = True
print "Created device ", device
interfaces = dpath.util.get(message, '/value/ansible_local/lldp/lldp') or []
for interface in interfaces:
pprint(interface)
for inner_interface in interface.get('interface', []):
name = inner_interface.get('name')
if not name:
continue
interface, created = Interface.objects.get_or_create(device_id=device.pk,
name=name,
defaults=dict(id=0))
if created:
interface.id = interface.pk
interface.save()
send_updates = True
print "Created interface ", interface
connected_interface = None
connected_device = None
for chassis in inner_interface.get('chassis', []):
name = chassis.get('name', [{}])[0].get('value')
if not name:
continue
connected_device, created = Device.objects.get_or_create(topology_id=topology_id,
name=name,
defaults=dict(x=0,
y=0,
type="switch",
id=0))
if created:
connected_device.id = connected_device.pk
connected_device.save()
send_updates = True
print "Created device ", connected_device
break
if connected_device:
for port in inner_interface.get('port', []):
for port_id in port.get('id', []):
if port_id['type'] == 'ifname':
name = port_id['value']
break
connected_interface, created = Interface.objects.get_or_create(device_id=connected_device.pk,
name=name,
defaults=dict(id=0))
if created:
connected_interface.id = connected_interface.pk
connected_interface.save()
print "Created interface ", connected_interface
send_updates = True
if connected_device and connected_interface:
exists = Link.objects.filter(Q(from_device_id=device.pk,
to_device_id=connected_device.pk,
from_interface_id=interface.pk,
to_interface_id=connected_interface.pk) |
Q(from_device_id=connected_device.pk,
to_device_id=device.pk,
from_interface_id=connected_interface.pk,
to_interface_id=interface.pk)).count() > 0
if not exists:
link = Link(from_device_id=device.pk,
to_device_id=connected_device.pk,
from_interface_id=interface.pk,
to_interface_id=connected_interface.pk,
id=0)
link.save()
link.id = link.pk
link.save()
print "Created link ", link
send_updates = True
if send_updates:
send_snapshot(Group("topology-%s" % topology_id), topology_id)
discovery = _Discovery()
# Ansible Connection Events
@channel_session
def ansible_connect(message):
data = urlparse.parse_qs(message.content['query_string'])
topology_id = parse_topology_id(data)
message.channel_session['topology_id'] = topology_id
@channel_session
def ansible_message(message):
# Channel('console_printer').send({"text": message['text']})
Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']})
Channel('discovery').send({"text": message['text'],
"topology": message.channel_session['topology_id']})
@channel_session
def ansible_disconnect(message):
pass
# UI Channel Events
@channel_session
def ws_connect(message):
# Accept connection
data = urlparse.parse_qs(message.content['query_string'])
topology_id = parse_topology_id(data)
topology, created = Topology.objects.get_or_create(
topology_id=topology_id, defaults=dict(name="topology", scale=1.0, panX=0, panY=0))
topology_id = topology.topology_id
message.channel_session['topology_id'] = topology_id
Group("topology-%s" % topology_id).add(message.reply_channel)
client = Client()
client.save()
message.channel_session['client_id'] = client.pk
message.reply_channel.send({"text": json.dumps(["id", client.pk])})
message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])})
topology_data = transform_dict(dict(topology_id='topology_id',
name='name',
panX='panX',
panY='panY',
scale='scale'), topology.__dict__)
message.reply_channel.send({"text": json.dumps(["Topology", topology_data])})
send_snapshot(message.reply_channel, topology_id)
send_history(message.reply_channel, topology_id)
def send_snapshot(channel, topology_id):
interfaces = defaultdict(list)
for i in (Interface.objects
.filter(device__topology_id=topology_id)
.values()):
interfaces[i['device_id']].append(i)
devices = list(Device.objects
.filter(topology_id=topology_id).values())
for device in devices:
device['interfaces'] = interfaces[device['device_id']]
links = [dict(id=x['id'],
name=x['name'],
from_device_id=x['from_device__id'],
to_device_id=x['to_device__id'],
from_interface_id=x['from_interface__id'],
to_interface_id=x['to_interface__id'])
for x in list(Link.objects
.filter(Q(from_device__topology_id=topology_id) |
Q(to_device__topology_id=topology_id))
.values('id',
'name',
'from_device__id',
'to_device__id',
'from_interface__id',
'to_interface__id'))]
snapshot = dict(sender=0,
devices=devices,
links=links)
channel.send({"text": json.dumps(["Snapshot", snapshot])})
def send_history(channel, topology_id):
history = list(TopologyHistory.objects
.filter(topology_id=topology_id)
.exclude(message_type__name__in=HISTORY_MESSAGE_IGNORE_TYPES)
.exclude(undone=True)
.order_by('pk')
.values_list('message_data', flat=True)[:1000])
channel.send({"text": json.dumps(["History", history])})
@channel_session
def ws_message(message):
# Send to debug printer
# Channel('console_printer').send({"text": message['text']})
# Send to all clients editing the topology
Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']})
# Send to persistence handler
Channel('persistence').send({"text": message['text'],
"topology": message.channel_session['topology_id'],
"client": message.channel_session['client_id']})
@channel_session
def ws_disconnect(message):
Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel)
def console_printer(message):
print message['text'] # pragma: no cover
# Worker channel events
@channel_session
def worker_connect(message):
Group("workers").add(message.reply_channel)
@channel_session
def worker_message(message):
# Channel('console_printer').send({"text": message['text']})
pass
@channel_session
def worker_disconnect(message):
pass
# Tester channel events
@channel_session
def tester_connect(message):
data = urlparse.parse_qs(message.content['query_string'])
topology_id = parse_topology_id(data)
message.channel_session['topology_id'] = topology_id
client = Client()
client.save()
message.channel_session['client_id'] = client.pk
message.reply_channel.send({"text": json.dumps(["id", client.pk])})
message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])})
@channel_session
def tester_message(message):
# Channel('console_printer').send({"text": message['text']})
Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']})
Channel('persistence').send({"text": message['text'],
"topology": message.channel_session['topology_id'],
"client": message.channel_session['client_id']})
@channel_session
def tester_disconnect(message):
pass
+142
View File
@@ -0,0 +1,142 @@
app: prototype
external_models: []
models:
- display: name
fields:
- name: device_id
pk: true
type: AutoField
- name: topology
ref: Topology
ref_field: topology_id
type: ForeignKey
- len: 200
name: name
type: CharField
- name: x
type: IntegerField
- name: y
type: IntegerField
- name: id
type: IntegerField
- len: 200
name: type
type: CharField
name: Device
x: 348
y: 124
- fields:
- name: link_id
pk: true
type: AutoField
- name: from_device
ref: Device
ref_field: device_id
related_name: from_link
type: ForeignKey
- name: to_device
ref: Device
ref_field: device_id
related_name: to_link
type: ForeignKey
- name: from_interface
ref: Interface
ref_field: interface_id
related_name: from_link
type: ForeignKey
- name: to_interface
ref: Interface
ref_field: interface_id
related_name: to_link
type: ForeignKey
- name: id
type: IntegerField
- len: 200
name: name
type: CharField
name: Link
x: 837
y: 10
- display: name
fields:
- name: topology_id
pk: true
type: AutoField
- len: 200
name: name
type: CharField
- name: scale
type: FloatField
- name: panX
type: FloatField
- name: panY
type: FloatField
name: Topology
x: 111
y: 127
- fields:
- name: client_id
pk: true
type: AutoField
name: Client
x: -455
y: 109
- fields:
- name: topology_history_id
pk: true
type: AutoField
- name: topology
ref: Topology
ref_field: topology_id
type: ForeignKey
- name: client
ref: Client
ref_field: client_id
type: ForeignKey
- name: message_type
ref: MessageType
ref_field: message_type_id
type: ForeignKey
- name: message_id
type: IntegerField
- name: message_data
type: TextField
- default: false
name: undone
type: BooleanField
name: TopologyHistory
x: -205
y: 282
- display: name
fields:
- name: message_type_id
pk: true
type: AutoField
- len: 200
name: name
type: CharField
name: MessageType
x: -509
y: 383
- display: name
fields:
- name: interface_id
pk: true
type: AutoField
- name: device
ref: Device
ref_field: device_id
type: ForeignKey
- len: 200
name: name
type: CharField
- name: id
type: IntegerField
name: Interface
x: 600
y: 243
modules: []
view:
panX: 213.72955551921206
panY: 189.44695909464298
scaleXY: 0.6900000000000002
@@ -0,0 +1,147 @@
from django.core.management.base import BaseCommand
from awx.network_ui.models import Topology, Device, Link, Interface
from collections import defaultdict
def natural_numbers():
i = 1
while True:
yield i
i += 1
class Command(BaseCommand):
help = '''Creates a 2 tier clos topology with n nodes in the 1st tier and m nodes
in the 2nd tier and h hosts per pair of switches'''
def add_arguments(self, parser):
parser.add_argument('n', type=int)
parser.add_argument('m', type=int)
parser.add_argument('h', type=int)
def handle(self, *args, **options):
n = options['n']
m = options['m']
h = options['h']
print "n", n
print "m", m
topology = Topology(name="test_{0}".format(n), scale=1.0, panX=0, panY=0)
topology.save()
devices = []
hosts_per_leaf = []
leaves = []
spines = []
id_seq = natural_numbers()
tier2 = 100
tier1 = 500
tier0 = 900
spacing = 200
tier2_centering = ((n - m) * 200) / 2
for i in xrange(n):
device = Device(name="Leaf{0}".format(i),
x=i * spacing,
y=tier1,
id=next(id_seq),
type="switch",
topology_id=topology.pk)
devices.append(device)
leaves.append(device)
for i in xrange(m):
device = Device(name="Spine{0}".format(i),
x=(i * spacing) + tier2_centering,
y=tier2,
id=next(id_seq),
type="switch",
topology_id=topology.pk)
devices.append(device)
spines.append(device)
for i in xrange(n / 2):
hosts = []
for j in xrange(h):
device = Device(name="Host{0}-{1}".format(i, j),
x=(i * 2 * spacing) + spacing / 2,
y=tier0 + (j * 40),
id=next(id_seq),
type="host",
topology_id=topology.pk)
devices.append(device)
hosts.append(device)
hosts_per_leaf.append(hosts)
print "leaves", leaves
print "spines", spines
print "hosts_per_leaf", hosts_per_leaf
Device.objects.bulk_create(devices)
devices = {x.id: x for x in Device.objects.filter(topology_id=topology.pk)}
links = []
interfaces = defaultdict(list)
for leaf in leaves:
for spine in spines:
from_interface = Interface(device=devices[leaf.id],
name="swp" + str(len(interfaces[leaf.id]) + 1),
id=(len(interfaces[leaf.id]) + 1))
from_interface.save()
interfaces[leaf.id].append(from_interface)
to_interface = Interface(device=devices[spine.id],
name="swp" + str(len(interfaces[spine.id]) + 1),
id=(len(interfaces[spine.id]) + 1))
to_interface.save()
interfaces[spine.id].append(to_interface)
link = Link(from_device=devices[leaf.id],
to_device=devices[spine.id],
from_interface=from_interface,
to_interface=to_interface)
links.append(link)
for i, hosts in enumerate(hosts_per_leaf):
leaf1 = leaves[2 * i]
leaf2 = leaves[2 * i + 1]
for j, host in enumerate(hosts):
from_interface = Interface(device=devices[leaf1.id],
name="swp" + str(len(interfaces[leaf1.id]) + 1),
id=(len(interfaces[leaf1.id]) + 1))
from_interface.save()
interfaces[leaf1.id].append(from_interface)
to_interface = Interface(device=devices[host.id],
name="eth" + str(len(interfaces[host.id]) + 1),
id=(len(interfaces[host.id]) + 1))
to_interface.save()
interfaces[host.id].append(to_interface)
link = Link(from_device=devices[leaf1.id],
to_device=devices[host.id],
from_interface=from_interface,
to_interface=to_interface)
links.append(link)
from_interface = Interface(device=devices[leaf2.id],
name="swp" + str(len(interfaces[leaf2.id]) + 1),
id=(len(interfaces[leaf2.id]) + 1))
from_interface.save()
interfaces[leaf2.id].append(from_interface)
to_interface = Interface(device=devices[host.id],
name="eth" + str(len(interfaces[host.id]) + 1),
id=(len(interfaces[host.id]) + 1))
to_interface.save()
interfaces[host.id].append(to_interface)
link = Link(from_device=devices[leaf2.id],
to_device=devices[host.id],
from_interface=from_interface,
to_interface=to_interface)
links.append(link)
Link.objects.bulk_create(links)
print "Topology: ", topology.pk
@@ -0,0 +1,54 @@
from django.core.management.base import BaseCommand
from awx.network_ui.models import Topology, Device, Link
import math
class Command(BaseCommand):
help = 'Creates a fully connected topology with n nodes'
def add_arguments(self, parser):
parser.add_argument('n', type=int)
def handle(self, *args, **options):
n = options['n']
topology = Topology(name="test_{0}".format(n), scale=1.0, panX=0, panY=0)
topology.save()
devices = []
r = 1000
if n > 0:
arc_radians = 2 * math.pi / n
else:
arc_radians = 2 * math.pi
for i in xrange(n):
device = Device(name="R{0}".format(i),
x=math.cos(arc_radians*i)*r,
y=math.sin(arc_radians*i)*r,
id=i,
type="router",
topology_id=topology.pk)
devices.append(device)
Device.objects.bulk_create(devices)
devices = {x.id: x for x in Device.objects.filter(topology_id=topology.pk)}
links = []
for i in xrange(n):
for j in xrange(i):
if i == j:
continue
link = Link(from_device=devices[i],
to_device=devices[j])
links.append(link)
Link.objects.bulk_create(links)
print "Topology: ", topology.pk
@@ -0,0 +1,16 @@
from django.core.management.base import BaseCommand
from awx.network_ui.serializers import yaml_serialize_topology
class Command(BaseCommand):
help = 'Dumps data of a topology to a yaml file'
def add_arguments(self, parser):
parser.add_argument('topology_id', type=int)
def handle(self, *args, **options):
topology_id = options['topology_id']
print yaml_serialize_topology(topology_id)
@@ -0,0 +1,23 @@
from django.core.management.base import BaseCommand
from django.db.models import Count
from awx.network_ui.models import Device
from pprint import pprint
class Command(BaseCommand):
def handle(self, *args, **options):
dups = list(Device.objects
.values('topology_id', 'id')
.annotate(Count('pk'))
.order_by()
.filter(pk__count__gt=1))
pprint(dups)
for dup in dups:
del dup['pk__count']
pprint(list(Device.objects
.filter(**dup)
.values()))
@@ -0,0 +1,52 @@
from django.core.management.base import BaseCommand
from websocket import create_connection
from ui_test import MessageHandler, _Time
from awx.network_ui.models import Device, TopologyHistory
import json
time = _Time()
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('topology_id', type=int)
parser.add_argument('recording')
parser.add_argument('--time-scale', dest="time_scale", default=1.0, type=float)
parser.add_argument('--delete-topology-at-start', dest="delete_topolgy", action="store_true", default=False)
def handle(self, *args, **options):
print options['topology_id']
print options['recording']
topology_id = options['topology_id']
if options['delete_topolgy'] is True:
TopologyHistory.objects.filter(topology_id=topology_id).delete()
Device.objects.filter(topology_id=topology_id).delete()
time.scale = options.get('time_scale', 1.0)
ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id={0}".format(options['topology_id'])))
ui.recv()
ui.recv()
ui.send('StopRecording')
ui.send('StartReplay')
if options['delete_topolgy'] is True:
ui.send_message(['History', []])
ui.send('Snapshot', sender=ui.client_id, devices=[], links=[])
messages = []
with open(options['recording']) as f:
for line in f.readlines():
messages.append(json.loads(line))
messages = sorted(messages, key=lambda x: x['message_id'])
for message in messages:
message['sender'] = ui.client_id
message['save'] = False
ui.send_message([message['msg_type'], message])
if message['msg_type'] == "ViewPort":
time.sleep(10)
else:
time.sleep(1)
ui.send('StopReplay')
ui.send('CoverageRequest')
ui.close()
@@ -0,0 +1,537 @@
from django.core.management.base import BaseCommand
import unittest
from websocket import create_connection
import json
import yaml
import time as real_time
import requests
class _Time(object):
def __init__(self, scale=1.0):
self.scale = scale
def sleep(self, n):
real_time.sleep(n * self.scale)
time = _Time()
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--time-scale', dest="time_scale", default=1.0, type=float)
parser.add_argument('--verbose', dest="verbose", action="store_true", default=False)
parser.add_argument('-q', '--quiet', dest="quiet", action="store_true", default=False)
parser.add_argument('-f', '--failfast', dest="failfast", action="store_true", default=False)
parser.add_argument('-b', '--buffer', dest="buffer", action="store_true", default=False)
parser.add_argument('suites', nargs="*")
def handle(self, *args, **options):
time.scale = options.get('time_scale', 1.0)
loader = unittest.TestLoader()
test_suites = [TestUI,
TestUIWebSocket,
TestUndoPersistence,
TestRedoPersistence,
TestPersistence,
TestViews,
TestWorkerWebSocket,
TestAnsibleWebSocket,
TestInvalidValues]
if options.get('suites'):
test_suites = [x for x in test_suites if x.__name__ in options['suites']]
tests = [loader.loadTestsFromTestCase(x) for x in test_suites]
unittest.TextTestRunner(failfast=options.get('failfast'),
verbosity=0 if options.get('quiet') else 2 if options.get('verbose') else 1,
buffer=options.get('buffer')).run(unittest.TestSuite(tests))
ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
ui.recv()
ui.recv()
ui.send('CoverageRequest')
ui.close()
class TestViews(unittest.TestCase):
def test_index(self):
requests.get("http://localhost:8001/network_ui")
class MessageHandler(object):
def __init__(self, ws):
self.ws = ws
self.client_id = None
self.topology_id = None
self.receieved_messages = []
self.message_id = 0
def handle_message(self, message):
message = json.loads(message)
self.receieved_messages.append(message)
if message[0] == "id":
self.client_id = message[1]
if message[0] == "topology_id":
self.topology_id = message[1]
def make_message(self, msg_type, **kwargs):
kwargs['sender'] = self.client_id
kwargs['msg_type'] = msg_type
kwargs['message_id'] = self.message_id
self.message_id += 1
return [msg_type, kwargs]
def send(self, msg_type, **kwargs):
self.ws.send(json.dumps(self.make_message(msg_type, **kwargs)))
def send_message(self, message):
self.ws.send(json.dumps(message))
def send_multiple(self, messages):
self.ws.send(json.dumps(['MultipleMessage', dict(sender=self.client_id, messages=messages)]))
def recv(self):
msg = self.ws.recv()
self.handle_message(msg)
return msg
def close(self):
self.ws.close()
class TestWorkerWebSocket(unittest.TestCase):
def test(self):
self.worker = MessageHandler(create_connection("ws://localhost:8001/network_ui/worker?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ui.recv()
self.ui.recv()
self.ui.send("Deploy")
self.assertTrue(self.worker.recv())
self.ui.send("Destroy")
self.assertTrue(self.worker.recv())
self.worker.send("Hi")
def tearDown(self):
self.worker.close()
self.ui.close()
class TestAnsibleWebSocket(unittest.TestCase):
def test(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/ansible?topology_id=143"))
self.ws.send('Facts', foo=5)
def tearDown(self):
self.ws.close()
class TestPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
def tearDown(self):
self.ws.close()
def test_DeviceCreate(self):
self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_DeviceLabelEdit(self):
self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
time.sleep(1)
self.ws.send('DeviceLabelEdit', name="Foo", previous_name="TestSwitch", id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_DeviceMove(self):
#self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send_multiple([
dict(msg_type='DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100),
dict(msg_type='DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=100),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=101),
dict(msg_type='LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)])
time.sleep(1)
for i in xrange(1, 1000):
time.sleep(0.01)
self.ws.send('DeviceMove', x=i, y=500, previous_x=i - 1, previous_y=500, id=100)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_LinkEdit_InterfaceEdit_LinkDestroy(self):
self.ws.send_multiple([
dict(msg_type='DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100),
dict(msg_type='DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=100),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=101),
dict(msg_type='LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)])
time.sleep(1)
self.ws.send('InterfaceLabelEdit', id=1, device_id=100, name="swp2", previous_name="swp1")
time.sleep(1)
self.ws.send('LinkLabelEdit', id=100, name="B to A", previous_name="A to B")
time.sleep(1)
self.ws.send('LinkDestroy', id=100, from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=101)
class TestUndoPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
def test_unsupported(self):
self.ws.send("Undo", original_message=['NotSupported', dict(sender=0, message_id=-1)])
def test_undo(self):
self.ws.send("Undo", original_message=['Undo', dict(sender=0, message_id=-1)])
def test_redo(self):
self.ws.send("Undo", original_message=['Redo', dict(sender=0, message_id=-1)])
def test_DeviceCreate(self):
msg = self.ws.make_message('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send_message(msg)
self.ws.send("Undo", original_message=msg)
def test_DeviceDestroy(self):
msg = self.ws.make_message('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send_message(msg)
msg = self.ws.make_message('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send_message(msg)
self.ws.send("Undo", original_message=msg)
def test_DeviceMove(self):
msg = self.ws.make_message('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send_message(msg)
msg = self.ws.make_message('DeviceMove', x=100, y=100, previous_x=0, previous_y=500, id=100)
self.ws.send_message(msg)
self.ws.send("Undo", original_message=msg)
def test_DeviceLabelEdit(self):
msg = self.ws.make_message('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send_message(msg)
msg = self.ws.make_message('DeviceLabelEdit', name="Foo", previous_name="TestSwitch", id=100)
self.ws.send_message(msg)
self.ws.send("Undo", original_message=msg)
def test_DeviceSelected_DeviceUnSelected(self):
self.ws.send("Undo", original_message=['DeviceSelected', dict(sender=0, message_id=-1)])
self.ws.send("Undo", original_message=['DeviceUnSelected', dict(sender=0, message_id=-1)])
def test_Snapshot(self):
self.ws.send("Undo", original_message=['Snapshot', dict(sender=0, message_id=-1)])
def tearDown(self):
self.ws.close()
def test_LinkEdit_InterfaceEdit_LinkDestroy(self):
self.ws.send_multiple([
dict(msg_type='DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100),
dict(msg_type='DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=100),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=101)])
time.sleep(1)
msg = self.ws.make_message('LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)
self.ws.send_message(msg)
self.ws.send('Undo', original_message=msg)
time.sleep(1)
self.ws.send('Redo', original_message=msg)
time.sleep(1)
msg = self.ws.make_message('LinkDestroy', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)
time.sleep(1)
self.ws.send_message(msg)
self.ws.send('Undo', original_message=msg)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=101)
class TestRedoPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
def test_unsupported(self):
self.ws.send("Redo", original_message=['NotSupported', dict(sender=0, message_id=-1)])
def test_undo(self):
self.ws.send("Redo", original_message=['Undo', dict(sender=0, message_id=-1)])
def test_redo(self):
self.ws.send("Redo", original_message=['Redo', dict(sender=0, message_id=-1)])
def test_DeviceSelected_DeviceUnSelected(self):
self.ws.send("Redo", original_message=['DeviceSelected', dict(sender=0, message_id=-1)])
self.ws.send("Redo", original_message=['DeviceUnSelected', dict(sender=0, message_id=-1)])
def test_Snapshot(self):
self.ws.send("Redo", original_message=['Snapshot', dict(sender=0, message_id=-1)])
def tearDown(self):
self.ws.close()
class TestUIWebSocket(unittest.TestCase):
def test(self):
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
self.ui.recv()
self.ui.recv()
self.ui.send("Hello")
def tearDown(self):
self.ui.close()
class TestUI(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
self.ws.recv()
self.ws.recv()
self.ui.recv()
self.ui.recv()
def tearDown(self):
self.ui.close()
self.ws.close()
def test_DeviceStatus(self):
self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send('DeviceMove', x=100, y=100, previous_x=0, previous_y=500, id=100)
self.ws.send('DeviceStatus', name="TestSwitch", working=True, status=None)
time.sleep(1)
self.ws.send('DeviceStatus', name="TestSwitch", working=False, status="pass")
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_TaskStatus(self):
self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send('DeviceMove', x=100, y=100, previous_x=0, previous_y=500, id=100)
self.ws.send('TaskStatus', device_name="TestSwitch", task_id="1", working=True, status=None)
time.sleep(1)
self.ws.send('TaskStatus', device_name="TestSwitch", task_id="1", working=False, status="pass")
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_DeviceSelect(self):
self.ws.send('DeviceCreate', name="TestSwitch", x=0, y=500, type="switch", id=100)
self.ws.send('DeviceMove', x=100, y=100, previous_x=0, previous_y=500, id=100)
self.ws.send('DeviceSelected', id=100)
time.sleep(1)
self.ws.send('DeviceUnSelected', id=100)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
def test_LinkSelect(self):
self.ws.send('DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100)
self.ws.send('DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101)
self.ws.send('InterfaceCreate', name="swp1", id=1, device_id=100)
self.ws.send('InterfaceCreate', name="swp1", id=1, device_id=101)
time.sleep(1)
self.ws.send('LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)
self.ws.send('LinkSelected', id=100)
time.sleep(1)
self.ws.send('LinkUnSelected', id=100)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=101)
def test_LinkSelect2(self):
self.ws.send_multiple([
dict(msg_type='DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100),
dict(msg_type='DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=100),
dict(msg_type='InterfaceCreate', name="swp1", id=1, device_id=101),
dict(msg_type='LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)])
self.ws.send('LinkSelected', id=100)
time.sleep(1)
self.ws.send('LinkUnSelected', id=100)
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=101)
def test_Facts(self):
self.ws.send('DeviceCreate', name="TestSwitchA", x=100, y=100, type="switch", id=100)
self.ws.send('DeviceCreate', name="TestSwitchB", x=900, y=100, type="switch", id=101)
self.ws.send('InterfaceCreate', name="swp1", id=1, device_id=100)
self.ws.send('InterfaceCreate', name="swp1", id=1, device_id=101)
time.sleep(1)
self.ws.send('LinkCreate', id=100, name="A to B", from_device_id=100, to_device_id=101, from_interface_id=1, to_interface_id=1)
time.sleep(1)
self.ws.send('Facts', key="TestSwitchA", value=dict(ansible_local=dict(ptm={'port': "swp1", 'cbl status': 'fail'})))
time.sleep(1)
self.ws.send('Facts', key="TestSwitchA", value=dict(ansible_local=dict(ptm={'port': "swp1", 'cbl status': 'pass'})))
time.sleep(1)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=100)
self.ws.send('DeviceDestroy',
previous_name="TestSwitch",
previous_x=0,
previous_y=500,
previous_type="switch",
id=101)
def test_Snapshot(self):
self.ws.send('Snapshot', **yaml.load('''
devices:
- id: 116
interfaces:
- id: 1
name: swp1
network: 186105
remote_device_name: Switch2
remote_interface_name: swp1
name: Switch1
type: switch
x: -1969
y: -320
- id: 117
interfaces:
- id: 1
name: swp1
network: 186105
remote_device_name: Switch1
remote_interface_name: swp1
name: Switch2
type: switch
x: -1711
y: -323
links:
- from_device: Switch2
from_device_id: 117
from_interface: swp1
from_interface_id: 1
network: 186105
to_device: Switch1
to_device_id: 116
to_interface: swp1
to_interface_id: 1
name: topology
topology_id: 143
'''))
time.sleep(1)
class TestInvalidValues(unittest.TestCase):
def test_bad_topology_id1(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=0"))
self.ws.close()
def test_bad_topology_id2(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=foo"))
self.ws.close()
def test_bad_sender(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws.ws.send(json.dumps(['DeviceCreate', dict(sender=-1, name="TestSwitchA", x=100, y=100, type="switch", id=100)]))
self.ws.ws.send(json.dumps(['DeviceDestroy', dict(sender=-1, previous_name="TestSwitchA",
previous_x=100, previous_y=100, previous_type="switch", id=100)]))
self.ws.close()
def test_unsupported_command(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
self.ws.send("NotSupported")
self.ws.send_multiple([dict(msg_type="NotSupported")])
self.ws.close()
+47
View File
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='Device',
fields=[
('device_id', models.AutoField(serialize=False, primary_key=True)),
('name', models.CharField(max_length=200)),
('x', models.IntegerField()),
('y', models.IntegerField()),
('id', models.IntegerField()),
],
),
migrations.CreateModel(
name='Link',
fields=[
('link_id', models.AutoField(serialize=False, primary_key=True)),
('from_device', models.ForeignKey(related_name='+', to='network_ui.Device')),
('to_device', models.ForeignKey(related_name='+', to='network_ui.Device')),
],
),
migrations.CreateModel(
name='Topology',
fields=[
('topology_id', models.AutoField(serialize=False, primary_key=True)),
('name', models.CharField(max_length=200)),
('id', models.IntegerField()),
('scale', models.FloatField()),
('panX', models.FloatField()),
('panY', models.FloatField()),
],
),
migrations.AddField(
model_name='device',
name='topology',
field=models.ForeignKey(to='network_ui.Topology'),
),
]
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='topology',
name='id',
),
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0002_remove_topology_id'),
]
operations = [
migrations.AddField(
model_name='device',
name='type',
field=models.CharField(default='', max_length=200),
preserve_default=False,
),
]
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0003_device_type'),
]
operations = [
migrations.CreateModel(
name='Client',
fields=[
('client_id', models.AutoField(serialize=False, primary_key=True)),
],
),
migrations.CreateModel(
name='MessageType',
fields=[
('message_type_id', models.AutoField(serialize=False, primary_key=True)),
('name', models.CharField(max_length=200)),
],
),
migrations.CreateModel(
name='TopologyHistory',
fields=[
('topology_history_id', models.AutoField(serialize=False, primary_key=True)),
('message_id', models.IntegerField()),
('message_data', models.TextField()),
('client', models.ForeignKey(to='network_ui.Client')),
('message_type', models.ForeignKey(to='network_ui.MessageType')),
('topology', models.ForeignKey(to='network_ui.Topology')),
],
),
]
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0004_client_messagetype_topologyhistory'),
]
operations = [
migrations.AddField(
model_name='topologyhistory',
name='undone',
field=models.BooleanField(default=b'False'),
),
]
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0005_topologyhistory_undone'),
]
operations = [
migrations.AlterField(
model_name='topologyhistory',
name='undone',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def forwards_func(apps, schema_editor):
Topology = apps.get_model("network_ui", "Topology")
Topology.objects.get_or_create(name="Unknown", topology_id=-1, panX=0, panY=0, scale=1.0)
Device = apps.get_model("network_ui", "Device")
Device.objects.get_or_create(name="Unknown", device_id=-1, x=0, y=0, type="unknown", id=-1, topology_id=-1)
Interface = apps.get_model("network_ui", "Interface")
Interface.objects.get_or_create(name="Unknown", device_id=-1, interface_id=-1)
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0006_auto_20170321_1236'),
]
operations = [
migrations.CreateModel(
name='Interface',
fields=[
('interface_id', models.AutoField(serialize=False, primary_key=True)),
('name', models.CharField(max_length=200)),
('device', models.ForeignKey(to='network_ui.Device')),
],
),
migrations.RunPython(forwards_func),
migrations.AddField(
model_name='link',
name='from_interface',
field=models.ForeignKey(related_name='+', default=-1, to='network_ui.Interface'),
preserve_default=False,
),
migrations.AddField(
model_name='link',
name='to_interface',
field=models.ForeignKey(related_name='+', default=-1, to='network_ui.Interface'),
preserve_default=False,
),
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0007_auto_20170328_1655'),
]
operations = [
migrations.AddField(
model_name='interface',
name='id',
field=models.IntegerField(default=0),
preserve_default=False,
),
]
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0008_interface_id'),
]
operations = [
migrations.AlterField(
model_name='link',
name='from_device',
field=models.ForeignKey(related_name='from_link', to='network_ui.Device'),
),
migrations.AlterField(
model_name='link',
name='from_interface',
field=models.ForeignKey(related_name='from_link', to='network_ui.Interface'),
),
migrations.AlterField(
model_name='link',
name='to_device',
field=models.ForeignKey(related_name='to_link', to='network_ui.Device'),
),
migrations.AlterField(
model_name='link',
name='to_interface',
field=models.ForeignKey(related_name='to_link', to='network_ui.Interface'),
),
]
+20
View File
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0009_auto_20170403_1912'),
]
operations = [
migrations.AddField(
model_name='link',
name='id',
field=models.IntegerField(default=0),
preserve_default=False,
),
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0010_link_id'),
]
operations = [
migrations.AddField(
model_name='link',
name='name',
field=models.CharField(default='', max_length=200),
preserve_default=False,
),
]
+74
View File
@@ -0,0 +1,74 @@
from django.db import models
class Device(models.Model):
device_id = models.AutoField(primary_key=True,)
topology = models.ForeignKey('Topology',)
name = models.CharField(max_length=200, )
x = models.IntegerField()
y = models.IntegerField()
id = models.IntegerField()
type = models.CharField(max_length=200, )
def __unicode__(self):
return self.name
class Link(models.Model):
link_id = models.AutoField(primary_key=True,)
from_device = models.ForeignKey('Device', related_name='from_link', )
to_device = models.ForeignKey('Device', related_name='to_link', )
from_interface = models.ForeignKey('Interface', related_name='from_link', )
to_interface = models.ForeignKey('Interface', related_name='to_link', )
id = models.IntegerField()
name = models.CharField(max_length=200, )
class Topology(models.Model):
topology_id = models.AutoField(primary_key=True,)
name = models.CharField(max_length=200, )
scale = models.FloatField()
panX = models.FloatField()
panY = models.FloatField()
def __unicode__(self):
return self.name
class Client(models.Model):
client_id = models.AutoField(primary_key=True,)
class TopologyHistory(models.Model):
topology_history_id = models.AutoField(primary_key=True,)
topology = models.ForeignKey('Topology',)
client = models.ForeignKey('Client',)
message_type = models.ForeignKey('MessageType',)
message_id = models.IntegerField()
message_data = models.TextField()
undone = models.BooleanField(default=False)
class MessageType(models.Model):
message_type_id = models.AutoField(primary_key=True,)
name = models.CharField(max_length=200, )
def __unicode__(self):
return self.name
class Interface(models.Model):
interface_id = models.AutoField(primary_key=True,)
device = models.ForeignKey('Device',)
name = models.CharField(max_length=200, )
id = models.IntegerField()
def __unicode__(self):
return self.name
+23
View File
@@ -0,0 +1,23 @@
from channels.routing import route
from awx.network_ui.consumers import ws_connect, ws_message, ws_disconnect, console_printer, persistence, discovery
from awx.network_ui.consumers import ansible_connect, ansible_message, ansible_disconnect
from awx.network_ui.consumers import worker_connect, worker_message, worker_disconnect
from awx.network_ui.consumers import tester_connect, tester_message, tester_disconnect
channel_routing = [
route("websocket.connect", ws_connect, path=r"^/network_ui/topology"),
route("websocket.receive", ws_message, path=r"^/network_ui/topology"),
route("websocket.disconnect", ws_disconnect, path=r"^/network_ui/topology"),
route("websocket.connect", ansible_connect, path=r"^/network_ui/ansible"),
route("websocket.receive", ansible_message, path=r"^/network_ui/ansible"),
route("websocket.disconnect", ansible_disconnect, path=r"^/network_ui/ansible"),
route("websocket.connect", worker_connect, path=r"^/network_ui/worker"),
route("websocket.receive", worker_message, path=r"^/network_ui/worker"),
route("websocket.disconnect", worker_disconnect, path=r"^/network_ui/worker"),
route("websocket.connect", tester_connect, path=r"^/network_ui/tester"),
route("websocket.receive", tester_message, path=r"^/network_ui/tester"),
route("websocket.disconnect", tester_disconnect, path=r"^/network_ui/tester"),
route("console_printer", console_printer),
route("persistence", persistence.handle),
route("discovery", discovery.handle),
]
+58
View File
@@ -0,0 +1,58 @@
from awx.network_ui.models import Topology, Device, Link, Interface
from django.db.models import Q
import yaml
NetworkAnnotatedInterface = Interface.objects.values('name',
'id',
'from_link__pk',
'to_link__pk',
'from_link__to_device__name',
'to_link__from_device__name',
'from_link__to_interface__name',
'to_link__from_interface__name')
def yaml_serialize_topology(topology_id):
data = dict(devices=[],
links=[])
topology = Topology.objects.get(pk=topology_id)
data['name'] = topology.name
data['topology_id'] = topology_id
links = list(Link.objects
.filter(Q(from_device__topology_id=topology_id) |
Q(to_device__topology_id=topology_id)))
interfaces = Interface.objects.filter(device__topology_id=topology_id)
for device in Device.objects.filter(topology_id=topology_id).order_by('name'):
interfaces = list(NetworkAnnotatedInterface.filter(device_id=device.pk).order_by('name'))
interfaces = [dict(name=x['name'],
network=x['from_link__pk'] or x['to_link__pk'],
remote_device_name=x['from_link__to_device__name'] or x['to_link__from_device__name'],
remote_interface_name=x['from_link__to_interface__name'] or x['to_link__from_interface__name'],
id=x['id'],
) for x in interfaces]
data['devices'].append(dict(name=device.name,
type=device.type,
x=device.x,
y=device.y,
id=device.id,
interfaces=interfaces))
for link in links:
data['links'].append(dict(from_device=link.from_device.name,
to_device=link.to_device.name,
from_interface=link.from_interface.name,
to_interface=link.to_interface.name,
from_device_id=link.from_device.id,
to_device_id=link.to_device.id,
from_interface_id=link.from_interface.id,
to_interface_id=link.to_interface.id,
network=link.pk))
return yaml.safe_dump(data, default_flow_style=False)
@@ -0,0 +1,8 @@
/bundle.js
/node_modules
/style.css
/extract.js
/css
/js
/src-instrumented
/index-instrumented.html
@@ -0,0 +1,47 @@
{
"browser": true,
"node": true,
"jquery": true,
"esnext": true,
"globalstrict": true,
"curly": true,
"immed": true,
"latedef": "nofunc",
"noarg": true,
"nonew": true,
"maxerr": 10000,
"notypeof": true,
"globals": {
"$ENV": true,
"require": true,
"global": true,
"beforeEach": false,
"inject": false,
"module": false,
"angular":false,
"alert":false,
"$AnsibleConfig":true,
"$basePath":true,
"jsyaml":false,
"_":false,
"d3":false,
"Donut3D":false,
"nv":false,
"it": false,
"xit": false,
"expect": false,
"context": false,
"describe": false,
"moment": false,
"spyOn": false,
"jasmine": false
},
"strict": false,
"quotmark": false,
"trailing": true,
"undef": true,
"unused": true,
"eqeqeq": true,
"indent": 4,
"newcap": false
}
+25
View File
@@ -0,0 +1,25 @@
.PHONY: all main lint lessc
all: clean lessc lint main istanbul
clean:
rm -rf src-instrumented
rm -f js/bundle.js
rm -f css/style.css
main:
webpack src/main.js js/bundle.js
cp vendor/*.js js/
lint:
jshint --verbose src/*js
lessc:
lessc src/style.less css/style.css
istanbul:
istanbul instrument --output src-instrumented src
webpack src-instrumented/main.js js/bundle-instrumented.js
cp index.html index-instrumented.html
sed -i "" "s/bundle.js/bundle-instrumented.js/g" index-instrumented.html
cp vendor/*.js js/
@@ -0,0 +1 @@
Build directory for css files.
@@ -0,0 +1,34 @@
app: button
panX: 53
panY: -52
scaleXY: 1
states:
- label: Start
size: 100
x: 468
y: 170
- label: Ready
size: 100
x: 471
y: 376
- label: Pressed
size: 100
x: 606
y: 563
- label: Clicked
size: 100
x: 331
y: 568
transitions:
- from_state: Start
label: start
to_state: Ready
- from_state: Ready
label: onMouseDown
to_state: Pressed
- from_state: Pressed
label: onMouseUp
to_state: Clicked
- from_state: Clicked
label: start
to_state: Ready
@@ -0,0 +1,27 @@
app: buttons
panX: 133
panY: 41
scaleXY: 1
states:
- label: Start
size: 100
x: 392
y: 88
- label: Ready
size: 100
x: 392
y: 281
- label: ButtonPressed
size: 100
x: 394
y: 491
transitions:
- from_state: Start
label: ''
to_state: Ready
- from_state: Ready
label: onMouseDown
to_state: ButtonPressed
- from_state: ButtonPressed
label: onMouseUp
to_state: Ready
@@ -0,0 +1,41 @@
app: link
panX: -15
panY: 0
scaleXY: 1
states:
- label: Ready
size: 100
x: 540
y: 307
- label: Start
size: 100
x: 533
y: 96
- label: Selecting
size: 100
x: 780
y: 299
- label: Connecting
size: 100
x: 782
y: 541
- label: Connected
size: 100
x: 546
y: 543
transitions:
- from_state: Start
label: start
to_state: Ready
- from_state: Ready
label: onNewLink
to_state: Selecting
- from_state: Selecting
label: onMouseUp
to_state: Connecting
- from_state: Connecting
label: onMouseUp
to_state: Connected
- from_state: Connected
label: free
to_state: Ready
@@ -0,0 +1,64 @@
app: move
panX: 285.92999999999995
panY: -151.52999999999997
scaleXY: 0.8700000000000001
states:
- label: Start
size: 100
x: 533
y: 121
- label: Ready
size: 100
x: 531
y: 320
- label: Selected1
size: 100
x: 226
y: 325
- label: Selected2
size: 100
x: 230
y: 582
- label: Move
size: 100
x: -54
y: 587
- label: EditLabel
size: 100
x: 535.7126436781609
y: 583.367816091954
- label: Selected3
size: 100
x: 231.11494252873567
y: 867.2758620689654
transitions:
- from_state: Start
label: start
to_state: Ready
- from_state: Ready
label: onMouseDown
to_state: Selected1
- from_state: Selected1
label: onMouseUp
to_state: Selected2
- from_state: Selected1
label: onMouseMove
to_state: Move
- from_state: Selected2
label: onMouseDown
to_state: Ready
- from_state: Move
label: onMouseUp
to_state: Selected2
- from_state: EditLabel
label: onMouseDown
to_state: Ready
- from_state: Selected2
label: onMouseDown
to_state: Selected3
- from_state: Selected3
label: onMouseMove
to_state: Move
- from_state: Selected3
label: onMouseUp
to_state: EditLabel
@@ -0,0 +1,27 @@
app: time
panX: 0
panY: 0
scaleXY: 1
states:
- label: Start
size: 100
x: 634
y: 117
- label: Present
size: 100
x: 632
y: 379
- label: Past
size: 100
x: 367
y: 369
transitions:
- from_state: Start
label: start
to_state: Present
- from_state: Present
label: onMouseWheel
to_state: Past
- from_state: Past
label: onMouseWheel
to_state: Present
@@ -0,0 +1,47 @@
app: view
panX: 1
panY: -67
scaleXY: 1
states:
- label: Start
size: 100
x: 498
y: 175
- label: Ready
size: 100
x: 506
y: 395
- label: Scale
size: 100
x: 310
y: 626
- label: Pan
size: 100
x: 741
y: 631
- label: Pressed
size: 100
x: 739
y: 392
transitions:
- from_state: Start
label: start
to_state: Ready
- from_state: Ready
label: onMouseWheel
to_state: Scale
- from_state: Scale
label: onTimeout
to_state: Ready
- from_state: Ready
label: onMouseDown
to_state: Pressed
- from_state: Pressed
label: onMouseMove
to_state: Pan
- from_state: Pressed
label: onMouseUp
to_state: Ready
- from_state: Pan
label: onMouseUp
to_state: Ready
+111
View File
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html ng-app="triangular">
<head>
<link rel="stylesheet" href="css/style.css" />
<script data-require="angular.js@1.6.2" src="js/angular.js" data-semver="1.6.2"></script>
<script src="js/reconnecting-websocket.js"></script>
<script src="js/bundle.js"></script>
<script src="js/hamster.js"></script>
<script src="js/ngTouch.js"></script>
<script src="js/mousewheel.js"></script>
</head>
<body ng-controller="MainCtrl" id="Main">
<svg id="frame"
ng-attr-height="{{graph.height}}"
ng-attr-width="{{graph.width}}"
ng-mousedown="onMouseDown($event)"
ng-mouseup="onMouseUp($event)"
ng-mouseenter="onMouseEnter($event)"
ng-mouseleave="onMouseLeave($event)"
ng-mousemove="onMouseMove($event)"
ng-mouseover="onMouseOver($event)"
ng-touchstart="onTouchStart($event)"
ng-touchmove="onTouchMove($event)"
ng-touchend="onTouchEnd($event)"
ng-tap="onTap($event)"
msd-wheel="onMouseWheel($event, $delta, $deltaX, $deltaY)">
<defs>
<filter x="0" y="0" width="1" height="1" id="selected">
<feFlood flood-color="#b3d8fd"/>
<feComposite in="SourceGraphic" operator="xor"/>
</filter>
<filter x="0" y="0" width="1" height="1" id="background">
<feFlood flood-color="#ffffff"/>
<feComposite in="SourceGraphic" operator="xor"/>
</filter>
</defs>
<g transform="scale(1.0)" id="frame_g">
<g ng-repeat="link in links">
<g link></g>
</g>
<g ng-repeat="link in links">
<g ng-if="link.selected || link.to_interface.selected || link.from_interface.selected" link></g>
</g>
<g ng-repeat="device in devices"
ng-attr-transform="translate({{device.x}},{{device.y}})"
ng-attr-class="{{device.type}}"
ng-switch on="device.type">
<g ng-switch-when="router"><!-- begin router -->
<g router></g>
</g> <!-- end router -->
<g ng-switch-when="switch"> <!-- begin switch -->
<g switch> </g>
</g> <!-- end switch -->
<g ng-switch-when="host"> <!-- begin host -->
<g host> </g>
</g> <!-- end host -->
<g ng-switch-when="rack"> <!-- begin rack -->
<g rack> </g>
</g> <!-- end rack -->
<g ng-switch-default> <!-- begin default -->
<g default></g>
</g> <!-- end default -->
<g status-light></g>
<g task-status></g>
</g> <!-- end devices -->
<g ng-attr-transform="translate({{scaledX}},{{scaledY}})" ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug-cursor" >
<line x1="-5" y1="0" x2="5" y2="0"/>
<line x1="0" y1="-5" x2="0" y2="5"/>
</g>
<g quadrants>
</g>
</g>
<g ng-if="!hide_buttons">
<g> <!-- buttons -->
<g ng-repeat="button in buttons"
ng-attr-transform="translate({{button.x}},{{button.y}})"
ng-attr-class="{{button.is_pressed ? 'button-pressed' : button.mouse_over ? 'button-hover' : 'button'}}">
<g button></g>
</g>
</g> <!-- end buttons -->
<g> <!-- stencils -->
<g ng-repeat="stencil in stencils"
ng-attr-transform="translate({{stencil.x}},{{stencil.y}})"
class="button">
<g stencil></g>
</g>
</g> <!-- end stencils -->
<g> <!-- layers -->
<g ng-repeat="layer in layers"
ng-attr-transform="translate({{layer.x}},{{layer.y}})"
class="button">
<g layer> </g>
</g>
</g> <!-- end layers -->
</g>
<g debug></g>
<g cursor></g>
<g ng-repeat="touch in touches">
<g touch></g>
</g>
</svg>
</body>
</html>
@@ -0,0 +1 @@
Build directory for javascript files
@@ -0,0 +1,24 @@
{
"name": "network_ui",
"version": "1.0.0",
"description": "Ansible Tower Networking UI",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ben Thomasson",
"license": "ISC",
"dependencies": {
"webpack": "",
"browserify": "",
"inherits": "",
"require": "",
"jshint": "",
"less": "",
"mathjs": ""
},
"devDependencies": {
"eslint": "^3.17.1",
"eslint-config-google": "^0.7.1"
}
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash -ex
python -m SimpleHTTPServer
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
function _State () {
}
inherits(_State, fsm._State);
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Clicked () {
this.name = 'Clicked';
}
inherits(_Clicked, _State);
var Clicked = new _Clicked();
exports.Clicked = Clicked;
function _Pressed () {
this.name = 'Pressed';
}
inherits(_Pressed, _State);
var Pressed = new _Pressed();
exports.Pressed = Pressed;
_Ready.prototype.onMouseDown = function (controller) {
controller.changeState(Pressed);
};
_Ready.prototype.onMouseDown.transitions = ['Pressed'];
_Ready.prototype.onTouchStart = _Ready.prototype.onMouseDown;
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Start.prototype.start.transitions = ['Ready'];
_Clicked.prototype.start = function (controller) {
controller.scope.callback(controller.scope);
controller.scope.is_pressed = false;
controller.changeState(Ready);
};
_Clicked.prototype.start.transitions = ['Ready'];
_Pressed.prototype.start = function (controller) {
controller.scope.is_pressed = true;
};
_Pressed.prototype.onMouseUp = function (controller) {
controller.changeState(Clicked);
};
_Pressed.prototype.onMouseUp.transitions = ['Clicked'];
_Pressed.prototype.onTouchEnd = _Pressed.prototype.onMouseUp;
@@ -0,0 +1,100 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
function _State () {
}
inherits(_State, fsm._State);
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _ButtonPressed () {
this.name = 'ButtonPressed';
}
inherits(_ButtonPressed, _State);
var ButtonPressed = new _ButtonPressed();
exports.ButtonPressed = ButtonPressed;
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
var i = 0;
var buttons = controller.scope.buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];
if (button.is_selected(controller.scope.mouseX, controller.scope.mouseY)) {
button.fsm.handle_message(msg_type, $event);
controller.changeState(ButtonPressed);
break;
}
button = null;
}
if (button === null) {
controller.next_controller.handle_message(msg_type, $event);
}
};
_Ready.prototype.onMouseDown.transitions = ['ButtonPressed'];
_Ready.prototype.onTouchStart = _Ready.prototype.onMouseDown;
_Ready.prototype.onMouseMove = function (controller, msg_type, $event) {
if (!controller.scope.hide_buttons) {
var i = 0;
var buttons = controller.scope.buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];
button.mouse_over = false;
if (button.is_selected(controller.scope.mouseX, controller.scope.mouseY)) {
button.mouse_over = true;
}
}
}
controller.next_controller.handle_message(msg_type, $event);
};
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Start.prototype.start.transitions = ['Ready'];
_ButtonPressed.prototype.onMouseUp = function (controller, msg_type, $event) {
var i = 0;
var buttons = controller.scope.buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];
button.fsm.handle_message(msg_type, $event);
}
controller.changeState(Ready);
};
_ButtonPressed.prototype.onMouseUp.transitions = ['Ready'];
_ButtonPressed.prototype.onTouchEnd = _ButtonPressed.prototype.onMouseUp;
@@ -0,0 +1,46 @@
function FSMController (scope, initial_state, next_controller) {
this.scope = scope;
this.state = initial_state;
this.state.start(this);
this.next_controller = next_controller;
}
exports.FSMController = FSMController;
FSMController.prototype.changeState = function (state) {
if(this.state !== null) {
this.state.end(this);
}
this.state = state;
if(state !== null) {
state.start(this);
}
};
FSMController.prototype.handle_message = function(msg_type, message) {
var handler_name = 'on' + msg_type;
if (typeof(this.state[handler_name]) !== "undefined") {
this.state[handler_name](this, msg_type, message);
} else {
this.default_handler(msg_type, message);
}
};
FSMController.prototype.default_handler = function(msg_type, message) {
if (this.next_controller !== null) {
this.next_controller.handle_message(msg_type, message);
}
};
function _State () {
}
_State.prototype.start = function () {
};
_State.prototype.end = function () {
};
var State = new _State();
exports.State = State;
exports._State = _State;
@@ -0,0 +1,146 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
var models = require('./models.js');
var messages = require('./messages.js');
function _State () {
}
inherits(_State, fsm._State);
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Connected () {
this.name = 'Connected';
}
inherits(_Connected, _State);
var Connected = new _Connected();
exports.Connected = Connected;
function _Connecting () {
this.name = 'Connecting';
}
inherits(_Connecting, _State);
var Connecting = new _Connecting();
exports.Connecting = Connecting;
function _Selecting () {
this.name = 'Selecting';
}
inherits(_Selecting, _State);
var Selecting = new _Selecting();
exports.Selecting = Selecting;
_Ready.prototype.onKeyDown = function(controller, msg_type, $event) {
if ($event.key === 'l') {
controller.handle_message("NewLink", $event);
}
controller.next_controller.handle_message(msg_type, $event);
};
_Ready.prototype.onNewLink = function (controller) {
controller.scope.clear_selections();
controller.changeState(Selecting);
};
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Connected.prototype.start = function (controller) {
controller.scope.clear_selections();
controller.changeState(Ready);
};
_Connecting.prototype.onMouseDown = function () {
};
_Connecting.prototype.onMouseUp = function (controller) {
var selected_device = controller.scope.select_items(false).last_selected_device;
var to_device_interface = null;
var from_device_interface = null;
var i = 0;
if (selected_device !== null) {
controller.scope.new_link.to_device = selected_device;
i = controller.scope.new_link.to_device.interface_seq();
to_device_interface = new models.Interface(i, "swp" + i);
controller.scope.new_link.to_device.interfaces.push(to_device_interface);
i = controller.scope.new_link.from_device.interface_seq();
from_device_interface = new models.Interface(i, "swp" + i);
controller.scope.new_link.from_device.interfaces.push(from_device_interface);
to_device_interface.link = controller.scope.new_link;
from_device_interface.link = controller.scope.new_link;
to_device_interface.device = controller.scope.new_link.to_device;
from_device_interface.device = controller.scope.new_link.from_device;
controller.scope.new_link.to_interface = to_device_interface;
controller.scope.new_link.from_interface = from_device_interface;
to_device_interface.dot();
from_device_interface.dot();
controller.scope.send_control_message(new messages.MultipleMessage(controller.scope.client_id, [
new messages.InterfaceCreate(controller.scope.client_id,
controller.scope.new_link.from_device.id,
from_device_interface.id,
from_device_interface.name),
new messages.InterfaceCreate(controller.scope.client_id,
controller.scope.new_link.to_device.id,
to_device_interface.id,
to_device_interface.name),
new messages.LinkCreate(controller.scope.client_id,
controller.scope.new_link.id,
controller.scope.new_link.from_device.id,
controller.scope.new_link.to_device.id,
from_device_interface.id,
to_device_interface.id)]));
controller.scope.new_link = null;
controller.changeState(Connected);
} else {
var index = controller.scope.links.indexOf(controller.scope.new_link);
if (index !== -1) {
controller.scope.links.splice(index, 1);
}
controller.scope.new_link = null;
controller.changeState(Ready);
}
};
_Selecting.prototype.onMouseDown = function () {
};
_Selecting.prototype.onMouseUp = function (controller) {
var selected_device = controller.scope.select_items(false).last_selected_device;
if (selected_device !== null) {
controller.scope.new_link = new models.Link(controller.scope.link_id_seq(), selected_device, null, null, null, true);
controller.scope.links.push(controller.scope.new_link);
controller.changeState(Connecting);
}
};
@@ -0,0 +1,2 @@
var app = require('./app.js');
exports.app = app;
@@ -0,0 +1,241 @@
function serialize(message) {
return JSON.stringify([message.constructor.name, message]);
}
exports.serialize = serialize;
function DeviceMove(sender, id, x, y, previous_x, previous_y) {
this.msg_type = "DeviceMove";
this.sender = sender;
this.id = id;
this.x = x;
this.y = y;
this.previous_x = previous_x;
this.previous_y = previous_y;
}
exports.DeviceMove = DeviceMove;
function DeviceCreate(sender, id, x, y, name, type) {
this.msg_type = "DeviceCreate";
this.sender = sender;
this.id = id;
this.x = x;
this.y = y;
this.name = name;
this.type = type;
}
exports.DeviceCreate = DeviceCreate;
function DeviceDestroy(sender, id, previous_x, previous_y, previous_name, previous_type) {
this.msg_type = "DeviceDestroy";
this.sender = sender;
this.id = id;
this.previous_x = previous_x;
this.previous_y = previous_y;
this.previous_name = previous_name;
this.previous_type = previous_type;
}
exports.DeviceDestroy = DeviceDestroy;
function DeviceLabelEdit(sender, id, name, previous_name) {
this.msg_type = "DeviceLabelEdit";
this.sender = sender;
this.id = id;
this.name = name;
this.previous_name = previous_name;
}
exports.DeviceLabelEdit = DeviceLabelEdit;
function DeviceSelected(sender, id) {
this.msg_type = "DeviceSelected";
this.sender = sender;
this.id = id;
}
exports.DeviceSelected = DeviceSelected;
function DeviceUnSelected(sender, id) {
this.msg_type = "DeviceUnSelected";
this.sender = sender;
this.id = id;
}
exports.DeviceUnSelected = DeviceUnSelected;
function InterfaceCreate(sender, device_id, id, name) {
this.msg_type = "InterfaceCreate";
this.sender = sender;
this.device_id = device_id;
this.id = id;
this.name = name;
}
exports.InterfaceCreate = InterfaceCreate;
function InterfaceLabelEdit(sender, id, device_id, name, previous_name) {
this.msg_type = "InterfaceLabelEdit";
this.sender = sender;
this.id = id;
this.device_id = device_id;
this.name = name;
this.previous_name = previous_name;
}
exports.InterfaceLabelEdit = InterfaceLabelEdit;
function LinkLabelEdit(sender, id, name, previous_name) {
this.msg_type = "LinkLabelEdit";
this.sender = sender;
this.id = id;
this.name = name;
this.previous_name = previous_name;
}
exports.LinkLabelEdit = LinkLabelEdit;
function LinkCreate(sender, id, from_device_id, to_device_id, from_interface_id, to_interface_id) {
this.msg_type = "LinkCreate";
this.id = id;
this.sender = sender;
this.name = '';
this.from_device_id = from_device_id;
this.to_device_id = to_device_id;
this.from_interface_id = from_interface_id;
this.to_interface_id = to_interface_id;
}
exports.LinkCreate = LinkCreate;
function LinkDestroy(sender, id, from_id, to_id) {
this.msg_type = "LinkDestroy";
this.id = id;
this.sender = sender;
this.from_id = from_id;
this.to_id = to_id;
this.name = '';
}
exports.LinkDestroy = LinkDestroy;
function LinkSelected(sender, id) {
this.msg_type = "LinkSelected";
this.sender = sender;
this.id = id;
}
exports.LinkSelected = LinkSelected;
function LinkUnSelected(sender, id) {
this.msg_type = "LinkUnSelected";
this.sender = sender;
this.id = id;
}
exports.LinkUnSelected = LinkUnSelected;
function Undo(sender, original_message) {
this.msg_type = "Undo";
this.sender = sender;
this.original_message = original_message;
}
exports.Undo = Undo;
function Redo(sender, original_message) {
this.msg_type = "Redo";
this.sender = sender;
this.original_message = original_message;
}
exports.Redo = Redo;
function Deploy(sender) {
this.msg_type = "Deploy";
this.sender = sender;
}
exports.Deploy = Deploy;
function Destroy(sender) {
this.msg_type = "Destroy";
this.sender = sender;
}
exports.Destroy = Destroy;
function Discover(sender) {
this.msg_type = "Discover";
this.sender = sender;
}
exports.Discover = Discover;
function Layout(sender) {
this.msg_type = "Layout";
this.sender = sender;
}
exports.Layout = Layout;
function MultipleMessage(sender, messages) {
this.msg_type = "MultipleMessage";
this.sender = sender;
this.messages = messages;
}
exports.MultipleMessage = MultipleMessage;
function Coverage(sender, coverage) {
this.msg_type = "Coverage";
this.sender = sender;
this.coverage = coverage;
}
exports.Coverage = Coverage;
function MouseEvent(sender, x, y, type) {
this.msg_type = "MouseEvent";
this.sender = sender;
this.x = x;
this.y = y;
this.type = type;
}
exports.MouseEvent = MouseEvent;
function MouseWheelEvent(sender, delta, deltaX, deltaY, type, metaKey) {
this.msg_type = "MouseWheelEvent";
this.sender = sender;
this.delta = delta;
this.deltaX = deltaX;
this.deltaY = deltaY;
this.type = type;
this.originalEvent = {metaKey: metaKey};
}
exports.MouseWheelEvent = MouseWheelEvent;
function KeyEvent(sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey) {
this.msg_type = "KeyEvent";
this.sender = sender;
this.key = key;
this.keyCode = keyCode;
this.type = type;
this.altKey = altKey;
this.shiftKey = shiftKey;
this.ctrlKey = ctrlKey;
this.metaKey = metaKey;
}
exports.KeyEvent = KeyEvent;
function TouchEvent(sender, type, touches) {
this.msg_type = "TouchEvent";
this.sender = sender;
this.type = type;
this.touches = touches;
}
exports.TouchEvent = TouchEvent;
function StartRecording(sender) {
this.msg_type = "StartRecording";
this.sender = sender;
}
exports.StartRecording = StartRecording;
function StopRecording(sender) {
this.msg_type = "StopRecording";
this.sender = sender;
}
exports.StopRecording = StopRecording;
function ViewPort(sender, scale, panX, panY) {
this.msg_type = "ViewPort";
this.sender = sender;
this.scale = scale;
this.panX = panX;
this.panY = panY;
}
exports.ViewPort = ViewPort;
@@ -0,0 +1,278 @@
var fsm = require('./fsm.js');
var button = require('./button.js');
var util = require('./util.js');
function Device(id, name, x, y, type) {
this.id = id;
this.name = name;
this.x = x;
this.y = y;
this.height = type === "host" ? 15 : 50;
this.width = 50;
this.size = 50;
this.type = type;
this.selected = false;
this.remote_selected = false;
this.edit_label = false;
this.status = null;
this.working = false;
this.tasks = [];
this.shape = type === "router" ? "circular" : "rectangular";
this.interface_seq = util.natural_numbers(0);
this.interfaces = [];
}
exports.Device = Device;
Device.prototype.is_selected = function (x, y) {
return (x > this.x - this.width &&
x < this.x + this.width &&
y > this.y - this.height &&
y < this.y + this.height);
};
Device.prototype.describeArc = util.describeArc;
function Interface(id, name) {
this.id = id;
this.name = name;
this.link = null;
this.device = null;
this.edit_label = false;
this.dot_x = null;
this.dot_y = null;
}
exports.Interface = Interface;
Interface.prototype.is_selected = function (x, y) {
if (this.link === null || this.device === null) {
return false;
}
var d = Math.sqrt(Math.pow(x - this.device.x, 2) + Math.pow(y - this.device.y, 2));
return this.link.is_selected(x, y) && (d < this.dot_d + 30);
};
Interface.prototype.dot_distance = function () {
this.dot_d = Math.sqrt(Math.pow(this.device.x - this.dot_x, 2) + Math.pow(this.device.y - this.dot_y, 2));
};
Interface.prototype.dot = function () {
if (this.link === null || this.device === null) {
return;
}
var p;
if (this.device.shape === "circular") {
var theta = this.link.slope_rads();
if (this.link.from_interface === this) {
theta = theta + Math.PI;
}
p = {x: this.device.x - this.device.size * Math.cos(theta),
y: this.device.y - this.device.size * Math.sin(theta)};
this.dot_x = p.x;
this.dot_y = p.y;
this.dot_distance();
return;
}
var x1;
var y1;
var x2;
var y2;
var x3;
var y3;
var x4;
var y4;
var param1;
var param2;
x3 = this.link.to_device.x;
y3 = this.link.to_device.y;
x4 = this.link.from_device.x;
y4 = this.link.from_device.y;
x1 = this.device.x - this.device.width;
y1 = this.device.y - this.device.height;
x2 = this.device.x + this.device.width;
y2 = this.device.y - this.device.height;
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
this.dot_x = p.x;
this.dot_y = p.y;
this.dot_distance();
return;
}
x1 = this.device.x - this.device.width;
y1 = this.device.y + this.device.height;
x2 = this.device.x + this.device.width;
y2 = this.device.y + this.device.height;
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
this.dot_x = p.x;
this.dot_y = p.y;
this.dot_distance();
return;
}
x1 = this.device.x + this.device.width;
y1 = this.device.y - this.device.height;
x2 = this.device.x + this.device.width;
y2 = this.device.y + this.device.height;
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
this.dot_x = p.x;
this.dot_y = p.y;
this.dot_distance();
return;
}
x1 = this.device.x - this.device.width;
y1 = this.device.y - this.device.height;
x2 = this.device.x - this.device.width;
y2 = this.device.y + this.device.height;
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
this.dot_x = p.x;
this.dot_y = p.y;
this.dot_distance();
return;
}
};
function Link(id, from_device, to_device, from_interface, to_interface) {
this.id = id;
this.from_device = from_device;
this.to_device = to_device;
this.from_interface = from_interface;
this.to_interface = to_interface;
this.selected = false;
this.remote_selected = false;
this.status = null;
this.edit_label = false;
this.name = "";
}
exports.Link = Link;
Link.prototype.is_selected = function (x, y) {
// Is the distance to the mouse location less than 25 if on the label side
// or 5 on the other from the shortest line to the link?
if (this.to_device === null) {
return false;
}
var d = util.pDistance(x,
y,
this.from_device.x,
this.from_device.y,
this.to_device.x,
this.to_device.y);
if (util.cross_z_pos(x,
y,
this.from_device.x,
this.from_device.y,
this.to_device.x,
this.to_device.y)) {
return d < 10;
} else {
return d < 10;
}
};
Link.prototype.slope_rads = function () {
//Return the slope in degrees for this link.
var x1 = this.from_device.x;
var y1 = this.from_device.y;
var x2 = this.to_device.x;
var y2 = this.to_device.y;
return Math.atan2(y2 - y1, x2 - x1);
};
Link.prototype.slope = function () {
//Return the slope in degrees for this link.
var x1 = this.from_device.x;
var y1 = this.from_device.y;
var x2 = this.to_device.x;
var y2 = this.to_device.y;
return Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI + 180;
};
Link.prototype.pDistanceLine = function (x, y) {
var x1 = this.from_device.x;
var y1 = this.from_device.y;
var x2 = this.to_device.x;
var y2 = this.to_device.y;
return util.pDistanceLine(x, y, x1, y1, x2, y2);
};
Link.prototype.length = function () {
//Return the length of this link.
var x1 = this.from_device.x;
var y1 = this.from_device.y;
var x2 = this.to_device.x;
var y2 = this.to_device.y;
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
};
Link.prototype.plength = function (x, y) {
//Return the length of this link.
var x1 = this.from_device.x;
var y1 = this.from_device.y;
var x2 = this.to_device.x;
var y2 = this.to_device.y;
return util.pDistance(x, y, x1, y1, x2, y2);
};
function Button(name, x, y, width, height, callback) {
this.name = name;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.callback = callback;
this.is_pressed = false;
this.mouse_over = false;
this.fsm = new fsm.FSMController(this, button.Start, null);
}
exports.Button = Button;
Button.prototype.is_selected = function (x, y) {
return (x > this.x &&
x < this.x + this.width &&
y > this.y &&
y < this.y + this.height);
};
function Task(id, name) {
this.id = id;
this.name = name;
this.status = null;
this.working = null;
}
exports.Task = Task;
Task.prototype.describeArc = util.describeArc;
@@ -0,0 +1,348 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
var models = require('./models.js');
var messages = require('./messages.js');
var util = require('./util.js');
function _State () {
}
inherits(_State, fsm._State);
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Selected2 () {
this.name = 'Selected2';
}
inherits(_Selected2, _State);
var Selected2 = new _Selected2();
exports.Selected2 = Selected2;
function _Selected3 () {
this.name = 'Selected3';
}
inherits(_Selected3, _State);
var Selected3 = new _Selected3();
exports.Selected3 = Selected3;
function _Move () {
this.name = 'Move';
}
inherits(_Move, _State);
var Move = new _Move();
exports.Move = Move;
function _Selected1 () {
this.name = 'Selected1';
}
inherits(_Selected1, _State);
var Selected1 = new _Selected1();
exports.Selected1 = Selected1;
function _EditLabel () {
this.name = 'EditLabel';
}
inherits(_EditLabel, _State);
var EditLabel = new _EditLabel();
exports.EditLabel = EditLabel;
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
var last_selected = controller.scope.select_items($event.shiftKey);
if (last_selected.last_selected_device !== null) {
controller.changeState(Selected1);
} else if (last_selected.last_selected_link !== null) {
controller.changeState(Selected1);
} else if (last_selected.last_selected_interface !== null) {
controller.changeState(Selected1);
} else {
controller.next_controller.handle_message(msg_type, $event);
}
};
_Ready.prototype.onMouseDown.transitions = ['Selected1'];
_Ready.prototype.onTouchStart = _Ready.prototype.onMouseDown;
_Ready.prototype.onKeyDown = function(controller, msg_type, $event) {
var scope = controller.scope;
var device = null;
if ($event.key === 'r') {
device = new models.Device(controller.scope.device_id_seq(),
"Router",
scope.scaledX,
scope.scaledY,
"router");
}
else if ($event.key === 's') {
device = new models.Device(controller.scope.device_id_seq(),
"Switch",
scope.scaledX,
scope.scaledY,
"switch");
}
else if ($event.key === 'a') {
device = new models.Device(controller.scope.device_id_seq(),
"Rack",
scope.scaledX,
scope.scaledY,
"rack");
}
else if ($event.key === 'h') {
device = new models.Device(controller.scope.device_id_seq(),
"Host",
scope.scaledX,
scope.scaledY,
"host");
}
if (device !== null) {
scope.devices.push(device);
scope.send_control_message(new messages.DeviceCreate(scope.client_id,
device.id,
device.x,
device.y,
device.name,
device.type));
}
controller.next_controller.handle_message(msg_type, $event);
};
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Start.prototype.start.transitions = ['Ready'];
_Selected2.prototype.onMouseDown = function (controller, msg_type, $event) {
var last_selected = null;
if (controller.scope.selected_devices.length === 1) {
var current_selected_device = controller.scope.selected_devices[0];
var last_selected_device = controller.scope.select_items($event.shiftKey).last_selected_device;
if (current_selected_device === last_selected_device) {
controller.changeState(Selected3);
return;
}
}
if (controller.scope.selected_links.length === 1) {
var current_selected_link = controller.scope.selected_links[0];
last_selected = controller.scope.select_items($event.shiftKey);
if (current_selected_link === last_selected.last_selected_link) {
controller.changeState(Selected3);
return;
}
}
if (controller.scope.selected_interfaces.length === 1) {
var current_selected_interface = controller.scope.selected_interfaces[0];
last_selected = controller.scope.select_items($event.shiftKey);
if (current_selected_interface === last_selected.last_selected_interface) {
controller.changeState(Selected3);
return;
}
}
controller.changeState(Ready);
controller.handle_message(msg_type, $event);
};
_Selected2.prototype.onMouseDown.transitions = ['Ready', 'Selected3'];
_Selected2.prototype.onTouchStart = _Selected2.prototype.onMouseDown;
_Selected2.prototype.onKeyDown = function (controller, msg_type, $event) {
if ($event.keyCode === 8) {
//Delete
controller.changeState(Ready);
var i = 0;
var j = 0;
var index = -1;
var devices = controller.scope.selected_devices;
var all_links = controller.scope.links.slice();
controller.scope.selected_devices = [];
controller.scope.selected_links = [];
for (i = 0; i < devices.length; i++) {
index = controller.scope.devices.indexOf(devices[i]);
if (index !== -1) {
controller.scope.devices.splice(index, 1);
controller.scope.send_control_message(new messages.DeviceDestroy(controller.scope.client_id,
devices[i].id,
devices[i].x,
devices[i].y,
devices[i].name,
devices[i].type));
}
for (j = 0; j < all_links.length; j++) {
if (all_links[j].to_device === devices[i] ||
all_links[j].from_device === devices[i]) {
index = controller.scope.links.indexOf(all_links[j]);
if (index !== -1) {
controller.scope.links.splice(index, 1);
}
}
}
}
}
};
_Selected2.prototype.onKeyDown.transitions = ['Ready'];
_Selected1.prototype.onMouseMove = function (controller) {
controller.changeState(Move);
};
_Selected1.prototype.onMouseMove.transitions = ['Move'];
_Selected1.prototype.onTouchMove = _Selected1.prototype.onMouseMove;
_Selected1.prototype.onMouseUp = function (controller) {
controller.changeState(Selected2);
};
_Selected1.prototype.onMouseUp.transitions = ['Selected2'];
_Selected1.prototype.onTouchEnd = _Selected1.prototype.onMouseUp;
_Selected1.prototype.onMouseDown = util.noop;
_Move.prototype.onMouseMove = function (controller) {
var devices = controller.scope.selected_devices;
var diffX = controller.scope.scaledX - controller.scope.pressedScaledX;
var diffY = controller.scope.scaledY - controller.scope.pressedScaledY;
var i = 0;
var j = 0;
var previous_x, previous_y;
for (i = 0; i < devices.length; i++) {
previous_x = devices[i].x;
previous_y = devices[i].y;
devices[i].x = devices[i].x + diffX;
devices[i].y = devices[i].y + diffY;
for (j = 0; j < devices[i].interfaces.length; j++) {
devices[i].interfaces[j].dot();
if (devices[i].interfaces[j].link !== null) {
devices[i].interfaces[j].link.to_interface.dot();
devices[i].interfaces[j].link.from_interface.dot();
}
}
controller.scope.send_control_message(new messages.DeviceMove(controller.scope.client_id,
devices[i].id,
devices[i].x,
devices[i].y,
previous_x,
previous_y));
}
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
};
_Move.prototype.onTouchMove = _Move.prototype.onMouseMove;
_Move.prototype.onMouseUp = function (controller, msg_type, $event) {
controller.changeState(Selected2);
controller.handle_message(msg_type, $event);
};
_Move.prototype.onMouseUp.transitions = ['Selected2'];
_Move.prototype.onTouchEnd = _Move.prototype.onMouseUp;
_Selected3.prototype.onMouseUp = function (controller) {
controller.changeState(EditLabel);
};
_Selected3.prototype.onMouseUp.transitions = ['EditLabel'];
_Selected3.prototype.onTouchEnd = function (controller) {
controller.changeState(Selected2);
};
_Selected3.prototype.onTouchEnd.transitions = ['Selected2'];
_Selected3.prototype.onMouseMove = function (controller) {
controller.changeState(Move);
};
_Selected3.prototype.onMouseMove.transitions = ['Move'];
_Selected3.prototype.onTouchMove = _Selected3.prototype.onMouseMove;
_EditLabel.prototype.start = function (controller) {
controller.scope.selected_items[0].edit_label = true;
};
_EditLabel.prototype.end = function (controller) {
controller.scope.selected_items[0].edit_label = false;
};
_EditLabel.prototype.onMouseDown = function (controller, msg_type, $event) {
controller.changeState(Ready);
controller.handle_message(msg_type, $event);
};
_EditLabel.prototype.onMouseDown.transitions = ['Ready'];
_EditLabel.prototype.onKeyDown = function (controller, msg_type, $event) {
//Key codes found here:
//https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
var item = controller.scope.selected_items[0];
var previous_name = item.name;
if ($event.keyCode === 8 || $event.keyCode === 46) { //Delete
item.name = item.name.slice(0, -1);
} else if ($event.keyCode >= 48 && $event.keyCode <=90) { //Alphanumeric
item.name += $event.key;
} else if ($event.keyCode >= 186 && $event.keyCode <=222) { //Punctuation
item.name += $event.key;
} else if ($event.keyCode === 13) { //Enter
controller.changeState(Selected2);
}
if (item.constructor.name === "Device") {
controller.scope.send_control_message(new messages.DeviceLabelEdit(controller.scope.client_id,
item.id,
item.name,
previous_name));
}
if (item.constructor.name === "Interface") {
controller.scope.send_control_message(new messages.InterfaceLabelEdit(controller.scope.client_id,
item.id,
item.device.id,
item.name,
previous_name));
}
if (item.constructor.name === "Link") {
controller.scope.send_control_message(new messages.LinkLabelEdit(controller.scope.client_id,
item.id,
item.name,
previous_name));
}
};
_EditLabel.prototype.onKeyDown.transitions = ['Selected2'];
@@ -0,0 +1,315 @@
/* Put your css in here */
@selected-red: #c9232c;
@selected-mango: #ff5850;
@selected-blue: #5bbddf;
@light-background: #ffffff;
@light-widget-detail: #ffffff;
@dark-widget-detail: #707070;
@widget-body: #D7D7D7;
@link: #D7D7D7;
@debug-copynot: rgb(77,200,242);
@button-body: #f6f6f6;
@button-text: #707070;
@button-outline: #b4b6b4;
@button-body-hover: #dfdfdf;
@button-body-pressed: #d5d5d5;
@green: #5CB85C;
@red: #D9534F;
html {
overflow: hidden;
}
body {
background-color: yellow;
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
background-color: @light-background;
cursor: none;
}
svg text {
fill: @button-text;
}
.debug text {
fill: @debug-copynot;
}
line.selected {
stroke: @selected-blue;
stroke-width: 6;
}
line.remote-selected {
stroke: @selected-mango;
stroke-width: 6;
}
line.selected-conflict {
stroke: @selected-red;
stroke-width: 6;
}
svg rect.debug {
fill-opacity: 0;
stroke: @debug-copynot;
stroke-width: 1;
}
svg line.link {
stroke: @link;
stroke-width: 2;
}
svg line.link-pass {
stroke: @green;
stroke-width: 2;
}
svg line.link-fail {
stroke: @red;
stroke-width: 2;
}
svg line.cursor {
stroke: @dark-widget-detail;
stroke-width: 2;
}
svg line.debug {
stroke: @debug-copynot;
stroke-width: 1;
}
.debug-cursor line {
stroke: @debug-copynot;
stroke-width: 4;
}
.hidden {
display: none;
}
.router circle {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.router circle.selected {
stroke: @selected-blue;
stroke-width: 4;
}
.router circle.remote-selected {
stroke: @selected-mango;
stroke-width: 4;
}
.router circle.selected-conflict {
stroke: @selected-red;
stroke-width: 4;
}
.router line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.router polygon {
fill: @light-widget-detail;
}
.switch rect {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.switch rect.selected {
stroke: @selected-blue;
stroke-width: 10;
}
.switch rect.remote-selected {
stroke: @selected-mango;
stroke-width: 10;
}
.switch rect.selected-conflict {
stroke: @selected-red;
stroke-width: 10;
}
.switch line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.switch polygon {
fill: @light-widget-detail;
}
.rack rect {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.rack rect.background {
fill: @light-background;
stroke: @light-background;
stroke-width: 2;
}
.rack rect.selected {
fill: @selected-blue;
stroke: @selected-blue;
stroke-width: 10;
}
.rack rect.remote-selected {
fill: @selected-mango;
stroke: @selected-mango;
stroke-width: 10;
}
.rack rect.selected-conflict {
fill: @selected-red;
stroke: @selected-red;
stroke-width: 10;
}
.rack line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.rack circle {
fill: @light-widget-detail;
}
.button rect {
fill: @button-body;
stroke: @button-outline;
stroke-width: 1;
}
.button text {
fill: @button-text;
}
.button-pressed rect {
fill: @button-body-pressed;
stroke: @button-outline;
stroke-width: 1;
}
.button-pressed text {
fill: @button-text;
}
.button-hover rect {
fill: @button-body-hover;
stroke: @button-outline;
stroke-width: 1;
}
.button-hover text {
fill: @button-text;
}
.host rect {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.host rect.background {
fill: @light-background;
stroke: @light-background;
stroke-width: 2;
}
.host rect.selected {
fill: @selected-blue;
stroke: @selected-blue;
stroke-width: 10;
}
.host rect.remote-selected {
fill: @selected-mango;
stroke: @selected-mango;
stroke-width: 10;
}
.host rect.selected-conflict {
fill: @selected-red;
stroke: @selected-red;
stroke-width: 10;
}
.host line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.host circle {
fill: @light-widget-detail;
}
circle.status {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.pass {
fill: @green;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.fail {
fill: @red;
stroke: @dark-widget-detail;
stroke-width: 2;
}
path.status {
fill: none;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.debug {
fill: @debug-copynot;
}
circle.interface {
fill: @dark-widget-detail;
}
circle.selected {
fill: @selected-blue;
}
text.interface {
font-size: 8px;
}
.touch circle {
stroke: @debug-copynot;
fill: none;
}
@@ -0,0 +1,521 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
var messages = require('./messages.js');
var util = require('./util.js');
function _State () {
}
inherits(_State, fsm._State);
function _Past () {
this.name = 'Past';
}
inherits(_Past, _State);
var Past = new _Past();
exports.Past = Past;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Present () {
this.name = 'Present';
}
inherits(_Present, _State);
var Present = new _Present();
exports.Present = Present;
_Past.prototype.start = function (controller) {
controller.scope.time_pointer = controller.scope.history.length - 1;
};
_Past.prototype.onMessage = function(controller, msg_type, message) {
//console.log(message.data);
var type_data = JSON.parse(message.data);
var type = type_data[0];
var data = type_data[1];
if (['DeviceCreate',
'DeviceDestroy',
'DeviceMove',
'DeviceLabelEdit',
'LinkLabelEdit',
'InterfaceLabelEdit',
'InterfaceCreate',
'LinkCreate',
'LinkDestroy'].indexOf(type) !== -1) {
controller.changeState(Present);
controller.scope.history.splice(controller.scope.time_pointer);
if (data.sender !== controller.scope.client_id) {
controller.handle_message(msg_type, message);
} else {
controller.scope.history.push(message.data);
}
} else {
controller.handle_message(type, data);
}
};
_Past.prototype.onMultipleMessage = function(controller, msg_type, message) {
var i = 0;
console.log(['MultipleMessage', message]);
if (message.sender !== controller.scope.client_id) {
for (i=0; i< message.messages.length; i++) {
controller.handle_message(message.messages[i].msg_type, message.messages[i]);
}
}
};
_Past.prototype.onDeviceSelected = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceSelected(message);
}
};
_Past.prototype.onDeviceUnSelected = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceUnSelected(message);
}
};
_Past.prototype.onUndo = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.time_pointer = Math.max(0, controller.scope.time_pointer - 1);
controller.scope.undo(message.original_message);
}
};
_Past.prototype.onRedo = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.time_pointer = Math.min(controller.scope.history.length, controller.scope.time_pointer + 1);
controller.scope.redo(message.original_message);
if (controller.scope.time_pointer === controller.scope.history.length) {
controller.changeState(Present);
}
}
};
_Past.prototype.onCoverageRequest = function(controller) {
controller.scope.send_coverage();
};
_Past.prototype.onStopRecording = function(controller) {
controller.scope.recording = false;
};
_Past.prototype.onStartReplay = function(controller) {
controller.scope.replay = true;
};
_Past.prototype.onStopReplay = function(controller) {
controller.scope.replay = false;
};
_Past.prototype.onViewPort = function(controller, msg_type, message) {
if (message.sender === controller.scope.client_id) {
return;
}
controller.scope.current_scale = message.scale;
controller.scope.panX = message.panX;
controller.scope.panY = message.panY;
controller.scope.updateScaledXY();
controller.scope.updatePanAndScale();
};
_Past.prototype.onTouchEvent = function(controller, msg_type, message) {
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
if (message.type === "touchstart") {
controller.scope.onTouchStart(message);
}
if (message.type === "touchend") {
controller.scope.onTouchEnd(message);
}
if (message.type === "touchmove") {
controller.scope.onTouchMove(message);
}
};
_Past.prototype.onMouseEvent = function(controller, msg_type, message) {
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
//console.log(message);
if (message.type === "mousemove") {
controller.scope.onMouseMove(message);
}
if (message.type === "mouseup") {
controller.scope.onMouseUp(message);
}
if (message.type === "mousedown") {
controller.scope.onMouseDown(message);
}
if (message.type === "mouseover") {
controller.scope.onMouseOver(message);
}
if (message.type === "mouseout") {
controller.scope.onMouseOver(message);
}
};
_Past.prototype.onMouseWheelEvent = function(controller, msg_type, message) {
console.log(message);
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
message.stopPropagation = util.noop;
controller.scope.onMouseWheel(message, message.delta, message.deltaX, message.deltaY);
};
_Past.prototype.onKeyEvent = function(controller, msg_type, message) {
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
//console.log(message);
if (message.type === "keydown") {
controller.scope.onKeyDown(message);
}
};
_Past.prototype.onMouseWheel = function (controller, msg_type, message) {
var $event = message[0];
var delta = message[1];
if ($event.originalEvent.metaKey) {
//console.log(delta);
if (delta < 0) {
this.undo(controller);
} else if (delta > 0) {
this.redo(controller);
}
} else {
controller.next_controller.handle_message(msg_type, message);
}
};
_Past.prototype.onMouseWheel.transitions = ['Past'];
_Past.prototype.onKeyDown = function(controller, msg_type, $event) {
//console.log($event);
if ($event.key === 'z' && $event.metaKey && ! $event.shiftKey) {
this.undo(controller);
return;
} else if ($event.key === 'z' && $event.ctrlKey && ! $event.shiftKey) {
this.undo(controller);
return;
} else if ($event.key === 'Z' && $event.metaKey && $event.shiftKey) {
this.redo(controller);
return;
} else if ($event.key === 'Z' && $event.ctrlKey && $event.shiftKey) {
this.redo(controller);
return;
} else {
controller.next_controller.handle_message(msg_type, $event);
}
};
_Past.prototype.onKeyDown.transitions = ['Past'];
_Past.prototype.undo = function(controller) {
//controller.changeState(Past);
controller.scope.time_pointer = Math.max(0, controller.scope.time_pointer - 1);
if (controller.scope.time_pointer >= 0) {
var change = controller.scope.history[controller.scope.time_pointer];
var type_data = JSON.parse(change);
controller.scope.send_control_message(new messages.Undo(controller.scope.client_id,
type_data));
controller.scope.undo(type_data);
}
};
_Past.prototype.redo = function(controller) {
if (controller.scope.time_pointer < controller.scope.history.length) {
var change = controller.scope.history[controller.scope.time_pointer];
var type_data = JSON.parse(change);
controller.scope.send_control_message(new messages.Redo(controller.scope.client_id,
type_data));
controller.scope.redo(type_data);
controller.scope.time_pointer = Math.min(controller.scope.history.length, controller.scope.time_pointer + 1);
if (controller.scope.time_pointer === controller.scope.history.length) {
controller.changeState(Present);
}
} else {
controller.changeState(Present);
}
};
_Start.prototype.start = function (controller) {
controller.changeState(Present);
};
_Start.prototype.start.transitions = ['Present'];
_Present.prototype.onMessage = function(controller, msg_type, message) {
//console.log(message.data);
var type_data = JSON.parse(message.data);
var type = type_data[0];
var data = type_data[1];
if (['DeviceCreate',
'DeviceDestroy',
'DeviceMove',
'DeviceLabelEdit',
'LinkLabelEdit',
'InterfaceLabelEdit',
'InterfaceCreate',
'LinkCreate',
'LinkDestroy',
'Snapshot'].indexOf(type) !== -1) {
controller.scope.history.push(message.data);
}
controller.handle_message(type, data);
};
_Present.prototype.onMessage.transitions = ['Past'];
_Present.prototype.onMultipleMessage = function(controller, msg_type, message) {
var i = 0;
console.log(['MultipleMessage', message]);
if (message.sender !== controller.scope.client_id) {
for (i = 0; i< message.messages.length; i++) {
controller.handle_message(message.messages[i].msg_type, message.messages[i]);
}
}
};
_Present.prototype.onDeviceStatus = function(controller, msg_type, message) {
controller.scope.onDeviceStatus(message);
};
_Present.prototype.onTaskStatus = function(controller, msg_type, message) {
controller.scope.onTaskStatus(message);
};
_Present.prototype.onFacts = function(controller, msg_type, message) {
controller.scope.onFacts(message);
};
_Present.prototype.onDeviceCreate = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceCreate(message);
}
};
_Present.prototype.onInterfaceCreate = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onInterfaceCreate(message);
}
};
_Present.prototype.onLinkCreate = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onLinkCreate(message);
}
};
_Present.prototype.onDeviceMove = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceMove(message);
}
};
_Present.prototype.onDeviceDestroy = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceDestroy(message);
}
};
_Present.prototype.onLinkDestroy = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onLinkDestroy(message);
}
};
_Present.prototype.onDeviceLabelEdit = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceLabelEdit(message);
}
};
_Present.prototype.onLinkLabelEdit = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onLinkLabelEdit(message);
}
};
_Present.prototype.onInterfaceLabelEdit = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onInterfaceLabelEdit(message);
}
};
_Present.prototype.onDeviceSelected = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceSelected(message);
}
};
_Present.prototype.onDeviceUnSelected = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onDeviceUnSelected(message);
}
};
_Present.prototype.onUndo = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.time_pointer = Math.max(0, controller.scope.time_pointer - 1);
controller.scope.undo(message.original_message);
controller.changeState(Past);
}
};
_Present.prototype.onSnapshot = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onSnapshot(message);
}
};
_Present.prototype.onid = function(controller, msg_type, message) {
controller.scope.onClientId(message);
};
_Present.prototype.onTopology = function(controller, msg_type, message) {
controller.scope.onTopology(message);
};
_Present.prototype.onHistory = function(controller, msg_type, message) {
controller.scope.onHistory(message);
};
_Present.prototype.onCoverageRequest = function(controller) {
controller.scope.send_coverage();
};
_Present.prototype.onStopRecording = function(controller) {
controller.scope.recording = false;
};
_Present.prototype.onStartReplay = function(controller) {
controller.scope.replay = true;
};
_Present.prototype.onStopReplay = function(controller) {
controller.scope.replay = false;
};
_Present.prototype.onViewPort = function(controller, msg_type, message) {
if (message.sender === controller.scope.client_id) {
return;
}
controller.scope.current_scale = message.scale;
controller.scope.panX = message.panX;
controller.scope.panY = message.panY;
controller.scope.updateScaledXY();
controller.scope.updatePanAndScale();
};
_Present.prototype.onTouchEvent = function(controller, msg_type, message) {
if (!controller.scope.replay) {
return;
}
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
if (message.type === "touchstart") {
controller.scope.onTouchStart(message);
}
if (message.type === "touchend") {
controller.scope.onTouchEnd(message);
}
if (message.type === "touchmove") {
controller.scope.onTouchMove(message);
}
};
_Present.prototype.onMouseEvent = function(controller, msg_type, message) {
if (!controller.scope.replay) {
return;
}
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
//console.log(message);
if (message.type === "mousemove") {
controller.scope.onMouseMove(message);
}
if (message.type === "mouseup") {
controller.scope.onMouseUp(message);
}
if (message.type === "mousedown") {
controller.scope.onMouseDown(message);
}
if (message.type === "mouseover") {
controller.scope.onMouseOver(message);
}
};
_Present.prototype.onMouseWheelEvent = function(controller, msg_type, message) {
if (!controller.scope.replay) {
return;
}
console.log(message);
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
message.stopPropagation = util.noop;
controller.scope.onMouseWheel(message, message.delta, message.deltaX, message.deltaY);
};
_Present.prototype.onKeyEvent = function(controller, msg_type, message) {
if (!controller.scope.replay) {
return;
}
if (message.sender === controller.scope.client_id) {
return;
}
message.preventDefault = util.noop;
//console.log(message);
if (message.type === "keydown") {
controller.scope.onKeyDown(message);
}
};
_Present.prototype.onMessage.transitions = ['Past'];
_Present.prototype.onMouseWheel = function (controller, msg_type, message) {
var $event = message[0];
var delta = message[1];
if ($event.originalEvent.metaKey) {
//console.log(delta);
if (delta < 0) {
this.undo(controller);
}
} else {
controller.next_controller.handle_message(msg_type, message);
}
};
_Present.prototype.onMouseWheel.transitions = ['Past'];
_Present.prototype.onKeyDown = function(controller, msg_type, $event) {
//console.log($event);
if ($event.key === 'z' && $event.metaKey && ! $event.shiftKey) {
this.undo(controller);
return;
} else if ($event.key === 'z' && $event.ctrlKey && ! $event.shiftKey) {
this.undo(controller);
return;
} else {
controller.next_controller.handle_message(msg_type, $event);
}
};
_Present.prototype.onKeyDown.transitions = ['Past'];
_Present.prototype.undo = function(controller) {
controller.scope.time_pointer = controller.scope.history.length - 1;
if (controller.scope.time_pointer >= 0) {
var change = controller.scope.history[controller.scope.time_pointer];
var type_data = JSON.parse(change);
controller.scope.send_control_message(new messages.Undo(controller.scope.client_id,
type_data));
controller.scope.undo(type_data);
controller.changeState(Past);
}
};
@@ -0,0 +1,205 @@
var math = require('mathjs');
function noop () {
}
exports.noop = noop;
function natural_numbers (start) {
var counter = start;
return function () {return counter += 1;};
}
exports.natural_numbers = natural_numbers;
// polarToCartesian
// @wdebeaum, @opsb
// from http://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
// describeArc
// @wdebeaum, @opsb
// from http://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle
function describeArc(x, y, radius, startAngle, endAngle){
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
exports.describeArc = describeArc;
function pDistanceLine(x, y, x1, y1, x2, y2) {
//Code from http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
//Joshua
// Find the dot product of two vectors <A, B>, <C, D>
// Divide by the length squared of <C, D>
// Use scalar project to find param
//
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var len_sq = C * C + D * D;
var param = -1;
if (len_sq !== 0) {
//in case of 0 length line
param = dot / len_sq;
}
var xx, yy;
//Find a point xx, yy where the projection and the <C, D> vector intersect.
//If less than 0 use x1, y1 as the closest point.
//If less than 1 use x2, y2 as the closest point.
//If between 0 and 1 use the projection intersection xx, yy
if (param < 0) {
xx = x1;
yy = y1;
}
else if (param > 1) {
xx = x2;
yy = y2;
}
else {
xx = x1 + param * C;
yy = y1 + param * D;
}
return {x1:x, y1:y, x2: xx, y2: yy};
}
exports.pDistanceLine = pDistanceLine;
function pDistance(x, y, x1, y1, x2, y2) {
//Code from http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
//Joshua
// Find the dot product of two vectors <A, B>, <C, D>
// Divide by the length squared of <C, D>
// Use scalar project to find param
//
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var len_sq = C * C + D * D;
var param = -1;
if (len_sq !== 0) {
//in case of 0 length line
param = dot / len_sq;
}
var xx, yy;
//Find a point xx, yy where the projection and the <C, D> vector intersect.
//If less than 0 use x1, y1 as the closest point.
//If less than 1 use x2, y2 as the closest point.
//If between 0 and 1 use the projection intersection xx, yy
if (param < 0) {
xx = x1;
yy = y1;
}
else if (param > 1) {
xx = x2;
yy = y2;
}
else {
xx = x1 + param * C;
yy = y1 + param * D;
}
var dx = x - xx;
var dy = y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
exports.pDistance = pDistance;
function cross_z_pos(x, y, x1, y1, x2, y2) {
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
return math.cross([A, B, 0], [C, D, 0])[2] > 0;
}
exports.cross_z_pos = cross_z_pos;
function intersection (x1, y1, x2, y2, x3, y3, x4, y4) {
//Find the point where lines through x1, y1, x2, y2 and x3, y3, x4, y4 intersect.
//
var Aslope;
var Aintercept;
var Bslope;
var Bintercept;
if ((x2 - x1) !== 0 && (x4 - x3) !== 0) {
Aslope = (y2 - y1)/(x2 - x1);
Aintercept = y1 - Aslope * x1;
Bslope = (y4 - y3)/(x4 - x3);
Bintercept = y3 - Bslope * x3;
var xi = (Bintercept - Aintercept) / (Aslope - Bslope);
var yi = Bslope * xi + Bintercept;
return {x: xi, y: yi};
}
if ((x2 - x1) === 0 && (x4 - x3) === 0) {
return {x: null, y: null};
}
if ((x2 - x1) === 0) {
Bslope = (y4 - y3)/(x4 - x3);
Bintercept = y3 - Bslope * x3;
return {x: x1, y: Bslope * x1 + Bintercept};
}
if ((x4 - x3) === 0) {
Aslope = (y2 - y1)/(x2 - x1);
Aintercept = y1 - Aslope * x1;
return {x: x3, y: Aslope * x3 + Aintercept};
}
}
exports.intersection = intersection;
function pCase(x, y, x1, y1, x2, y2) {
//Code from http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
//Joshua
// Find the dot product of two vectors <A, B>, <C, D>
// Divide by the length squared of <C, D>
// Use scalar project to find param
//
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var len_sq = C * C + D * D;
var param = -1;
if (len_sq !== 0) {
//in case of 0 length line
param = dot / len_sq;
}
return param;
}
exports.pCase = pCase;
@@ -0,0 +1,190 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
function _State () {
}
inherits(_State, fsm._State);
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Scale () {
this.name = 'Scale';
}
inherits(_Scale, _State);
var Scale = new _Scale();
exports.Scale = Scale;
function _Pressed () {
this.name = 'Pressed';
}
inherits(_Pressed, _State);
var Pressed = new _Pressed();
exports.Pressed = Pressed;
function _Pan () {
this.name = 'Pan';
}
inherits(_Pan, _State);
var Pan = new _Pan();
exports.Pan = Pan;
_Ready.prototype.onMouseDown = function (controller) {
controller.scope.pressedX = controller.scope.mouseX;
controller.scope.pressedY = controller.scope.mouseY;
controller.scope.lastPanX = controller.scope.panX;
controller.scope.lastPanY = controller.scope.panY;
controller.changeState(Pressed);
};
_Ready.prototype.onTouchStart = function (controller, msg_type, event) {
if (event.touches.length === 2) {
controller.scope.lastPanX = controller.scope.panX;
controller.scope.lastPanY = controller.scope.panY;
controller.scope.lastScale = controller.scope.current_scale;
var x1 = event.touches[0].screenX;
var y1 = event.touches[0].screenY;
var x2 = event.touches[1].screenX;
var y2 = event.touches[1].screenY;
var d = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
var xb = (x2 + x1) / 2;
var yb = (y2 + y1) / 2;
controller.scope.touch_data = {x1: x1,
y1: y1,
x2: x2,
y2: y2,
d: d,
xb: xb,
yb: yb};
controller.changeState(Pressed);
}
};
_Ready.prototype.onMouseWheel = function (controller, msg_type, $event) {
controller.changeState(Scale);
controller.handle_message(msg_type, $event);
};
_Ready.prototype.onKeyDown = function(controller, msg_type, $event) {
var scope = controller.scope;
if ($event.key === 'd') {
scope.debug.hidden = !scope.debug.hidden;
return;
}
if ($event.key === 'p') {
scope.cursor.hidden = !scope.cursor.hidden;
return;
}
if ($event.key === 'b') {
scope.hide_buttons = !scope.hide_buttons;
return;
}
if ($event.key === 'i') {
scope.hide_interfaces = !scope.hide_interfaces;
return;
}
};
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Scale.prototype.onMouseWheel = function (controller, msg_type, message) {
var delta = message[1];
var new_scale = Math.max(0.1, Math.min(10, (controller.scope.current_scale + delta / 100)));
var new_panX = controller.scope.mouseX - new_scale * ((controller.scope.mouseX - controller.scope.panX) / controller.scope.current_scale);
var new_panY = controller.scope.mouseY - new_scale * ((controller.scope.mouseY - controller.scope.panY) / controller.scope.current_scale);
controller.scope.updateScaledXY();
controller.scope.current_scale = new_scale;
controller.scope.panX = new_panX;
controller.scope.panY = new_panY;
controller.scope.updatePanAndScale();
controller.changeState(Ready);
};
_Pressed.prototype.onMouseUp = function (controller) {
controller.changeState(Ready);
};
_Pressed.prototype.onTouchEnd = _Pressed.prototype.onMouseUp;
_Pressed.prototype.onMouseMove = function (controller, msg_type, $event) {
controller.changeState(Pan);
controller.handle_message(msg_type, $event);
};
_Pressed.prototype.onTouchMove = _Pressed.prototype.onMouseMove;
_Pan.prototype.onMouseMove = function (controller) {
controller.scope.panX = (controller.scope.mouseX - controller.scope.pressedX) + controller.scope.lastPanX;
controller.scope.panY = (controller.scope.mouseY - controller.scope.pressedY) + controller.scope.lastPanY;
controller.scope.updateScaledXY();
controller.scope.updatePanAndScale();
};
_Pan.prototype.onTouchMove = function (controller, msg_type, event) {
if (event.touches.length === 2) {
var x1 = event.touches[0].screenX;
var y1 = event.touches[0].screenY;
var x2 = event.touches[1].screenX;
var y2 = event.touches[1].screenY;
var d = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
var xb = (x2 + x1) / 2;
var yb = (y2 + y1) / 2;
var delta = d - controller.scope.touch_data.d;
controller.scope.panX = (xb - controller.scope.touch_data.xb) + controller.scope.lastPanX;
controller.scope.panY = (yb - controller.scope.touch_data.yb) + controller.scope.lastPanY;
controller.scope.updateScaledXY();
var new_scale = Math.max(0.1, Math.min(10, (controller.scope.lastScale + delta / 100)));
var new_panX = xb - new_scale * ((xb - controller.scope.panX) / controller.scope.lastScale);
var new_panY = yb - new_scale * ((yb - controller.scope.panY) / controller.scope.lastScale);
controller.scope.current_scale = new_scale;
controller.scope.panX = new_panX;
controller.scope.panY = new_panY;
controller.scope.updatePanAndScale();
}
};
_Pan.prototype.onMouseUp = function (controller) {
controller.changeState(Ready);
};
_Pan.prototype.onTouchEnd = _Pan.prototype.onMouseUp;
@@ -0,0 +1,26 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
function _State () {
}
inherits(_State, fsm._State);
{%for state, functions in states%}
function _{{state}} () {
this.name = '{{state}}';
}
inherits(_{{state}}, _State);
var {{state}} = new _{{state}}();
exports.{{state}} = {{state}};
{%endfor%}
{%for state, functions in states%}
{%for fn, transitions in functions%}
_{{state}}.prototype.{{fn}} = function (controller) {
{%for tn in transitions %}
controller.changeState({{tn.to_state}});
{%endfor%}
};
_{{state}}.prototype.{{fn}}.transitions = [{%for t in transitions%}'{{t.to_state}}'{% if not loop.last%}, {%endif%}{%endfor%}];
{%endfor%}
{%endfor%}
File diff suppressed because it is too large Load Diff
+327
View File
@@ -0,0 +1,327 @@
/*
* Hamster.js v1.1.2
* (c) 2013 Monospaced http://monospaced.com
* License: MIT
*/
(function(window, document){
'use strict';
/**
* Hamster
* use this to create instances
* @returns {Hamster.Instance}
* @constructor
*/
var Hamster = function(element) {
return new Hamster.Instance(element);
};
// default event name
Hamster.SUPPORT = 'wheel';
// default DOM methods
Hamster.ADD_EVENT = 'addEventListener';
Hamster.REMOVE_EVENT = 'removeEventListener';
Hamster.PREFIX = '';
// until browser inconsistencies have been fixed...
Hamster.READY = false;
Hamster.Instance = function(element){
if (!Hamster.READY) {
// fix browser inconsistencies
Hamster.normalise.browser();
// Hamster is ready...!
Hamster.READY = true;
}
this.element = element;
// store attached event handlers
this.handlers = [];
// return instance
return this;
};
/**
* create new hamster instance
* all methods should return the instance itself, so it is chainable.
* @param {HTMLElement} element
* @returns {Hamster.Instance}
* @constructor
*/
Hamster.Instance.prototype = {
/**
* bind events to the instance
* @param {Function} handler
* @param {Boolean} useCapture
* @returns {Hamster.Instance}
*/
wheel: function onEvent(handler, useCapture){
Hamster.event.add(this, Hamster.SUPPORT, handler, useCapture);
// handle MozMousePixelScroll in older Firefox
if (Hamster.SUPPORT === 'DOMMouseScroll') {
Hamster.event.add(this, 'MozMousePixelScroll', handler, useCapture);
}
return this;
},
/**
* unbind events to the instance
* @param {Function} handler
* @param {Boolean} useCapture
* @returns {Hamster.Instance}
*/
unwheel: function offEvent(handler, useCapture){
// if no handler argument,
// unbind the last bound handler (if exists)
if (handler === undefined && (handler = this.handlers.slice(-1)[0])) {
handler = handler.original;
}
Hamster.event.remove(this, Hamster.SUPPORT, handler, useCapture);
// handle MozMousePixelScroll in older Firefox
if (Hamster.SUPPORT === 'DOMMouseScroll') {
Hamster.event.remove(this, 'MozMousePixelScroll', handler, useCapture);
}
return this;
}
};
Hamster.event = {
/**
* cross-browser 'addWheelListener'
* @param {Instance} hamster
* @param {String} eventName
* @param {Function} handler
* @param {Boolean} useCapture
*/
add: function add(hamster, eventName, handler, useCapture){
// store the original handler
var originalHandler = handler;
// redefine the handler
handler = function(originalEvent){
if (!originalEvent) {
originalEvent = window.event;
}
// create a normalised event object,
// and normalise "deltas" of the mouse wheel
var event = Hamster.normalise.event(originalEvent),
delta = Hamster.normalise.delta(originalEvent);
// fire the original handler with normalised arguments
return originalHandler(event, delta[0], delta[1], delta[2]);
};
// cross-browser addEventListener
hamster.element[Hamster.ADD_EVENT](Hamster.PREFIX + eventName, handler, useCapture || false);
// store original and normalised handlers on the instance
hamster.handlers.push({
original: originalHandler,
normalised: handler
});
},
/**
* removeWheelListener
* @param {Instance} hamster
* @param {String} eventName
* @param {Function} handler
* @param {Boolean} useCapture
*/
remove: function remove(hamster, eventName, handler, useCapture){
// find the normalised handler on the instance
var originalHandler = handler,
lookup = {},
handlers;
for (var i = 0, len = hamster.handlers.length; i < len; ++i) {
lookup[hamster.handlers[i].original] = hamster.handlers[i];
}
handlers = lookup[originalHandler];
handler = handlers.normalised;
// cross-browser removeEventListener
hamster.element[Hamster.REMOVE_EVENT](Hamster.PREFIX + eventName, handler, useCapture || false);
// remove original and normalised handlers from the instance
for (var h in hamster.handlers) {
if (hamster.handlers[h] === handlers) {
hamster.handlers.splice(h, 1);
break;
}
}
}
};
/**
* these hold the lowest deltas,
* used to normalise the delta values
* @type {Number}
*/
var lowestDelta,
lowestDeltaXY;
Hamster.normalise = {
/**
* fix browser inconsistencies
*/
browser: function normaliseBrowser(){
// detect deprecated wheel events
if (!('onwheel' in document || document.documentMode >= 9)) {
Hamster.SUPPORT = document.onmousewheel !== undefined ?
'mousewheel' : // webkit and IE < 9 support at least "mousewheel"
'DOMMouseScroll'; // assume remaining browsers are older Firefox
}
// detect deprecated event model
if (!window.addEventListener) {
// assume IE < 9
Hamster.ADD_EVENT = 'attachEvent';
Hamster.REMOVE_EVENT = 'detachEvent';
Hamster.PREFIX = 'on';
}
},
/**
* create a normalised event object
* @param {Function} originalEvent
* @returns {Object} event
*/
event: function normaliseEvent(originalEvent){
var event = {
// keep a reference to the original event object
originalEvent: originalEvent,
target: originalEvent.target || originalEvent.srcElement,
type: 'wheel',
deltaMode: originalEvent.type === 'MozMousePixelScroll' ? 0 : 1,
deltaX: 0,
delatZ: 0,
preventDefault: function(){
if (originalEvent.preventDefault) {
originalEvent.preventDefault();
} else {
originalEvent.returnValue = false;
}
},
stopPropagation: function(){
if (originalEvent.stopPropagation) {
originalEvent.stopPropagation();
} else {
originalEvent.cancelBubble = false;
}
}
};
// calculate deltaY (and deltaX) according to the event
// 'mousewheel'
if (originalEvent.wheelDelta) {
event.deltaY = - 1/40 * originalEvent.wheelDelta;
}
// webkit
if (originalEvent.wheelDeltaX) {
event.deltaX = - 1/40 * originalEvent.wheelDeltaX;
}
// 'DomMouseScroll'
if (originalEvent.detail) {
event.deltaY = originalEvent.detail;
}
return event;
},
/**
* normalise 'deltas' of the mouse wheel
* @param {Function} originalEvent
* @returns {Array} deltas
*/
delta: function normaliseDelta(originalEvent){
var delta = 0,
deltaX = 0,
deltaY = 0,
absDelta = 0,
absDeltaXY = 0,
fn;
// normalise deltas according to the event
// 'wheel' event
if (originalEvent.deltaY) {
deltaY = originalEvent.deltaY * -1;
delta = deltaY;
}
if (originalEvent.deltaX) {
deltaX = originalEvent.deltaX;
delta = deltaX * -1;
}
// 'mousewheel' event
if (originalEvent.wheelDelta) {
delta = originalEvent.wheelDelta;
}
// webkit
if (originalEvent.wheelDeltaY) {
deltaY = originalEvent.wheelDeltaY;
}
if (originalEvent.wheelDeltaX) {
deltaX = originalEvent.wheelDeltaX * -1;
}
// 'DomMouseScroll' event
if (originalEvent.detail) {
delta = originalEvent.detail * -1;
}
// Don't return NaN
if (delta === 0) {
return [0, 0, 0];
}
// look for lowest delta to normalize the delta values
absDelta = Math.abs(delta);
if (!lowestDelta || absDelta < lowestDelta) {
lowestDelta = absDelta;
}
absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
if (!lowestDeltaXY || absDeltaXY < lowestDeltaXY) {
lowestDeltaXY = absDeltaXY;
}
// convert deltas to whole numbers
fn = delta > 0 ? 'floor' : 'ceil';
delta = Math[fn](delta / lowestDelta);
deltaX = Math[fn](deltaX / lowestDeltaXY);
deltaY = Math[fn](deltaY / lowestDeltaXY);
return [delta, deltaX, deltaY];
}
};
if (typeof window.define === 'function' && window.define.amd) {
// AMD
window.define('hamster', [], function(){
return Hamster;
});
} else if (typeof exports === 'object') {
// CommonJS
module.exports = Hamster;
} else {
// Browser global
window.Hamster = Hamster;
}
})(window, window.document);
+52
View File
@@ -0,0 +1,52 @@
/*
* angular-mousewheel v1.0.5
* (c) 2013 Monospaced http://monospaced.com
* License: MIT
*/
angular.module('monospaced.mousewheel', [])
.directive('msdWheel', ['$parse', function($parse){
return {
restrict: 'A, C',
link: function(scope, element, attr) {
var expr = $parse(attr['msdWheel']),
fn = function(event, delta, deltaX, deltaY){
scope.$apply(function(){
expr(scope, {
$event: event,
$delta: delta,
$deltaX: deltaX,
$deltaY: deltaY
});
});
},
hamster;
if (typeof Hamster === 'undefined') {
// fallback to standard wheel event
element.bind('wheel', function(event){
scope.$apply(function() {
expr(scope, {
$event: event
});
});
});
return;
}
// don't create multiple Hamster instances per element
if (!(hamster = element.data('hamster'))) {
hamster = Hamster(element[0]);
element.data('hamster', hamster);
}
// bind Hamster wheel event
hamster.wheel(fn);
// unbind Hamster wheel event
scope.$on('$destroy', function(){
hamster.unwheel(fn);
});
}
};
}]);
+87
View File
@@ -0,0 +1,87 @@
/*
* ngTouch.js v1.0.2
* (c) 2015 Mark Topper
* License: MIT
*/
"use strict";
angular.module("ngTouch", [])
.directive("ngTouchstart", function () {
return {
controller: ["$scope", "$element", function ($scope, $element) {
$element.bind("touchstart", onTouchStart);
function onTouchStart(event) {
var method = $element.attr("ng-touchstart");
$scope.$event = event;
$scope.$apply(method);
}
}]
}
})
.directive("ngTouchmove", function () {
return {
controller: ["$scope", "$element", function ($scope, $element) {
$element.bind("touchstart", onTouchStart);
function onTouchStart(event) {
event.preventDefault();
$element.bind("touchmove", onTouchMove);
$element.bind("touchend", onTouchEnd);
}
function onTouchMove(event) {
var method = $element.attr("ng-touchmove");
$scope.$event = event;
$scope.$apply(method);
}
function onTouchEnd(event) {
event.preventDefault();
$element.unbind("touchmove", onTouchMove);
$element.unbind("touchend", onTouchEnd);
}
}]
}
})
.directive("ngTouchend", function () {
return {
controller: ["$scope", "$element", function ($scope, $element) {
$element.bind("touchend", onTouchEnd);
function onTouchEnd(event) {
var method = $element.attr("ng-touchend");
$scope.$event = event;
$scope.$apply(method);
}
}]
}
})
.directive("ngTap", function () {
return {
controller: ["$scope", "$element", function ($scope, $element) {
var moved = false;
$element.bind("touchstart", onTouchStart);
function onTouchStart(event) {
$element.bind("touchmove", onTouchMove);
$element.bind("touchend", onTouchEnd);
}
function onTouchMove(event) {
moved = true;
}
function onTouchEnd(event) {
$element.unbind("touchmove", onTouchMove);
$element.unbind("touchend", onTouchEnd);
if (!moved) {
var method = $element.attr("ng-tap");
$scope.$apply(method);
}
}
}]
}
});
@@ -0,0 +1,365 @@
// MIT License:
//
// Copyright (c) 2010-2012, Joe Walnes
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* This behaves like a WebSocket in every way, except if it fails to connect,
* or it gets disconnected, it will repeatedly poll until it successfully connects
* again.
*
* It is API compatible, so when you have:
* ws = new WebSocket('ws://....');
* you can replace with:
* ws = new ReconnectingWebSocket('ws://....');
*
* The event stream will typically look like:
* onconnecting
* onopen
* onmessage
* onmessage
* onclose // lost connection
* onconnecting
* onopen // sometime later...
* onmessage
* onmessage
* etc...
*
* It is API compatible with the standard WebSocket API, apart from the following members:
*
* - `bufferedAmount`
* - `extensions`
* - `binaryType`
*
* Latest version: https://github.com/joewalnes/reconnecting-websocket/
* - Joe Walnes
*
* Syntax
* ======
* var socket = new ReconnectingWebSocket(url, protocols, options);
*
* Parameters
* ==========
* url - The url you are connecting to.
* protocols - Optional string or array of protocols.
* options - See below
*
* Options
* =======
* Options can either be passed upon instantiation or set after instantiation:
*
* var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
*
* or
*
* var socket = new ReconnectingWebSocket(url);
* socket.debug = true;
* socket.reconnectInterval = 4000;
*
* debug
* - Whether this instance should log debug messages. Accepts true or false. Default: false.
*
* automaticOpen
* - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
*
* reconnectInterval
* - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
*
* maxReconnectInterval
* - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
*
* reconnectDecay
* - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
*
* timeoutInterval
* - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
*
*/
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module !== 'undefined' && module.exports){
module.exports = factory();
} else {
global.ReconnectingWebSocket = factory();
}
})(this, function () {
if (!('WebSocket' in window)) {
return;
}
function ReconnectingWebSocket(url, protocols, options) {
// Default settings
var settings = {
/** Whether this instance should log debug messages. */
debug: false,
/** Whether or not the websocket should attempt to connect immediately upon instantiation. */
automaticOpen: true,
/** The number of milliseconds to delay before attempting to reconnect. */
reconnectInterval: 1000,
/** The maximum number of milliseconds to delay a reconnection attempt. */
maxReconnectInterval: 30000,
/** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
reconnectDecay: 1.5,
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
timeoutInterval: 2000,
/** The maximum number of reconnection attempts to make. Unlimited if null. */
maxReconnectAttempts: null,
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
binaryType: 'blob'
}
if (!options) { options = {}; }
// Overwrite and define settings with options if they exist.
for (var key in settings) {
if (typeof options[key] !== 'undefined') {
this[key] = options[key];
} else {
this[key] = settings[key];
}
}
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
this.url = url;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
this.reconnectAttempts = 0;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
this.readyState = WebSocket.CONNECTING;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
this.protocol = null;
// Private state variables
var self = this;
var ws;
var forcedClose = false;
var timedOut = false;
var eventTarget = document.createElement('div');
// Wire up "on*" properties as event handlers
eventTarget.addEventListener('open', function(event) { self.onopen(event); });
eventTarget.addEventListener('close', function(event) { self.onclose(event); });
eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
eventTarget.addEventListener('error', function(event) { self.onerror(event); });
// Expose the API required by EventTarget
this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
/**
* This function generates an event that is compatible with standard
* compliant browsers and IE9 - IE11
*
* This will prevent the error:
* Object doesn't support this action
*
* http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
* @param s String The name that the event should use
* @param args Object an optional object that the event will use
*/
function generateEvent(s, args) {
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(s, false, false, args);
return evt;
};
this.open = function (reconnectAttempt) {
ws = new WebSocket(self.url, protocols || []);
ws.binaryType = this.binaryType;
if (reconnectAttempt) {
if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
return;
}
} else {
eventTarget.dispatchEvent(generateEvent('connecting'));
this.reconnectAttempts = 0;
}
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
}
var localWs = ws;
var timeout = setTimeout(function() {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
}
timedOut = true;
localWs.close();
timedOut = false;
}, self.timeoutInterval);
ws.onopen = function(event) {
clearTimeout(timeout);
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onopen', self.url);
}
self.protocol = ws.protocol;
self.readyState = WebSocket.OPEN;
self.reconnectAttempts = 0;
var e = generateEvent('open');
e.isReconnect = reconnectAttempt;
reconnectAttempt = false;
eventTarget.dispatchEvent(e);
};
ws.onclose = function(event) {
clearTimeout(timeout);
ws = null;
if (forcedClose) {
self.readyState = WebSocket.CLOSED;
eventTarget.dispatchEvent(generateEvent('close'));
} else {
self.readyState = WebSocket.CONNECTING;
var e = generateEvent('connecting');
e.code = event.code;
e.reason = event.reason;
e.wasClean = event.wasClean;
eventTarget.dispatchEvent(e);
if (!reconnectAttempt && !timedOut) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onclose', self.url);
}
eventTarget.dispatchEvent(generateEvent('close'));
}
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
setTimeout(function() {
self.reconnectAttempts++;
self.open(true);
}, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
}
};
ws.onmessage = function(event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
}
var e = generateEvent('message');
e.data = event.data;
eventTarget.dispatchEvent(e);
};
ws.onerror = function(event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
}
eventTarget.dispatchEvent(generateEvent('error'));
};
}
// Whether or not to create a websocket upon instantiation
if (this.automaticOpen == true) {
this.open(false);
}
/**
* Transmits data to the server over the WebSocket connection.
*
* @param data a text string, ArrayBuffer or Blob to send to the server.
*/
this.send = function(data) {
if (ws) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'send', self.url, data);
}
return ws.send(data);
} else {
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
}
};
/**
* Closes the WebSocket connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
this.close = function(code, reason) {
// Default CLOSE_NORMAL code
if (typeof code == 'undefined') {
code = 1000;
}
forcedClose = true;
if (ws) {
ws.close(code, reason);
}
};
/**
* Additional public API method to refresh the connection if still open (close, re-open).
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
this.refresh = function() {
if (ws) {
ws.close();
}
};
}
/**
* An event listener to be called when the WebSocket connection's readyState changes to OPEN;
* this indicates that the connection is ready to send and receive data.
*/
ReconnectingWebSocket.prototype.onopen = function(event) {};
/** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
ReconnectingWebSocket.prototype.onclose = function(event) {};
/** An event listener to be called when a connection begins being attempted. */
ReconnectingWebSocket.prototype.onconnecting = function(event) {};
/** An event listener to be called when a message is received from the server. */
ReconnectingWebSocket.prototype.onmessage = function(event) {};
/** An event listener to be called when an error occurs. */
ReconnectingWebSocket.prototype.onerror = function(event) {};
/**
* Whether all instances of ReconnectingWebSocket should log debug messages.
* Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
*/
ReconnectingWebSocket.debugAll = false;
ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
ReconnectingWebSocket.OPEN = WebSocket.OPEN;
ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
return ReconnectingWebSocket;
});
+250
View File
@@ -0,0 +1,250 @@
/**
* @license svg-crowbar
* (c) 2013 The New York Times
* License: MIT
*/
function svg_crowbar () {
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
window.URL = (window.URL || window.webkitURL);
var body = document.body;
var prefix = {
xmlns: "http://www.w3.org/2000/xmlns/",
xlink: "http://www.w3.org/1999/xlink",
svg: "http://www.w3.org/2000/svg"
}
initialize();
function initialize() {
var documents = [window.document],
SVGSources = [];
iframes = document.querySelectorAll("iframe"),
objects = document.querySelectorAll("object");
[].forEach.call(iframes, function(el) {
try {
if (el.contentDocument) {
documents.push(el.contentDocument);
}
} catch(err) {
console.log(err)
}
});
[].forEach.call(objects, function(el) {
try {
if (el.contentDocument) {
documents.push(el.contentDocument);
}
} catch(err) {
console.log(err)
}
});
documents.forEach(function(doc) {
var styles = getStyles(doc);
var newSources = getSources(doc, styles);
// because of prototype on NYT pages
for (var i = 0; i < newSources.length; i++) {
SVGSources.push(newSources[i]);
};
})
if (SVGSources.length > 1) {
createPopover(SVGSources);
} else if (SVGSources.length > 0) {
download(SVGSources[0]);
} else {
alert("The Crowbar couldnt find any SVG nodes.");
}
}
function createPopover(sources) {
cleanup();
sources.forEach(function(s1) {
sources.forEach(function(s2) {
if (s1 !== s2) {
if ((Math.abs(s1.top - s2.top) < 38) && (Math.abs(s1.left - s2.left) < 38)) {
s2.top += 38;
s2.left += 38;
}
}
})
});
var buttonsContainer = document.createElement("div");
body.appendChild(buttonsContainer);
buttonsContainer.setAttribute("class", "svg-crowbar");
buttonsContainer.style["z-index"] = 1e7;
buttonsContainer.style["position"] = "absolute";
buttonsContainer.style["top"] = 0;
buttonsContainer.style["left"] = 0;
var background = document.createElement("div");
body.appendChild(background);
background.setAttribute("class", "svg-crowbar");
background.style["background"] = "rgba(255, 255, 255, 0.7)";
background.style["position"] = "fixed";
background.style["left"] = 0;
background.style["top"] = 0;
background.style["width"] = "100%";
background.style["height"] = "100%";
sources.forEach(function(d, i) {
var buttonWrapper = document.createElement("div");
buttonsContainer.appendChild(buttonWrapper);
buttonWrapper.setAttribute("class", "svg-crowbar");
buttonWrapper.style["position"] = "absolute";
buttonWrapper.style["top"] = (d.top + document.body.scrollTop) + "px";
buttonWrapper.style["left"] = (document.body.scrollLeft + d.left) + "px";
buttonWrapper.style["padding"] = "4px";
buttonWrapper.style["border-radius"] = "3px";
buttonWrapper.style["color"] = "white";
buttonWrapper.style["text-align"] = "center";
buttonWrapper.style["font-family"] = "'Helvetica Neue'";
buttonWrapper.style["background"] = "rgba(0, 0, 0, 0.8)";
buttonWrapper.style["box-shadow"] = "0px 4px 18px rgba(0, 0, 0, 0.4)";
buttonWrapper.style["cursor"] = "move";
buttonWrapper.textContent = "SVG #" + i + ": " + (d.id ? "#" + d.id : "") + (d.class ? "." + d.class : "");
var button = document.createElement("button");
buttonWrapper.appendChild(button);
button.setAttribute("data-source-id", i)
button.style["width"] = "150px";
button.style["font-size"] = "12px";
button.style["line-height"] = "1.4em";
button.style["margin"] = "5px 0 0 0";
button.textContent = "Download";
button.onclick = function(el) {
// console.log(el, d, i, sources)
download(d);
};
});
}
function cleanup() {
var crowbarElements = document.querySelectorAll(".svg-crowbar");
[].forEach.call(crowbarElements, function(el) {
el.parentNode.removeChild(el);
});
}
function getSources(doc, styles) {
var svgInfo = [],
svgs = doc.querySelectorAll("svg");
styles = (styles === undefined) ? "" : styles;
[].forEach.call(svgs, function (svg) {
svg.setAttribute("version", "1.1");
var defsEl = document.createElement("defs");
svg.insertBefore(defsEl, svg.firstChild); //TODO .insert("defs", ":first-child")
// defsEl.setAttribute("class", "svg-crowbar");
var styleEl = document.createElement("style")
defsEl.appendChild(styleEl);
styleEl.setAttribute("type", "text/css");
// removing attributes so they aren't doubled up
svg.removeAttribute("xmlns");
svg.removeAttribute("xlink");
// These are needed for the svg
if (!svg.hasAttributeNS(prefix.xmlns, "xmlns")) {
svg.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg);
}
if (!svg.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) {
svg.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink);
}
var source = (new XMLSerializer()).serializeToString(svg).replace('</style>', '<![CDATA[' + styles + ']]></style>');
var rect = svg.getBoundingClientRect();
svgInfo.push({
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height,
class: svg.getAttribute("class"),
id: svg.getAttribute("id"),
childElementCount: svg.childElementCount,
source: [doctype + source]
});
});
return svgInfo;
}
function download(source) {
var filename = "untitled";
if (source.id) {
filename = source.id;
} else if (source.class) {
filename = source.class;
} else if (window.document.title) {
filename = window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase();
}
var url = window.URL.createObjectURL(new Blob(source.source, { "type" : "text\/xml" }));
var a = document.createElement("a");
body.appendChild(a);
a.setAttribute("class", "svg-crowbar");
a.setAttribute("download", filename + ".svg");
a.setAttribute("href", url);
a.style["display"] = "none";
a.click();
setTimeout(function() {
window.URL.revokeObjectURL(url);
}, 10);
}
function getStyles(doc) {
var styles = "",
styleSheets = doc.styleSheets;
if (styleSheets) {
for (var i = 0; i < styleSheets.length; i++) {
processStyleSheet(styleSheets[i]);
}
}
function processStyleSheet(ss) {
if (ss.cssRules) {
for (var i = 0; i < ss.cssRules.length; i++) {
var rule = ss.cssRules[i];
if (rule.type === 3) {
// Import Rule
processStyleSheet(rule.styleSheet);
} else {
// hack for illustrator crashing on descendent selectors
if (rule.selectorText) {
if (rule.selectorText.indexOf(">") === -1) {
styles += "\n" + rule.cssText;
}
}
}
}
}
}
return styles;
}
}
exports.svg_crowbar = svg_crowbar;
@@ -0,0 +1,11 @@
<rect x=0
y=0
ng-attr-width={{button.width}}
ng-attr-height={{button.height}}
rx=3></rect>
<text ng-attr-x="{{button.width/2}}"
ng-attr-y="{{button.height/2}}"
dy=".3em"
text-anchor="middle">{{button.name}}</text>
@@ -0,0 +1,4 @@
<g ng-attr-transform="translate({{cursor.x}},{{cursor.y}})" ng-attr-class="{{cursor.hidden && 'hidden' || ''}}" >
<line x1="-15" y1="0" x2="15" y2="0" class="cursor"/>
<line x1="0" y1="-15" x2="0" y2="15" class="cursor"/>
</g>
@@ -0,0 +1,32 @@
<g ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug">
<text ng-attr-x="{{graph.right_column}}" y="15">width: {{graph.width}}</text>
<text ng-attr-x="{{graph.right_column}}" y="35">height: {{graph.height}}</text>
<text ng-attr-x="{{graph.right_column}}" y="55">rc: {{graph.right_column}}</text>
<text ng-attr-x="{{graph.right_column}}" y="75">Mouse down: {{onMouseDownResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="95">Mouse up: {{onMouseUpResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="115">Mouse move: {{onMouseMoveResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="135">Mouse over: {{onMouseOverResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="155">Mouse enter: {{onMouseEnterResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="175">Mouse leave: {{onMouseLeaveResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="195">Current scale: {{current_scale.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="215">Pan X: {{panX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="235">Pan Y: {{panY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="255">View State: {{view_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="275">Mouse X: {{mouseX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="295">Mouse Y: {{mouseY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="315">Scaled X: {{scaledX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="335">Scaled Y: {{scaledY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="355">Key: {{last_key}}</text>
<text ng-attr-x="{{graph.right_column}}" y="375">Key Code: {{last_key_code}}</text>
<text ng-attr-x="{{graph.right_column}}" y="395">Move State: {{move_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="415">Selected devices: {{selected_devices.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="435">Selected links: {{selected_links.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="455">Link State: {{link_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="475">Buttons State: {{buttons_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="495">Time State: {{time_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="515">Time Pointer: {{time_pointer}}</text>
<text ng-attr-x="{{graph.right_column}}" y="535">History: {{history.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="555">Touch Data: {{touch_data.xb}} {{touch_data.yb}} {{touch_data.d}}</text>
</g>
@@ -0,0 +1,36 @@
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="{{-50 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{50 + 10}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-50}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{50 * 2}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<circle
cx="0"
cy="0"
ng-attr-r="{{50 + 2}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'selected-conflict' : device.selected ? 'selected' : 'remote-selected' : 'hidden'}}">
</circle>
<circle
cx="0"
cy="0"
ng-attr-r="{{50}}">
</circle>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="75"> {{device.name}} </text>
<text text-anchor="middle" x="0" y="75">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>
@@ -0,0 +1,42 @@
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="{{-15 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{15 + 10}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-15}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{15 * 2}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect
x="-52"
y="-17"
ng-attr-width="{{100 + 4}}"
ng-attr-height="{{30 + 4}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'selected-conflict' : device.selected ? 'selected' : 'remote-selected' : 'hidden'}}"
rx=10>
</rect>
<rect
x="-50"
y="-15"
ng-attr-width="{{100}}"
ng-attr-height="{{30}}"
rx=10>
</rect>
<circle cx="30" cy="0" r=7 />
</circle>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="5"> {{device.name}} </text>
<text text-anchor="middle" x="0" y="5">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>
@@ -0,0 +1,11 @@
<rect x=0
y=0
ng-attr-width={{layer.size}}
ng-attr-height={{layer.size}}
rx=3></rect>
<text ng-attr-x="{{layer.size/2}}"
ng-attr-y="{{layer.size/2}}"
dy=".3em"
text-anchor="middle">{{layer.name}}</text>
@@ -0,0 +1,125 @@
<line ng-attr-x1="{{link.from_device.x}}"
ng-attr-y1="{{link.from_device.y}}"
ng-attr-x2="{{link.to_device !== null ? link.to_device.x : scaledX}}"
ng-attr-y2="{{link.to_device !== null ? link.to_device.y : scaledY}}"
ng-attr-class="{{link.selected && 'selected' || 'hidden'}}"/>
<line ng-attr-x1="{{link.from_device.x}}"
ng-attr-y1="{{link.from_device.y}}"
ng-attr-x2="{{link.to_device !== null ? link.to_device.x : scaledX}}"
ng-attr-y2="{{link.to_device !== null ? link.to_device.y : scaledY}}"
class="{{link.status === null ? 'link' : link.status ? 'link-pass' : 'link-fail'}}"/>
<g ng-if="!debug.hidden && current_scale > 0.5">
<line ng-if="link.to_device !== null && link.plength(scaledX, scaledY) < 100"
ng-attr-x1="{{link.pDistanceLine(scaledX, scaledY).x2}}"
ng-attr-y1="{{link.pDistanceLine(scaledX, scaledY).y2}}"
ng-attr-x2="{{scaledX}}"
ng-attr-y2="{{scaledY}}"
ng-attr-class="debug" />
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
rotate({{link.slope()}})
translate({{link.length()/2}}, 0)">
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
rotate({{link.slope()}})
translate({{link.length()/2}}, 0)">
<line x1="0" y1=-20 x2=0 y2=20 class="debug"/>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
rotate({{link.slope()}})
translate({{link.to_device.size}}, 0)">
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.from_device.x}},
{{link.from_device.y}})
rotate({{link.slope()}})
translate({{-link.from_device.size}}, 0)">
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
</g>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
rotate({{link.slope()}})
translate({{link.to_device.size}}, 0)
rotate(180)
translate(-19, -9)
">
</g>
<g ng-if="(!hide_interfaces && selected_links.length === 0 && selected_interfaces.length === 0) || link.selected || link.to_interface.selected || link.from_interface.selected">
<g ng-if="current_scale > 1.0 && link.to_device !== null"
ng-attr-transform="translate({{link.from_device.x}},
{{link.from_device.y}})
rotate({{link.slope()}})
translate({{-link.length()/2}}, 0)
rotate({{-link.slope()}})
translate(0, -5)
">
<text ng-attr-class="{{link.selected && ! link.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="0"> {{link.name}}</text>
<text text-anchor="middle" x="0" y="0">{{link.name}}{{link.edit_label?'_':''}}</text>
</g>
<g ng-if="current_scale > 1.0 && link.to_device !== null"
ng-attr-transform="translate({{link.from_device.x}},
{{link.from_device.y}})
rotate({{link.slope()}})
translate({{-link.from_interface.dot_d - 20}}, 0)
rotate({{-link.slope()}})
">
<text ng-attr-class="interface {{link.from_interface.selected && ! link.from_interface.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="0"> {{link.from_interface.name}}</text>
<text class="interface" text-anchor="middle" x="0" y="0">{{link.from_interface.name}}{{link.from_interface.edit_label ?'_':''}}</text>
</g>
<g ng-if="current_scale > 1.0 && link.to_device !== null"
ng-attr-transform="translate({{link.from_device.x}},
{{link.from_device.y}})
rotate({{link.slope()}})
translate({{-link.length() + link.to_interface.dot_d + 20}}, 0)
rotate({{-link.slope()}})
">
<text ng-attr-class="interface {{link.to_interface.selected && ! link.to_interface.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="0"> {{link.to_interface.name}}</text>
<text class="interface" text-anchor="middle" x="0" y="0">{{link.to_interface.name}}{{link.to_interface.edit_label?'_':''}}</text>
</g>
<g ng-if="current_scale > 1.0 && link.to_device !== null">
<circle ng-attr-cx="{{link.from_interface.dot_x}}"
ng-attr-cy="{{link.from_interface.dot_y}}"
r=14
ng-attr-class="{{link.from_interface.selected && ! link.from_interface.edit_label ? 'selected' : 'hidden'}}" ></circle>
<circle ng-attr-cx="{{link.from_interface.dot_x}}"
ng-attr-cy="{{link.from_interface.dot_y}}"
r=10
class="interface" ></circle>
<circle ng-attr-cx="{{link.to_interface.dot_x}}"
ng-attr-cy="{{link.to_interface.dot_y}}"
r=14
ng-attr-class="{{link.to_interface.selected && ! link.to_interface.edit_label ? 'selected' : 'hidden'}}" ></circle>
<circle ng-attr-cx="{{link.to_interface.dot_x}}"
ng-attr-cy="{{link.to_interface.dot_y}}"
r=10
class="interface" ></circle>
</g>
</g> <!-- end hide_interfaces -->
@@ -0,0 +1,10 @@
<line ng-attr-x1="-100000"
ng-attr-y1="0"
ng-attr-x2="100000"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="-100000"
ng-attr-x2="0"
ng-attr-y2="100000"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
@@ -0,0 +1,67 @@
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="{{-50 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{50 + 10}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-50}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{50 * 2}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect
x="-52"
y="-52"
ng-attr-width="{{100 + 4}}"
ng-attr-height="{{100 + 4}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'selected-conflict' : device.selected ? 'selected' : 'remote-selected' : 'hidden'}}"
rx=10>
</rect>
<rect
x="-50"
y="-50"
ng-attr-width="{{100}}"
ng-attr-height="{{100}}"
rx=10
class="background">
</rect>
<rect
x="-50"
y="-50"
ng-attr-width="{{100}}"
ng-attr-height="{{30}}"
rx=10>
</rect>
<rect
x="-50"
y="-15"
ng-attr-width="{{100}}"
ng-attr-height="{{30}}"
rx=10>
</rect>
<rect
x="-50"
y="20"
ng-attr-width="{{100}}"
ng-attr-height="{{30}}"
rx=10>
</rect>
<circle cx="30" cy="-35" r=7 />
<circle cx="30" cy="0" r=7 />
<circle cx="30" cy="35" r=7 />
</circle>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="75"> {{device.name}} </text>
<text text-anchor="middle" x="0" y="75">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>
@@ -0,0 +1,59 @@
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="{{-50 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{50 + 10}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-50}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{50 * 2}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<circle
cx="0"
cy="0"
ng-attr-r="{{50 + 2}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'selected-conflict' : device.selected ? 'selected' : 'remote-selected' : 'hidden'}}">
</circle>
<circle
cx="0"
cy="0"
ng-attr-r="{{50}}">
</circle>
<g transform="rotate(45)">
<line ng-attr-x1="12"
ng-attr-y1="0"
ng-attr-x2="{{50-18}}"
ng-attr-y2="0"/>
<line ng-attr-x1="-12"
ng-attr-y1="0"
ng-attr-x2="{{-50+18}}"
ng-attr-y2="0"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(0) translate({{50-22}}, -20) scale(2.0)"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(180) translate({{50-22}}, -20) scale(2.0)"/>
<line ng-attr-x1="0"
ng-attr-y1="17"
ng-attr-x2="0"
ng-attr-y2="{{50-5}}"/>
<line ng-attr-x1="0"
ng-attr-y1="-17"
ng-attr-x2="0"
ng-attr-y2="{{-50+5}}"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(90) translate(-22, -20) scale(2.0)" />
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(270) translate(-22, -20) scale(2.0)"/>
</g>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="0"> {{device.name}}</text>
<text text-anchor="middle" x="0" y="0">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>
@@ -0,0 +1,10 @@
<g ng-if="device.working">
<path ng-attr-transform="translate({{-device.width}}, {{-device.height}}) rotate({{frame/3}})" class="status" ng-attr-d="{{device.describeArc(0, 0, 10, 0, 270)}}"/>
</g>
<g ng-if="!device.working">
<circle ng-attr-cx="{{-device.width}}"
ng-attr-cy="{{-device.height}}"
r=10
ng-attr-class="{{device.status === null ? 'hidden' : device.status ? 'pass': 'fail'}} status">
</circle>
</g>
@@ -0,0 +1,11 @@
<rect x=0
y=0
ng-attr-width={{stencil.size}}
ng-attr-height={{stencil.size}}
rx=3></rect>
<text ng-attr-x="{{stencil.size/2}}"
ng-attr-y="{{stencil.size/2}}"
dy=".3em"
text-anchor="middle">{{stencil.name}}</text>
@@ -0,0 +1,61 @@
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<line ng-attr-x1="0"
ng-attr-y1="{{-50 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{50 + 10}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-50}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{50 * 2}}"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
<rect
x="-52"
y="-52"
ng-attr-width="{{100 + 4}}"
ng-attr-height="{{100 + 4}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'selected-conflict' : device.selected ? 'selected' : 'remote-selected' : 'hidden'}}"
rx=10>
</rect>
<rect
x="-50"
y="-50"
ng-attr-width="{{100}}"
ng-attr-height="{{100}}"
rx=10>
</rect>
<line ng-attr-x1="2"
ng-attr-y1="-28"
ng-attr-x2="38"
ng-attr-y2="-28"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(0) translate(28, -48) scale(2.0)"/>
<line ng-attr-x1="2"
ng-attr-y1="14"
ng-attr-x2="38"
ng-attr-y2="14"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(0) translate(28, -6) scale(2.0)"/>
<line ng-attr-x1="-2"
ng-attr-y1="28"
ng-attr-x2="-38"
ng-attr-y2="28"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(180) translate(28, -48) scale(2.0)"/>
<line ng-attr-x1="-2"
ng-attr-y1="-14"
ng-attr-x2="-38"
ng-attr-y2="-14"/>
<polygon points="0 0, 10 10, 0 20" ng-attr-transform="rotate(180) translate(28, -6) scale(2.0)"/>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"
y="0"> {{device.name}} </text>
<text text-anchor="middle" x="0" y="0">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>
@@ -0,0 +1,15 @@
<g ng-attr-transform="translate({{-device.width}}, {{-device.height}})">
<g ng-repeat="task in device.tasks | limitTo: -7">
<g ng-if="task.working && current_scale > 0.5">
<path ng-attr-transform="translate({{$index * 12 + 17}}, -5 ) rotate({{frame/3}})" class="status" ng-attr-d="{{task.describeArc(0, 0, 5, 0, 270)}}"/>
</g>
<g ng-if="!task.working && current_scale > 0.5">
<circle ng-attr-cx="{{$index * 12 + 17}}"
ng-attr-cy="-5"
r=5
ng-attr-class="{{task.status === null ? 'hidden' : task.status ? 'pass': 'fail'}} status"
</circle>
</g>
</g>
</g>
@@ -0,0 +1,3 @@
<g ng-attr-transform="translate({{touch.screenX}},{{touch.screenY}})" ng-attr-class="touch {{touch.hidden && 'hidden' || ''}}" >
<circle cx=0 cy=0 r=40></circle>
</g>
+12
View File
@@ -0,0 +1,12 @@
from django.contrib import admin
{%for model in models%}
from {{app}}.models import {{model.name}}
{%endfor%}
{%for model in models%}
class {{model.name}}Admin(admin.ModelAdmin):
fields = ({%for field in model.fields%}{%if not field.pk%}'{{field.name}}',{%endif%}{%endfor%})
raw_id_fields = ({%for field in model.fields%}{%if field.ref%}'{{field.name}}',{%endif%}{%endfor%})
admin.site.register({{model.name}}, {{model.name}}Admin)
{%endfor%}
+15
View File
@@ -0,0 +1,15 @@
from django.db import models
{%for model in models%}
class {{model.name}}(models.Model):
{%for field in model.fields%}{{field.name}} = models.{{field.type}}( {%if field.ref%}'{{field.ref}}', {%endif%}{%if field.pk%}primary_key=True, {%endif%} {%if field.len%}max_length={{field.len}}, {%endif%}{%if field.related_name%}related_name='{{field.related_name}}', {%endif%}{%if field.default is defined%}default={{field.default}}{%endif%})
{%endfor%}
{%if model.display%}
def __unicode__(self):
return self.{{model.display}}
{%endif%}
{%endfor%}
@@ -0,0 +1,11 @@
<ul>
<li><a href="/static/prototype/index.html">New</a></li>
{%for o in topologies%}
<li>
<a href="/static/prototype/index.html#!?topology_id={{o.pk}}">{{o.pk}} {{o}}</a>
{%for device in o.device_set.all%}
{{device}}
{%endfor%}
</li>
{%endfor%}
</ul>
+15
View File
@@ -0,0 +1,15 @@
from django.test import TestCase
# Create your tests here.
from awx.network_ui.models import Topology, Device, Interface, MessageType, Link
class TestToString(TestCase):
def test(self):
print str(Topology(name='foo'))
print str(Device(name='foo'))
print str(Device(name='foo'))
print str(Interface(name='foo'))
print str(MessageType(name='foo'))
+11
View File
@@ -0,0 +1,11 @@
from django.conf.urls import include, url
import sys
from . import views
import awx.network_ui.routing
app_name = 'network_ui'
urlpatterns = [
url(r'^$', views.index, name='index'),
]
+4
View File
@@ -0,0 +1,4 @@
def transform_dict(dict_map, d):
return {to_key: d[from_key] for from_key, to_key in dict_map.iteritems()}
+8
View File
@@ -0,0 +1,8 @@
from django.shortcuts import render
# Create your views here.
from .models import Topology
def index(request):
return render(request, "network_ui/index.html", dict(topologies=Topology.objects.all().order_by('-pk')))