Begins network-ui prototype integration into Tower UI.

* Moves network ui into a directive
* Adds awxNet prefix to network ui directives
* Adds a module to integrate the stand alone network UI with
    Tower UI.
* Adds reconnectingwebsocket to webpack bundle
* Adds configuration for webpack
* Moves ngTouch and hamsterjs to webpack vendor bundle
* Moves angular to network UI vendor bundle
* Adds ui-router dependency
* Changes CSS to BEM style
* Adds unique id sequences for devices and links on Topology and interfaces on Device
* Adds group widget with move, resize, delete, and edit label support
This commit is contained in:
Ben Thomasson
2017-05-09 17:07:13 -04:00
committed by Ben Thomasson
parent 640e687f3e
commit d0e402c39a
78 changed files with 2839 additions and 1007 deletions

View File

@@ -6,8 +6,8 @@ all: models admin
models:
jinja2 templates/models.pyt designs/models.yml > models.py
autopep8 -i models.py
autopep8 -i models.py --ignore-local-config --max-line-length 160
admin:
jinja2 templates/admin.pyt designs/models.yml > admin.py
autopep8 -i admin.py
autopep8 -i admin.py --ignore-local-config --max-line-length 160

View File

@@ -14,9 +14,13 @@ from awx.network_ui.models import MessageType
from awx.network_ui.models import Interface
from awx.network_ui.models import Group
from awx.network_ui.models import GroupDevice
class DeviceAdmin(admin.ModelAdmin):
fields = ('topology', 'name', 'x', 'y', 'id', 'type',)
fields = ('topology', 'name', 'x', 'y', 'id', 'type', 'interface_id_seq',)
raw_id_fields = ('topology',)
@@ -32,8 +36,8 @@ admin.site.register(Link, LinkAdmin)
class TopologyAdmin(admin.ModelAdmin):
fields = ('name', 'scale', 'panX', 'panY',)
raw_id_fields = ()
fields = ('name', 'scale', 'panX', 'panY', 'device_id_seq', 'link_id_seq', 'group_id_seq',)
raw_id_fields = ('device_id_seq',)
admin.site.register(Topology, TopologyAdmin)
@@ -69,3 +73,19 @@ class InterfaceAdmin(admin.ModelAdmin):
admin.site.register(Interface, InterfaceAdmin)
class GroupAdmin(admin.ModelAdmin):
fields = ('id', 'name', 'x1', 'y1', 'x2', 'y2', 'topology',)
raw_id_fields = ('id', 'y1', 'x2', 'topology',)
admin.site.register(Group, GroupAdmin)
class GroupDeviceAdmin(admin.ModelAdmin):
fields = ('group', 'device',)
raw_id_fields = ('group', 'device',)
admin.site.register(GroupDevice, GroupDeviceAdmin)

View File

@@ -2,6 +2,8 @@
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.models import Group as DeviceGroup
from awx.network_ui.models import GroupDevice as GroupDeviceMap
from awx.network_ui.serializers import yaml_serialize_topology
import urlparse
from django.db.models import Q
@@ -9,10 +11,11 @@ from collections import defaultdict
from django.conf import settings
import math
import random
import logging
from awx.network_ui.utils import transform_dict
from pprint import pprint
import dpath.util
from pprint import pformat
import json
import time
@@ -33,6 +36,8 @@ SPACING = 200
RACK_SPACING = 50
settings.RECORDING = False
logger = logging.getLogger("awx.network_ui.consumers")
def circular_layout(topology_id):
n = Device.objects.filter(topology_id=topology_id).count()
@@ -172,22 +177,17 @@ def tier_layout(topology_id):
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:
@@ -211,8 +211,6 @@ def tier_layout(topology_id):
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:
@@ -227,7 +225,7 @@ def tier_layout(topology_id):
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.y = SPACING * 3 + i * RACK_SPACING
device.save()
send_snapshot(Group("topology-%s" % topology_id), topology_id)
@@ -288,6 +286,9 @@ class _Persistence(object):
d.y = device['y']
d.type = device['type']
d.save()
(Topology.objects
.filter(topology_id=topology_id, device_id_seq__lt=device['id'])
.update(device_id_seq=device['id']))
def onDeviceDestroy(self, device, topology_id, client_id):
Device.objects.filter(topology_id=topology_id, id=device['id']).delete()
@@ -313,6 +314,11 @@ class _Persistence(object):
topology_id=topology_id).pk,
id=interface['id'],
defaults=dict(name=interface['name']))
(Device.objects
.filter(id=interface['device_id'],
topology_id=topology_id,
interface_id_seq__lt=interface['id'])
.update(interface_id_seq=interface['id']))
def onLinkCreate(self, link, topology_id, client_id):
device_map = dict(Device.objects
@@ -326,6 +332,9 @@ class _Persistence(object):
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)
(Topology.objects
.filter(topology_id=topology_id, link_id_seq__lt=link['id'])
.update(link_id_seq=link['id']))
def onLinkDestroy(self, link, topology_id, client_id):
device_map = dict(Device.objects
@@ -370,13 +379,13 @@ class _Persistence(object):
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)])})
DeviceGroup("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])})
DeviceGroup("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)])})
DeviceGroup("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)
@@ -408,6 +417,53 @@ class _Persistence(object):
onMouseWheelEvent = write_event
onKeyEvent = write_event
def onGroupCreate(self, group, topology_id, client_id):
group = transform_dict(dict(x1='x1',
y1='y1',
x2='x2',
y2='y2',
name='name',
id='id'), group)
d, _ = DeviceGroup.objects.get_or_create(topology_id=topology_id, id=group['id'], defaults=group)
d.x1 = group['x1']
d.y1 = group['y1']
d.x2 = group['x2']
d.y2 = group['y2']
d.save()
(Topology.objects
.filter(topology_id=topology_id, group_id_seq__lt=group['id'])
.update(group_id_seq=group['id']))
def onGroupDestroy(self, group, topology_id, client_id):
DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).delete()
def onGroupLabelEdit(self, group, topology_id, client_id):
DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).update(name=group['name'])
def onGroupMove(self, group, topology_id, client_id):
DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).update(x1=group['x1'],
y1=group['y1'],
x2=group['x2'],
y2=group['y2'])
def onGroupMembership(self, group_membership, topology_id, client_id):
members = set(group_membership['members'])
group = DeviceGroup.objects.get(topology_id=topology_id, id=group_membership['id'])
existing = set(GroupDeviceMap.objects.filter(group=group).values_list('device__id', flat=True))
new = members - existing
removed = existing - members
GroupDeviceMap.objects.filter(group__group_id=group.group_id,
device__id__in=list(removed)).delete()
device_map = dict(Device.objects.filter(topology_id=topology_id, id__in=list(new)).values_list('id', 'device_id'))
new_entries = []
for i in new:
new_entries.append(GroupDeviceMap(group=group,
device_id=device_map[i]))
if new_entries:
GroupDeviceMap.objects.bulk_create(new_entries)
persistence = _Persistence()
@@ -525,7 +581,9 @@ class _Discovery(object):
def onFacts(self, message, topology_id):
send_updates = False
print message['key']
logger.info("onFacts message key %s", message['key'])
logger.info("onFacts message %s", pformat(message))
return
name = message['key']
device, created = Device.objects.get_or_create(topology_id=topology_id,
name=name,
@@ -538,11 +596,14 @@ class _Discovery(object):
device.id = device.pk
device.save()
send_updates = True
print "Created device ", device
logger.info("onFacts Created device %s", device)
interfaces = dpath.util.get(message, '/value/ansible_local/lldp/lldp') or []
try:
interfaces = dpath.util.get(message, '/value/ansible_local/lldp/lldp')
except KeyError:
interfaces = []
for interface in interfaces:
pprint(interface)
logger.info("onFacts %s: ", pformat(interface))
for inner_interface in interface.get('interface', []):
name = inner_interface.get('name')
if not name:
@@ -662,7 +723,10 @@ def ws_connect(message):
name='name',
panX='panX',
panY='panY',
scale='scale'), topology.__dict__)
scale='scale',
link_id_seq='link_id_seq',
device_id_seq='device_id_seq',
group_id_seq='group_id_seq'), topology.__dict__)
message.reply_channel.send({"text": json.dumps(["Topology", topology_data])})
send_snapshot(message.reply_channel, topology_id)
@@ -696,9 +760,19 @@ def send_snapshot(channel, topology_id):
'to_device__id',
'from_interface__id',
'to_interface__id'))]
groups = list(DeviceGroup.objects
.filter(topology_id=topology_id).values())
group_map = {g['id']: g for g in groups}
for group_id, device_id in GroupDeviceMap.objects.filter(group__topology_id=topology_id).values_list('group__id', 'device__id'):
if 'members' not in group_map[group_id]:
group_map[group_id]['members'] = [device_id]
else:
group_map[group_id]['members'].append(device_id)
snapshot = dict(sender=0,
devices=devices,
links=links)
links=links,
groups=groups)
channel.send({"text": json.dumps(["Snapshot", snapshot])})

View File

@@ -1,4 +1,4 @@
app: prototype
app: awx.network_ui
external_models: []
models:
- display: name
@@ -22,6 +22,9 @@ models:
- len: 200
name: type
type: CharField
- default: 0
name: interface_id_seq
type: IntegerField
name: Device
x: 348
y: 124
@@ -71,6 +74,17 @@ models:
type: FloatField
- name: panY
type: FloatField
- default: 0
name: device_id_seq
ref: Topology
ref_field: device_id_seq
type: IntegerField
- default: 0
name: link_id_seq
type: IntegerField
- default: 0
name: group_id_seq
type: IntegerField
name: Topology
x: 111
y: 127
@@ -135,6 +149,51 @@ models:
name: Interface
x: 600
y: 243
- fields:
- name: group_id
pk: true
type: AutoField
- name: id
ref: Group
ref_field: id
type: IntegerField
- len: 200
name: name
type: CharField
- name: x1
type: IntegerField
- name: y1
ref: Group
ref_field: y1
type: IntegerField
- name: x2
ref: Group
ref_field: x2
type: IntegerField
- name: y2
type: IntegerField
- name: topology
ref: Topology
ref_field: topology_id
type: ForeignKey
name: Group
x: 907
y: 520
- fields:
- name: group_device_id
pk: true
type: AutoField
- name: group
ref: GroupDevice
ref_field: group
type: ForeignKey
- name: device
ref: Device
ref_field: device_id
type: ForeignKey
name: GroupDevice
x: 631
y: 561
modules: []
view:
panX: 213.72955551921206

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0011_link_name'),
]
operations = [
migrations.AddField(
model_name='device',
name='interface_id_seq',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='topology',
name='device_id_seq',
field=models.IntegerField(default=0, verbose_name=b'Topology'),
),
migrations.AddField(
model_name='topology',
name='link_id_seq',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0012_auto_20170706_1526'),
]
operations = [
migrations.CreateModel(
name='Group',
fields=[
('group_id', models.AutoField(serialize=False, primary_key=True)),
('id', models.IntegerField(verbose_name=b'Group')),
('name', models.CharField(max_length=200)),
('x1', models.IntegerField()),
('y1', models.IntegerField(verbose_name=b'Group')),
('x2', models.IntegerField(verbose_name=b'Group')),
('y2', models.IntegerField()),
],
),
migrations.CreateModel(
name='GroupDevice',
fields=[
('group_device_id', models.AutoField(serialize=False, primary_key=True)),
('device', models.ForeignKey(to='network_ui.Device')),
('group', models.ForeignKey(to='network_ui.GroupDevice')),
],
),
migrations.AddField(
model_name='topology',
name='group_id_seq',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0013_auto_20170710_1840'),
]
operations = [
migrations.AddField(
model_name='group',
name='topology',
field=models.ForeignKey(default=1, to='network_ui.Topology'),
preserve_default=False,
),
]

View File

@@ -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', '0014_group_topology'),
]
operations = [
migrations.AlterField(
model_name='groupdevice',
name='group',
field=models.ForeignKey(to='network_ui.Group'),
),
]

View File

@@ -5,11 +5,12 @@ class Device(models.Model):
device_id = models.AutoField(primary_key=True,)
topology = models.ForeignKey('Topology',)
name = models.CharField(max_length=200, )
name = models.CharField(max_length=200,)
x = models.IntegerField()
y = models.IntegerField()
id = models.IntegerField()
type = models.CharField(max_length=200, )
type = models.CharField(max_length=200,)
interface_id_seq = models.IntegerField(default=0)
def __unicode__(self):
return self.name
@@ -18,21 +19,24 @@ class Device(models.Model):
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', )
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, )
name = models.CharField(max_length=200,)
class Topology(models.Model):
topology_id = models.AutoField(primary_key=True,)
name = models.CharField(max_length=200, )
name = models.CharField(max_length=200,)
scale = models.FloatField()
panX = models.FloatField()
panY = models.FloatField()
device_id_seq = models.IntegerField('Topology', default=0)
link_id_seq = models.IntegerField(default=0)
group_id_seq = models.IntegerField(default=0)
def __unicode__(self):
return self.name
@@ -57,7 +61,7 @@ class TopologyHistory(models.Model):
class MessageType(models.Model):
message_type_id = models.AutoField(primary_key=True,)
name = models.CharField(max_length=200, )
name = models.CharField(max_length=200,)
def __unicode__(self):
return self.name
@@ -67,8 +71,27 @@ class Interface(models.Model):
interface_id = models.AutoField(primary_key=True,)
device = models.ForeignKey('Device',)
name = models.CharField(max_length=200, )
name = models.CharField(max_length=200,)
id = models.IntegerField()
def __unicode__(self):
return self.name
class Group(models.Model):
group_id = models.AutoField(primary_key=True,)
id = models.IntegerField('Group',)
name = models.CharField(max_length=200,)
x1 = models.IntegerField()
y1 = models.IntegerField('Group',)
x2 = models.IntegerField('Group',)
y2 = models.IntegerField()
topology = models.ForeignKey('Topology',)
class GroupDevice(models.Model):
group_device_id = models.AutoField(primary_key=True,)
group = models.ForeignKey('Group',)
device = models.ForeignKey('Device',)

View File

