diff --git a/awx/network_ui/Makefile b/awx/network_ui/Makefile index 3399013451..80878d207d 100644 --- a/awx/network_ui/Makefile +++ b/awx/network_ui/Makefile @@ -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 diff --git a/awx/network_ui/admin.py b/awx/network_ui/admin.py index 4128d167dd..5a371ca476 100644 --- a/awx/network_ui/admin.py +++ b/awx/network_ui/admin.py @@ -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) diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py index f9b88899ba..eeadc12f84 100644 --- a/awx/network_ui/consumers.py +++ b/awx/network_ui/consumers.py @@ -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])}) diff --git a/awx/network_ui/designs/models.yml b/awx/network_ui/designs/models.yml index 748c10d779..3a52a4a23f 100644 --- a/awx/network_ui/designs/models.yml +++ b/awx/network_ui/designs/models.yml @@ -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 diff --git a/awx/network_ui/migrations/0012_auto_20170706_1526.py b/awx/network_ui/migrations/0012_auto_20170706_1526.py new file mode 100644 index 0000000000..13ef7b27a5 --- /dev/null +++ b/awx/network_ui/migrations/0012_auto_20170706_1526.py @@ -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), + ), + ] diff --git a/awx/network_ui/migrations/0013_auto_20170710_1840.py b/awx/network_ui/migrations/0013_auto_20170710_1840.py new file mode 100644 index 0000000000..16ba192aa5 --- /dev/null +++ b/awx/network_ui/migrations/0013_auto_20170710_1840.py @@ -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), + ), + ] diff --git a/awx/network_ui/migrations/0014_group_topology.py b/awx/network_ui/migrations/0014_group_topology.py new file mode 100644 index 0000000000..e3e0de45ed --- /dev/null +++ b/awx/network_ui/migrations/0014_group_topology.py @@ -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, + ), + ] diff --git a/awx/network_ui/migrations/0015_auto_20170710_1937.py b/awx/network_ui/migrations/0015_auto_20170710_1937.py new file mode 100644 index 0000000000..21b2d7db03 --- /dev/null +++ b/awx/network_ui/migrations/0015_auto_20170710_1937.py @@ -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'), + ), + ] diff --git a/awx/network_ui/models.py b/awx/network_ui/models.py index 0256e7494e..72420130fb 100644 --- a/awx/network_ui/models.py +++ b/awx/network_ui/models.py @@ -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',) diff --git a/awx/network_ui/serializers.py b/awx/network_ui/serializers.py index 4d1f533c20..0290d4ee42 100644 --- a/awx/network_ui/serializers.py +++ b/awx/network_ui/serializers.py @@ -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 diff --git a/awx/network_ui/static/network_ui/CONTRIBUTING.md b/awx/network_ui/static/network_ui/CONTRIBUTING.md new file mode 100644 index 0000000000..2b835bdae2 --- /dev/null +++ b/awx/network_ui/static/network_ui/CONTRIBUTING.md @@ -0,0 +1,10 @@ + + +To build the UI: + +make + + +To push the UI to tower code base: + +make deploy diff --git a/awx/network_ui/static/network_ui/Makefile b/awx/network_ui/static/network_ui/Makefile index 6a5f3bfbe6..b24ed19334 100644 --- a/awx/network_ui/static/network_ui/Makefile +++ b/awx/network_ui/static/network_ui/Makefile @@ -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/ diff --git a/awx/network_ui/static/network_ui/designs/group.yml b/awx/network_ui/static/network_ui/designs/group.yml new file mode 100644 index 0000000000..c5b3f146c8 --- /dev/null +++ b/awx/network_ui/static/network_ui/designs/group.yml @@ -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 diff --git a/awx/network_ui/static/network_ui/designs/hotkeys.yml b/awx/network_ui/static/network_ui/designs/hotkeys.yml new file mode 100644 index 0000000000..95fd1d3982 --- /dev/null +++ b/awx/network_ui/static/network_ui/designs/hotkeys.yml @@ -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 diff --git a/awx/network_ui/static/network_ui/designs/move.yml b/awx/network_ui/static/network_ui/designs/move.yml index a3a18757cc..4425aaa4d0 100644 --- a/awx/network_ui/static/network_ui/designs/move.yml +++ b/awx/network_ui/static/network_ui/designs/move.yml @@ -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 diff --git a/awx/network_ui/static/network_ui/designs/null.yml b/awx/network_ui/static/network_ui/designs/null.yml new file mode 100644 index 0000000000..d8e3e8dd06 --- /dev/null +++ b/awx/network_ui/static/network_ui/designs/null.yml @@ -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 diff --git a/awx/network_ui/static/network_ui/designs/time.yml b/awx/network_ui/static/network_ui/designs/time.yml index b3953607c1..4c431fabd3 100644 --- a/awx/network_ui/static/network_ui/designs/time.yml +++ b/awx/network_ui/static/network_ui/designs/time.yml @@ -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 diff --git a/awx/network_ui/static/network_ui/index.html b/awx/network_ui/static/network_ui/index.html index 852bee9ae8..e5cc3d53ae 100644 --- a/awx/network_ui/static/network_ui/index.html +++ b/awx/network_ui/static/network_ui/index.html @@ -1,111 +1,12 @@ - + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/awx/network_ui/static/network_ui/index2.html b/awx/network_ui/static/network_ui/index2.html new file mode 100644 index 0000000000..0fc5f099c9 --- /dev/null +++ b/awx/network_ui/static/network_ui/index2.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/awx/network_ui/static/network_ui/package.json b/awx/network_ui/static/network_ui/package.json index 164c171b5e..02379a0b65 100644 --- a/awx/network_ui/static/network_ui/package.json +++ b/awx/network_ui/static/network_ui/package.json @@ -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", diff --git a/awx/network_ui/static/network_ui/run.sh b/awx/network_ui/static/network_ui/run.sh index ee8b903bbe..717246ffd3 100755 --- a/awx/network_ui/static/network_ui/run.sh +++ b/awx/network_ui/static/network_ui/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -ex -python -m SimpleHTTPServer +python -m SimpleHTTPServer 8080 diff --git a/awx/network_ui/static/network_ui/src/button.directive.js b/awx/network_ui/static/network_ui/src/button.directive.js new file mode 100644 index 0000000000..ea1515a585 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/button.directive.js @@ -0,0 +1,5 @@ + +function button () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/button.html' }; +} +exports.button = button; diff --git a/awx/network_ui/static/network_ui/src/button.js b/awx/network_ui/static/network_ui/src/button.js index 276a1d7a41..1d9ea3a1bf 100644 --- a/awx/network_ui/static/network_ui/src/button.js +++ b/awx/network_ui/static/network_ui/src/button.js @@ -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']; diff --git a/awx/network_ui/static/network_ui/src/buttons.js b/awx/network_ui/static/network_ui/src/buttons.js index 4e2017d5dd..d63f73b636 100644 --- a/awx/network_ui/static/network_ui/src/buttons.js +++ b/awx/network_ui/static/network_ui/src/buttons.js @@ -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]; diff --git a/awx/network_ui/static/network_ui/src/cursor.directive.js b/awx/network_ui/static/network_ui/src/cursor.directive.js new file mode 100644 index 0000000000..27c8982ca5 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/cursor.directive.js @@ -0,0 +1,5 @@ + +function cursor () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/cursor.html' }; +} +exports.cursor = cursor; diff --git a/awx/network_ui/static/network_ui/src/debug.directive.js b/awx/network_ui/static/network_ui/src/debug.directive.js new file mode 100644 index 0000000000..d26f979bc6 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/debug.directive.js @@ -0,0 +1,6 @@ + +function debug () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/debug.html' }; +} + +exports.debug = debug; diff --git a/awx/network_ui/static/network_ui/src/default.directive.js b/awx/network_ui/static/network_ui/src/default.directive.js new file mode 100644 index 0000000000..7c0c865ad2 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/default.directive.js @@ -0,0 +1,5 @@ + +function defaultd () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/default.html' }; +} +exports.defaultd = defaultd; diff --git a/awx/network_ui/static/network_ui/src/group.directive.js b/awx/network_ui/static/network_ui/src/group.directive.js new file mode 100644 index 0000000000..78ea434658 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/group.directive.js @@ -0,0 +1,5 @@ + +function group () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/group.html' }; +} +exports.group = group; diff --git a/awx/network_ui/static/network_ui/src/group.js b/awx/network_ui/static/network_ui/src/group.js new file mode 100644 index 0000000000..f0bcdd5cae --- /dev/null +++ b/awx/network_ui/static/network_ui/src/group.js @@ -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']; + diff --git a/awx/network_ui/static/network_ui/src/host.directive.js b/awx/network_ui/static/network_ui/src/host.directive.js new file mode 100644 index 0000000000..c1543430ed --- /dev/null +++ b/awx/network_ui/static/network_ui/src/host.directive.js @@ -0,0 +1,5 @@ + +function host () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/host.html' }; +} +exports.host = host; diff --git a/awx/network_ui/static/network_ui/src/hotkeys.fsm.js b/awx/network_ui/static/network_ui/src/hotkeys.fsm.js new file mode 100644 index 0000000000..5ae9c78f6f --- /dev/null +++ b/awx/network_ui/static/network_ui/src/hotkeys.fsm.js @@ -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']; + + diff --git a/awx/network_ui/static/network_ui/src/layer.directive.js b/awx/network_ui/static/network_ui/src/layer.directive.js new file mode 100644 index 0000000000..195f598ceb --- /dev/null +++ b/awx/network_ui/static/network_ui/src/layer.directive.js @@ -0,0 +1,5 @@ + +function layer () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/layer.html' }; +} +exports.layer = layer; diff --git a/awx/network_ui/static/network_ui/src/link.directive.js b/awx/network_ui/static/network_ui/src/link.directive.js new file mode 100644 index 0000000000..14c791e709 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/link.directive.js @@ -0,0 +1,5 @@ + +function link () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/link.html' }; +} +exports.link = link; diff --git a/awx/network_ui/static/network_ui/src/link.js b/awx/network_ui/static/network_ui/src/link.js index 118fc7398e..1de49e4921 100644 --- a/awx/network_ui/static/network_ui/src/link.js +++ b/awx/network_ui/static/network_ui/src/link.js @@ -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; diff --git a/awx/network_ui/static/network_ui/src/main.js b/awx/network_ui/static/network_ui/src/main.js index c778c38c09..6a37717da7 100644 --- a/awx/network_ui/static/network_ui/src/main.js +++ b/awx/network_ui/static/network_ui/src/main.js @@ -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; diff --git a/awx/network_ui/static/network_ui/src/messages.js b/awx/network_ui/static/network_ui/src/messages.js index cbf1b55c6c..6a40b8eac5 100644 --- a/awx/network_ui/static/network_ui/src/messages.js +++ b/awx/network_ui/static/network_ui/src/messages.js @@ -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; diff --git a/awx/network_ui/static/network_ui/src/models.js b/awx/network_ui/static/network_ui/src/models.js index 8ea2b470a6..15b3d79b37 100644 --- a/awx/network_ui/static/network_ui/src/models.js +++ b/awx/network_ui/static/network_ui/src/models.js @@ -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]; +}; diff --git a/awx/network_ui/static/network_ui/src/move.js b/awx/network_ui/static/network_ui/src/move.js index ca3d4d08a7..60b813ffc7 100644 --- a/awx/network_ui/static/network_ui/src/move.js +++ b/awx/network_ui/static/network_ui/src/move.js @@ -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']; + + diff --git a/awx/network_ui/static/network_ui/src/network.ui.app.js b/awx/network_ui/static/network_ui/src/network.ui.app.js new file mode 100644 index 0000000000..eda65ee413 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/network.ui.app.js @@ -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; diff --git a/awx/network_ui/static/network_ui/src/app.js b/awx/network_ui/static/network_ui/src/network.ui.controller.js similarity index 78% rename from awx/network_ui/static/network_ui/src/app.js rename to awx/network_ui/static/network_ui/src/network.ui.controller.js index 367b25be28..d320d65971 100644 --- a/awx/network_ui/static/network_ui/src/app.js +++ b/awx/network_ui/static/network_ui/src/network.ui.controller.js @@ -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"); diff --git a/awx/network_ui/static/network_ui/src/network.ui.directive.js b/awx/network_ui/static/network_ui/src/network.ui.directive.js new file mode 100644 index 0000000000..e225ccb5f9 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/network.ui.directive.js @@ -0,0 +1,5 @@ + +function awxNetworkUI () { + return { restrict: 'E', templateUrl: '/static/network_ui/widgets/network_ui.html' }; +} +exports.awxNetworkUI = awxNetworkUI; diff --git a/awx/network_ui/static/network_ui/src/null.fsm.js b/awx/network_ui/static/network_ui/src/null.fsm.js new file mode 100644 index 0000000000..887e811b80 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/null.fsm.js @@ -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']; + + + + diff --git a/awx/network_ui/static/network_ui/src/quadrants.directive.js b/awx/network_ui/static/network_ui/src/quadrants.directive.js new file mode 100644 index 0000000000..60a1ae4519 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/quadrants.directive.js @@ -0,0 +1,5 @@ + +function quadrants () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/quadrants.html' }; +} +exports.quadrants = quadrants; diff --git a/awx/network_ui/static/network_ui/src/rack.directive.js b/awx/network_ui/static/network_ui/src/rack.directive.js new file mode 100644 index 0000000000..45612f9b8d --- /dev/null +++ b/awx/network_ui/static/network_ui/src/rack.directive.js @@ -0,0 +1,5 @@ + +function rack () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/rack.html' }; +} +exports.rack = rack; diff --git a/awx/network_ui/static/network_ui/src/router.directive.js b/awx/network_ui/static/network_ui/src/router.directive.js new file mode 100644 index 0000000000..753090f82b --- /dev/null +++ b/awx/network_ui/static/network_ui/src/router.directive.js @@ -0,0 +1,5 @@ + +function router () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/router.html' }; +} +exports.router = router; diff --git a/awx/network_ui/static/network_ui/src/status.light.directive.js b/awx/network_ui/static/network_ui/src/status.light.directive.js new file mode 100644 index 0000000000..e2ee6eec45 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/status.light.directive.js @@ -0,0 +1,5 @@ + +function statusLight () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/status_light.html' }; +} +exports.statusLight = statusLight; diff --git a/awx/network_ui/static/network_ui/src/stencil.directive.js b/awx/network_ui/static/network_ui/src/stencil.directive.js new file mode 100644 index 0000000000..d6c7a42af3 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/stencil.directive.js @@ -0,0 +1,5 @@ + +function stencil () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/stencil.html' }; +} +exports.stencil = stencil; diff --git a/awx/network_ui/static/network_ui/src/style.less b/awx/network_ui/static/network_ui/src/style.less index e69693957b..ff11fd4bc3 100644 --- a/awx/network_ui/static/network_ui/src/style.less +++ b/awx/network_ui/static/network_ui/src/style.less @@ -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'; +} diff --git a/awx/network_ui/static/network_ui/src/switch.directive.js b/awx/network_ui/static/network_ui/src/switch.directive.js new file mode 100644 index 0000000000..7d123e1b9d --- /dev/null +++ b/awx/network_ui/static/network_ui/src/switch.directive.js @@ -0,0 +1,5 @@ + +function switchd () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/switch.html' }; +} +exports.switchd = switchd; diff --git a/awx/network_ui/static/network_ui/src/task.status.directive.js b/awx/network_ui/static/network_ui/src/task.status.directive.js new file mode 100644 index 0000000000..31db3fb435 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/task.status.directive.js @@ -0,0 +1,5 @@ + +function taskStatus () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/task_status.html' }; +} +exports.taskStatus = taskStatus; diff --git a/awx/network_ui/static/network_ui/src/time.js b/awx/network_ui/static/network_ui/src/time.js index ce8b61d237..6b731acefc 100644 --- a/awx/network_ui/static/network_ui/src/time.js +++ b/awx/network_ui/static/network_ui/src/time.js @@ -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) { diff --git a/awx/network_ui/static/network_ui/src/touch.directive.js b/awx/network_ui/static/network_ui/src/touch.directive.js new file mode 100644 index 0000000000..6c373fee32 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/touch.directive.js @@ -0,0 +1,5 @@ + +function touch () { + return { restrict: 'A', templateUrl: '/static/network_ui/widgets/touch.html' }; +} +exports.touch = touch; diff --git a/awx/network_ui/static/network_ui/src/tower.app.js b/awx/network_ui/static/network_ui/src/tower.app.js new file mode 100644 index 0000000000..44890e9805 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/tower.app.js @@ -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: 'Topology' + }); + + $stateProvider + .state({ + name: 'topology', + url: '/topology', + template: "" + }); +}); + +exports.tower = tower; +exports.ui_router = ui_router; + diff --git a/awx/network_ui/static/network_ui/src/util.js b/awx/network_ui/static/network_ui/src/util.js index a0db4d38a6..abd8d78902 100644 --- a/awx/network_ui/static/network_ui/src/util.js +++ b/awx/network_ui/static/network_ui/src/util.js @@ -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 diff --git a/awx/network_ui/static/network_ui/src/view.js b/awx/network_ui/static/network_ui/src/view.js index 4b3497be40..4f55d57ade 100644 --- a/awx/network_ui/static/network_ui/src/view.js +++ b/awx/network_ui/static/network_ui/src/view.js @@ -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; diff --git a/awx/network_ui/static/network_ui/templates/fsm.jst b/awx/network_ui/static/network_ui/templates/fsm.jst index ea3378933f..afe75f5cd8 100644 --- a/awx/network_ui/static/network_ui/templates/fsm.jst +++ b/awx/network_ui/static/network_ui/templates/fsm.jst @@ -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%} diff --git a/awx/network_ui/static/network_ui/tools/transform_fsm.py b/awx/network_ui/static/network_ui/tools/transform_fsm.py new file mode 100755 index 0000000000..21da6e74ce --- /dev/null +++ b/awx/network_ui/static/network_ui/tools/transform_fsm.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Usage: + transform_fsm [options] + +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['']) 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[''], 'w') as f: + f.write(yaml.safe_dump(data, default_flow_style=False)) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) + diff --git a/awx/network_ui/static/network_ui/vendor/ngTouch.js b/awx/network_ui/static/network_ui/vendor/ngTouch.js index 2fd3adb42d..91da5c1c03 100644 --- a/awx/network_ui/static/network_ui/vendor/ngTouch.js +++ b/awx/network_ui/static/network_ui/vendor/ngTouch.js @@ -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", []) } }] - } + }; }); diff --git a/awx/network_ui/static/network_ui/vendor/reconnecting-websocket.js b/awx/network_ui/static/network_ui/vendor/reconnecting-websocket.js deleted file mode 100644 index 0cd4332dcf..0000000000 --- a/awx/network_ui/static/network_ui/vendor/reconnecting-websocket.js +++ /dev/null @@ -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; -}); diff --git a/awx/network_ui/static/network_ui/vendor/svg-crowbar.js b/awx/network_ui/static/network_ui/vendor/svg-crowbar.js index 359563054d..bba3093801 100644 --- a/awx/network_ui/static/network_ui/vendor/svg-crowbar.js +++ b/awx/network_ui/static/network_ui/vendor/svg-crowbar.js @@ -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() { diff --git a/awx/network_ui/static/network_ui/webpack.config.js b/awx/network_ui/static/network_ui/webpack.config.js new file mode 100644 index 0000000000..2b1e8399da --- /dev/null +++ b/awx/network_ui/static/network_ui/webpack.config.js @@ -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' }) + ] +}; diff --git a/awx/network_ui/static/network_ui/widgets/button.html b/awx/network_ui/static/network_ui/widgets/button.html index 318dae9520..682e4d2531 100644 --- a/awx/network_ui/static/network_ui/widgets/button.html +++ b/awx/network_ui/static/network_ui/widgets/button.html @@ -1,11 +1,13 @@ - - + {{button.name}} diff --git a/awx/network_ui/static/network_ui/widgets/cursor.html b/awx/network_ui/static/network_ui/widgets/cursor.html index 592703c4c1..496c105463 100644 --- a/awx/network_ui/static/network_ui/widgets/cursor.html +++ b/awx/network_ui/static/network_ui/widgets/cursor.html @@ -1,4 +1,4 @@ - - - + + + diff --git a/awx/network_ui/static/network_ui/widgets/debug.html b/awx/network_ui/static/network_ui/widgets/debug.html index 6a4400ad26..2257c390d9 100644 --- a/awx/network_ui/static/network_ui/widgets/debug.html +++ b/awx/network_ui/static/network_ui/widgets/debug.html @@ -1,32 +1,35 @@ - - width: {{graph.width}} - height: {{graph.height}} - rc: {{graph.right_column}} - Mouse down: {{onMouseDownResult}} - Mouse up: {{onMouseUpResult}} - Mouse move: {{onMouseMoveResult}} - Mouse over: {{onMouseOverResult}} - Mouse enter: {{onMouseEnterResult}} - Mouse leave: {{onMouseLeaveResult}} - Current scale: {{current_scale.toFixed(2)}} - Pan X: {{panX.toFixed(2)}} - Pan Y: {{panY.toFixed(2)}} - View State: {{view_controller.state.name}} - Mouse X: {{mouseX.toFixed(2)}} - Mouse Y: {{mouseY.toFixed(2)}} - Scaled X: {{scaledX.toFixed(2)}} - Scaled Y: {{scaledY.toFixed(2)}} - Key: {{last_key}} - Key Code: {{last_key_code}} - Move State: {{move_controller.state.name}} - Selected devices: {{selected_devices.length}} - Selected links: {{selected_links.length}} - Link State: {{link_controller.state.name}} - Buttons State: {{buttons_controller.state.name}} - Time State: {{time_controller.state.name}} - Time Pointer: {{time_pointer}} - History: {{history.length}} - Touch Data: {{touch_data.xb}} {{touch_data.yb}} {{touch_data.d}} + + width: {{graph.width}} + height: {{graph.height}} + rc: {{graph.right_column}} + Mouse down: {{onMouseDownResult}} + Mouse up: {{onMouseUpResult}} + Mouse move: {{onMouseMoveResult}} + Mouse over: {{onMouseOverResult}} + Mouse enter: {{onMouseEnterResult}} + Mouse leave: {{onMouseLeaveResult}} + Current scale: {{current_scale.toFixed(2)}} + Pan X: {{panX.toFixed(2)}} + Pan Y: {{panY.toFixed(2)}} + View State: {{view_controller.state.name}} + Mouse X: {{mouseX.toFixed(2)}} + Mouse Y: {{mouseY.toFixed(2)}} + Scaled X: {{scaledX.toFixed(2)}} + Scaled Y: {{scaledY.toFixed(2)}} + Key: {{last_key}} + Key Code: {{last_key_code}} + Move State: {{move_controller.state.name}} + Selected devices: {{selected_devices.length}} + Selected links: {{selected_links.length}} + Link State: {{link_controller.state.name}} + Buttons State: {{buttons_controller.state.name}} + Time State: {{time_controller.state.name}} + Time Pointer: {{time_pointer}} + History: {{history.length}} + Touch Data: {{touch_data.xb}} {{touch_data.yb}} {{touch_data.d}} + Group State: {{group_controller.state.name}} + Selected groups: {{selected_groups.length}} + Hotkeys State: {{hotkeys_controller.state.name}} diff --git a/awx/network_ui/static/network_ui/widgets/group.html b/awx/network_ui/static/network_ui/widgets/group.html new file mode 100644 index 0000000000..a3804843b8 --- /dev/null +++ b/awx/network_ui/static/network_ui/widgets/group.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + {{group.name}} +{{group.name}}{{group.edit_label?'_':''}} + diff --git a/awx/network_ui/static/network_ui/widgets/host.html b/awx/network_ui/static/network_ui/widgets/host.html index 9e8ce63fa1..2d065df54d 100644 --- a/awx/network_ui/static/network_ui/widgets/host.html +++ b/awx/network_ui/static/network_ui/widgets/host.html @@ -1,26 +1,29 @@ + + class="NetworkUI--debug"> + class="NetworkUI--debug"> + class="NetworkUI--debug"> + + + - {{device.name}} - {{device.name}}{{device.edit_label?'_':''}} + {{device.name}}{{device.edit_label?'_':''}} diff --git a/awx/network_ui/static/network_ui/widgets/layer.html b/awx/network_ui/static/network_ui/widgets/layer.html index 44d9b56338..b33526ed0d 100644 --- a/awx/network_ui/static/network_ui/widgets/layer.html +++ b/awx/network_ui/static/network_ui/widgets/layer.html @@ -1,11 +1,13 @@ - - + {{layer.name}} diff --git a/awx/network_ui/static/network_ui/widgets/link.html b/awx/network_ui/static/network_ui/widgets/link.html index 57e7dd2850..66bb38e334 100644 --- a/awx/network_ui/static/network_ui/widgets/link.html +++ b/awx/network_ui/static/network_ui/widgets/link.html @@ -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'}}"/> + class="{{link.status === null ? 'NetworkUI__link' : link.status ? 'NetworkUI__link--link-pass' : 'NetworkUI__link--link-fail'}}"/> + ng-attr-class="NetworkUI__link--debug" /> + class="NetworkUI__circle-debug" > - + + class="NetworkUI__circle-debug" > + class="NetworkUI__circle-debug" > - {{link.name}} -{{link.name}}{{link.edit_label?'_':''}} +{{link.name}}{{link.edit_label?'_':''}} - {{link.from_interface.name}} -{{link.from_interface.name}}{{link.from_interface.edit_label ?'_':''}} +{{link.from_interface.name}}{{link.from_interface.edit_label ?'_':''}} - {{link.to_interface.name}} -{{link.to_interface.name}}{{link.to_interface.edit_label?'_':''}} +{{link.to_interface.name}}{{link.to_interface.edit_label?'_':''}} + ng-attr-class="{{link.from_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" > + class="NetworkUI__interface" > + ng-attr-class="{{link.to_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" > + class="NetworkUI__interface" > diff --git a/awx/network_ui/static/network_ui/widgets/network_ui.html b/awx/network_ui/static/network_ui/widgets/network_ui.html new file mode 100644 index 0000000000..8cbc64678f --- /dev/null +++ b/awx/network_ui/static/network_ui/widgets/network_ui.html @@ -0,0 +1,102 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/awx/network_ui/static/network_ui/widgets/quadrants.html b/awx/network_ui/static/network_ui/widgets/quadrants.html index eceb02cb3a..d1c3310812 100644 --- a/awx/network_ui/static/network_ui/widgets/quadrants.html +++ b/awx/network_ui/static/network_ui/widgets/quadrants.html @@ -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'}}" /> + ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || 'NetworkUI--debug'}}" /> diff --git a/awx/network_ui/static/network_ui/widgets/rack.html b/awx/network_ui/static/network_ui/widgets/rack.html index 92e4bfe2b8..89fcf4742e 100644 --- a/awx/network_ui/static/network_ui/widgets/rack.html +++ b/awx/network_ui/static/network_ui/widgets/rack.html @@ -1,35 +1,38 @@ + + class="NetworkUI--debug"> + class="NetworkUI--debug"> + class="NetworkUI--debug"> + + + class="NetworkUI__rack-background"> + - + class="NetworkUI--debug"> + class="NetworkUI--debug"> + class="NetworkUI--debug"> + + + 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'}}"> +
- {{device.name}} - {{device.name}}{{device.edit_label?'_':''}} + {{device.name}}{{device.edit_label?'_':''}} diff --git a/awx/network_ui/static/network_ui/widgets/status_light.html b/awx/network_ui/static/network_ui/widgets/status_light.html index c53a434306..354964fb6a 100644 --- a/awx/network_ui/static/network_ui/widgets/status_light.html +++ b/awx/network_ui/static/network_ui/widgets/status_light.html @@ -1,10 +1,10 @@ - + + ng-attr-class="{{device.status === null ? 'NetworkUI--hidden' : device.status ? 'NetworkUI__status--pass': 'NetworkUI__status--fail'}}"> diff --git a/awx/network_ui/static/network_ui/widgets/stencil.html b/awx/network_ui/static/network_ui/widgets/stencil.html index 80509facd1..8cccc5da0d 100644 --- a/awx/network_ui/static/network_ui/widgets/stencil.html +++ b/awx/network_ui/static/network_ui/widgets/stencil.html @@ -1,11 +1,13 @@ - - + {{stencil.name}} diff --git a/awx/network_ui/static/network_ui/widgets/switch.html b/awx/network_ui/static/network_ui/widgets/switch.html index 919e736b21..5d0ab6ac9f 100644 --- a/awx/network_ui/static/network_ui/widgets/switch.html +++ b/awx/network_ui/static/network_ui/widgets/switch.html @@ -1,29 +1,31 @@ - - - + + + + + - + + - {{device.name}} - {{device.name}}{{device.edit_label?'_':''}} + {{device.name}}{{device.edit_label?'_':''}} diff --git a/awx/network_ui/static/network_ui/widgets/task_status.html b/awx/network_ui/static/network_ui/widgets/task_status.html index 05eab187c2..b850a238ec 100644 --- a/awx/network_ui/static/network_ui/widgets/task_status.html +++ b/awx/network_ui/static/network_ui/widgets/task_status.html @@ -1,13 +1,13 @@ - + diff --git a/awx/network_ui/static/network_ui/widgets/touch.html b/awx/network_ui/static/network_ui/widgets/touch.html index 32b1193760..c7a3365610 100644 --- a/awx/network_ui/static/network_ui/widgets/touch.html +++ b/awx/network_ui/static/network_ui/widgets/touch.html @@ -1,3 +1,3 @@ - - + + diff --git a/awx/network_ui/templates/models.pyt b/awx/network_ui/templates/models.pyt index e6d52fd497..dbba7c748d 100644 --- a/awx/network_ui/templates/models.pyt +++ b/awx/network_ui/templates/models.pyt @@ -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%}