mirror of
https://github.com/ZwareBear/awx.git
synced 2026-05-14 15:58:38 -05:00
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:
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
alter table prototype_device add constraint prototype_device_topology_id_unique unique (topology_id, id);
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Executable
+3
@@ -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%}
|
||||
+33134
File diff suppressed because it is too large
Load Diff
+327
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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 couldn’t 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>
|
||||
@@ -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%}
|
||||
|
||||
@@ -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>
|
||||
@@ -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'))
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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')))
|
||||
Reference in New Issue
Block a user