@@ -1,8 +1,9 @@
from awx.network_ui.models import Topology, Device, Link, Interface
from awx.network_ui.models import Topology, Device, Link, Interface, Group, GroupDevice
from django.db.models import Q
import yaml
import json
from collections import defaultdict
NetworkAnnotatedInterface = Interface.objects.values('name',
'id',
@@ -17,13 +18,29 @@ NetworkAnnotatedInterface = Interface.objects.values('name',
def topology_data(topology_id):
data = dict(devices=[],
links=[])
links=[],
groups=[])
topology = Topology.objects.get(pk=topology_id)
data['name'] = topology.name
data['topology_id'] = topology_id
groups = list(Group.objects.filter(topology_id=topology_id).values())
group_devices = GroupDevice.objects.filter(group__topology_id=topology_id).values('group_id', 'device_id', 'device__name', 'group__name')
group_device_map = defaultdict(list)
for group_device in group_devices:
group_device_map[group_device['group_id']].append(group_device)
device_group_map = defaultdict(list)
for group_device in group_devices:
device_group_map[group_device['device_id']].append(group_device)
for group in groups:
group['members'] = [x['device__name'] for x in group_device_map[group['group_id']]]
data['groups'] = groups
links = list(Link.objects
.filter(Q(from_device__topology_id=topology_id) |
Q(to_device__topology_id=topology_id)))
@@ -43,7 +60,8 @@ def topology_data(topology_id):
x=device.x,
y=device.y,
id=device.id,
interfaces=interfaces))
interfaces=interfaces,
groups=[x['group__name'] for x in device_group_map[device.device_id]]))
for link in links:
data['links'].append(dict(from_device=link.from_device.name,
@@ -54,7 +72,9 @@ def topology_data(topology_id):
to_device_id=link.to_device.id,
from_interface_id=link.from_interface.id,
to_interface_id=link.to_interface.id,
name=link.name,
network=link.pk))
return data

View File

@@ -0,0 +1,10 @@
To build the UI:
make
To push the UI to tower code base:
make deploy

View File

@@ -1,15 +1,18 @@
.PHONY: all main lint lessc
.PHONY: all main lint lessc install simple-server deploy
all: clean lessc lint main istanbul
all: clean install lessc lint main
clean:
rm -rf src-instrumented
rm -f js/bundle.js
rm -f js/vendor.bundle.js
rm -f css/style.css
main:
webpack src/main.js js/bundle.js
cp vendor/*.js js/
install:
npm i
main: install
webpack
lint:
jshint --verbose src/*js
@@ -23,3 +26,11 @@ istanbul:
cp index.html index-instrumented.html
sed -i "s/bundle.js/bundle-instrumented.js/g" index-instrumented.html
cp vendor/*.js js/
simple-server:
python -m SimpleHTTPServer
deploy: main
rsync -av src/ ../../../../awx/ui/client/src/network_ui/

View File

@@ -0,0 +1,98 @@
finite_state_machine_id: 102
name: fsm
states:
- id: 1
label: Resize
x: 571
y: 911
- id: 2
label: Start
x: 744
y: 69
- id: 3
label: CornerSelected
x: 359
y: 682
- id: 4
label: Selected1
x: 839
y: 640
- id: 5
label: Selected3
x: 1528
y: 360
- id: 6
label: Move
x: 1297
y: 786
- id: 7
label: Ready
x: 740
y: 324
- id: 8
label: EditLabel
x: 1056
y: 148
- id: 9
label: Selected2
x: 1179
y: 435
- id: 10
label: Placing
x: 410
y: 295
transitions:
- from_state: Ready
label: onMouseDown
to_state: Selected1
- from_state: EditLabel
label: onMouseDown
to_state: Ready
- from_state: Selected2
label: onMouseDown
to_state: Ready
- from_state: Selected2
label: onMouseDown
to_state: Selected3
- from_state: Selected1
label: onMouseUp
to_state: Selected2
- from_state: Move
label: onMouseUp
to_state: Selected2
- from_state: Selected1
label: onMouseMove
to_state: Move
- from_state: Start
label: start
to_state: Ready
- from_state: Ready
label: onMouseDown
to_state: CornerSelected
- from_state: Selected3
label: onMouseMove
to_state: Move
- from_state: Selected3
label: onMouseUp
to_state: EditLabel
- from_state: Ready
label: onNewGroup
to_state: Placing
- from_state: Placing
label: onMouseDown
to_state: CornerSelected
- from_state: CornerSelected
label: onMouseMove
to_state: Resize
- from_state: Resize
label: onMouseUp
to_state: Selected1
- from_state: Selected2
label: onNewGroup
to_state: Ready
- from_state: CornerSelected
label: onMouseUp
to_state: Selected1
- from_state: Move
label: onMouseDown
to_state: Selected1

View File

@@ -0,0 +1,25 @@
finite_state_machine_id: 113
name: hotkeys
states:
- id: 2
label: Enabled
x: 585
y: 396
- id: 1
label: Start
x: 585
y: 160
- id: 3
label: Disabled
x: 331
y: 408
transitions:
- from_state: Enabled
label: onDisable
to_state: Disabled
- from_state: Disabled
label: onEnable
to_state: Enabled
- from_state: Start
label: start
to_state: Enabled

View File

@@ -1,36 +1,38 @@
app: move
panX: 285.92999999999995
panY: -151.52999999999997
scaleXY: 0.8700000000000001
finite_state_machine_id: 106
name: move
states:
- label: Start
size: 100
- id: 1
label: Start
x: 533
y: 121
- label: Ready
size: 100
- id: 2
label: Ready
x: 531
y: 320
- label: Selected1
size: 100
- id: 3
label: Selected1
x: 226
y: 325
- label: Selected2
size: 100
- id: 4
label: Selected2
x: 230
y: 582
- label: Move
size: 100
- id: 5
label: Move
x: -54
y: 587
- label: EditLabel
size: 100
x: 535.7126436781609
y: 583.367816091954
- label: Selected3
size: 100
x: 231.11494252873567
y: 867.2758620689654
- id: 6
label: EditLabel
x: 566
y: 677
- id: 7
label: Selected3
x: 175
y: 886
- id: 8
label: Placing
x: 92
y: 82
transitions:
- from_state: Start
label: start
@@ -49,6 +51,9 @@ transitions:
to_state: Ready
- from_state: Move
label: onMouseUp
to_state: Selected1
- from_state: EditLabel
label: onKeyDown
to_state: Selected2
- from_state: EditLabel
label: onMouseDown
@@ -62,3 +67,21 @@ transitions:
- from_state: Selected3
label: onMouseUp
to_state: EditLabel
- from_state: Placing
label: onMouseMove
to_state: Move
- from_state: Ready
label: onNewDevice
to_state: Placing
- from_state: Placing
label: onMouseDown
to_state: Selected1
- from_state: Move
label: onMouseDown
to_state: Selected1
- from_state: Selected2
label: onNewDevice
to_state: Ready
- from_state: Selected2
label: onKeyDown
to_state: Ready

View File

@@ -0,0 +1,15 @@
finite_state_machine_id: 114
name: fsm
states:
- id: 1
label: Start
x: 391
y: 132
- id: 2
label: Ready
x: 402
y: 346
transitions:
- from_state: Start
label: start
to_state: Ready

View File

@@ -1,27 +1,43 @@
app: time
panX: 0
panY: 0
scaleXY: 1
finite_state_machine_id: 89
name: src/time
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
- id: 3
label: Present
x: 256
y: 123
- id: 2
label: Start
x: 245
y: -161
- id: 1
label: Past
x: -115
y: 129
transitions:
- from_state: Past
label: onMessage
to_state: Present
- from_state: Past
label: onRedo
to_state: Present
- from_state: Past
label: onMouseWheel
to_state: Present
- from_state: Past
label: onKeyDown
to_state: Present
- from_state: Start
label: start
to_state: Present
- from_state: Present
label: onMessage
to_state: Past
- from_state: Present
label: onUndo
to_state: Past
- from_state: Present
label: onMouseWheel
to_state: Past
- from_state: Past
label: onMouseWheel
to_state: Present
- from_state: Present
label: onKeyDown
to_state: Past

View File

@@ -1,111 +1,12 @@
<!DOCTYPE html>
<html ng-app="triangular">
<html ng-app="networkUI">
<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>
<script src="js/vendor.bundle.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>
<awx-network-ui></awx-network-ui>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html ng-app="tower">
<head>
<link rel="stylesheet" href="css/style.css" />
<script src="js/vendor.bundle.js"></script>
<script src="js/bundle.js"></script>
</head>
<body>
<ui-view></ui-view>
</body>
</html>

View File

@@ -9,13 +9,18 @@
"author": "Ben Thomasson",
"license": "ISC",
"dependencies": {
"angular": "~1.6.2",
"angular-ui-router": "",
"webpack": "",
"browserify": "",
"inherits": "",
"require": "",
"jshint": "",
"less": "",
"mathjs": ""
"mathjs": "",
"reconnectingwebsocket": "^1.0.0",
"hamsterjs": "~1.1.2",
"angular-mousewheel": "~1.0.5"
},
"devDependencies": {
"eslint": "^3.17.1",

View File

@@ -1,3 +1,3 @@
#!/bin/bash -ex
python -m SimpleHTTPServer
python -m SimpleHTTPServer 8080

View File

@@ -0,0 +1,5 @@
function button () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/button.html' };
}
exports.button = button;

View File

@@ -55,8 +55,8 @@ _Start.prototype.start.transitions = ['Ready'];
_Clicked.prototype.start = function (controller) {
controller.scope.callback(controller.scope);
controller.scope.is_pressed = false;
controller.scope.callback(controller.scope);
controller.changeState(Ready);
};
_Clicked.prototype.start.transitions = ['Ready'];

View File

@@ -33,7 +33,7 @@ exports.ButtonPressed = ButtonPressed;
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
var i = 0;
var buttons = controller.scope.buttons;
var buttons = controller.scope.all_buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];
@@ -58,7 +58,7 @@ _Ready.prototype.onMouseMove = function (controller, msg_type, $event) {
if (!controller.scope.hide_buttons) {
var i = 0;
var buttons = controller.scope.buttons;
var buttons = controller.scope.all_buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];
@@ -85,7 +85,7 @@ _Start.prototype.start.transitions = ['Ready'];
_ButtonPressed.prototype.onMouseUp = function (controller, msg_type, $event) {
var i = 0;
var buttons = controller.scope.buttons;
var buttons = controller.scope.all_buttons;
var button = null;
for (i = 0; i < buttons.length; i++) {
button = buttons[i];

View File

@@ -0,0 +1,5 @@
function cursor () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/cursor.html' };
}
exports.cursor = cursor;

View File

@@ -0,0 +1,6 @@
function debug () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/debug.html' };
}
exports.debug = debug;

View File

@@ -0,0 +1,5 @@
function defaultd () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/default.html' };
}
exports.defaultd = defaultd;

View File

@@ -0,0 +1,5 @@
function group () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/group.html' };
}
exports.group = group;

View File

@@ -0,0 +1,527 @@
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 _Resize () {
this.name = 'Resize';
}
inherits(_Resize, _State);
var Resize = new _Resize();
exports.Resize = Resize;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _CornerSelected () {
this.name = 'CornerSelected';
}
inherits(_CornerSelected, _State);
var CornerSelected = new _CornerSelected();
exports.CornerSelected = CornerSelected;
function _Selected1 () {
this.name = 'Selected1';
}
inherits(_Selected1, _State);
var Selected1 = new _Selected1();
exports.Selected1 = Selected1;
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 _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
function _EditLabel () {
this.name = 'EditLabel';
}
inherits(_EditLabel, _State);
var EditLabel = new _EditLabel();
exports.EditLabel = EditLabel;
function _Selected2 () {
this.name = 'Selected2';
}
inherits(_Selected2, _State);
var Selected2 = new _Selected2();
exports.Selected2 = Selected2;
function _Placing () {
this.name = 'Placing';
}
inherits(_Placing, _State);
var Placing = new _Placing();
exports.Placing = Placing;
_Resize.prototype.onMouseUp = function (controller, msg_type, $event) {
controller.changeState(Selected1);
controller.handle_message(msg_type, $event);
};
_Resize.prototype.onMouseUp.transitions = ['Selected1'];
_Resize.prototype.onMouseMove = function (controller) {
var groups = controller.scope.selected_groups;
var diffX = controller.scope.scaledX - controller.scope.pressedScaledX;
var diffY = controller.scope.scaledY - controller.scope.pressedScaledY;
var i = 0;
var j = 0;
var membership_old_new = [];
var previous_x1, previous_y1, previous_x2, previous_y2;
for (i = 0; i < groups.length; i++) {
previous_x1 = groups[i].x1;
previous_y1 = groups[i].y1;
previous_x2 = groups[i].x2;
previous_y2 = groups[i].y2;
if (groups[i].selected_corner === models.TOP_LEFT) {
groups[i].x1 = groups[i].x1 + diffX;
groups[i].y1 = groups[i].y1 + diffY;
}
if (groups[i].selected_corner === models.BOTTOM_RIGHT) {
groups[i].x2 = groups[i].x2 + diffX;
groups[i].y2 = groups[i].y2 + diffY;
}
if (groups[i].selected_corner === models.TOP_RIGHT) {
groups[i].x2 = groups[i].x2 + diffX;
groups[i].y1 = groups[i].y1 + diffY;
}
if (groups[i].selected_corner === models.BOTTOM_LEFT) {
groups[i].x1 = groups[i].x1 + diffX;
groups[i].y2 = groups[i].y2 + diffY;
}
membership_old_new = groups[i].update_membership(controller.scope.devices);
for(j = 0; j < membership_old_new[0].length; j++) {
membership_old_new[0][j].selected = false;
}
for(j = 0; j < membership_old_new[1].length; j++) {
membership_old_new[1][j].selected = true;
}
controller.scope.send_control_message(new messages.GroupMove(controller.scope.client_id,
groups[i].id,
groups[i].x1,
groups[i].y1,
groups[i].x2,
groups[i].y2,
previous_x1,
previous_y1,
previous_x2,
previous_y2));
controller.scope.send_control_message(new messages.GroupMembership(controller.scope.client_id,
groups[i].id,
membership_old_new[2]));
}
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
};
_Resize.prototype.end = function (controller) {
var groups = controller.scope.selected_groups;
var i = 0;
var j = 0;
for (i = 0; i < groups.length; i++) {
for(j = 0; j < groups[i].devices.length; j++) {
groups[i].devices[j].selected = false;
}
}
};
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Start.prototype.start.transitions = ['Ready'];
_CornerSelected.prototype.start = function (controller) {
var groups = controller.scope.selected_groups;
var i = 0;
var x = controller.scope.scaledX;
var y = controller.scope.scaledY;
for (i = 0; i < groups.length; i++) {
groups[i].selected_corner = groups[i].select_corner(x, y);
}
};
_CornerSelected.prototype.onMouseMove = function (controller) {
controller.changeState(Resize);
};
_CornerSelected.prototype.onMouseMove.transitions = ['Resize'];
_CornerSelected.prototype.onMouseUp = function (controller, msg_type, $event) {
controller.changeState(Selected1);
controller.handle_message(msg_type, $event);
};
_CornerSelected.prototype.onMouseUp.transitions = ['Selected1'];
_Selected1.prototype.onMouseMove = function (controller) {
controller.changeState(Move);
};
_Selected1.prototype.onMouseMove.transitions = ['Move'];
_Selected1.prototype.onMouseUp = function (controller) {
controller.changeState(Selected2);
};
_Selected1.prototype.onMouseUp.transitions = ['Selected2'];
_Selected3.prototype.onMouseMove = function (controller) {
controller.changeState(Move);
};
_Selected3.prototype.onMouseMove.transitions = ['Move'];
_Selected3.prototype.onMouseUp = function (controller) {
controller.changeState(EditLabel);
};
_Selected3.prototype.onMouseUp.transitions = ['EditLabel'];
_Move.prototype.onMouseMove = function (controller) {
var groups = controller.scope.selected_groups;
var diffX = controller.scope.scaledX - controller.scope.pressedScaledX;
var diffY = controller.scope.scaledY - controller.scope.pressedScaledY;
var i = 0;
var j = 0;
var membership_old_new = [];
var previous_x1, previous_y1, previous_x2, previous_y2;
for (i = 0; i < groups.length; i++) {
previous_x1 = groups[i].x1;
previous_y1 = groups[i].y1;
previous_x2 = groups[i].x2;
previous_y2 = groups[i].y2;
groups[i].x1 = groups[i].x1 + diffX;
groups[i].y1 = groups[i].y1 + diffY;
groups[i].x2 = groups[i].x2 + diffX;
groups[i].y2 = groups[i].y2 + diffY;
membership_old_new = groups[i].update_membership(controller.scope.devices);
for(j = 0; j < membership_old_new[0].length; j++) {
membership_old_new[0][j].selected = false;
}
for(j = 0; j < membership_old_new[1].length; j++) {
membership_old_new[1][j].selected = true;
}
controller.scope.send_control_message(new messages.GroupMove(controller.scope.client_id,
groups[i].id,
groups[i].x1,
groups[i].y1,
groups[i].x2,
groups[i].y2,
previous_x1,
previous_y1,
previous_x2,
previous_y2));
controller.scope.send_control_message(new messages.GroupMembership(controller.scope.client_id,
groups[i].id,
membership_old_new[2]));
}
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
};
_Move.prototype.onMouseUp = function (controller) {
controller.changeState(Selected2);
};
_Move.prototype.onMouseUp.transitions = ['Selected2'];
_Move.prototype.onMouseDown = function (controller) {
controller.changeState(Selected1);
};
_Move.prototype.onMouseDown.transitions = ['Selected1'];
_Move.prototype.end = function (controller) {
var groups = controller.scope.selected_groups;
var i = 0;
var j = 0;
for (i = 0; i < groups.length; i++) {
for(j = 0; j < groups[i].devices.length; j++) {
groups[i].devices[j].selected = false;
}
}
};
_Ready.prototype.onMouseMove = function (controller, msg_type, $event) {
if (controller.scope.hide_groups) {
controller.next_controller.handle_message(msg_type, $event);
return;
}
var i = 0;
for (i = 0; i < controller.scope.groups.length; i++) {
controller.scope.groups[i].update_hightlighted(controller.scope.scaledX, controller.scope.scaledY);
}
controller.next_controller.handle_message(msg_type, $event);
};
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
if (controller.scope.hide_groups) {
controller.next_controller.handle_message(msg_type, $event);
return;
}
//
var i = 0;
for (i = 0; i < controller.scope.groups.length; i++) {
controller.scope.groups[i].selected = false;
}
controller.scope.selected_groups = [];
for (i = 0; i < controller.scope.groups.length; i++) {
if (controller.scope.groups[i].has_corner_selected(controller.scope.scaledX, controller.scope.scaledY)) {
controller.scope.clear_selections();
if (controller.scope.selected_groups.indexOf(controller.scope.groups[i]) === -1) {
controller.scope.selected_groups.push(controller.scope.groups[i]);
}
controller.scope.groups[i].selected = true;
controller.changeState(CornerSelected);
controller.scope.pressedX = controller.scope.mouseX;
controller.scope.pressedY = controller.scope.mouseY;
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
return;
} else if (controller.scope.groups[i].is_selected(controller.scope.scaledX, controller.scope.scaledY)) {
controller.scope.clear_selections();
if (controller.scope.selected_groups.indexOf(controller.scope.groups[i]) === -1) {
controller.scope.selected_groups.push(controller.scope.groups[i]);
}
controller.scope.groups[i].selected = true;
controller.changeState(Selected1);
controller.scope.pressedX = controller.scope.mouseX;
controller.scope.pressedY = controller.scope.mouseY;
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
return;
}
}
controller.next_controller.handle_message(msg_type, $event);
};
_Ready.prototype.onMouseDown.transitions = ['Selected1', 'CornerSelected'];
_Ready.prototype.onNewGroup = function (controller) {
controller.scope.hide_groups = false;
controller.changeState(Placing);
};
_Ready.prototype.onNewGroup.transitions = ['Placing'];
_EditLabel.prototype.start = function (controller) {
controller.scope.selected_groups[0].edit_label = true;
};
_EditLabel.prototype.end = function (controller) {
controller.scope.selected_groups[0].edit_label = false;
};
_EditLabel.prototype.onMouseDown = function (controller) {
controller.changeState(Ready);
};
_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_groups[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);
} else if ($event.keyCode === 32) { //Space
item.name += " ";
} else {
console.log($event.keyCode);
}
controller.scope.send_control_message(new messages.GroupLabelEdit(controller.scope.client_id,
item.id,
item.name,
previous_name));
};
_EditLabel.prototype.onKeyDown.transitions = ['Selected2'];
_Selected2.prototype.onNewGroup = function (controller, msg_type, $event) {
controller.changeState(Ready);
controller.handle_message(msg_type, $event);
};
_Selected2.prototype.onNewGroup.transitions = ['Ready'];
_Selected2.prototype.onMouseDown = function (controller, msg_type, $event) {
controller.scope.pressedX = controller.scope.mouseX;
controller.scope.pressedY = controller.scope.mouseY;
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
var groups = controller.scope.selected_groups;
controller.scope.selected_groups = [];
var i = 0;
for (i = 0; i < groups.length; i++) {
if (groups[i].has_corner_selected(controller.scope.scaledX, controller.scope.scaledY)) {
controller.scope.selected_groups = [];
break;
}
else if (groups[i].is_selected(controller.scope.scaledX, controller.scope.scaledY)) {
if (controller.scope.selected_groups.indexOf(groups[i]) === -1) {
controller.scope.selected_groups.push(groups[i]);
}
}
}
if (controller.scope.selected_groups.length > 0) {
controller.changeState(Selected3);
} else {
controller.changeState(Ready);
controller.handle_message(msg_type, $event);
}
};
_Selected2.prototype.onMouseDown.transitions = ['Ready', 'Selected3'];
_Selected2.prototype.onKeyDown = function (controller, msg_type, $event) {
if ($event.keyCode === 8) {
//Delete
controller.changeState(Ready);
var i = 0;
var index = -1;
var groups = controller.scope.selected_groups;
controller.scope.selected_groups = [];
for (i = 0; i < groups.length; i++) {
index = controller.scope.groups.indexOf(groups[i]);
if (index !== -1) {
groups[i].selected = false;
groups[i].remote_selected = false;
controller.scope.groups.splice(index, 1);
}
controller.scope.send_control_message(new messages.GroupDestroy(controller.scope.client_id,
groups[i].id,
groups[i].x1,
groups[i].y1,
groups[i].x2,
groups[i].y2,
groups[i].name));
}
}
};
_Selected2.prototype.onKeyDown.transitions = ['Ready'];
_Placing.prototype.onMouseDown = function (controller) {
var scope = controller.scope;
var group = null;
scope.pressedX = scope.mouseX;
scope.pressedY = scope.mouseY;
scope.pressedScaledX = scope.scaledX;
scope.pressedScaledY = scope.scaledY;
scope.clear_selections();
var id = scope.group_id_seq();
group = new models.Group(id,
"Group" + id,
scope.scaledX,
scope.scaledY,
scope.scaledX,
scope.scaledY,
false);
scope.send_control_message(new messages.GroupCreate(scope.client_id,
group.id,
group.x1,
group.y1,
group.x2,
group.y2,
group.name));
scope.groups.push(group);
scope.selected_groups.push(group);
group.selected = true;
group.selected_corner = models.BOTTOM_RIGHT;
controller.changeState(Resize);
};
_Placing.prototype.onMouseDown.transitions = ['CornerSelected'];

View File

@@ -0,0 +1,5 @@
function host () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/host.html' };
}
exports.host = host;

View File

@@ -0,0 +1,104 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
var messages = require('./messages.js');
function _State () {
}
inherits(_State, fsm._State);
function _Enabled () {
this.name = 'Enabled';
}
inherits(_Enabled, _State);
var Enabled = new _Enabled();
exports.Enabled = Enabled;
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Disabled () {
this.name = 'Disabled';
}
inherits(_Disabled, _State);
var Disabled = new _Disabled();
exports.Disabled = Disabled;
_Enabled.prototype.onDisable = function (controller) {
controller.changeState(Disabled);
};
_Enabled.prototype.onDisable.transitions = ['Disabled'];
_Enabled.prototype.onKeyDown = function(controller, msg_type, $event) {
var scope = controller.scope;
if ($event.key === 'l') {
scope.first_controller.handle_message("NewLink", $event);
return;
}
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;
}
if ($event.key === 'r') {
scope.first_controller.handle_message("NewDevice", new messages.NewDevice("router"));
return;
}
else if ($event.key === 's') {
scope.first_controller.handle_message("NewDevice", new messages.NewDevice("switch"));
return;
}
else if ($event.key === 'a') {
scope.first_controller.handle_message("NewDevice", new messages.NewDevice("rack"));
return;
}
else if ($event.key === 'h') {
scope.first_controller.handle_message("NewDevice", new messages.NewDevice("host"));
return;
}
controller.next_controller.handle_message(msg_type, $event);
};
_Start.prototype.start = function (controller) {
controller.changeState(Enabled);
};
_Start.prototype.start.transitions = ['Enabled'];
_Disabled.prototype.onEnable = function (controller) {
controller.changeState(Enabled);
};
_Disabled.prototype.onEnable.transitions = ['Enabled'];

View File

@@ -0,0 +1,5 @@
function layer () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/layer.html' };
}
exports.layer = layer;

View File

@@ -0,0 +1,5 @@
function link () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/link.html' };
}
exports.link = link;

View File

@@ -46,19 +46,11 @@ 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) {
_Ready.prototype.onNewLink = function (controller, msg_type, message) {
controller.scope.clear_selections();
controller.changeState(Selecting);
controller.next_controller.handle_message(msg_type, message);
};
@@ -90,10 +82,10 @@ _Connecting.prototype.onMouseUp = function (controller) {
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);
to_device_interface = new models.Interface(i, "eth" + 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);
from_device_interface = new models.Interface(i, "eth" + 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;

View File

@@ -1,2 +1,4 @@
var app = require('./app.js');
exports.app = app;
var networkUI = require('./network.ui.app.js');
var tower = require('./tower.app.js');
exports.networkUI = networkUI.networkUI;
exports.tower = tower.tower;

View File

@@ -101,13 +101,15 @@ function LinkCreate(sender, id, from_device_id, to_device_id, from_interface_id,
}
exports.LinkCreate = LinkCreate;
function LinkDestroy(sender, id, from_id, to_id) {
function LinkDestroy(sender, id, from_device_id, to_device_id, from_interface_id, to_interface_id, name) {
this.msg_type = "LinkDestroy";
this.id = id;
this.sender = sender;
this.from_id = from_id;
this.to_id = to_id;
this.name = '';
this.name = 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.LinkDestroy = LinkDestroy;
@@ -239,3 +241,79 @@ function ViewPort(sender, scale, panX, panY) {
this.panY = panY;
}
exports.ViewPort = ViewPort;
function NewDevice(type) {
this.type = type;
}
exports.NewDevice = NewDevice;
function GroupMove(sender, id, x1, y1, x2, y2, previous_x1, previous_y1, previous_x2, previous_y2) {
this.msg_type = "GroupMove";
this.sender = sender;
this.id = id;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.previous_x1 = previous_x1;
this.previous_y1 = previous_y1;
this.previous_x2 = previous_x2;
this.previous_y2 = previous_y2;
}
exports.GroupMove = GroupMove;
function GroupCreate(sender, id, x1, y1, x2, y2, name) {
this.msg_type = "GroupCreate";
this.sender = sender;
this.id = id;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.name = name;
}
exports.GroupCreate = GroupCreate;
function GroupDestroy(sender, id, previous_x1, previous_y1, previous_x2, previous_y2, previous_name, previous_type) {
this.msg_type = "GroupDestroy";
this.sender = sender;
this.id = id;
this.previous_x1 = previous_x1;
this.previous_y1 = previous_y1;
this.previous_x2 = previous_x2;
this.previous_y2 = previous_y2;
this.previous_name = previous_name;
this.previous_type = previous_type;
}
exports.GroupDestroy = GroupDestroy;
function GroupLabelEdit(sender, id, name, previous_name) {
this.msg_type = "GroupLabelEdit";
this.sender = sender;
this.id = id;
this.name = name;
this.previous_name = previous_name;
}
exports.GroupLabelEdit = GroupLabelEdit;
function GroupSelected(sender, id) {
this.msg_type = "GroupSelected";
this.sender = sender;
this.id = id;
}
exports.GroupSelected = GroupSelected;
function GroupUnSelected(sender, id) {
this.msg_type = "GroupUnSelected";
this.sender = sender;
this.id = id;
}
exports.GroupUnSelected = GroupUnSelected;
function GroupMembership(sender, id, members) {
this.msg_type = "GroupMembership";
this.sender = sender;
this.id = id;
this.members = members;
}
exports.GroupMembership = GroupMembership;

View File

@@ -1,6 +1,7 @@
var fsm = require('./fsm.js');
var button = require('./button.js');
var util = require('./util.js');
var inherits = require('inherits');
function Device(id, name, x, y, type) {
this.id = id;
@@ -267,6 +268,36 @@ Button.prototype.is_selected = function (x, y) {
};
function ToggleButton(name, x, y, width, height, toggle_callback, untoggle_callback, default_toggled) {
this.name = name;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.callback = this.toggle;
this.is_pressed = default_toggled;
this.toggled = default_toggled;
this.toggle_callback = toggle_callback;
this.untoggle_callback = untoggle_callback;
this.mouse_over = false;
this.fsm = new fsm.FSMController(this, button.Start, null);
}
inherits(ToggleButton, Button);
exports.ToggleButton = ToggleButton;
ToggleButton.prototype.toggle = function () {
this.toggled = !this.toggled;
this.is_pressed = this.toggled;
if (this.toggled) {
this.toggle_callback();
} else {
this.untoggle_callback();
}
};
function Task(id, name) {
this.id = id;
this.name = name;
@@ -276,3 +307,189 @@ function Task(id, name) {
exports.Task = Task;
Task.prototype.describeArc = util.describeArc;
function Group(id, name, x1, y1, x2, y2, selected) {
this.id = id;
this.name = name;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.selected = selected;
this.highlighted = false;
this.fsm = null;
this.selected_corner = null;
this.devices = [];
}
exports.Group = Group;
Group.prototype.update_hightlighted = function (x, y) {
this.highlighted = this.is_highlighted(x, y);
};
Group.prototype.is_highlighted = function (x, y) {
return (x > this.left_extent() &&
x < this.right_extent() &&
y > this.top_extent() &&
y < this.bottom_extent());
};
var TOP_LEFT = 0;
exports.TOP_LEFT = TOP_LEFT;
var TOP_RIGHT = 1;
exports.TOP_RIGHT = TOP_RIGHT;
var BOTTOM_LEFT = 2;
exports.BOTTOM_LEFT = BOTTOM_LEFT;
var BOTTOM_RIGHT = 3;
exports.BOTTOM_RIGHT = BOTTOM_RIGHT;
Group.prototype.has_corner_selected = function (x, y) {
if (x > this.left_extent() &&
x < this.left_extent() + 10 &&
y > this.top_extent() &&
y < this.top_extent() + 10) {
return true;
}
if (x > this.left_extent() &&
x < this.left_extent() + 10 &&
y > this.bottom_extent() - 10 &&
y < this.bottom_extent()) {
return true;
}
if (x > this.right_extent() - 10 &&
x < this.right_extent() &&
y > this.bottom_extent() - 10 &&
y < this.bottom_extent()) {
return true;
}
if (x > this.right_extent() - 10 &&
x < this.right_extent() &&
y > this.top_extent() &&
y < this.top_extent() + 10) {
return true;
}
return false;
};
Group.prototype.select_corner = function (x, y) {
var corners = [[util.distance(this.x1, this.y1, x, y), TOP_LEFT],
[util.distance(this.x2, this.y2, x, y), BOTTOM_RIGHT],
[util.distance(this.x1, this.y2, x, y), BOTTOM_LEFT],
[util.distance(this.x2, this.y1, x, y), TOP_RIGHT]];
console.log(corners);
corners.sort(function(a, b) {
return a[0] - b[0];
});
if (corners[0][0] > 30) {
return null;
}
return corners[0][1];
};
Group.prototype.is_selected = function (x, y) {
if (util.pDistance(x,
y,
this.left_extent(),
this.top_extent(),
this.left_extent(),
this.bottom_extent()) < 10) {
return true;
}
if (util.pDistance(x,
y,
this.left_extent(),
this.top_extent(),
this.right_extent(),
this.top_extent()) < 10) {
return true;
}
if (util.pDistance(x,
y,
this.left_extent(),
this.top_extent(),
this.right_extent(),
this.top_extent()) < 40 && y > this.top_extent()) {
return true;
}
if (util.pDistance(x,
y,
this.right_extent(),
this.bottom_extent(),
this.right_extent(),
this.top_extent()) < 10) {
return true;
}
if (util.pDistance(x,
y,
this.right_extent(),
this.bottom_extent(),
this.left_extent(),
this.bottom_extent()) < 10) {
return true;
}
return false;
};
Group.prototype.width = function (scaledX) {
var x2 = this.x2 !== null ? this.x2 : scaledX;
return Math.abs(this.x1 - x2);
};
Group.prototype.height = function (scaledY) {
var y2 = this.y2 !== null ? this.y2 : scaledY;
return Math.abs(this.y1 - y2);
};
Group.prototype.top_extent = function (scaledY) {
var y2 = this.y2 !== null ? this.y2 : scaledY;
return (this.y1 < y2? this.y1 : y2);
};
Group.prototype.left_extent = function (scaledX) {
var x2 = this.x2 !== null ? this.x2 : scaledX;
return (this.x1 < x2? this.x1 : x2);
};
Group.prototype.bottom_extent = function (scaledY) {
var y2 = this.y2 !== null ? this.y2 : scaledY;
return (this.y1 > y2? this.y1 : y2);
};
Group.prototype.right_extent = function (scaledX) {
var x2 = this.x2 !== null ? this.x2 : scaledX;
return (this.x1 > x2? this.x1 : x2);
};
Group.prototype.update_membership = function (devices) {
var i = 0;
var y1 = this.top_extent();
var x1 = this.left_extent();
var y2 = this.bottom_extent();
var x2 = this.right_extent();
var old_devices = this.devices;
var device_ids = [];
this.devices = [];
for (i = 0; i < devices.length; i++) {
if (devices[i].x > x1 &&
devices[i].y > y1 &&
devices[i].x < x2 &&
devices[i].y < y2) {
this.devices.push(devices[i]);
device_ids.push(devices[i].id);
}
}
return [old_devices, this.devices, device_ids];
};

View File

@@ -59,6 +59,75 @@ inherits(_EditLabel, _State);
var EditLabel = new _EditLabel();
exports.EditLabel = EditLabel;
function _Placing () {
this.name = 'Placing';
}
inherits(_Placing, _State);
var Placing = new _Placing();
exports.Placing = Placing;
_Ready.prototype.onNewDevice = function (controller, msg_type, message) {
var scope = controller.scope;
var device = null;
var id = null;
scope.pressedX = scope.mouseX;
scope.pressedY = scope.mouseY;
scope.pressedScaledX = scope.scaledX;
scope.pressedScaledY = scope.scaledY;
scope.clear_selections();
if (message.type === "router") {
id = controller.scope.device_id_seq();
device = new models.Device(id,
"Router" + id,
scope.scaledX,
scope.scaledY,
"router");
}
else if (message.type === "switch") {
id = controller.scope.device_id_seq();
device = new models.Device(id,
"Switch" + id,
scope.scaledX,
scope.scaledY,
"switch");
}
else if (message.type === "rack") {
id = controller.scope.device_id_seq();
device = new models.Device(id,
"Rack" + id,
scope.scaledX,
scope.scaledY,
"rack");
}
else if (message.type === "host") {
id = controller.scope.device_id_seq();
device = new models.Device(id,
"Host" + id,
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));
scope.selected_devices.push(device);
device.selected = true;
controller.changeState(Placing);
}
};
_Ready.prototype.onNewDevice.transitions = ['Placing'];
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
var last_selected = controller.scope.select_items($event.shiftKey);
@@ -78,53 +147,6 @@ _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);
@@ -133,6 +155,12 @@ _Start.prototype.start = function (controller) {
_Start.prototype.start.transitions = ['Ready'];
_Selected2.prototype.onNewDevice = function (controller, msg_type, message) {
controller.changeState(Ready);
controller.handle_message(msg_type, message);
};
_Selected2.prototype.onNewDevice.transitions = ['Ready'];
_Selected2.prototype.onMouseDown = function (controller, msg_type, $event) {
@@ -182,9 +210,25 @@ _Selected2.prototype.onKeyDown = function (controller, msg_type, $event) {
var j = 0;
var index = -1;
var devices = controller.scope.selected_devices;
var links = controller.scope.selected_links;
var all_links = controller.scope.links.slice();
controller.scope.selected_devices = [];
controller.scope.selected_links = [];
for (i = 0; i < links.length; i++) {
index = controller.scope.links.indexOf(links[i]);
if (index !== -1) {
links[i].selected = false;
links[i].remote_selected = false;
controller.scope.links.splice(index, 1);
controller.scope.send_control_message(new messages.LinkDestroy(controller.scope.client_id,
links[i].id,
links[i].from_device.id,
links[i].to_device.id,
links[i].from_interface.id,
links[i].to_interface.id,
links[i].name));
}
}
for (i = 0; i < devices.length; i++) {
index = controller.scope.devices.indexOf(devices[i]);
if (index !== -1) {
@@ -207,6 +251,8 @@ _Selected2.prototype.onKeyDown = function (controller, msg_type, $event) {
}
}
}
controller.next_controller.handle_message(msg_type, $event);
};
_Selected2.prototype.onKeyDown.transitions = ['Ready'];
@@ -234,12 +280,14 @@ _Selected1.prototype.onMouseDown = util.noop;
_Move.prototype.onMouseMove = function (controller) {
var devices = controller.scope.selected_devices;
var groups = controller.scope.groups;
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;
var membership_old_new;
for (i = 0; i < devices.length; i++) {
previous_x = devices[i].x;
previous_y = devices[i].y;
@@ -261,6 +309,15 @@ _Move.prototype.onMouseMove = function (controller) {
}
controller.scope.pressedScaledX = controller.scope.scaledX;
controller.scope.pressedScaledY = controller.scope.scaledY;
//TODO: Improve the performance of this code from O(n^2) to O(n) or better
for (i = 0; i < groups.length; i++) {
membership_old_new = groups[i].update_membership(controller.scope.devices);
controller.scope.send_control_message(new messages.GroupMembership(controller.scope.client_id,
groups[i].id,
membership_old_new[2]));
}
};
_Move.prototype.onTouchMove = _Move.prototype.onMouseMove;
@@ -268,13 +325,19 @@ _Move.prototype.onTouchMove = _Move.prototype.onMouseMove;
_Move.prototype.onMouseUp = function (controller, msg_type, $event) {
controller.changeState(Selected2);
controller.changeState(Selected1);
controller.handle_message(msg_type, $event);
};
_Move.prototype.onMouseUp.transitions = ['Selected2'];
_Move.prototype.onMouseUp.transitions = ['Selected1'];
_Move.prototype.onTouchEnd = _Move.prototype.onMouseUp;
_Move.prototype.onMouseDown = function (controller) {
controller.changeState(Selected1);
};
_Move.prototype.onMouseDown.transitions = ['Selected1'];
_Selected3.prototype.onMouseUp = function (controller) {
controller.changeState(EditLabel);
};
@@ -346,3 +409,20 @@ _EditLabel.prototype.onKeyDown = function (controller, msg_type, $event) {
}
};
_EditLabel.prototype.onKeyDown.transitions = ['Selected2'];
_Placing.prototype.onMouseDown = function (controller) {
controller.changeState(Selected1);
};
_Placing.prototype.onMouseDown.transitions = ['Selected1'];
_Placing.prototype.onMouseMove = function (controller) {
controller.changeState(Move);
};
_Placing.prototype.onMouseMove.transitions = ['Move'];

View File

@@ -0,0 +1,46 @@
//console.log = function () { };
var angular = require('angular');
var NetworkUIController = require('./network.ui.controller.js');
var cursor = require('./cursor.directive.js');
var touch = require('./touch.directive.js');
var router = require('./router.directive.js');
var switchd = require('./switch.directive.js');
var host = require('./host.directive.js');
var link = require('./link.directive.js');
var rack = require('./rack.directive.js');
var group = require('./group.directive.js');
var defaultd = require('./default.directive.js');
var quadrants = require('./quadrants.directive.js');
var stencil = require('./stencil.directive.js');
var layer = require('./layer.directive.js');
var button = require('./button.directive.js');
var statusLight = require('./status.light.directive.js');
var taskStatus = require('./task.status.directive.js');
var debug = require('./debug.directive.js');
var awxNetworkUI = require('./network.ui.directive.js');
var networkUI = angular.module('networkUI', [
'monospaced.mousewheel',
'ngTouch'
])
.controller('NetworkUIController', NetworkUIController.NetworkUIController)
.directive('awxNetCursor', cursor.cursor)
.directive('awxNetTouch', touch.touch)
.directive('awxNetDebug', debug.debug)
.directive('awxNetRouter', router.router)
.directive('awxNetSwitch', switchd.switchd)
.directive('awxNetHost', host.host)
.directive('awxNetLink', link.link)
.directive('awxNetRack', rack.rack)
.directive('awxNetGroup', group.group)
.directive('awxNetDefault', defaultd.defaultd)
.directive('awxNetQuadrants', quadrants.quadrants)
.directive('awxNetStencil', stencil.stencil)
.directive('awxNetLayer', layer.layer)
.directive('awxNetButton', button.button)
.directive('awxNetStatusLight', statusLight.statusLight)
.directive('awxNetTaskStatus', taskStatus.taskStatus)
.directive('awxNetworkUi', awxNetworkUI.awxNetworkUI);
exports.networkUI = networkUI;

View File

@@ -1,28 +1,41 @@
//console.log = function () { };
var app = angular.module('triangular', ['monospaced.mousewheel', 'ngTouch']);
var angular = require('angular');
var fsm = require('./fsm.js');
var null_fsm = require('./null.fsm.js');
var hotkeys = require('./hotkeys.fsm.js');
var view = require('./view.js');
var move = require('./move.js');
var link = require('./link.js');
var group = require('./group.js');
var buttons = require('./buttons.js');
var time = require('./time.js');
var util = require('./util.js');
var models = require('./models.js');
var messages = require('./messages.js');
var svg_crowbar = require('../vendor/svg-crowbar.js');
var ReconnectingWebSocket = require('reconnectingwebsocket');
app.controller('MainCtrl', function($scope, $document, $location, $window) {
var NetworkUIController = function($scope, $document, $location, $window) {
window.scope = $scope;
$scope.api_token = '';
$scope.disconnected = false;
$scope.topology_id = $location.search().topology_id || 0;
// Create a web socket to connect to the backend server
$scope.control_socket = new window.ReconnectingWebSocket("ws://" + window.location.host + "/network_ui/topology?topology_id=" + $scope.topology_id,
if (!$scope.disconnected) {
$scope.control_socket = new ReconnectingWebSocket("ws://" + window.location.host + "/network_ui/topology?topology_id=" + $scope.topology_id,
null,
{debug: false, reconnectInterval: 300});
} else {
$scope.control_socket = {
on_message: util.noop
};
}
$scope.history = [];
$scope.client_id = 0;
$scope.onMouseDownResult = "";
@@ -48,11 +61,15 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.selected_links = [];
$scope.selected_interfaces = [];
$scope.selected_items = [];
$scope.selected_groups = [];
$scope.new_link = null;
$scope.view_controller = new fsm.FSMController($scope, view.Start, null);
$scope.null_controller = new fsm.FSMController($scope, null_fsm.Start, null);
$scope.hotkeys_controller = new fsm.FSMController($scope, hotkeys.Start, $scope.null_controller);
$scope.view_controller = new fsm.FSMController($scope, view.Start, $scope.hotkeys_controller);
$scope.move_controller = new fsm.FSMController($scope, move.Start, $scope.view_controller);
$scope.link_controller = new fsm.FSMController($scope, link.Start, $scope.move_controller);
$scope.buttons_controller = new fsm.FSMController($scope, buttons.Start, $scope.link_controller);
$scope.group_controller = new fsm.FSMController($scope, group.Start, $scope.link_controller);
$scope.buttons_controller = new fsm.FSMController($scope, buttons.Start, $scope.group_controller);
$scope.time_controller = new fsm.FSMController($scope, time.Start, $scope.buttons_controller);
$scope.first_controller = $scope.time_controller;
$scope.last_key = "";
@@ -62,12 +79,15 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.debug = {'hidden': true};
$scope.hide_buttons = false;
$scope.hide_links = false;
$scope.hide_interfaces = false;
$scope.hide_groups = false;
$scope.graph = {'width': window.innerWidth,
'right_column': window.innerWidth - 300,
'height': window.innerHeight};
$scope.device_id_seq = util.natural_numbers(0);
$scope.link_id_seq = util.natural_numbers(0);
$scope.group_id_seq = util.natural_numbers(0);
$scope.message_id_seq = util.natural_numbers(0);
$scope.time_pointer = -1;
$scope.frame = 0;
@@ -75,25 +95,10 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.replay = false;
$scope.touch_data = {};
$scope.touches = [];
$scope.devices = [
];
$scope.stencils = [
//{"name": "router", "size":50, 'x':10, 'y':100},
//{"name": "switch", "size":50, 'x':10, 'y':160},
//{"name": "rack", "size":50, 'x':10, 'y':220},
];
$scope.layers = [
//{"name": "Layer 3", "size":60, 'x':window.innerWidth - 70, 'y':10},
//{"name": "Layer 2", "size":60, 'x':window.innerWidth - 70, 'y':80},
//{"name": "Layer 1", "size":60, 'x':window.innerWidth - 70, 'y':150},
];
$scope.links = [
];
$scope.devices = [];
$scope.stencils = [];
$scope.links = [];
$scope.groups = [];
@@ -117,10 +122,12 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
var j = 0;
var devices = $scope.devices;
var links = $scope.links;
var groups = $scope.groups;
$scope.selected_items = [];
$scope.selected_devices = [];
$scope.selected_links = [];
$scope.selected_interfaces = [];
$scope.selected_groups = [];
for (i = 0; i < devices.length; i++) {
for (j = 0; j < devices[i].interfaces.length; j++) {
devices[i].interfaces[j].selected = false;
@@ -136,6 +143,9 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
}
links[i].selected = false;
}
for (i = 0; i < groups.length; i++) {
groups[i].selected = false;
}
};
$scope.select_items = function (multiple_selection) {
@@ -426,43 +436,96 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.onDiscoverButton = function (button) {
console.log(button.name);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://" + window.location.host + "/api/v1/job_templates/12/launch/", true);
xhr.open("POST", "http://" + window.location.host + "/api/v1/job_templates/7/launch/", true);
xhr.onload = function () {
console.log(xhr.readyState);
};
xhr.onerror = function () {
console.error(xhr.statusText);
};
xhr.setRequestHeader('Authorization', 'Token ' + $scope.api_token);
xhr.send();
};
$scope.onConfigureButton = function (button) {
console.log(button.name);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://" + window.location.host + "/api/v1/job_templates/11/launch/", true);
xhr.open("POST", "http://" + window.location.host + "/api/v1/job_templates/9/launch/", true);
xhr.onload = function () {
console.log(xhr.readyState);
};
xhr.onerror = function () {
console.error(xhr.statusText);
};
xhr.setRequestHeader('Authorization', 'Token ' + $scope.api_token);
xhr.send();
};
$scope.onTogglePhysical = function () {
$scope.hide_links = false;
};
$scope.onUnTogglePhysical = function () {
$scope.hide_links = true;
};
$scope.onToggleGroup = function () {
$scope.hide_groups = false;
};
$scope.onUnToggleGroup = function () {
$scope.hide_groups = true;
$scope.group_controller.changeState(group.Ready);
};
// Buttons
$scope.buttons = [
new models.Button("Deploy", 10, 10, 60, 50, $scope.onDeployButton),
new models.Button("Destroy", 80, 10, 60, 50, $scope.onDestroyButton),
new models.Button("Record", 150, 10, 60, 50, $scope.onRecordButton),
new models.Button("Export", 220, 10, 60, 50, $scope.onExportButton),
new models.Button("Discover", 290, 10, 80, 50, $scope.onDiscoverButton),
new models.Button("Layout", 380, 10, 60, 50, $scope.onLayoutButton),
new models.Button("Configure", 450, 10, 80, 50, $scope.onConfigureButton)
new models.Button("DEPLOY", 10, 10, 70, 30, $scope.onDeployButton),
new models.Button("DESTROY", 90, 10, 80, 30, $scope.onDestroyButton),
new models.Button("RECORD", 180, 10, 80, 30, $scope.onRecordButton),
new models.Button("EXPORT", 270, 10, 70, 30, $scope.onExportButton),
new models.Button("DISCOVER", 350, 10, 80, 30, $scope.onDiscoverButton),
new models.Button("LAYOUT", 440, 10, 70, 30, $scope.onLayoutButton),
new models.Button("CONFIGURE", 520, 10, 90, 30, $scope.onConfigureButton)
];
var LAYERS_X = 160;
$scope.layers = [
new models.ToggleButton("APPLICATION", $scope.graph.width - LAYERS_X, 10, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("PRESENTATION", $scope.graph.width - LAYERS_X, 50, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("SESSION", $scope.graph.width - LAYERS_X, 90, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("TRANSPORT", $scope.graph.width - LAYERS_X, 130, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("NETWORK", $scope.graph.width - LAYERS_X, 170, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("DATA-LINK", $scope.graph.width - LAYERS_X, 210, 120, 30, util.noop, util.noop, true),
new models.ToggleButton("PHYSICAL",
$scope.graph.width - LAYERS_X, 250, 120, 30,
$scope.onTogglePhysical,
$scope.onUnTogglePhysical,
true),
new models.ToggleButton("GROUP",
$scope.graph.width - LAYERS_X, 290, 120, 30,
$scope.onToggleGroup,
$scope.onUnToggleGroup,
true)
];
var STENCIL_X = 10;
var STENCIL_Y = 100;
var STENCIL_SPACING = 40;
$scope.stencils = [
new models.Button("Switch", STENCIL_X, STENCIL_Y + STENCIL_SPACING * 0, 70, 30, function () {$scope.first_controller.handle_message("NewDevice", new messages.NewDevice("switch"));}),
new models.Button("Router", STENCIL_X, STENCIL_Y + STENCIL_SPACING * 1, 70, 30, function () {$scope.first_controller.handle_message("NewDevice", new messages.NewDevice("router"));}),
new models.Button("Host", STENCIL_X, STENCIL_Y + STENCIL_SPACING * 2, 70, 30, function () {$scope.first_controller.handle_message("NewDevice", new messages.NewDevice("host"));}),
new models.Button("Link", STENCIL_X, STENCIL_Y + STENCIL_SPACING * 3, 70, 30, function () { $scope.first_controller.handle_message("NewLink");}),
new models.Button("Group", STENCIL_X, STENCIL_Y + STENCIL_SPACING * 4, 70, 30, function () { $scope.first_controller.handle_message("NewGroup");}),
];
$scope.all_buttons = [];
$scope.all_buttons.extend($scope.buttons);
$scope.all_buttons.extend($scope.layers);
$scope.all_buttons.extend($scope.stencils);
$scope.onTaskStatus = function(data) {
var i = 0;
var j = 0;
@@ -513,18 +576,42 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
var k = 0;
var device = null;
var keys = null;
var peers = null;
var ptm = null;
var intf = null;
for (i = 0; i < $scope.devices.length; i++) {
device = $scope.devices[i];
if (device.name === data.key) {
keys = Object.keys(data.value.ansible_local.ptm);
for (j = 0; j < keys.length; j++) {
ptm = data.value.ansible_local.ptm[keys[j]];
for (k = 0; k < device.interfaces.length; k++) {
intf = device.interfaces[k];
if (intf.name === ptm.port) {
intf.link.status = ptm['cbl status'] === 'pass';
//Check PTM
if (data.value.ansible_local !== undefined &&
data.value.ansible_local.ptm !== undefined) {
keys = Object.keys(data.value.ansible_local.ptm);
for (j = 0; j < keys.length; j++) {
ptm = data.value.ansible_local.ptm[keys[j]];
for (k = 0; k < device.interfaces.length; k++) {
intf = device.interfaces[k];
if (intf.name === ptm.port) {
intf.link.status = ptm['cbl status'] === 'pass';
}
}
}
}
//Check LLDP
if (data.value.ansible_net_neighbors !== undefined) {
keys = Object.keys(data.value.ansible_net_neighbors);
for (j = 0; j < keys.length; j++) {
peers = data.value.ansible_net_neighbors[keys[j]];
for (k = 0; k < peers.length; k++) {
intf = $scope.getDeviceInterface(device.name, keys[j]);
if (intf !== null && intf.link !== null) {
if (intf.link.to_interface === intf) {
intf.link.status = ($scope.getDeviceInterface(peers[k].host, peers[k].port) === intf.link.from_interface);
} else {
intf.link.status = ($scope.getDeviceInterface(peers[k].host, peers[k].port) === intf.link.to_interface);
}
}
}
}
}
@@ -534,6 +621,34 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.$apply();
};
$scope.getDevice = function(name) {
var i = 0;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].name === name) {
return $scope.devices[i];
}
}
return null;
};
$scope.getDeviceInterface = function(device_name, interface_name) {
var i = 0;
var k = 0;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].name === device_name) {
for (k = 0; k < $scope.devices[i].interfaces.length; k++) {
if ($scope.devices[i].interfaces[k].name === interface_name) {
return $scope.devices[i].interfaces[k];
}
}
}
}
return null;
};
$scope.onDeviceCreate = function(data) {
$scope.create_device(data);
};
@@ -583,6 +698,16 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
}
};
$scope.forGroup = function(group_id, data, fn) {
var i = 0;
for (i = 0; i < $scope.groups.length; i++) {
if ($scope.groups[i].id === group_id) {
fn($scope.groups[i], data);
break;
}
}
};
$scope.onDeviceLabelEdit = function(data) {
$scope.edit_device_label(data);
};
@@ -674,7 +799,7 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
link = $scope.links[i];
if (link.id === data.id &&
link.from_device.id === data.from_device_id &&
link.to_device.id === data.to_device_id &&
link.to_device.id === data.to_device_id &&
link.to_interface.id === data.to_interface_id &&
link.from_interface.id === data.from_interface_id) {
link.from_interface.link = null;
@@ -751,6 +876,17 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
}
};
$scope.onGroupLabelEdit = function(data) {
$scope.edit_group_label(data);
};
$scope.edit_group_label = function(data) {
$scope.forGroup(data.id, data, function(group, data) {
group.name = data.name;
});
};
$scope.redo = function(type_data) {
var type = type_data[0];
var data = type_data[1];
@@ -831,6 +967,9 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.panX = data.panX;
$scope.panY = data.panX;
$scope.current_scale = data.scale;
$scope.link_id_seq = util.natural_numbers(data.link_id_seq);
$scope.group_id_seq = util.natural_numbers(data.group_id_seq);
$scope.device_id_seq = util.natural_numbers(data.device_id_seq);
$location.search({topology_id: data.topology_id});
};
@@ -863,7 +1002,6 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.onSnapshot = function (data) {
//Erase the existing state
$scope.devices = [];
$scope.links = [];
@@ -878,11 +1016,13 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
var new_intf = null;
var max_device_id = null;
var max_link_id = null;
var max_group_id = null;
var min_x = null;
var min_y = null;
var max_x = null;
var max_y = null;
var new_link = null;
var new_group = null;
//Build the devices
for (i = 0; i < data.devices.length; i++) {
@@ -907,6 +1047,7 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
device.x,
device.y,
device.type);
new_device.interface_seq = util.natural_numbers(device.interface_id_seq);
$scope.devices.push(new_device);
device_map[device.id] = new_device;
device_interface_map[device.id] = {};
@@ -938,6 +1079,28 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
device_interface_map[link.to_device_id][link.to_interface_id].link = new_link;
}
//Build the groups
var group = null;
for (i = 0; i < data.groups.length; i++) {
group = data.groups[i];
if (max_group_id === null || group.id > max_group_id) {
max_group_id = group.id;
}
new_group = new models.Group(group.id,
group.name,
group.x1,
group.y1,
group.x2,
group.y2,
false);
if (group.members !== undefined) {
for (j=0; j < group.members.length; j++) {
new_group.devices.push(device_map[group.members[j]]);
}
}
$scope.groups.push(new_group);
}
var diff_x;
var diff_y;
@@ -969,6 +1132,10 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
if (max_link_id !== null) {
$scope.link_id_seq = util.natural_numbers(max_link_id);
}
//Update the group_id_seq to be greater than all group ids to prevent duplicate ids.
if (max_group_id !== null) {
$scope.group_id_seq = util.natural_numbers(max_group_id);
}
$scope.updateInterfaceDots();
};
@@ -1034,7 +1201,11 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
}
}
var data = messages.serialize(message);
$scope.control_socket.send(data);
if (!$scope.disconnected) {
$scope.control_socket.send(data);
} else {
console.log(data);
}
};
@@ -1047,6 +1218,8 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.graph.right_column = $window.innerWidth - 300;
$scope.graph.height = $window.innerHeight;
$scope.update_size();
// manuall $digest required as resize event
// is outside of angular
$scope.$digest();
@@ -1057,68 +1230,21 @@ app.controller('MainCtrl', function($scope, $document, $location, $window) {
$scope.frame = Math.floor(window.performance.now());
$scope.$apply();
}, 17);
});
app.directive('cursor', function() {
return { restrict: 'A', templateUrl: 'widgets/cursor.html' };
});
console.log("Network UI started");
app.directive('touch', function() {
return { restrict: 'A', templateUrl: 'widgets/touch.html' };
});
$scope.$on('$destroy', function () {
console.log("Network UI stopping");
$document.unbind('keydown', $scope.onKeyDown);
});
app.directive('debug', function() {
return { restrict: 'A', templateUrl: 'widgets/debug.html' };
});
$scope.update_size = function () {
var i = 0;
for (i = 0; i < $scope.layers.length; i++) {
$scope.layers[i].x = $scope.graph.width - 140;
}
};
};
app.directive('router', function() {
return { restrict: 'A', templateUrl: 'widgets/router.html' };
});
app.directive('switch', function() {
return { restrict: 'A', templateUrl: 'widgets/switch.html' };
});
app.directive('host', function() {
return { restrict: 'A', templateUrl: 'widgets/host.html' };
});
app.directive('link', function() {
return { restrict: 'A', templateUrl: 'widgets/link.html' };
});
app.directive('rack', function() {
return { restrict: 'A', templateUrl: 'widgets/rack.html' };
});
app.directive('default', function() {
return { restrict: 'A', templateUrl: 'widgets/default.html' };
});
app.directive('quadrants', function() {
return { restrict: 'A', templateUrl: 'widgets/quadrants.html' };
});
app.directive('stencil', function() {
return { restrict: 'A', templateUrl: 'widgets/stencil.html' };
});
app.directive('layer', function() {
return { restrict: 'A', templateUrl: 'widgets/layer.html' };
});
app.directive('button', function() {
return { restrict: 'A', templateUrl: 'widgets/button.html' };
});
app.directive('statusLight', function() {
return { restrict: 'A', templateUrl: 'widgets/status_light.html' };
});
app.directive('taskStatus', function() {
return { restrict: 'A', templateUrl: 'widgets/task_status.html' };
});
exports.app = app;
exports.NetworkUIController = NetworkUIController;
console.log("Network UI loaded");

View File

@@ -0,0 +1,5 @@
function awxNetworkUI () {
return { restrict: 'E', templateUrl: '/static/network_ui/widgets/network_ui.html' };
}
exports.awxNetworkUI = awxNetworkUI;

View File

@@ -0,0 +1,35 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
function _State () {
}
inherits(_State, fsm._State);
function _Start () {
this.name = 'Start';
}
inherits(_Start, _State);
var Start = new _Start();
exports.Start = Start;
function _Ready () {
this.name = 'Ready';
}
inherits(_Ready, _State);
var Ready = new _Ready();
exports.Ready = Ready;
_Start.prototype.start = function (controller) {
controller.changeState(Ready);
};
_Start.prototype.start.transitions = ['Ready'];

View File

@@ -0,0 +1,5 @@
function quadrants () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/quadrants.html' };
}
exports.quadrants = quadrants;

View File

@@ -0,0 +1,5 @@
function rack () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/rack.html' };
}
exports.rack = rack;

View File

@@ -0,0 +1,5 @@
function router () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/router.html' };
}
exports.router = router;

View File

@@ -0,0 +1,5 @@
function statusLight () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/status_light.html' };
}
exports.statusLight = statusLight;

View File

@@ -0,0 +1,5 @@
function stencil () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/stencil.html' };
}
exports.stencil = stencil;

View File

@@ -1,4 +1,17 @@
/* Put your css in here */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(/static/assets/OpenSans-Regular.ttf);
}
@font-face {
font-family: 'Open Sans';
font-style: bold;
font-weight: 600;
src: url(/static/assets/OpenSans-Bold.ttf);
}
@selected-red: #c9232c;
@selected-mango: #ff5850;
@@ -8,308 +21,469 @@
@dark-widget-detail: #707070;
@widget-body: #D7D7D7;
@link: #D7D7D7;
@group: #707070;
@debug-copynot: rgb(77,200,242);
@button-body: #f6f6f6;
@button-body: #ffffff;
@button-text: #707070;
@button-outline: #b4b6b4;
@button-body-hover: #dfdfdf;
@button-body-pressed: #d5d5d5;
@button-outline: #b7b7b7;
@button-body-hover: #f2f2f2;
@button-body-pressed: #848992;
@button-text-pressed: #ffffff;
@green: #5CB85C;
@red: #D9534F;
html {
overflow: hidden;
}
body {
background-color: yellow;
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
.NetworkUI {
background-color: @light-background;
cursor: none;
}
svg text {
.NetworkUI__text {
fill: @button-text;
font-family: 'Open Sans';
}
.debug text {
.NetworkUI__debug-text {
fill: @debug-copynot;
font-family: 'Open Sans';
}
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 {
.NetworkUI--debug {
fill-opacity: 0;
stroke: @debug-copynot;
stroke-width: 1;
}
svg line.link {
.NetworkUI__link--selected {
stroke: @selected-blue;
stroke-width: 6;
}
.NetworkUI__link--remote-selected {
stroke: @selected-mango;
stroke-width: 6;
}
.NetworkUI__link--selected-conflict {
stroke: @selected-red;
stroke-width: 6;
}
.NetworkUI__link {
stroke: @link;
stroke-width: 2;
}
svg line.link-pass {
.NetworkUI__link--link-pass {
stroke: @green;
stroke-width: 2;
}
svg line.link-fail {
.NetworkUI__link--link-fail {
stroke: @red;
stroke-width: 2;
}
svg line.cursor {
stroke: @dark-widget-detail;
stroke-width: 2;
}
svg line.debug {
.NetworkUI__link--debug {
stroke: @debug-copynot;
stroke-width: 1;
}
.debug-cursor line {
.NetworkUI__cursor {
stroke: @dark-widget-detail;
stroke-width: 2;
}
.NetworkUI__debug-cursor {
stroke: @debug-copynot;
stroke-width: 4;
}
.hidden {
.NetworkUI--hidden {
display: none;
}
.router circle {
.NetworkUI__router {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.router circle.selected {
.NetworkUI__router--selected {
stroke: @selected-blue;
stroke-width: 4;
}
.router circle.remote-selected {
.NetworkUI__router--remote-selected {
stroke: @selected-mango;
stroke-width: 4;
}
.router circle.selected-conflict {
.NetworkUI__router--selected-conflict {
stroke: @selected-red;
stroke-width: 4;
}
.router line {
.NetworkUI__router line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.router polygon {
.NetworkUI__router polygon {
fill: @light-widget-detail;
stroke: none;
}
.switch rect {
.NetworkUI__router-text {
fill: @button-text;
font-family: 'Open Sans';
}
.NetworkUI__router-text--selected {
font-family: 'Open Sans';
}
.NetworkUI__switch {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.switch rect.selected {
.NetworkUI__switch--selected {
stroke: @selected-blue;
stroke-width: 10;
}
.switch rect.remote-selected {
.NetworkUI__switch--remote-selected {
stroke: @selected-mango;
stroke-width: 10;
}
.switch rect.selected-conflict {
.NetworkUI__switch--selected-conflict {
stroke: @selected-red;
stroke-width: 10;
}
.switch line {
.NetworkUI__switch line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.switch polygon {
.NetworkUI__switch polygon {
fill: @light-widget-detail;
stroke: none;
}
.rack rect {
.NetworkUI__switch-text {
fill: @button-text;
font-family: 'Open Sans';
}
.NetworkUI__switch-text--selected {
font-family: 'Open Sans';
}
.NetworkUI__rack {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.rack rect.background {
.NetworkUI__rack-background {
fill: @light-background;
stroke: @light-background;
stroke-width: 2;
}
.rack rect.selected {
.NetworkUI__rack--selected {
fill: @selected-blue;
stroke: @selected-blue;
stroke-width: 10;
}
.rack rect.remote-selected {
.NetworkUI__rack--remote-selected {
fill: @selected-mango;
stroke: @selected-mango;
stroke-width: 10;
}
.rack rect.selected-conflict {
.NetworkUI__rack--selected-conflict {
fill: @selected-red;
stroke: @selected-red;
stroke-width: 10;
}
.rack line {
.NetworkUI__rack line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.rack circle {
.NetworkUI__rack circle {
fill: @light-widget-detail;
stroke: none;
}
.button rect {
.NetworkUI__button {
fill: @button-body;
stroke: @button-outline;
stroke-width: 1;
}
.button text {
.NetworkUI__button-text {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.button-pressed rect {
.NetworkUI__button--button-pressed {
fill: @button-body-pressed;
stroke: @button-outline;
stroke-width: 1;
}
.button-pressed text {
fill: @button-text;
.NetworkUI__button-text--button-pressed {
fill: @button-text-pressed;
font-family: 'Open Sans';
font-size: 14px;
}
.button-hover rect {
.NetworkUI__button--button-hover {
fill: @button-body-hover;
stroke: @button-outline;
stroke-width: 1;
}
.button-hover text {
.NetworkUI__button-text--button-hover {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.host rect {
.NetworkUI__layer {
fill: @button-body;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__layer-text {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__layer--button-pressed {
fill: @button-body-pressed;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__layer-text--button-pressed {
fill: @button-text-pressed;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__layer--button-hover {
fill: @button-body-hover;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__layer-text--button-hover {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__stencil {
fill: @button-body;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__stencil-text {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__stencil--button-pressed {
fill: @button-body-pressed;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__stencil-text--button-pressed {
fill: @button-text-pressed;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__stencil--button-hover {
fill: @button-body-hover;
stroke: @button-outline;
stroke-width: 1;
}
.NetworkUI__stencil-text--button-hover {
fill: @button-text;
font-family: 'Open Sans';
font-size: 14px;
}
.NetworkUI__host {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
.host rect.background {
.NetworkUI__host-background {
fill: @light-background;
stroke: @light-background;
stroke-width: 2;
}
.host rect.selected {
.NetworkUI__host--selected {
fill: @selected-blue;
stroke: @selected-blue;
stroke-width: 10;
}
.host rect.remote-selected {
.NetworkUI__host--remote-selected {
fill: @selected-mango;
stroke: @selected-mango;
stroke-width: 10;
}
.host rect.selected-conflict {
.NetworkUI__host--selected-conflict {
fill: @selected-red;
stroke: @selected-red;
stroke-width: 10;
}
.host line {
.NetworkUI__host line {
stroke: @light-widget-detail;
stroke-width: 20;
}
.host circle {
.NetworkUI__host circle {
fill: @light-widget-detail;
stroke: none;
}
circle.status {
.NetworkUI__host-text {
fill: @button-text;
font-family: 'Open Sans';
}
.NetworkUI__host-text--selected {
font-family: 'Open Sans';
}
.NetworkUI__status {
fill: @widget-body;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.pass {
.NetworkUI__status--pass {
fill: @green;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.fail {
.NetworkUI__status--fail {
fill: @red;
stroke: @dark-widget-detail;
stroke-width: 2;
}
path.status {
.NetworkUI__status-path {
fill: none;
stroke: @dark-widget-detail;
stroke-width: 2;
}
circle.debug {
.NetworkUI__circle-debug {
fill: @debug-copynot;
}
circle.interface {
.NetworkUI__interface {
fill: @dark-widget-detail;
}
circle.selected {
.NetworkUI__interface--selected {
fill: @selected-blue;
}
text.interface {
.NetworkUI__interface-text {
fill: @button-text;
font-size: 8px;
font-family: 'Open Sans';
}
.touch circle {
.NetworkUI__interface-text--selected {
font-size: 8px;
font-family: 'Open Sans';
}
.NetworkUI__link-text {
fill: @button-text;
font-size: 8px;
font-family: 'Open Sans';
}
.NetworkUI__touch {
stroke: @debug-copynot;
fill: none;
}
.NetworkUI__group--selected {
stroke: @selected-blue;
stroke-width: 6;
fill: none;
}
.NetworkUI__group--remote-selected {
stroke: @selected-mango;
stroke-width: 6;
fill: none;
}
.NetworkUI__group--selected-conflict {
stroke: @selected-red;
stroke-width: 6;
fill: none;
}
.NetworkUI__group {
stroke: @group;
stroke-width: 2;
fill: none;
}
.NetworkUI__group--debug {
stroke: @debug-copynot;
stroke-width: 1;
fill: none;
}
.NetworkUI__group-text {
fill: @button-text;
font-family: 'Open Sans';
}
.NetworkUI__group-text--selected {
font-family: 'Open Sans';
}

View File

@@ -0,0 +1,5 @@
function switchd () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/switch.html' };
}
exports.switchd = switchd;

View File

@@ -0,0 +1,5 @@
function taskStatus () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/task_status.html' };
}
exports.taskStatus = taskStatus;

View File

@@ -45,6 +45,7 @@ _Past.prototype.onMessage = function(controller, msg_type, message) {
'DeviceDestroy',
'DeviceMove',
'DeviceLabelEdit',
'GroupLabelEdit',
'LinkLabelEdit',
'InterfaceLabelEdit',
'InterfaceCreate',
@@ -98,6 +99,7 @@ _Past.prototype.onRedo = function(controller, msg_type, message) {
}
}
};
_Past.prototype.onRedo.transitions = ['Present'];
_Past.prototype.onCoverageRequest = function(controller) {
controller.scope.send_coverage();
@@ -195,7 +197,7 @@ _Past.prototype.onMouseWheel = function (controller, msg_type, message) {
}
};
_Past.prototype.onMouseWheel.transitions = ['Past'];
_Past.prototype.onMouseWheel.transitions = ['Present'];
_Past.prototype.onKeyDown = function(controller, msg_type, $event) {
@@ -217,7 +219,7 @@ _Past.prototype.onKeyDown = function(controller, msg_type, $event) {
controller.next_controller.handle_message(msg_type, $event);
}
};
_Past.prototype.onKeyDown.transitions = ['Past'];
_Past.prototype.onKeyDown.transitions = ['Present'];
_Past.prototype.undo = function(controller) {
@@ -271,18 +273,18 @@ _Present.prototype.onMessage = function(controller, msg_type, message) {
'DeviceDestroy',
'DeviceMove',
'DeviceLabelEdit',
'LinkLabelEdit',
'InterfaceLabelEdit',
'GroupLabelEdit',
'InterfaceCreate',
'InterfaceLabelEdit',
'LinkCreate',
'LinkDestroy',
'LinkLabelEdit',
'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) {
@@ -342,6 +344,11 @@ _Present.prototype.onDeviceLabelEdit = function(controller, msg_type, message) {
controller.scope.onDeviceLabelEdit(message);
}
};
_Present.prototype.onGroupLabelEdit = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onGroupLabelEdit(message);
}
};
_Present.prototype.onLinkLabelEdit = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onLinkLabelEdit(message);
@@ -369,6 +376,7 @@ _Present.prototype.onUndo = function(controller, msg_type, message) {
controller.changeState(Past);
}
};
_Present.prototype.onUndo.transitions = ['Past'];
_Present.prototype.onSnapshot = function(controller, msg_type, message) {
if (message.sender !== controller.scope.client_id) {
controller.scope.onSnapshot(message);
@@ -471,7 +479,6 @@ _Present.prototype.onMouseWheelEvent = function(controller, msg_type, message) {
controller.scope.onKeyDown(message);
}
};
_Present.prototype.onMessage.transitions = ['Past'];
_Present.prototype.onMouseWheel = function (controller, msg_type, message) {

View File

@@ -0,0 +1,5 @@
function touch () {
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/touch.html' };
}
exports.touch = touch;

View File

@@ -0,0 +1,28 @@
var angular = require('angular');
var ui_router = require('angular-ui-router');
var tower = angular.module('tower', ['networkUI', 'ui.router']);
tower.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/index');
$stateProvider
.state({
name: 'index',
url: '/index',
template: '<a href="#!/topology">Topology</a>'
});
$stateProvider
.state({
name: 'topology',
url: '/topology',
template: "<awx-network-ui></awx-network-ui>"
});
});
exports.tower = tower;
exports.ui_router = ui_router;

View File

@@ -1,3 +1,12 @@
Array.prototype.extend = function (other_array) {
/* you should include a test to check whether other_array really is an array */
var i = 0;
for (i = 0; i < other_array.length; i++) {
this.push(other_array[i]);
}
};
var math = require('mathjs');
function noop () {
@@ -10,6 +19,13 @@ function natural_numbers (start) {
}
exports.natural_numbers = natural_numbers;
function distance (x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
exports.distance = distance;
// polarToCartesian
// @wdebeaum, @opsb
// from http://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle

View File

@@ -86,28 +86,6 @@ _Ready.prototype.onMouseWheel = function (controller, msg_type, $event) {
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) {
@@ -127,6 +105,7 @@ _Scale.prototype.onMouseWheel = function (controller, msg_type, message) {
controller.scope.updatePanAndScale();
controller.changeState(Ready);
};
_Scale.prototype.onMouseWheel.transitions = ['Ready'];
_Pressed.prototype.onMouseUp = function (controller) {
@@ -134,6 +113,7 @@ _Pressed.prototype.onMouseUp = function (controller) {
controller.changeState(Ready);
};
_Pressed.prototype.onMouseUp.transitions = ['Ready'];
_Pressed.prototype.onTouchEnd = _Pressed.prototype.onMouseUp;
@@ -142,6 +122,7 @@ _Pressed.prototype.onMouseMove = function (controller, msg_type, $event) {
controller.changeState(Pan);
controller.handle_message(msg_type, $event);
};
_Pressed.prototype.onMouseMove.transitions = ['Pan'];
_Pressed.prototype.onTouchMove = _Pressed.prototype.onMouseMove;
@@ -186,5 +167,6 @@ _Pan.prototype.onMouseUp = function (controller) {
controller.changeState(Ready);
};
_Pan.prototype.onMouseUp.transitions = ['Ready'];
_Pan.prototype.onTouchEnd = _Pan.prototype.onMouseUp;

View File

@@ -5,22 +5,22 @@ function _State () {
}
inherits(_State, fsm._State);
{%for state, functions in states%}
function _{{state}} () {
this.name = '{{state}}';
{%for state in states%}
function _{{state.label}} () {
this.name = '{{state.label}}';
}
inherits(_{{state}}, _State);
var {{state}} = new _{{state}}();
exports.{{state}} = {{state}};
inherits(_{{state.label}}, _State);
var {{state.label}} = new _{{state.label}}();
exports.{{state.label}} = {{state.label}};
{%endfor%}
{%for state, functions in states%}
{%for fn, transitions in functions%}
_{{state}}.prototype.{{fn}} = function (controller) {
{%for state in states%}
{%for fn, transitions in state.functions%}
_{{state.label}}.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%}];
_{{state.label}}.prototype.{{fn}}.transitions = [{%for t in transitions%}'{{t.to_state}}'{% if not loop.last%}, {%endif%}{%endfor%}];
{%endfor%}
{%endfor%}

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Usage:
transform_fsm [options] <input> <output>
Options:
-h, --help Show this page
--debug Show debug logging
--verbose Show verbose logging
"""
from docopt import docopt
import logging
import sys
import yaml
logger = logging.getLogger('transform_fsm')
def main(args=None):
if args is None:
args = sys.argv[1:]
parsed_args = docopt(__doc__, args)
if parsed_args['--debug']:
logging.basicConfig(level=logging.DEBUG)
elif parsed_args['--verbose']:
logging.basicConfig(level=logging.INFO)
else:
logging.basicConfig(level=logging.WARNING)
with open(parsed_args['<input>']) as f:
data = yaml.load(f.read())
state_map = dict()
for state in data['states']:
state_map[state['label']] = state
state['functions'] = dict()
for transition in data['transitions']:
state = state_map.get(transition['from_state'], dict(label=transition['from_state'], functions=dict()))
state_map[transition['from_state']] = state
if state not in data['states']:
data['states'].append(state)
function_transitions = state['functions'].get(transition['label'], list())
function_transitions.append(dict(to_state=transition['to_state']))
state['functions'][transition['label']] = function_transitions
for state in data['states']:
state['functions'] = sorted(state['functions'].items())
with open(parsed_args['<output>'], 'w') as f:
f.write(yaml.safe_dump(data, default_flow_style=False))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@@ -20,7 +20,7 @@ angular.module("ngTouch", [])
}
}]
}
};
})
.directive("ngTouchmove", function () {
return {
@@ -44,7 +44,7 @@ angular.module("ngTouch", [])
}
}]
}
};
})
.directive("ngTouchend", function () {
return {
@@ -58,7 +58,7 @@ angular.module("ngTouch", [])
}
}]
}
};
})
.directive("ngTap", function () {
return {
@@ -66,14 +66,14 @@ angular.module("ngTouch", [])
var moved = false;
$element.bind("touchstart", onTouchStart);
function onTouchStart(event) {
function onTouchStart() {
$element.bind("touchmove", onTouchMove);
$element.bind("touchend", onTouchEnd);
}
function onTouchMove(event) {
function onTouchMove() {
moved = true;
}
function onTouchEnd(event) {
function onTouchEnd() {
$element.unbind("touchmove", onTouchMove);
$element.unbind("touchend", onTouchEnd);
if (!moved) {
@@ -83,5 +83,5 @@ angular.module("ngTouch", [])
}
}]
}
};
});

View File

@@ -1,365 +0,0 @@
// 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;
});

View File

@@ -14,13 +14,13 @@ function svg_crowbar () {
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 = [];
SVGSources = [],
iframes = document.querySelectorAll("iframe"),
objects = document.querySelectorAll("object");
@@ -30,7 +30,7 @@ function svg_crowbar () {
documents.push(el.contentDocument);
}
} catch(err) {
console.log(err)
console.log(err);
}
});
@@ -40,7 +40,7 @@ function svg_crowbar () {
documents.push(el.contentDocument);
}
} catch(err) {
console.log(err)
console.log(err);
}
});
@@ -50,8 +50,8 @@ function svg_crowbar () {
// 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) {
@@ -72,7 +72,7 @@ function svg_crowbar () {
s2.left += 38;
}
}
})
});
});
var buttonsContainer = document.createElement("div");
@@ -80,9 +80,9 @@ function svg_crowbar () {
buttonsContainer.setAttribute("class", "svg-crowbar");
buttonsContainer.style["z-index"] = 1e7;
buttonsContainer.style["position"] = "absolute";
buttonsContainer.style["top"] = 0;
buttonsContainer.style["left"] = 0;
buttonsContainer.style.position = "absolute";
buttonsContainer.style.top = 0;
buttonsContainer.style.left = 0;
@@ -90,40 +90,40 @@ function svg_crowbar () {
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%";
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.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.color = "white";
buttonWrapper.style["text-align"] = "center";
buttonWrapper.style["font-family"] = "'Helvetica Neue'";
buttonWrapper.style["background"] = "rgba(0, 0, 0, 0.8)";
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.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.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.style.margin = "5px 0 0 0";
button.textContent = "Download";
button.onclick = function(el) {
button.onclick = function() {
// console.log(el, d, i, sources)
download(d);
};
@@ -155,7 +155,7 @@ function svg_crowbar () {
svg.insertBefore(defsEl, svg.firstChild); //TODO .insert("defs", ":first-child")
// defsEl.setAttribute("class", "svg-crowbar");
var styleEl = document.createElement("style")
var styleEl = document.createElement("style");
defsEl.appendChild(styleEl);
styleEl.setAttribute("type", "text/css");
@@ -207,7 +207,7 @@ function svg_crowbar () {
a.setAttribute("class", "svg-crowbar");
a.setAttribute("download", filename + ".svg");
a.setAttribute("href", url);
a.style["display"] = "none";
a.style.display = "none";
a.click();
setTimeout(function() {

View File

@@ -0,0 +1,19 @@
var webpack = require('webpack');
module.exports = {
entry: {
app: "./src/main.js",
vendor: ["angular",
"angular-ui-router",
"hamsterjs",
"angular-mousewheel",
"reconnectingwebsocket"],
},
output: {
path: __dirname + "/js",
filename: "bundle.js",
},
plugins: [
new webpack.ProvidePlugin({Hamster: 'hamsterjs'}),
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' })
]
};

View File

@@ -1,11 +1,13 @@
<rect x=0
<rect ng-attr-class="{{button.is_pressed ? 'NetworkUI__button--button-pressed' : button.mouse_over ? 'NetworkUI__button--button-hover' : 'NetworkUI__button'}}"
x=0
y=0
ng-attr-width={{button.width}}
ng-attr-height={{button.height}}
rx=3></rect>
<text ng-attr-x="{{button.width/2}}"
rx=5></rect>
<text ng-attr-class="{{button.is_pressed ? 'NetworkUI__button-text--button-pressed' : button.mouse_over ? 'NetworkUI__button-text--button-hover' : 'NetworkUI__button-text'}}"
ng-attr-x="{{button.width/2}}"
ng-attr-y="{{button.height/2}}"
dy=".3em"
text-anchor="middle">{{button.name}}</text>

View File

@@ -1,4 +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 ng-attr-transform="translate({{cursor.x}},{{cursor.y}})" ng-attr-class="{{cursor.hidden && 'NetworkUI--hidden' || ''}}" >
<line x1="-15" y1="0" x2="15" y2="0" class="NetworkUI__cursor"/>
<line x1="0" y1="-15" x2="0" y2="15" class="NetworkUI__cursor"/>
</g>

View File

@@ -1,32 +1,35 @@
<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 ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || ''}}">
<text ng-attr-x="{{graph.right_column}}" y="115" class="NetworkUI__debug-text">width: {{graph.width}}</text>
<text ng-attr-x="{{graph.right_column}}" y="135" class="NetworkUI__debug-text">height: {{graph.height}}</text>
<text ng-attr-x="{{graph.right_column}}" y="155" class="NetworkUI__debug-text">rc: {{graph.right_column}}</text>
<text ng-attr-x="{{graph.right_column}}" y="175" class="NetworkUI__debug-text">Mouse down: {{onMouseDownResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="195" class="NetworkUI__debug-text">Mouse up: {{onMouseUpResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="215" class="NetworkUI__debug-text">Mouse move: {{onMouseMoveResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="235" class="NetworkUI__debug-text">Mouse over: {{onMouseOverResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="255" class="NetworkUI__debug-text">Mouse enter: {{onMouseEnterResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="275" class="NetworkUI__debug-text">Mouse leave: {{onMouseLeaveResult}}</text>
<text ng-attr-x="{{graph.right_column}}" y="295" class="NetworkUI__debug-text">Current scale: {{current_scale.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="315" class="NetworkUI__debug-text">Pan X: {{panX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="335" class="NetworkUI__debug-text">Pan Y: {{panY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="355" class="NetworkUI__debug-text">View State: {{view_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="375" class="NetworkUI__debug-text">Mouse X: {{mouseX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="395" class="NetworkUI__debug-text">Mouse Y: {{mouseY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="415" class="NetworkUI__debug-text">Scaled X: {{scaledX.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="435" class="NetworkUI__debug-text">Scaled Y: {{scaledY.toFixed(2)}}</text>
<text ng-attr-x="{{graph.right_column}}" y="455" class="NetworkUI__debug-text">Key: {{last_key}}</text>
<text ng-attr-x="{{graph.right_column}}" y="475" class="NetworkUI__debug-text">Key Code: {{last_key_code}}</text>
<text ng-attr-x="{{graph.right_column}}" y="495" class="NetworkUI__debug-text">Move State: {{move_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="515" class="NetworkUI__debug-text">Selected devices: {{selected_devices.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="535" class="NetworkUI__debug-text">Selected links: {{selected_links.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="555" class="NetworkUI__debug-text">Link State: {{link_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="575" class="NetworkUI__debug-text">Buttons State: {{buttons_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="595" class="NetworkUI__debug-text">Time State: {{time_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="615" class="NetworkUI__debug-text">Time Pointer: {{time_pointer}}</text>
<text ng-attr-x="{{graph.right_column}}" y="635" class="NetworkUI__debug-text">History: {{history.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="655" class="NetworkUI__debug-text">Touch Data: {{touch_data.xb}} {{touch_data.yb}} {{touch_data.d}}</text>
<text ng-attr-x="{{graph.right_column}}" y="675" class="NetworkUI__debug-text">Group State: {{group_controller.state.name}}</text>
<text ng-attr-x="{{graph.right_column}}" y="695" class="NetworkUI__debug-text">Selected groups: {{selected_groups.length}}</text>
<text ng-attr-x="{{graph.right_column}}" y="715" class="NetworkUI__debug-text">Hotkeys State: {{hotkeys_controller.state.name}}</text>
</g>

View File

@@ -0,0 +1,70 @@
<g ng-if="!hide_groups">
<rect ng-attr-width="{{group.width(scaledX)}}"
ng-attr-height="{{group.height(scaledY)}}"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
ng-attr-class="{{group.selected && 'NetworkUI__group--selected' || 'NetworkUI--hidden'}}"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
ng-attr-class="{{group.selected && 'NetworkUI__group--selected' || 'NetworkUI--hidden'}}"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.right_extent(scaledX) - 10}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
ng-attr-class="{{group.selected && 'NetworkUI__group--selected' || 'NetworkUI--hidden'}}"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.right_extent(scaledX) - 10}}"
ng-attr-y="{{group.bottom_extent(scaledY) - 10}}"
ng-attr-class="{{group.selected && 'NetworkUI__group--selected' || 'NetworkUI--hidden'}}"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.bottom_extent(scaledY) - 10}}"
ng-attr-class="{{group.selected && 'NetworkUI__group--selected' || 'NetworkUI--hidden'}}"/>
<g ng-if="group.highlighted || group.selected">
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
class="NetworkUI__group"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.right_extent(scaledX) - 10}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
class="NetworkUI__group"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.right_extent(scaledX) - 10}}"
ng-attr-y="{{group.bottom_extent(scaledY) - 10}}"
class="NetworkUI__group"/>
<rect ng-attr-width="10"
ng-attr-height="10"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.bottom_extent(scaledY) - 10}}"
class="NetworkUI__group"/>
</g>
<rect ng-attr-width="{{group.width(scaledX)}}"
ng-attr-height="{{group.height(scaledY)}}"
ng-attr-x="{{group.left_extent(scaledX)}}"
ng-attr-y="{{group.top_extent(scaledY)}}"
class="NetworkUI__group"/>
<text ng-attr-class="{{group.selected && ! group.edit_label ? 'NetworkUI__group-text--selected' : 'NetworkUI--hidden'}}"
filter="url(#selected)"
text-anchor="left"
ng-attr-x="{{group.left_extent(scaledX) + 20}}"
ng-attr-y="{{group.top_extent(scaledY) + 32}}"> {{group.name}} </text>
<text class="NetworkUI__group-text" text-anchor="left" ng-attr-x="{{group.left_extent(scaledX) + 20}}" ng-attr-y="{{group.top_extent(scaledY) + 32}}">{{group.name}}{{group.edit_label?'_':''}}</text>
</g>

View File

@@ -1,26 +1,29 @@
<g ng-if="!debug.hidden">
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></rect>
</g>
<g class="NetworkUI__host">
<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'}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'NetworkUI__host--selected-conflict' : device.selected ? 'NetworkUI__host--selected' : 'NetworkUI__host--remote-selected' : 'NetworkUI--hidden'}}"
rx=10>
</rect>
<rect
@@ -32,11 +35,12 @@
</rect>
<circle cx="30" cy="0" r=7 />
</circle>
</g>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'NetworkUI__host-text--selected' : 'NetworkUI--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>
<text class="NetworkUI__host-text" text-anchor="middle" x="0" y="5">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>

View File

@@ -1,11 +1,13 @@
<rect x=0
<rect ng-attr-class="{{layer.is_pressed ? 'NetworkUI__layer--button-pressed' : layer.mouse_over ? 'NetworkUI__layer--button-hover' : 'NetworkUI__layer'}}"
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}}"
ng-attr-width={{layer.width}}
ng-attr-height={{layer.height}}
rx=5></rect>
<text ng-attr-class="{{layer.is_pressed ? 'NetworkUI__layer-text--button-pressed' : layer.mouse_over ? 'NetworkUI__layer-text--button-hover' : 'NetworkUI__layer-text'}}"
ng-attr-x="{{layer.width/2}}"
ng-attr-y="{{layer.height/2}}"
dy=".3em"
text-anchor="middle">{{layer.name}}</text>

View File

@@ -2,19 +2,19 @@
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'}}"/>
ng-attr-class="{{link.selected && 'NetworkUI__link--selected' || 'NetworkUI--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'}}"/>
class="{{link.status === null ? 'NetworkUI__link' : link.status ? 'NetworkUI__link--link-pass' : 'NetworkUI__link--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" />
ng-attr-class="NetworkUI__link--debug" />
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
rotate({{link.slope()}})
@@ -22,13 +22,13 @@
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
class="NetworkUI__circle-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"/>
<line x1="0" y1=-20 x2=0 y2=20 class="NetworkUI__link--debug"/>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
{{link.to_device.y}})
@@ -37,7 +37,7 @@
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
class="NetworkUI__circle-debug" ></circle>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.from_device.x}},
{{link.from_device.y}})
@@ -46,7 +46,7 @@
<circle ng-attr-cx="0"
ng-attr-cy="0"
r=10
class="debug" ></circle>
class="NetworkUI__circle-debug" ></circle>
</g>
</g>
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
@@ -67,59 +67,61 @@
rotate({{-link.slope()}})
translate(0, -5)
">
<text ng-attr-class="{{link.selected && ! link.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{link.selected && ! link.edit_label ? 'selected' : 'NetworkUI--hidden'}}"
filter="url(#selected)"
text-anchor="middle"
font-size="8"
x="0"
y="0"> {{link.name}}</text>
<text text-anchor="middle" x="0" y="0">{{link.name}}{{link.edit_label?'_':''}}</text>
<text class="NetworkUI__link-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)
translate({{-link.from_interface.dot_d - 25}}, 0)
rotate({{-link.slope()}})
">
<text ng-attr-class="interface {{link.from_interface.selected && ! link.from_interface.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{link.from_interface.selected && ! link.from_interface.edit_label ? 'NetworkUI__interface-text--selected' : 'NetworkUI--hidden'}}"
filter="url(#selected)"
text-anchor="middle"
font-size="8"
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>
<text class="NetworkUI__interface-text" 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)
translate({{-link.length() + link.to_interface.dot_d + 25}}, 0)
rotate({{-link.slope()}})
">
<text ng-attr-class="interface {{link.to_interface.selected && ! link.to_interface.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{link.to_interface.selected && ! link.to_interface.edit_label ? 'NetworkUI__interface-text--selected' : 'NetworkUI--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>
<text class="NetworkUI__interface-text" 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>
ng-attr-class="{{link.from_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" ></circle>
<circle ng-attr-cx="{{link.from_interface.dot_x}}"
ng-attr-cy="{{link.from_interface.dot_y}}"
r=10
class="interface" ></circle>
class="NetworkUI__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>
ng-attr-class="{{link.to_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" ></circle>
<circle ng-attr-cx="{{link.to_interface.dot_x}}"
ng-attr-cy="{{link.to_interface.dot_y}}"
r=10
class="interface" ></circle>
class="NetworkUI__interface" ></circle>
</g>
</g> <!-- end hide_interfaces -->

View File

@@ -0,0 +1,102 @@
<div ng-controller="NetworkUIController">
<svg id="frame" class="NetworkUI"
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-if="!hide_links">
<g ng-repeat="link in links">
<g awx-net-link></g>
</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 awx-net-router></g>
</g> <!-- end router -->
<g ng-switch-when="switch"> <!-- begin switch -->
<g awx-net-switch> </g>
</g> <!-- end switch -->
<g ng-switch-when="host"> <!-- begin host -->
<g awx-net-host> </g>
</g> <!-- end host -->
<g ng-switch-when="rack"> <!-- begin rack -->
<g awx-net-rack> </g>
</g> <!-- end rack -->
<g ng-switch-default> <!-- begin default -->
<g awx-net-default></g>
</g> <!-- end default -->
<g awx-net-status-light></g>
<g awx-net-task-status></g>
</g> <!-- end devices -->
<g ng-repeat="group in groups">
<g awx-net-group></g>
</g>
<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 awx-net-quadrants>
</g>
</g>
<g ng-if="!hide_buttons">
<g> <!-- buttons -->
<g ng-repeat="button in buttons"
ng-attr-transform="translate({{button.x}},{{button.y}})">
<g awx-net-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 awx-net-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 awx-net-layer> </g>
</g>
</g> <!-- end layers -->
</g>
<g awx-net-debug></g>
<g awx-net-cursor></g>
<g ng-repeat="touch in touches">
<g awx-net-touch></g>
</g>
</svg>
</div>

View File

@@ -2,9 +2,9 @@
ng-attr-y1="0"
ng-attr-x2="100000"
ng-attr-y2="0"
ng-attr-class="{{debug.hidden && 'hidden' || ''}} debug" />
ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || 'NetworkUI--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" />
ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || 'NetworkUI--debug'}}" />

View File

@@ -1,35 +1,38 @@
<g ng-if="!debug.hidden">
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></rect>
</g>
<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'}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'NetworkUI__rack--selected-conflict' : device.selected ? 'NetworkUI__rack--selected' : 'NetworkUI__rack--remote-selected' : 'NetworkUI--hidden'}}"
rx=10>
</rect>
<g class="NetworkUI__rack">
<rect
x="-50"
y="-50"
ng-attr-width="{{100}}"
ng-attr-height="{{100}}"
rx=10
class="background">
class="NetworkUI__rack-background">
</rect>
<rect
x="-50"
@@ -56,8 +59,9 @@
<circle cx="30" cy="0" r=7 />
<circle cx="30" cy="35" r=7 />
</circle>
</g>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'NetworkUI--hidden'}}"
filter="url(#selected)"
text-anchor="middle"
x="0"

View File

@@ -1,25 +1,28 @@
<g ng-if="!debug.hidden">
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></line>
<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" />
class="NetworkUI--debug"></rect>
</g>
<g class="NetworkUI__router">
<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'}}">
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'NetworkUI__router--selected-conflict' : device.selected ? 'NetworkUI__router--selected' : 'NetworkUI__router--remote-selected' : 'NetworkUI--hidden'}}">
</circle>
<circle
cx="0"
@@ -48,12 +51,13 @@
<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>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'NetworkUI__router-text--selected' : 'NetworkUI--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>
<text class="NetworkUI__router-text" text-anchor="middle" x="0" y="0">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>

View File

@@ -1,10 +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)}}"/>
<path ng-attr-transform="translate({{-device.width}}, {{-device.height}}) rotate({{frame/3}})" class="NetworkUI__status-path" 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">
ng-attr-class="{{device.status === null ? 'NetworkUI--hidden' : device.status ? 'NetworkUI__status--pass': 'NetworkUI__status--fail'}}">
</circle>
</g>

View File

@@ -1,11 +1,13 @@
<rect x=0
<rect ng-attr-class="{{stencil.is_pressed ? 'NetworkUI__stencil--button-pressed' : stencil.mouse_over ? 'NetworkUI__stencil--button-hover' : 'NetworkUI__stencil'}}"
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}}"
ng-attr-width={{stencil.width}}
ng-attr-height={{stencil.height}}
rx=5></rect>
<text ng-attr-class="{{stencil.is_pressed ? 'NetworkUI__stencil-text--button-pressed' : stencil.mouse_over ? 'NetworkUI__stencil-text--button-hover' : 'NetworkUI__stencil-text'}}"
ng-attr-x="{{stencil.width/2}}"
ng-attr-y="{{stencil.height/2}}"
dy=".3em"
text-anchor="middle">{{stencil.name}}</text>

View File

@@ -1,29 +1,31 @@
<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" />
<g ng-if="!debug.hidden">
<line ng-attr-x1="{{-50 - 10}}"
ng-attr-y1="0"
ng-attr-x2="{{50 + 10}}"
ng-attr-y2="0"
class="NetworkUI--debug"></line>
<line ng-attr-x1="0"
ng-attr-y1="{{-50 - 10}}"
ng-attr-x2="0"
ng-attr-y2="{{50 + 10}}"
class="NetworkUI--debug"></line>
<rect ng-attr-x="{{-50}}"
ng-attr-y="{{-50}}"
ng-attr-width="{{50 * 2}}"
ng-attr-height="{{50 * 2}}"
class="NetworkUI--debug"></rect>
</g>
<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'}}"
ng-attr-class="{{device.selected || device.remote_selected ? device.selected && device.remote_selected ? 'NetworkUI__switch--selected-conflict' : device.selected ? 'NetworkUI__switch--selected' : 'NetworkUI__switch--remote-selected' : 'NetworkUI--hidden'}}"
rx=10>
</rect>
<g class="NetworkUI__switch">
<rect
x="-50"
y="-50"
@@ -51,11 +53,12 @@
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>
<g ng-show="current_scale > 0.5">
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'selected' : 'hidden'}}"
<text ng-attr-class="{{device.selected && ! device.edit_label ? 'NetworkUI__switch-text--selected' : 'NetworkUI--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>
<text class="NetworkUI__switch-text" text-anchor="middle" x="0" y="0">{{device.name}}{{device.edit_label?'_':''}}</text>
</g>

View File

@@ -1,13 +1,13 @@
<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)}}"/>
<path ng-attr-transform="translate({{$index * 12 + 17}}, -5 ) rotate({{frame/3}})" class="NetworkUI__status-path" 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"
ng-attr-class="{{task.status === null ? 'NetworkUI--hidden' : task.status ? 'NetworkUI__status--pass': 'NetworkUI__status--fail'}}"
</circle>
</g>

View File

@@ -1,3 +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 ng-attr-transform="translate({{touch.screenX}},{{touch.screenY}})">
<circle ng-attr-class="{{touch.hidden && 'NetworkUI--hidden' || 'NetworkUI__touch'}}" cx=0 cy=0 r=40></circle>
</g>

View File

@@ -4,7 +4,7 @@ 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%})
{%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%}