Merge pull request #1352 from ansible/network_ui_3_3
Graphical UI for Network Inventory
@ -178,7 +178,7 @@ class URLModificationMiddleware(object):
|
||||
return '/'.join(url_units)
|
||||
|
||||
def process_request(self, request):
|
||||
if 'REQUEST_URI' in request.environ:
|
||||
if hasattr(request, 'environ') and 'REQUEST_URI' in request.environ:
|
||||
old_path = six.moves.urllib.parse.urlsplit(request.environ['REQUEST_URI']).path
|
||||
old_path = old_path[request.path.find(request.path_info):]
|
||||
else:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
from channels.routing import route
|
||||
from awx.network_ui.routing import channel_routing as network_ui_routing
|
||||
|
||||
|
||||
channel_routing = [
|
||||
@ -6,3 +7,6 @@ channel_routing = [
|
||||
route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'),
|
||||
route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'),
|
||||
]
|
||||
|
||||
|
||||
channel_routing += network_ui_routing
|
||||
|
||||
133
awx/network_ui/CONTRIBUTING.md
Normal file
@ -0,0 +1,133 @@
|
||||
Network UI
|
||||
==========
|
||||
|
||||
See [awx/ui/client/src/network-ui/CONTRIBUTING.md](../ui/client/src/network-ui/CONTRIBUTING.md) for the introduction
|
||||
to the Network UI client-side development.
|
||||
|
||||
Server-Side Development
|
||||
-----------------------
|
||||
|
||||
This document covers the Network UI server-side development.
|
||||
|
||||
The Network UI is a UX driven feature to provide a graphical user
|
||||
experience that fits well into the network engineer's normal workflow. Their
|
||||
normal workflow includes a diagram drawn in a graphical drawing program, a
|
||||
spreadsheet, and the command line interface of their network gear. Network
|
||||
architects design the network on the graphical diagram and then hand off the
|
||||
architecture to network operators who implement the architecture on the network
|
||||
using spreadsheets to manage their data and manually converting the data into
|
||||
CLI commands using their networking expertise and expertise with their physical
|
||||
gear.
|
||||
|
||||
The server-side code supports the persistence needed to provide this graphical
|
||||
user experience of architecting a network and using that information along with
|
||||
additional information (stored in vars files) to configure the network devices
|
||||
using the CLI or NETCONF using Ansible playbooks and roles.
|
||||
|
||||
Network UI Data Schema
|
||||
----------------------
|
||||
|
||||
For the 3.3 release the persistence needed includes the position information of
|
||||
the devices on the virtual canvas and the type of the devices as well as
|
||||
information about the interfaces on the devices and the links connecting those
|
||||
interfaces.
|
||||
|
||||
These requirements determine the database schema needed for the network UI which
|
||||
requires these models: Topology, Device, Interface, Link, Client, and TopologyInventory.
|
||||
|
||||

|
||||
|
||||
This diagram shows the relationships between the models in the Network UI schema.
|
||||
|
||||
The models are:
|
||||
|
||||
* Device - a host, switch, router, or other networking device
|
||||
* Interface - a connection point on a device for a link
|
||||
* Link - a physical connection between two devices to their respective interfaces
|
||||
* Topology - a collection of devices and links
|
||||
* TopologyInventory - a mapping between topologies and Tower inventories
|
||||
* Client - a UI client session
|
||||
|
||||
|
||||
Network UI Websocket Protocol
|
||||
-----------------------------
|
||||
|
||||
Persistence for the network UI canvas state is implemented using an
|
||||
asynchronous websocket protocol to send information from the client to the
|
||||
server and vice-versa. This two-way communication was chosen to support future
|
||||
features for streaming data to the canvas, broadcast messaging between clients,
|
||||
and for interaction performance on the UI.
|
||||
|
||||
|
||||
Messages
|
||||
--------
|
||||
|
||||
JSON messages are passed over the `/network_ui/topology` websocket between the
|
||||
test client and the test server. The protocol that is used for all messages is
|
||||
in ABNF (RFC5234):
|
||||
|
||||
|
||||
message_type = 'DeviceMove' / 'DeviceCreate' / 'DeviceDestroy' / 'DeviceLabelEdit' / 'DeviceSelected' / 'DeviceUnSelected' / 'InterfaceCreate' / 'InterfaceLabelEdit' / 'LinkLabelEdit' / 'LinkCreate' / 'LinkDestroy' / 'LinkSelected' / 'LinkUnSelected' / 'MultipleMessage' / 'Snapshot'
|
||||
message_data = '{' 'msg_type' ': ' message_type ', ' key-value *( ', ' key-value ) '}'
|
||||
message = '[ id , ' posint ']' / '[ topology_id , ' posint ']' / '[' message_type ', ' message_data ']'
|
||||
|
||||
See https://github.com/AndyA/abnfgen/blob/master/andy/json.abnf for the rest of
|
||||
the JSON ABNF.
|
||||
|
||||
See [designs/messages.yml](designs/messages.yml) for the allowable keys and
|
||||
values for each message type.
|
||||
|
||||
|
||||
Initially when the websocket is first opened the server will send four messages
|
||||
to the client. These are:
|
||||
|
||||
* the client id using the `id` message type.
|
||||
* the topology id using the `topology` message type.
|
||||
* a Topology record containing data for the canvas itself.
|
||||
* a Snapshot message containing all the data of the data on the canvas.
|
||||
|
||||
As the user interacts with the canvas messages will be generated by the client
|
||||
and the `network_ui.consumers.Persistence` class will update the models that
|
||||
represent the canvas.
|
||||
|
||||
|
||||
|
||||
Persistence
|
||||
-----------
|
||||
|
||||
The class `awx.network_uiconsumers.Persistence` provides persistence for the Network UI canvas.
|
||||
It does so by providing message handlers that handle storage of the canvas change events
|
||||
into the database. Each event has a message handle with name `onX` where `X` is the name of the message
|
||||
type. The handlers use the `filter/values_list`, `filter/values`, `filter/update`, and `filter/delete`
|
||||
patterns to update the data in the database quickly with a constant O(1) number of queries per event
|
||||
often with only one query needed. With `filter/update` and `filter/delete` all the work is done
|
||||
in the database and Python never needs to instaniate and garbage collect the model objects.
|
||||
|
||||
Bulk operations (`filter/values`) in `send_snapshot` are used to produce a constant number of
|
||||
queries produce a snapshot when the canvas is first loaded. This method avoids creating
|
||||
the model objects since it only produces dicts that are JSON serializable which are bundled
|
||||
together for the `Snapshot` message type.
|
||||
|
||||
This method of persistence uses Django as a database query-compiler for transforms from
|
||||
the event types to the database types. Using Django in this way is very performant since
|
||||
Python does very little work processing the data and when possible the data never leaves
|
||||
the database.
|
||||
|
||||
|
||||
Client Tracking
|
||||
---------------
|
||||
|
||||
Each user session to the network UI canvas is tracked with the `Client` model. Multiple
|
||||
clients can view and interact with the network UI canvas at a time. They will see each other's
|
||||
edits to the canvas in real time. This works by broadcasting the canvas change events to
|
||||
all clients viewing the same topology.
|
||||
|
||||
```
|
||||
# Send to all clients editing the topology
|
||||
Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']})
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
There is no user accessible API for this feature in the 3.3 release.
|
||||
2
awx/network_ui/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
|
||||
280
awx/network_ui/consumers.py
Normal file
@ -0,0 +1,280 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
from channels import Group
|
||||
from channels.sessions import channel_session
|
||||
from awx.network_ui.models import Topology, Device, Link, Client, Interface
|
||||
from awx.network_ui.models import TopologyInventory
|
||||
import urlparse
|
||||
from django.db.models import Q
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
|
||||
|
||||
from awx.network_ui.utils import transform_dict
|
||||
|
||||
import json
|
||||
|
||||
logger = logging.getLogger("awx.network_ui.consumers")
|
||||
|
||||
|
||||
def parse_inventory_id(data):
|
||||
inventory_id = data.get('inventory_id', ['null'])
|
||||
try:
|
||||
inventory_id = int(inventory_id[0])
|
||||
except ValueError:
|
||||
inventory_id = None
|
||||
if not inventory_id:
|
||||
inventory_id = None
|
||||
return inventory_id
|
||||
|
||||
|
||||
class NetworkingEvents(object):
|
||||
|
||||
'''
|
||||
Provides handlers for the networking events for the topology canvas.
|
||||
'''
|
||||
|
||||
def parse_message_text(self, message_text, client_id):
|
||||
'''
|
||||
See the Messages of CONTRIBUTING.md for the message format.
|
||||
'''
|
||||
data = json.loads(message_text)
|
||||
if len(data) == 2:
|
||||
message_type = data.pop(0)
|
||||
message_value = data.pop(0)
|
||||
if isinstance(message_value, list):
|
||||
logger.error("Message has no sender")
|
||||
return None, None
|
||||
if isinstance(message_value, dict) and client_id != message_value.get('sender'):
|
||||
logger.error("client_id mismatch expected: %s actual %s", client_id, message_value.get('sender'))
|
||||
return None, None
|
||||
return message_type, message_value
|
||||
else:
|
||||
logger.error("Invalid message text")
|
||||
return None, None
|
||||
|
||||
def handle(self, message):
|
||||
'''
|
||||
Dispatches a message based on the message type to a handler function
|
||||
of name onX where X is the message type.
|
||||
'''
|
||||
topology_id = message.get('topology')
|
||||
assert topology_id is not None, "No topology_id"
|
||||
client_id = message.get('client')
|
||||
assert client_id is not None, "No client_id"
|
||||
message_type, message_value = self.parse_message_text(message['text'], client_id)
|
||||
if message_type is None:
|
||||
return
|
||||
handler = self.get_handler(message_type)
|
||||
if handler is not None:
|
||||
handler(message_value, topology_id, client_id)
|
||||
else:
|
||||
logger.warning("Unsupported message %s: no handler", message_type)
|
||||
|
||||
def get_handler(self, message_type):
|
||||
return getattr(self, "on{0}".format(message_type), None)
|
||||
|
||||
def onDeviceCreate(self, device, topology_id, client_id):
|
||||
device = transform_dict(dict(x='x',
|
||||
y='y',
|
||||
name='name',
|
||||
type='device_type',
|
||||
id='cid',
|
||||
host_id='host_id'), device)
|
||||
logger.info("Device %s", device)
|
||||
d, _ = Device.objects.get_or_create(topology_id=topology_id, cid=device['cid'], defaults=device)
|
||||
d.x = device['x']
|
||||
d.y = device['y']
|
||||
d.device_type = device['device_type']
|
||||
d.host_id = device['host_id']
|
||||
d.save()
|
||||
(Topology.objects
|
||||
.filter(pk=topology_id, device_id_seq__lt=device['cid'])
|
||||
.update(device_id_seq=device['cid']))
|
||||
|
||||
def onDeviceDestroy(self, device, topology_id, client_id):
|
||||
Device.objects.filter(topology_id=topology_id, cid=device['id']).delete()
|
||||
|
||||
def onDeviceMove(self, device, topology_id, client_id):
|
||||
Device.objects.filter(topology_id=topology_id, cid=device['id']).update(x=device['x'], y=device['y'])
|
||||
|
||||
def onDeviceInventoryUpdate(self, device, topology_id, client_id):
|
||||
Device.objects.filter(topology_id=topology_id, cid=device['id']).update(host_id=device['host_id'])
|
||||
|
||||
def onDeviceLabelEdit(self, device, topology_id, client_id):
|
||||
Device.objects.filter(topology_id=topology_id, cid=device['id']).update(name=device['name'])
|
||||
|
||||
def onInterfaceLabelEdit(self, interface, topology_id, client_id):
|
||||
(Interface.objects
|
||||
.filter(device__topology_id=topology_id,
|
||||
cid=interface['id'],
|
||||
device__cid=interface['device_id'])
|
||||
.update(name=interface['name']))
|
||||
|
||||
def onLinkLabelEdit(self, link, topology_id, client_id):
|
||||
Link.objects.filter(from_device__topology_id=topology_id, cid=link['id']).update(name=link['name'])
|
||||
|
||||
def onInterfaceCreate(self, interface, topology_id, client_id):
|
||||
Interface.objects.get_or_create(device_id=Device.objects.get(cid=interface['device_id'],
|
||||
topology_id=topology_id).pk,
|
||||
cid=interface['id'],
|
||||
defaults=dict(name=interface['name']))
|
||||
(Device.objects
|
||||
.filter(cid=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
|
||||
.filter(topology_id=topology_id, cid__in=[link['from_device_id'], link['to_device_id']])
|
||||
.values_list('cid', 'pk'))
|
||||
Link.objects.get_or_create(cid=link['id'],
|
||||
name=link['name'],
|
||||
from_device_id=device_map[link['from_device_id']],
|
||||
to_device_id=device_map[link['to_device_id']],
|
||||
from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']],
|
||||
cid=link['from_interface_id']).pk,
|
||||
to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']],
|
||||
cid=link['to_interface_id']).pk)
|
||||
(Topology.objects
|
||||
.filter(pk=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
|
||||
.filter(topology_id=topology_id, cid__in=[link['from_device_id'], link['to_device_id']])
|
||||
.values_list('cid', 'pk'))
|
||||
if link['from_device_id'] not in device_map:
|
||||
return
|
||||
if link['to_device_id'] not in device_map:
|
||||
return
|
||||
Link.objects.filter(cid=link['id'],
|
||||
from_device_id=device_map[link['from_device_id']],
|
||||
to_device_id=device_map[link['to_device_id']],
|
||||
from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']],
|
||||
cid=link['from_interface_id']).pk,
|
||||
to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']],
|
||||
cid=link['to_interface_id']).pk).delete()
|
||||
|
||||
def onDeviceSelected(self, message_value, topology_id, client_id):
|
||||
'Ignore DeviceSelected messages'
|
||||
pass
|
||||
|
||||
def onDeviceUnSelected(self, message_value, topology_id, client_id):
|
||||
'Ignore DeviceSelected messages'
|
||||
pass
|
||||
|
||||
def onLinkSelected(self, message_value, topology_id, client_id):
|
||||
'Ignore LinkSelected messages'
|
||||
pass
|
||||
|
||||
def onLinkUnSelected(self, message_value, topology_id, client_id):
|
||||
'Ignore LinkSelected messages'
|
||||
pass
|
||||
|
||||
def onMultipleMessage(self, message_value, topology_id, client_id):
|
||||
for message in message_value['messages']:
|
||||
handler = self.get_handler(message['msg_type'])
|
||||
if handler is not None:
|
||||
handler(message, topology_id, client_id)
|
||||
else:
|
||||
logger.warning("Unsupported message %s", message['msg_type'])
|
||||
|
||||
|
||||
networking_events_dispatcher = NetworkingEvents()
|
||||
|
||||
|
||||
@channel_session
|
||||
def ws_connect(message):
|
||||
data = urlparse.parse_qs(message.content['query_string'])
|
||||
inventory_id = parse_inventory_id(data)
|
||||
topology_ids = list(TopologyInventory.objects.filter(inventory_id=inventory_id).values_list('pk', flat=True))
|
||||
topology_id = None
|
||||
if len(topology_ids) > 0:
|
||||
topology_id = topology_ids[0]
|
||||
if topology_id is not None:
|
||||
topology = Topology.objects.get(pk=topology_id)
|
||||
else:
|
||||
topology = Topology(name="topology", scale=1.0, panX=0, panY=0)
|
||||
topology.save()
|
||||
TopologyInventory(inventory_id=inventory_id, topology_id=topology.pk).save()
|
||||
topology_id = topology.pk
|
||||
message.channel_session['topology_id'] = topology_id
|
||||
Group("topology-%s" % topology_id).add(message.reply_channel)
|
||||
client = Client()
|
||||
client.save()
|
||||
message.channel_session['client_id'] = client.pk
|
||||
Group("client-%s" % client.pk).add(message.reply_channel)
|
||||
message.reply_channel.send({"text": json.dumps(["id", client.pk])})
|
||||
message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])})
|
||||
topology_data = transform_dict(dict(id='topology_id',
|
||||
name='name',
|
||||
panX='panX',
|
||||
panY='panY',
|
||||
scale='scale',
|
||||
link_id_seq='link_id_seq',
|
||||
device_id_seq='device_id_seq'), topology.__dict__)
|
||||
|
||||
message.reply_channel.send({"text": json.dumps(["Topology", topology_data])})
|
||||
send_snapshot(message.reply_channel, topology_id)
|
||||
|
||||
|
||||
def send_snapshot(channel, topology_id):
|
||||
interfaces = defaultdict(list)
|
||||
|
||||
for i in (Interface.objects
|
||||
.filter(device__topology_id=topology_id)
|
||||
.values()):
|
||||
i = transform_dict(dict(cid='id',
|
||||
device_id='device_id',
|
||||
id='interface_id',
|
||||
name='name'), i)
|
||||
interfaces[i['device_id']].append(i)
|
||||
devices = list(Device.objects.filter(topology_id=topology_id).values())
|
||||
devices = [transform_dict(dict(cid='id',
|
||||
id='device_id',
|
||||
device_type='device_type',
|
||||
host_id='host_id',
|
||||
name='name',
|
||||
x='x',
|
||||
y='y',
|
||||
interface_id_seq='interface_id_seq'), x) for x in devices]
|
||||
for device in devices:
|
||||
device['interfaces'] = interfaces[device['device_id']]
|
||||
|
||||
links = [dict(id=x['cid'],
|
||||
name=x['name'],
|
||||
from_device_id=x['from_device__cid'],
|
||||
to_device_id=x['to_device__cid'],
|
||||
from_interface_id=x['from_interface__cid'],
|
||||
to_interface_id=x['to_interface__cid'])
|
||||
for x in list(Link.objects
|
||||
.filter(Q(from_device__topology_id=topology_id) |
|
||||
Q(to_device__topology_id=topology_id))
|
||||
.values('cid',
|
||||
'name',
|
||||
'from_device__cid',
|
||||
'to_device__cid',
|
||||
'from_interface__cid',
|
||||
'to_interface__cid'))]
|
||||
snapshot = dict(sender=0,
|
||||
devices=devices,
|
||||
links=links)
|
||||
channel.send({"text": json.dumps(["Snapshot", snapshot])})
|
||||
|
||||
|
||||
@channel_session
|
||||
def ws_message(message):
|
||||
# Send to all clients editing the topology
|
||||
Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']})
|
||||
# Send to networking_events handler
|
||||
networking_events_dispatcher.handle({"text": message['text'],
|
||||
"topology": message.channel_session['topology_id'],
|
||||
"client": message.channel_session['client_id']})
|
||||
|
||||
|
||||
@channel_session
|
||||
def ws_disconnect(message):
|
||||
if 'topology_id' in message.channel_session:
|
||||
Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel)
|
||||
|
||||
8
awx/network_ui/docs/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
The design files in this directory are used in the database schema designer tool.
|
||||
|
||||
* [models.png](models.png) - An image of the database schema design for network UI.
|
||||
* [models.yml](models.yml) - Provides the main schema design for the network UI project.
|
||||
|
||||

|
||||
19
awx/network_ui/docs/messages.yml
Normal file
@ -0,0 +1,19 @@
|
||||
messages:
|
||||
- {msg_type: DeviceMove, fields: [msg_type, sender, id, x, y, previous_x, previous_y]}
|
||||
- {msg_type: DeviceCreate, fields: [msg_type, sender, id, x, y, name, type, host_id]}
|
||||
- {msg_type: DeviceDestroy, fields: [msg_type, sender, id, previous_x, previous_y, previous_name, previous_type, previous_host_id]}
|
||||
- {msg_type: DeviceLabelEdit, fields: [msg_type, sender, id, name, previous_name]}
|
||||
- {msg_type: DeviceSelected, fields: [msg_type, sender, id]}
|
||||
- {msg_type: DeviceUnSelected, fields: [msg_type, sender, id]}
|
||||
- {msg_type: InterfaceCreate, fields: [msg_type, sender, device_id, id, name]}
|
||||
- {msg_type: InterfaceLabelEdit, fields: [msg_type, sender, id, device_id, name, previous_name]}
|
||||
- {msg_type: LinkLabelEdit, fields: [msg_type, sender, id, name, previous_name]}
|
||||
- {msg_type: LinkCreate, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]}
|
||||
- {msg_type: LinkDestroy, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]}
|
||||
- {msg_type: LinkSelected, fields: [msg_type, sender, id]}
|
||||
- {msg_type: LinkUnSelected, fields: [msg_type, sender, id]}
|
||||
- {msg_type: MultipleMessage, fields: [msg_type, sender, messages]}
|
||||
- {msg_type: Snapshot, fields: [msg_type, sender, devices, links, order, trace_id]}
|
||||
- {msg_type: id, type: int}
|
||||
- {msg_type: topology_id, type: int}
|
||||
- {msg_type: Topology, fields: [topology_id, name, panX, panY, scale, link_id_seq, device_id_seq]}
|
||||
BIN
awx/network_ui/docs/models.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
130
awx/network_ui/docs/models.yml
Normal file
@ -0,0 +1,130 @@
|
||||
app: awx.network_ui
|
||||
external_models: []
|
||||
models:
|
||||
- display: name
|
||||
fields:
|
||||
- name: device_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
- name: topology
|
||||
ref: Topology
|
||||
ref_field: topology_id
|
||||
type: ForeignKey
|
||||
- len: 200
|
||||
name: name
|
||||
type: CharField
|
||||
- name: x
|
||||
type: IntegerField
|
||||
- name: y
|
||||
type: IntegerField
|
||||
- name: id
|
||||
type: IntegerField
|
||||
- len: 200
|
||||
name: device_type
|
||||
type: CharField
|
||||
- default: 0
|
||||
name: interface_id_seq
|
||||
type: IntegerField
|
||||
- default: 0
|
||||
name: host_id
|
||||
type: IntegerField
|
||||
name: Device
|
||||
x: 348
|
||||
y: 124
|
||||
- fields:
|
||||
- name: link_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
- name: from_device
|
||||
ref: Device
|
||||
ref_field: device_id
|
||||
related_name: from_link
|
||||
type: ForeignKey
|
||||
- name: to_device
|
||||
ref: Device
|
||||
ref_field: device_id
|
||||
related_name: to_link
|
||||
type: ForeignKey
|
||||
- name: from_interface
|
||||
ref: Interface
|
||||
ref_field: interface_id
|
||||
related_name: from_link
|
||||
type: ForeignKey
|
||||
- name: to_interface
|
||||
ref: Interface
|
||||
ref_field: interface_id
|
||||
related_name: to_link
|
||||
type: ForeignKey
|
||||
- name: id
|
||||
type: IntegerField
|
||||
- len: 200
|
||||
name: name
|
||||
type: CharField
|
||||
name: Link
|
||||
x: 731
|
||||
y: -33
|
||||
- display: name
|
||||
fields:
|
||||
- name: topology_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
- len: 200
|
||||
name: name
|
||||
type: CharField
|
||||
- name: scale
|
||||
type: FloatField
|
||||
- name: panX
|
||||
type: FloatField
|
||||
- name: panY
|
||||
type: FloatField
|
||||
- default: 0
|
||||
name: device_id_seq
|
||||
type: IntegerField
|
||||
- default: 0
|
||||
name: link_id_seq
|
||||
type: IntegerField
|
||||
name: Topology
|
||||
x: 111
|
||||
y: 127
|
||||
- fields:
|
||||
- name: client_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
name: Client
|
||||
x: -162
|
||||
y: 282
|
||||
- display: name
|
||||
fields:
|
||||
- name: interface_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
- name: device
|
||||
ref: Device
|
||||
ref_field: device_id
|
||||
type: ForeignKey
|
||||
- len: 200
|
||||
name: name
|
||||
type: CharField
|
||||
- name: id
|
||||
type: IntegerField
|
||||
name: Interface
|
||||
x: 977
|
||||
y: 312
|
||||
- fields:
|
||||
- name: topology_inventory_id
|
||||
pk: true
|
||||
type: AutoField
|
||||
- name: topology
|
||||
ref: Topology
|
||||
ref_field: topology_id
|
||||
type: ForeignKey
|
||||
- name: inventory_id
|
||||
type: IntegerField
|
||||
name: TopologyInventory
|
||||
x: -204
|
||||
y: 12
|
||||
modules: []
|
||||
view:
|
||||
panX: 213.729555519212
|
||||
panY: 189.446959094643
|
||||
scaleXY: 0.69
|
||||
83
awx/network_ui/migrations/0001_initial.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-03-23 20:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('main', '0026_v330_emitted_events'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Client',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Device',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=200)),
|
||||
('x', models.IntegerField()),
|
||||
('y', models.IntegerField()),
|
||||
('cid', models.IntegerField()),
|
||||
('device_type', models.CharField(blank=True, max_length=200)),
|
||||
('interface_id_seq', models.IntegerField(default=0)),
|
||||
('host', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Host')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Interface',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=200)),
|
||||
('cid', models.IntegerField()),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Device')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Link',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('cid', models.IntegerField()),
|
||||
('name', models.CharField(blank=True, max_length=200)),
|
||||
('from_device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_link', to='network_ui.Device')),
|
||||
('from_interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_link', to='network_ui.Interface')),
|
||||
('to_device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_link', to='network_ui.Device')),
|
||||
('to_interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_link', to='network_ui.Interface')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Topology',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=200)),
|
||||
('scale', models.FloatField()),
|
||||
('panX', models.FloatField()),
|
||||
('panY', models.FloatField()),
|
||||
('device_id_seq', models.IntegerField(default=0)),
|
||||
('link_id_seq', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TopologyInventory',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('inventory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Inventory')),
|
||||
('topology', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Topology')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='topology',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Topology'),
|
||||
),
|
||||
]
|
||||
0
awx/network_ui/migrations/__init__.py
Normal file
65
awx/network_ui/models.py
Normal file
@ -0,0 +1,65 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Device(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True,)
|
||||
topology = models.ForeignKey('Topology',)
|
||||
name = models.CharField(max_length=200, blank=True)
|
||||
x = models.IntegerField()
|
||||
y = models.IntegerField()
|
||||
cid = models.IntegerField()
|
||||
device_type = models.CharField(max_length=200, blank=True)
|
||||
interface_id_seq = models.IntegerField(default=0,)
|
||||
host = models.ForeignKey('main.Host', default=None, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Link(models.Model):
|
||||
|
||||
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',)
|
||||
cid = models.IntegerField()
|
||||
name = models.CharField(max_length=200, blank=True)
|
||||
|
||||
|
||||
class Topology(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True,)
|
||||
name = models.CharField(max_length=200, blank=True)
|
||||
scale = models.FloatField()
|
||||
panX = models.FloatField()
|
||||
panY = models.FloatField()
|
||||
device_id_seq = models.IntegerField(default=0,)
|
||||
link_id_seq = models.IntegerField(default=0,)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Client(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True,)
|
||||
|
||||
|
||||
class Interface(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True,)
|
||||
device = models.ForeignKey('Device',)
|
||||
name = models.CharField(max_length=200, blank=True)
|
||||
cid = models.IntegerField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class TopologyInventory(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True,)
|
||||
topology = models.ForeignKey('Topology',)
|
||||
inventory = models.ForeignKey('main.Inventory')
|
||||
9
awx/network_ui/routing.py
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
from channels.routing import route
|
||||
from awx.network_ui.consumers import ws_connect, ws_message, ws_disconnect
|
||||
|
||||
channel_routing = [
|
||||
route("websocket.connect", ws_connect, path=r"^/network_ui/topology"),
|
||||
route("websocket.receive", ws_message, path=r"^/network_ui/topology"),
|
||||
route("websocket.disconnect", ws_disconnect, path=r"^/network_ui/topology"),
|
||||
]
|
||||
10
awx/network_ui/urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
from django.conf.urls import url
|
||||
|
||||
from awx.network_ui import views
|
||||
|
||||
app_name = 'network_ui'
|
||||
urlpatterns = [
|
||||
url(r'^topology.json$', views.json_topology_data, name='json_topology_data'),
|
||||
url(r'^topology.yaml$', views.yaml_topology_data, name='yaml_topology_data'),
|
||||
]
|
||||
6
awx/network_ui/utils.py
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
|
||||
|
||||
def transform_dict(dict_map, d):
|
||||
return {to_key: d[from_key] for from_key, to_key in dict_map.iteritems()}
|
||||
|
||||
98
awx/network_ui/views.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc
|
||||
from django.shortcuts import render
|
||||
from django import forms
|
||||
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse
|
||||
from awx.network_ui.models import Topology, Device, Link, Interface
|
||||
from django.db.models import Q
|
||||
import yaml
|
||||
import json
|
||||
|
||||
NetworkAnnotatedInterface = Interface.objects.values('name',
|
||||
'cid',
|
||||
'from_link__pk',
|
||||
'to_link__pk',
|
||||
'from_link__to_device__name',
|
||||
'to_link__from_device__name',
|
||||
'from_link__to_interface__name',
|
||||
'to_link__from_interface__name')
|
||||
|
||||
|
||||
def topology_data(topology_id):
|
||||
|
||||
data = dict(devices=[],
|
||||
links=[])
|
||||
|
||||
topology = Topology.objects.get(pk=topology_id)
|
||||
|
||||
data['name'] = topology.name
|
||||
data['topology_id'] = topology_id
|
||||
|
||||
links = list(Link.objects
|
||||
.filter(Q(from_device__topology_id=topology_id) |
|
||||
Q(to_device__topology_id=topology_id)))
|
||||
|
||||
interfaces = Interface.objects.filter(device__topology_id=topology_id)
|
||||
|
||||
for device in Device.objects.filter(topology_id=topology_id).order_by('name'):
|
||||
interfaces = list(NetworkAnnotatedInterface.filter(device_id=device.pk).order_by('name'))
|
||||
interfaces = [dict(name=x['name'],
|
||||
network=x['from_link__pk'] or x['to_link__pk'],
|
||||
remote_device_name=x['from_link__to_device__name'] or x['to_link__from_device__name'],
|
||||
remote_interface_name=x['from_link__to_interface__name'] or x['to_link__from_interface__name'],
|
||||
id=x['cid'],
|
||||
) for x in interfaces]
|
||||
data['devices'].append(dict(name=device.name,
|
||||
type=device.device_type,
|
||||
x=device.x,
|
||||
y=device.y,
|
||||
id=device.cid,
|
||||
interfaces=interfaces))
|
||||
|
||||
for link in links:
|
||||
data['links'].append(dict(from_device=link.from_device.name,
|
||||
to_device=link.to_device.name,
|
||||
from_interface=link.from_interface.name,
|
||||
to_interface=link.to_interface.name,
|
||||
from_device_id=link.from_device.cid,
|
||||
to_device_id=link.to_device.cid,
|
||||
from_interface_id=link.from_interface.cid,
|
||||
to_interface_id=link.to_interface.cid,
|
||||
name=link.name,
|
||||
network=link.pk))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def yaml_serialize_topology(topology_id):
|
||||
return yaml.safe_dump(topology_data(topology_id), default_flow_style=False)
|
||||
|
||||
|
||||
def json_serialize_topology(topology_id):
|
||||
return json.dumps(topology_data(topology_id))
|
||||
|
||||
|
||||
def index(request):
|
||||
return render(request, "network_ui/index.html", dict(topologies=Topology.objects.all().order_by('-pk')))
|
||||
|
||||
|
||||
class TopologyForm(forms.Form):
|
||||
topology_id = forms.IntegerField()
|
||||
|
||||
|
||||
def json_topology_data(request):
|
||||
form = TopologyForm(request.GET)
|
||||
if form.is_valid():
|
||||
return JsonResponse(topology_data(form.cleaned_data['topology_id']))
|
||||
else:
|
||||
return HttpResponseBadRequest(form.errors)
|
||||
|
||||
|
||||
def yaml_topology_data(request):
|
||||
form = TopologyForm(request.GET)
|
||||
if form.is_valid():
|
||||
return HttpResponse(yaml.safe_dump(topology_data(form.cleaned_data['topology_id']),
|
||||
default_flow_style=False),
|
||||
content_type='application/yaml')
|
||||
else:
|
||||
return HttpResponseBadRequest(form.errors)
|
||||
|
||||
@ -279,6 +279,7 @@ INSTALLED_APPS = (
|
||||
'awx.ui',
|
||||
'awx.sso',
|
||||
'solo',
|
||||
'awx.network_ui'
|
||||
)
|
||||
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
|
||||
@ -22,6 +22,7 @@ const SRC_PATH = path.join(CLIENT_PATH, 'src');
|
||||
const STATIC_PATH = path.join(UI_PATH, 'static');
|
||||
const TEST_PATH = path.join(UI_PATH, 'test');
|
||||
const THEME_PATH = path.join(LIB_PATH, 'theme');
|
||||
const NETWORK_UI_PATH = path.join(SRC_PATH, 'network-ui');
|
||||
|
||||
const APP_ENTRY = path.join(SRC_PATH, 'app.js');
|
||||
const VENDOR_ENTRY = path.join(SRC_PATH, 'vendor.js');
|
||||
@ -57,6 +58,17 @@ const base = {
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'istanbul-instrumenter-loader',
|
||||
options: { esModules: true }
|
||||
},
|
||||
enforce: 'pre',
|
||||
include: [
|
||||
/src\/network-ui\//
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
@ -97,6 +109,15 @@ const base = {
|
||||
/src\//
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: ['ngtemplate-loader', 'html-loader'],
|
||||
include: [
|
||||
/lib\/components\//,
|
||||
/features\//,
|
||||
/src\//
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader',
|
||||
@ -187,6 +208,7 @@ const base = {
|
||||
'~test': TEST_PATH,
|
||||
'~theme': THEME_PATH,
|
||||
'~ui': UI_PATH,
|
||||
'~network-ui': NETWORK_UI_PATH,
|
||||
d3$: '~node_modules/d3/d3.min.js',
|
||||
'codemirror.jsonlint$': '~node_modules/codemirror/addon/lint/json-lint.js',
|
||||
jquery: '~node_modules/jquery/dist/jquery.js',
|
||||
|
||||
@ -51,6 +51,7 @@ const watch = {
|
||||
stats: 'minimal',
|
||||
publicPath: '/static/',
|
||||
host: '127.0.0.1',
|
||||
https: true,
|
||||
port: 3000,
|
||||
https: true,
|
||||
proxy: {
|
||||
@ -64,6 +65,11 @@ const watch = {
|
||||
target: TARGET,
|
||||
secure: false,
|
||||
ws: true
|
||||
},
|
||||
'/network_ui': {
|
||||
target: TARGET,
|
||||
secure: false,
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
</head>
|
||||
|
||||
<body data-user-agent="{{userAgent}}">
|
||||
<div ui-view="networking"><div>
|
||||
<at-layout>
|
||||
<bread-crumb></bread-crumb>
|
||||
<toast></toast>
|
||||
|
||||
@ -170,3 +170,9 @@
|
||||
* the transition.
|
||||
*/
|
||||
@import '_resets';
|
||||
|
||||
/**
|
||||
* Network Visualization Style
|
||||
*
|
||||
*/
|
||||
@import '../../src/network-ui/style.less';
|
||||
|
||||
@ -10,7 +10,6 @@ if ($basePath) {
|
||||
}
|
||||
|
||||
import start from './app.start';
|
||||
|
||||
import portalMode from './portal-mode/main';
|
||||
import systemTracking from './system-tracking/main';
|
||||
import inventoriesHosts from './inventories-hosts/main';
|
||||
@ -46,6 +45,8 @@ import atLibComponents from '~components';
|
||||
import atLibModels from '~models';
|
||||
import atLibServices from '~services';
|
||||
|
||||
import networkUI from '~network-ui/main';
|
||||
|
||||
start.bootstrap(() => {
|
||||
angular.bootstrap(document.body, ['awApp']);
|
||||
});
|
||||
@ -95,6 +96,7 @@ angular
|
||||
users.name,
|
||||
projects.name,
|
||||
scheduler.name,
|
||||
networkUI.tower.name,
|
||||
|
||||
'Utilities',
|
||||
'templates',
|
||||
|
||||
@ -93,6 +93,13 @@ export default ['i18n', function(i18n) {
|
||||
|
||||
fieldActions: {
|
||||
columnClass: 'col-md-2 col-sm-3 col-xs-4',
|
||||
network: {
|
||||
label: i18n._('Network Visualization'),
|
||||
ngClick: 'goToGraph(inventory)',
|
||||
awToolTip: i18n._('Network Visualization'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: '!inventory.pending_deletion'
|
||||
},
|
||||
edit: {
|
||||
label: i18n._('Edit'),
|
||||
ngClick: 'editInventory(inventory)',
|
||||
|
||||
@ -85,6 +85,10 @@ function InventoriesList($scope,
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.goToGraph = function(inventory){
|
||||
$state.go('inventories.edit.networking', {inventory_id: inventory.id, inventory_name: inventory.name});
|
||||
};
|
||||
|
||||
$scope.editInventory = function (inventory) {
|
||||
if(inventory.kind && inventory.kind === 'smart') {
|
||||
$state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id});
|
||||
|
||||
1
awx/ui/client/src/network-ui/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/extracted
|
||||
951
awx/ui/client/src/network-ui/CONTRIBUTING.md
Normal file
@ -0,0 +1,951 @@
|
||||
|
||||
|
||||
Getting Started With Network UI Development
|
||||
===========================================
|
||||
|
||||
|
||||
**Introduction**
|
||||
|
||||
The Networking UI component of AWX works differently from the rest of the AWX
|
||||
web UI to support high-scale interactive graphical design of networking
|
||||
topologies.
|
||||
|
||||
The Networking UI is a virtual graphical canvas where graphical elements are
|
||||
drawn upon. This canvas supports panning (scrolling horizontally and
|
||||
vertically) and scaling (zooming in and out), dynamic changing of modes, and
|
||||
other features that would be very difficult or impossible to implement with
|
||||
standard HTML events and rendering.
|
||||
|
||||
This interface is more like computer graphics than it is building a styled text
|
||||
document with interactive components. A good grasp of Cartesian coordinates,
|
||||
trigonometry, and analytic geometry are useful when working with this code.
|
||||
|
||||
* See: <https://en.wikipedia.org/wiki/Analytic_geometry>
|
||||
|
||||
**Design choices**
|
||||
|
||||
Certain design choices were made to make the UI performant and scale to a large
|
||||
number of nodes in a diagram. These include the use of simple ES5 functions for
|
||||
better performance over more advanced functions. For instance C-style for-loops
|
||||
were many times faster than implementations of `forEach` or iterators which make
|
||||
function calls during each iteration. This basic ES5 style should be followed
|
||||
throughout the implementation of the Network UI.
|
||||
|
||||
**AngularJS**
|
||||
|
||||
The Networking UI component uses AngularJS 1.6.x for part of the rendering pipeline
|
||||
but it is not a normal AngularJS web application. AngularJS makes use of
|
||||
data-binding and watchers which I found do not scale to the number of elements
|
||||
we are trying to support in the Networking UI. The Networking UI only uses
|
||||
AngularJS for SVG rendering (using AngularJS templates) which does scale
|
||||
sufficiently.
|
||||
|
||||
|
||||
**AngularJS Controllers**
|
||||
|
||||
Instead of creating many AngularJS controllers and directives the networking UI
|
||||
uses one big controller to hold the state of the entire UI. Normally this is
|
||||
an anti-pattern in AngularJS. Here is was necessary to scale to a large number
|
||||
of on-screen elements.
|
||||
|
||||
**AngularJS Directives**
|
||||
|
||||
* See: <https://docs.angularjs.org/guide/directive>
|
||||
|
||||
AngularJS directives are used in the networking UI application using the element
|
||||
matching style and the `templateUrl` option to include a template. A majority of
|
||||
the directives are defined in `network.ui.app.js`.
|
||||
|
||||
* See: [network.ui.app.js](network.ui.app.js)
|
||||
```
|
||||
.directive('awxNetDeviceDetail', deviceDetail.deviceDetail)
|
||||
```
|
||||
|
||||
* See: [device.detail.directive.js](device.detail.directive.js)
|
||||
```
|
||||
function deviceDetail () {
|
||||
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/device_detail.html' };
|
||||
}
|
||||
```
|
||||
|
||||
**AngularJS Templates**
|
||||
|
||||
* See: <https://docs.angularjs.org/guide/templates>
|
||||
|
||||
Normal AngularJS templates are used with the networking UI controller.
|
||||
The templates can be found in `/widgets`. Child
|
||||
scopes are created for sub-templates using the `ng-repeat` directive.
|
||||
|
||||
In this example the `awx-net-link` directive expects a Link model to be
|
||||
passed to it. The Link model is defined in the `models.js` file.
|
||||
|
||||
* See: [link.directive.js](link.directive.js)
|
||||
* See: [link.partial.svg](link.partial.svg)
|
||||
|
||||
* See: [network_ui.html](network_ui.partial.svg)
|
||||
```
|
||||
<g ng-repeat="link in links">
|
||||
<g awx-net-link></g>
|
||||
</g>
|
||||
```
|
||||
|
||||
* See: [models.js](models.js)
|
||||
```
|
||||
function Link(id, from_device, to_device, from_interface, to_interface) {
|
||||
this.id = id;
|
||||
this.from_device = from_device;
|
||||
this.to_device = to_device;
|
||||
this.from_interface = from_interface;
|
||||
this.to_interface = to_interface;
|
||||
this.selected = false;
|
||||
this.remote_selected = false;
|
||||
this.status = null;
|
||||
this.edit_label = false;
|
||||
this.name = "";
|
||||
}
|
||||
```
|
||||
|
||||
The following example sets the toolbox.selected_item value to the variable
|
||||
item which the directives used in the child scope expect to be set.
|
||||
|
||||
* See: [inventory_toolbox.html](inventory_toolbox.partial.svg)
|
||||
```
|
||||
<g ng-repeat="item in [toolbox.selected_item]">
|
||||
```
|
||||
|
||||
|
||||
**DOM (Document Object Model)**
|
||||
|
||||
No state is stored in or attached to the DOM. All state is stored in
|
||||
javascript objects attached to the network ui controller.
|
||||
|
||||
Direct DOM manipulation should not be used in the network UI unless absolutely
|
||||
necessary. JQuery should not be used. The DOM is generated through the use of
|
||||
AngularJS templates.
|
||||
|
||||
**SVG (Scalable Vector Graphics)**
|
||||
|
||||
* See: <https://developer.mozilla.org/en-US/docs/Web/SVG>
|
||||
|
||||
The network UI is built as one large SVG element (the SVG canvas) with other
|
||||
graphical elements (lines, circles, rectangles, paths, and text) absolutely
|
||||
positioned within the outer most SVG element. The browser is not involved with
|
||||
layout of the elements within the SVG. Each "widget" in the network UI needs
|
||||
to track or calculate its own position on the SVG canvas. The z-level of the
|
||||
elements are determined by the draw order on the canvas which is defined
|
||||
in `network_ui.partial.svg`. Elements drawn first will be hidden behind
|
||||
elements drawn later.
|
||||
|
||||
|
||||
|
||||
**Rendering Pipeline**
|
||||
|
||||
Event -> Javscript objects -> AngularJS templates -> SVG
|
||||
|
||||
AngularJS is used to render the SVG inside the SVG canvas using directives
|
||||
and templates. AngularJS is also used to schedule when the SVG canvas will
|
||||
be updated. When an input event comes from the user, or an event is received
|
||||
over the websocket, javascript objects will be updated according the the network
|
||||
UI code. Then AngularJS will be notified that it needs to update the templates
|
||||
either automatically for some events or explicitly using `$scope.$apply();` if
|
||||
not handled automatically by AngularJS. The templates will render to SVG and be
|
||||
included in the DOM for the rest of the AWX UI.
|
||||
|
||||
Because the networking UI does not use watchers nor data-binding features of
|
||||
AngularJS events flow in one way from event to javascript to angular to SVG.
|
||||
Events do not flow backwards through this pipeline.
|
||||
|
||||
Clicking on an SVG element will not send the event to that SVG element directly
|
||||
from the browser. It must be routed through the network UI code first.
|
||||
|
||||
|
||||
**SVG Primer**
|
||||
|
||||
SVG uses tags to define graphical elements just like HTML uses tags to define
|
||||
text documents. Commonly use tags include g, circle, rect, path, and text.
|
||||
SVG elements are absolutely positioned within an SVG canvas. The group tag, g,
|
||||
is similar to the div tag in HTML. Text in SVG must be contained in the text
|
||||
tag and cannot be outside tags as in HTML.
|
||||
|
||||
* See: <https://developer.mozilla.org/en-US/docs/Web/SVG/Element>
|
||||
|
||||
Each tag that describes a visual element requires X and Y coordinates as input
|
||||
to position that element. These coordinates are relative to position of the SVG
|
||||
canvas. The network UI uses the entire page height and width for the SVG canvas
|
||||
so that the position on the SVG on the canvas is the same as the position on
|
||||
the page.
|
||||
|
||||
|
||||
SVG supports graphical transformations on several tags to allow relative
|
||||
positioning of sub-elements which makes calculating the X and Y positions
|
||||
easier. The network UI uses transformations often for this purpose.
|
||||
Transformations that are often used here are the translate, scale, and rotate
|
||||
transforms. Translate moves the origin of the coordinate system to a new point
|
||||
for the sub-elements. Scale multiplies the size of the units in a coordinate
|
||||
system by some factor. Rotate performs a rotation about the origin by some
|
||||
number of degrees. These functions are converted to a matrix operation on the
|
||||
coordinate system which can be efficiently applied. It is often useful to use
|
||||
the transforms to simplify the calculations of X and Y coordinates instead of
|
||||
calculating those values in Javascript. Also these transforms make developing
|
||||
widgets much easier since we only need to keep up with a single point for the
|
||||
widget and all other points can be relatively positioned from that point.
|
||||
Hard-coding positions in widget development is the normal case since transforms
|
||||
can change the size and position of the widget when the widget is applied to
|
||||
the canvas. Only when necessary should we calculate positions of parts of a
|
||||
widget in javascript.
|
||||
|
||||
* See: <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform>
|
||||
|
||||
|
||||
SVG paths are a mini-language for defining graphics operations in one tag. It
|
||||
is often used to create shapes that are more complex than lines, rectangles,
|
||||
and circles. It is very useful for defining arcs.
|
||||
|
||||
* See: <https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths>
|
||||
|
||||
**SVG and CSS**
|
||||
|
||||
CSS and SVG work really nicely together for setting style, colors, and fonts in SVG.
|
||||
The SVG uses different attributes for setting colors than does HTML elements.
|
||||
Most SVG elements use `stroke` and `fill` to define the colors and `stroke-width`
|
||||
to define the width of lines and curves. The attributes `font-family` and `font-size`
|
||||
are used to set the font for text elements in SVG. The network UI uses the Less
|
||||
CSS compiler and BEM naming conventions to simplify and organize CSS.
|
||||
|
||||
* See: [style.less](style.less)
|
||||
* See: <http://lesscss.org/>
|
||||
* See: <http://getbem.com/introduction/>
|
||||
|
||||
**Events**
|
||||
|
||||
All mouse and keyboard events are captured by the outer most element of the
|
||||
network UI. Mouse movements, mouse clicks, and key presses are all routed by
|
||||
the network UI code and not by the browser. This is done to implement
|
||||
interactions with the virtual graphical canvas that are not supported by the
|
||||
browser. "Simple" things like buttons and text fields have to be handled by
|
||||
the network UI code instead of relying on the browser to route the mouse click
|
||||
to the appropriate object.
|
||||
|
||||
|
||||
The following code captures all the mouse movements, mouse clicks, mouse wheel,
|
||||
and touch events and sends them to the corresponding network UI controller functions.
|
||||
|
||||
* See: [network_ui.partial.svg](network_ui.partial.svg#L3)
|
||||
|
||||
```
|
||||
<svg id="frame" class="NetworkUI"
|
||||
ng-attr-height="{{graph.height}}"
|
||||
ng-attr-width="{{graph.width}}"
|
||||
ng-mousedown="onMouseDown($event)"
|
||||
ng-mouseup="onMouseUp($event)"
|
||||
ng-mouseenter="onMouseEnter($event)"
|
||||
ng-mouseleave="onMouseLeave($event)"
|
||||
ng-mousemove="onMouseMove($event)"
|
||||
ng-mouseover="onMouseOver($event)"
|
||||
ng-touchstart="onTouchStart($event)"
|
||||
ng-touchmove="onTouchMove($event)"
|
||||
ng-touchend="onTouchEnd($event)"
|
||||
ng-tap="onTap($event)"
|
||||
msd-wheel="onMouseWheel($event, $delta, $deltaX, $deltaY)">
|
||||
```
|
||||
|
||||
|
||||
Key events are captured by the following code:
|
||||
|
||||
* See: [network.ui.controller.js](network.ui.controller.js)
|
||||
|
||||
```
|
||||
$document.bind("keydown", $scope.onKeyDown);
|
||||
```
|
||||
|
||||
**Event Processing**
|
||||
|
||||
This code works as an event processing pipeline where the source of the events
|
||||
may be mouse clicks, keystrokes, or messages from the server over the
|
||||
websocket. This allows the appropriate processor to handle each event in turn
|
||||
or delegate the message to another processor.
|
||||
|
||||
The following diagram documents the pipeline processors that handle the events.
|
||||
Events are injected into to the pipeline at `Start` and travel through the
|
||||
pipeline along the arrows. Events may be handled at a node in the pipeline,
|
||||
passed along to the next node, discarded, or transformed into another message
|
||||
and sent along the pipeline. For instance `hotkeys_fsm` generates new and
|
||||
different type of events based on key presses that are injected at the
|
||||
beginning of the pipeline.
|
||||
|
||||

|
||||
|
||||
|
||||
**Describing Behavior with Finite State Machines**
|
||||
|
||||
To implement complex UI interactions predictably and correctly is a tough
|
||||
problem. Often the problem is solved by creating a large library of generic
|
||||
reusable components that are rigorously tested and hardened by a large group of
|
||||
developers over a period of several years. Eventually the myriad bugs are
|
||||
hammered out at great expense. Only then can the UI components be reliably
|
||||
used. This code does not follow that approach.
|
||||
|
||||
The workflows this code supports require very specific UI components that are
|
||||
not found in generic libraries. The interactions we want to support are not
|
||||
available in generic libraries. This code develops from scratch only the
|
||||
components that are necessary to implement the workflows of designing and
|
||||
operating networks of devices.
|
||||
|
||||
This code defines those elements using finite state machines to process the
|
||||
events from user input and other software components. Programming with finite
|
||||
state machines allows us to define formally complex behavior that would
|
||||
normally be informally defined by branches, functions, object interactions, and
|
||||
object inheritance. Formal definition eliminates much of the unexpected
|
||||
behavior that causes defects in the software.
|
||||
|
||||
* See: <https://en.wikipedia.org/wiki/Finite-state_machine>
|
||||
|
||||
Finite state machines can be represented as a directed graph of labeled nodes and labeled edges
|
||||
which can be both be represented visually and in machine readable code.
|
||||
|
||||
The network UI uses finite state machines to describe what happens when the software receives
|
||||
an input.
|
||||
|
||||
**Link FSM**
|
||||
|
||||

|
||||
|
||||
For example the link FSM describes how to connect devices with links. The FSM
|
||||
diagram above maps out the states and events that will select a device to connect and another
|
||||
device to connect to. FSMs traditionally start in the `Start` state. We get a free transition
|
||||
to the `Ready` state by handling the special event called `start` and changing state to `Ready`.
|
||||
Then when the `NewLink` event is received from a hot key or button click the FSM changes
|
||||
state to the `Selecting` state. On a `MouseUp` event the FSM changes to the the `Connecting` state
|
||||
if the mouse is over a device icon otherwise it stays in the `Selecting` state. In the `Connecting`
|
||||
state the FSM changes to the `Connected` state when it receives a `MouseUp` event and the
|
||||
mouse is over another device otherwise it goes back to the `Ready` state since the user cancelled
|
||||
the connecting operation. Finally in the `Connected` state the FSM changes to the `Ready` state
|
||||
for free using the `start` event so that the user can connect another set of devices.
|
||||
|
||||
* See: [designs/link.yml](designs/link.yml)
|
||||
* See: [link.js](link.js)
|
||||
|
||||
The link FSM diagram has an equivalent machine readable representation in `designs/link.yml`. This
|
||||
representation is useful for comparing the current implementation in `link.js` to the design to see if they
|
||||
are out-of-sync. If they are out-of-sync either the design or the implementation can be updated depending
|
||||
on if you are changing the design or implementation first.
|
||||
|
||||
Tools are provided to facilitate the design-first and the implementation-first workflows.
|
||||
|
||||
**Design-First Workflow**
|
||||
|
||||
In the design-first workflow, first change the design and then update the
|
||||
implementation to match. In this workflow we use the
|
||||
[fsm-designer-svg](https://github.com/benthomasson/fsm-designer-svg) tool to
|
||||
change the FSM diagram, then export the FSM to a file, then generate a skeleton
|
||||
of the javascript code that implements the FSM. Then development of the logic
|
||||
inside the event handlers can begin with a clear understanding of the state of
|
||||
the system and what that event handler should do.
|
||||
|
||||
Use `tools/fsm_generate_diffs.py` to generate the new skeleton code:
|
||||
|
||||
```
|
||||
./tools/fsm_generate_diffs.py designs/link.yml ./link.js
|
||||
```
|
||||
|
||||
This will print out code for additional states or transitions needed in the implementation.
|
||||
Copy those lines into the implementation code and fill out the event handler functions.
|
||||
|
||||
|
||||
**Implementation-First Workflow**
|
||||
|
||||
In the implementation-first workflow, first change the code and then update the
|
||||
design to reflect the changes. This workflow is useful when the design doesn't
|
||||
survive its impact with reality and the code adds additional requirements to
|
||||
the design. Often in usabilty testing we find that we forgot to consider
|
||||
handling a certain interaction or user input in a state. We can quickly add
|
||||
that transition to the code and test it out. Once confirmed that the interaction
|
||||
is correct we can update the design and run `./tools/fsm-diff` to make sure the two
|
||||
are in sync.
|
||||
|
||||
|
||||
Use `./extract.js` and `tools/fsm-diff` to compare the implementation to the design
|
||||
and add any additional transitions to the FSM design.
|
||||
|
||||
```
|
||||
./extract.js link.js > ./extracted/link.yml
|
||||
./tools/fsm-diff designs/link.yml extracted/link.yml
|
||||
```
|
||||
|
||||
|
||||
**Validating That Design Matches Implementation**
|
||||
|
||||
Use the `make extract` and `make diff` Makefile targets to do a mass extact of the
|
||||
FSMs from the implementation and a mass comparison against the designs. Take
|
||||
note of any differences between design and implementation and update the appropriate
|
||||
files as outlined in the workflows above.
|
||||
|
||||
```
|
||||
make extract; make diff
|
||||
```
|
||||
|
||||
|
||||
**Finite State Machine Implementation**
|
||||
|
||||
The implementation of a finite state machine in the network UI is split into
|
||||
two parts: the declaration of the states and the event-handlers which may cause
|
||||
FSM transitions using `controller.changeState`.
|
||||
|
||||
**FSM States**
|
||||
|
||||
* See: <https://en.wikipedia.org/wiki/Flyweight_pattern>
|
||||
* See: <https://en.wikipedia.org/wiki/Singleton_pattern>
|
||||
|
||||
States are implemented using an object-oriented style in ES5 using the
|
||||
flyweight and singleton patterns. This means that the state objects store no
|
||||
information on themselves and that there is only one instance of each state
|
||||
class. All states should provide a `start` and `end` function which will be
|
||||
called when a FSM state is entered and exited respectively. Subclassing
|
||||
[fsm.State](fsm.js#L36) will provide empty `start` and `end` functions that
|
||||
can be overridden as necessary.
|
||||
|
||||
* See: [fsm.js](fsm.js#L2)
|
||||
|
||||
The state variable is stored on another object called an FSMController (which
|
||||
should not be confused with an AngularJS controller). The FSMController holds
|
||||
all the state for each FSM instance. If you need more than one copy of an FSM
|
||||
(for buttons for instance) use more than one instance of FSMController and
|
||||
pass the same FSM starting state to their constructor e.g. `button.Start`.
|
||||
Variables other than `state` should not be stored on the FSMController. A
|
||||
special variable named `scope` is useful for that. The scope can be used
|
||||
to hold arbitrary data that the FSM code will use in addition to the messages
|
||||
in the event handlers. In the network UI often the `scope` is a reference
|
||||
to the network UI AngularJS controller's scope. In the case of a button
|
||||
the scope is a reference to the `Button` model.
|
||||
|
||||
* See: [models.js](models.js#302)
|
||||
|
||||
The following code creates a new instance of `FSMController` using the
|
||||
`Button` model as the scope and the `button.Start` state as the initial
|
||||
state.
|
||||
|
||||
```
|
||||
this.fsm = new fsm.FSMController(this, button.Start, null);
|
||||
```
|
||||
|
||||
* See: [link.js](link.js#L40)
|
||||
|
||||
This code block defines the `_Selecting` class in ES5 style and uses the
|
||||
`inherits` NPM module to define that the class is a subclass of `_State`. We
|
||||
also create a single instance (a singleton) of this class named `Selecting`.
|
||||
|
||||
```
|
||||
function _Selecting () {
|
||||
this.name = 'Selecting';
|
||||
}
|
||||
inherits(_Selecting, _State);
|
||||
var Selecting = new _Selecting();
|
||||
exports.Selecting = Selecting;
|
||||
```
|
||||
|
||||
**FSM Event Handlers and Transitions**
|
||||
|
||||
After all the states are defined the event handlers for those state classes can be defined.
|
||||
We do this to prevent forward references in the file.
|
||||
|
||||
* See: [link.js](link.js#L134)
|
||||
|
||||
In this code we define an event handler for the `MouseUp` event on the `Selecting` state. This
|
||||
code should select a single device if the mouse is over that device. It should store
|
||||
that device somewhere and change to the `Connecting` state. The code below creates a new
|
||||
`Link` model and stores the `selected_device` in that object. The `new_link` object is
|
||||
stored in the `controller.scope` for later use in the FSM. Finally the event handler changes
|
||||
state using `controller.changeState` to change the state of the FSM to `Connecting`.
|
||||
|
||||
Event handlers must start with the prefix of `on` and a suffix of the name of the messsage
|
||||
type. The special functions `start` and `end` do not follow this rule nor do
|
||||
they receive a message.
|
||||
|
||||
The event handler must also define its `transitions` as a list so that `./extract.js` can
|
||||
find them.
|
||||
|
||||
```
|
||||
_Selecting.prototype.onMouseUp = function (controller) {
|
||||
|
||||
var selected_device = controller.scope.select_items(false).last_selected_device;
|
||||
if (selected_device !== null) {
|
||||
controller.scope.new_link = new models.Link(controller.scope.link_id_seq(), selected_device, null, null, null, true);
|
||||
controller.scope.links.push(controller.scope.new_link);
|
||||
controller.changeState(Connecting);
|
||||
}
|
||||
};
|
||||
_Selecting.prototype.onMouseUp.transitions = ['Connecting'];
|
||||
|
||||
```
|
||||
|
||||
**FSM Designs**
|
||||
|
||||
All the finite state machines for the network UI have been designed using the
|
||||
[fsm-designer-svg](https://github.com/benthomasson/fsm-designer-svg) tool
|
||||
and their designs are stored in the `designs` directory.
|
||||
|
||||
* See: [designs/README.md](designs/README.md)
|
||||
|
||||
|
||||
**Data Models**
|
||||
|
||||
There are two types of data structures used in the network UI: messages and
|
||||
models. Models are used for long-lived data that is used to render the UI
|
||||
whereas messages are used for ephemeral data that is passed from one part of
|
||||
the system to another. Models may be unpacked or serialized into messages that
|
||||
are sent to other FSMControllers in the client or sent over a websocket to the
|
||||
server.
|
||||
|
||||
* See: [models.js](models.js)
|
||||
|
||||
The models defined in [models.js](models.js) are:
|
||||
|
||||
* Device - a networking device i.e. a router, a switch, or a host
|
||||
* Interface - a networking interface
|
||||
* Link - a connection between interfaces
|
||||
* Button - a UI button
|
||||
* ToggleButton - a togglable UI button
|
||||
* Task - a playbook task
|
||||
* Group - a grouping of devices
|
||||
* ToolBox - a UI element for holding things that can be placed on the virtual canvas
|
||||
* Configuration - a configuration for a device
|
||||
* Process - an application running on a device
|
||||
* Stream - a flow of data between applications
|
||||
|
||||
|
||||
**Message Types**
|
||||
|
||||
Message types define the structure of the data that is passed between the server
|
||||
and the client and between different parts of the client. This provides a known and
|
||||
well defined data structure that can be counted up on the code.
|
||||
|
||||
* See: [messages.js](messages.js)
|
||||
|
||||
The messages defined are [messages.js](messages.js):
|
||||
|
||||
* DeviceMove - Device has changed x,y position
|
||||
* DeviceCreate - A device was created
|
||||
* DeviceDestroy - A device was destroyed
|
||||
* DeviceLabelEdit - The label of a device was changed
|
||||
* DeviceSelected - A device was selected
|
||||
* DeviceUnSelected - A device was unselected
|
||||
* InterfaceCreate - An interface was created
|
||||
* InterfaceLabelEdit - The label of an interface was changed
|
||||
* LinkLabelEdit - The label of a link was changed
|
||||
* LinkCreate - A link was created
|
||||
* LinkDestroy - A link was destroyed
|
||||
* LinkSelected - A link was selected
|
||||
* LinkUnSelected - A link was unselected
|
||||
* Undo - Undo the last operation
|
||||
* Redo - Redo the last undone operation
|
||||
* Deploy - Call the deploy playbook
|
||||
* Destroy - Call the destroy playbook
|
||||
* Discover - Call the discover playbook
|
||||
* Layout - Call the layout function
|
||||
* MultipleMessage - A collection of messages that should be handled together
|
||||
* Coverage - A coverage report
|
||||
* MouseEvent - A generic mouse event
|
||||
* MouseWheelEvent - A mouse wheel event
|
||||
* KeyEvent - A key press event
|
||||
* TouchEvent - A touch screen event
|
||||
* StartRecording - Start recording user interactions
|
||||
* StopRecording - Stop recording user interactions
|
||||
* ViewPort - Update the view port onto the virtual canvas
|
||||
* NewDevice - Request for a new device
|
||||
* PasteDevice - Paste a device from a toolbox
|
||||
* PasteProcess - Paste a process from a toolbox
|
||||
* NewGroup - Request for a new group
|
||||
* PasteGroup - Paste a group from a toolbox
|
||||
* PasteRack - Paste a rack from a toolbox
|
||||
* PasteSite - Paste a site from a toolbox
|
||||
* CopySite - Copy a stie to a toolbox
|
||||
* GroupMove - A group has changed its x, y coordinates
|
||||
* GroupCreate - A new group was created
|
||||
* GroupDestroy - A group was destroyed
|
||||
* GroupLabelEdit - The label for a group was changed
|
||||
* GroupSelected - A group was selected
|
||||
* GroupUnSelected - A group was unselected
|
||||
* GroupMembership - The device membership of a group changed
|
||||
* TableCellEdit - A table cell was chaged
|
||||
* ProcessCreate - A new process was created
|
||||
* StreamCreate - A new stream was created
|
||||
* StreamDestroy - A stream was destroyed
|
||||
* StreamLabelEdit - The label of a stream was changed
|
||||
* StreamSelected - A stream was selected
|
||||
* StreamUnSelected - A stream was unselected
|
||||
|
||||
|
||||
Widget Development
|
||||
==================
|
||||
|
||||
When developing a new UI widget follow this process:
|
||||
|
||||
For a widget named `new widget` do this:
|
||||
|
||||
* Add a template in `widgets` for the new widget with name `new_widget.html`
|
||||
* Add a directive that loads that template in `src` with name `new.widget.directive.js`
|
||||
* Register the directive with the network UI application in `src/network.ui.app.js` using name `awxNetNewWidget`
|
||||
* Add a tag that loads the directive into an existing template in `widgets`. If you are not sure add it to `widgets/network_ui.html`.
|
||||
* Test that the directive is loaded when the page renders in a browser
|
||||
* Iterate on the template for the new widget until the UI look matches the mockup
|
||||
* Design the interaction behavior using [fsm-designer-svg](https://github.com/benthomasson/fsm-designer-svg)
|
||||
* Export the FSM design to `designs` in a file named `designs/new_widget.yml`
|
||||
* Create a new empty FSM implementation file in `src` named `src/new.wiget.fsm.js`
|
||||
* Use the `./tools/fsm_generate_diffs.py` tool to generate the skeleton for the new FSM implementation
|
||||
* Decide if you need any new data structures for your UI widget. If so, add them to `src/models.js`.
|
||||
* Decide if you need any new messages to communicate between the UI and the server or between pieces of the UI.
|
||||
If so, add them to `src/messages.js`
|
||||
* Add the FSM implementation to a FSMController in `src/network.ui.controller.js`
|
||||
* Write the logic in the event handlers to update the models, send any messages, and change states according to the design.
|
||||
* Test the interaction manually in a browser
|
||||
* Iterate on changing the event handlers until the desired interaction is acheived
|
||||
* Update the design to match the implementation
|
||||
|
||||
**Widget Development Example**
|
||||
|
||||
This example follows development of the inventory toolbox widget.
|
||||
|
||||
* Add a template in `widgets` for the new widget with name [inventory_toolbox.partial.svg](inventory_toolbox.partial.svg)
|
||||
|
||||
```
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
|
||||
<g ng-if="toolbox.enabled">
|
||||
<rect class="NetworkUI__toolbox"
|
||||
ng-attr-x="{{toolbox.x}}"
|
||||
ng-attr-y="{{toolbox.y}}"
|
||||
ng-attr-width="{{toolbox.width}}"
|
||||
ng-attr-height="{{toolbox.height}}"
|
||||
rx=5></rect>
|
||||
...
|
||||
</g> <!-- ng-if toolbox.enabled -->
|
||||
```
|
||||
|
||||
* Add a directive that loads that template in `src/network-ui` with name [inventory.toolbox.directive.js](inventory.toolbox.directive.js)
|
||||
|
||||
```
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
function inventoryToolbox () {
|
||||
return { restrict: 'A', templateUrl: '/static/network_ui/widgets/inventory_toolbox.html' };
|
||||
}
|
||||
exports.inventoryToolbox = inventoryToolbox;
|
||||
```
|
||||
|
||||
|
||||
* Register the directive with the network UI application in [network.ui.app.js](network.ui.app.js#L61) using name `awxNetInventoryToolbox`
|
||||
|
||||
```
|
||||
...
|
||||
var inventoryToolbox = require('./inventory.toolbox.directive.js');
|
||||
...
|
||||
.directive('awxNetInventoryToolbox', inventoryToolbox.inventoryToolbox)
|
||||
...
|
||||
```
|
||||
|
||||
* Add a tag that loads the directive into an existing template in `src/network-ui` in [network_ui.partial.svg](network_ui.partial.svg#L94)
|
||||
|
||||
```
|
||||
<g awx-net-inventory-toolbox></g>
|
||||
```
|
||||
|
||||
* Test that the directive is loaded when the page renders in a browser
|
||||
* Iterate on the template for the new widget until the UI look matches the mockup
|
||||
* Design the interaction behavior using [fsm-designer-svg](https://github.com/benthomasson/fsm-designer-svg)
|
||||
|
||||

|
||||
|
||||
* Export the FSM design to `designs` in a file named `designs/toolbox.yml`
|
||||
|
||||
```
|
||||
finite_state_machine_id: 14
|
||||
name: toolbox
|
||||
states:
|
||||
- id: 2
|
||||
label: Selected
|
||||
x: 1180
|
||||
y: 959
|
||||
- id: 6
|
||||
label: Move
|
||||
x: 1409
|
||||
y: 741
|
||||
- id: 3
|
||||
label: Ready
|
||||
x: 892
|
||||
y: 429
|
||||
- id: 4
|
||||
label: Scrolling
|
||||
x: 567
|
||||
y: 431
|
||||
- id: 5
|
||||
label: Start
|
||||
x: 892
|
||||
y: 216
|
||||
- id: 7
|
||||
label: Selecting
|
||||
x: 888
|
||||
y: 710
|
||||
- id: 1
|
||||
label: Dropping
|
||||
x: 1358
|
||||
y: 431
|
||||
transitions:
|
||||
- from_state: Selecting
|
||||
label: onMouseDown
|
||||
to_state: Selected
|
||||
- from_state: Selected
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selecting
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
- from_state: Dropping
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Scrolling
|
||||
label: onMouseWheel
|
||||
to_state: Ready
|
||||
- from_state: Ready
|
||||
label: onMouseWheel
|
||||
to_state: Scrolling
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selecting
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Dropping
|
||||
```
|
||||
|
||||
* Create a new empty FSM implementation file in `src/network-ui` named `toolbox.fsm.js`
|
||||
|
||||
```
|
||||
touch toolbox.fsm.js
|
||||
```
|
||||
|
||||
* Use the `./tools/fsm_generate_diffs.py` tool to generate the skeleton for the new FSM implementation
|
||||
|
||||
```
|
||||
./tools/fsm_generate_diffs.py designs/toolbox.yml src/toolbox.fsm.js --append
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
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 _Selected () {
|
||||
this.name = 'Selected';
|
||||
}
|
||||
inherits(_Selected, _State);
|
||||
var Selected = new _Selected();
|
||||
exports.Selected = Selected;
|
||||
|
||||
function _Dropping () {
|
||||
this.name = 'Dropping';
|
||||
}
|
||||
inherits(_Dropping, _State);
|
||||
var Dropping = new _Dropping();
|
||||
exports.Dropping = Dropping;
|
||||
|
||||
function _Ready () {
|
||||
this.name = 'Ready';
|
||||
}
|
||||
inherits(_Ready, _State);
|
||||
var Ready = new _Ready();
|
||||
exports.Ready = Ready;
|
||||
|
||||
function _Selecting () {
|
||||
this.name = 'Selecting';
|
||||
}
|
||||
inherits(_Selecting, _State);
|
||||
var Selecting = new _Selecting();
|
||||
exports.Selecting = Selecting;
|
||||
|
||||
function _Move () {
|
||||
this.name = 'Move';
|
||||
}
|
||||
inherits(_Move, _State);
|
||||
var Move = new _Move();
|
||||
exports.Move = Move;
|
||||
|
||||
function _Scrolling () {
|
||||
this.name = 'Scrolling';
|
||||
}
|
||||
inherits(_Scrolling, _State);
|
||||
var Scrolling = new _Scrolling();
|
||||
exports.Scrolling = Scrolling;
|
||||
|
||||
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
|
||||
_Selected.prototype.onMouseMove = function (controller) {
|
||||
|
||||
controller.changeState(Move);
|
||||
|
||||
};
|
||||
_Selected.prototype.onMouseMove.transitions = ['Move'];
|
||||
|
||||
_Selected.prototype.onMouseUp = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Selected.prototype.onMouseUp.transitions = ['Ready'];
|
||||
|
||||
|
||||
|
||||
_Dropping.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Dropping.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
|
||||
_Ready.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Selecting);
|
||||
|
||||
};
|
||||
_Ready.prototype.onMouseDown.transitions = ['Selecting'];
|
||||
|
||||
_Ready.prototype.onMouseWheel = function (controller) {
|
||||
|
||||
controller.changeState(Scrolling);
|
||||
|
||||
};
|
||||
_Ready.prototype.onMouseWheel.transitions = ['Scrolling'];
|
||||
|
||||
|
||||
|
||||
_Selecting.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
controller.changeState(Selected);
|
||||
|
||||
};
|
||||
_Selecting.prototype.onMouseDown.transitions = ['Ready', 'Selected'];
|
||||
|
||||
|
||||
|
||||
_Move.prototype.onMouseUp = function (controller) {
|
||||
|
||||
controller.changeState(Dropping);
|
||||
|
||||
};
|
||||
_Move.prototype.onMouseUp.transitions = ['Dropping'];
|
||||
|
||||
|
||||
|
||||
_Scrolling.prototype.onMouseWheel = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Scrolling.prototype.onMouseWheel.transitions = ['Ready'];
|
||||
};
|
||||
_Ready.prototype.onMouseWheel.transitions = ['Scrolling'];
|
||||
|
||||
|
||||
|
||||
_Selecting.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
controller.changeState(Selected);
|
||||
|
||||
};
|
||||
_Selecting.prototype.onMouseDown.transitions = ['Ready', 'Selected'];
|
||||
|
||||
|
||||
|
||||
_Move.prototype.onMouseUp = function (controller) {
|
||||
|
||||
controller.changeState(Dropping);
|
||||
|
||||
};
|
||||
_Move.prototype.onMouseUp.transitions = ['Dropping'];
|
||||
|
||||
|
||||
|
||||
_Scrolling.prototype.onMouseWheel = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Scrolling.prototype.onMouseWheel.transitions = ['Ready'];
|
||||
|
||||
```
|
||||
|
||||
* Decide if you need any new data structures for your UI widget. If so, add them to [src/models.js](src/models.js#L608).
|
||||
|
||||
```
|
||||
function ToolBox(id, name, type, x, y, width, height) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.items = [];
|
||||
this.spacing = 200;
|
||||
this.scroll_offset = 0;
|
||||
this.selected_item = null;
|
||||
this.enabled = true;
|
||||
}
|
||||
exports.ToolBox = ToolBox;
|
||||
```
|
||||
|
||||
* Decide if you need any new messages to communicate between the UI and the server or between pieces of the UI.
|
||||
If so, add them to [messages.js](messages.js#L251)
|
||||
|
||||
```
|
||||
function PasteDevice(device) {
|
||||
this.device = device;
|
||||
}
|
||||
exports.PasteDevice = PasteDevice;
|
||||
```
|
||||
|
||||
* Write the logic in the event handlers to update the models, send any messages, and change states according to the design.
|
||||
|
||||
See: [toolbox.fsm.js](toolbox.fsm.js)
|
||||
|
||||
* Add the FSM implementation to a FSMController in [network.ui.controller.js](network.ui.controller.js#L145)
|
||||
|
||||
```
|
||||
$scope.inventory_toolbox_controller = new fsm.FSMController($scope, toolbox_fsm.Start, $scope.app_toolbox_controller);
|
||||
```
|
||||
|
||||
* Test the interaction manually in a browser
|
||||
* Iterate on changing the event handlers until the desired interaction is achieved
|
||||
* Update the design to match the implementation
|
||||
19
awx/ui/client/src/network-ui/Makefile
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
.PHONY: check extract
|
||||
|
||||
FSMS = animation time test mode buttons button toolbox site rack group stream link details.panel move device.detail view keybindings hotkeys null
|
||||
|
||||
|
||||
extract:
|
||||
mkdir -p extracted
|
||||
for fsm in $(FSMS); do \
|
||||
./extract.js ./$${fsm}.fsm.js > extracted/$${fsm}.yml; \
|
||||
done
|
||||
|
||||
|
||||
check: extract
|
||||
for fsm in $(FSMS); do \
|
||||
./tools/fsm-diff ../../../../network_ui/designs/$$fsm.yml extracted/$$fsm.yml; \
|
||||
./tools/copy-layout.py ../../../../network_ui/designs/$$fsm.yml extracted/$$fsm.yml; \
|
||||
done
|
||||
81
awx/ui/client/src/network-ui/animation.fsm.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* Copyright (c) 2018 Benjamin Thomasson */
|
||||
/* Copyright (c) 2018 Red Hat, Inc. */
|
||||
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, fsm._State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Completed () {
|
||||
this.name = 'Completed';
|
||||
}
|
||||
inherits(_Completed, fsm._State);
|
||||
var Completed = new _Completed();
|
||||
exports.Completed = Completed;
|
||||
|
||||
function _Cancelled () {
|
||||
this.name = 'Cancelled';
|
||||
}
|
||||
inherits(_Cancelled, fsm._State);
|
||||
var Cancelled = new _Cancelled();
|
||||
exports.Cancelled = Cancelled;
|
||||
|
||||
function _Running () {
|
||||
this.name = 'Running';
|
||||
}
|
||||
inherits(_Running, fsm._State);
|
||||
var Running = new _Running();
|
||||
exports.Running = Running;
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Running);
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Running'];
|
||||
|
||||
_Running.prototype.start = function (controller) {
|
||||
|
||||
controller.scope.interval = setInterval(function () {
|
||||
controller.scope.frame_number = controller.scope.frame_number_seq();
|
||||
if (!controller.scope.active) {
|
||||
return;
|
||||
}
|
||||
if (controller.scope.frame_number > controller.scope.steps) {
|
||||
controller.scope.fsm.handle_message('AnimationCompleted');
|
||||
return;
|
||||
}
|
||||
controller.scope.callback(controller.scope);
|
||||
controller.scope.scope.$apply();
|
||||
}, controller.scope.frame_delay);
|
||||
};
|
||||
|
||||
_Running.prototype.onAnimationCancelled = function (controller) {
|
||||
|
||||
controller.changeState(Cancelled);
|
||||
|
||||
};
|
||||
_Running.prototype.onAnimationCancelled.transitions = ['Cancelled'];
|
||||
|
||||
_Running.prototype.onAnimationCompleted = function (controller) {
|
||||
|
||||
controller.changeState(Completed);
|
||||
|
||||
};
|
||||
_Running.prototype.onAnimationCompleted.transitions = ['Completed'];
|
||||
|
||||
_Completed.prototype.start = function (controller) {
|
||||
controller.scope.active = false;
|
||||
clearInterval(controller.scope.interval);
|
||||
};
|
||||
|
||||
_Cancelled.prototype.start = function (controller) {
|
||||
controller.scope.active = false;
|
||||
clearInterval(controller.scope.interval);
|
||||
};
|
||||
23
awx/ui/client/src/network-ui/animations.js
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
function scale_animation (scope) {
|
||||
|
||||
var initial_height = ((1 / scope.data.current_scale) - 1);
|
||||
var height = (scope.data.end_height - initial_height) * (scope.frame_number / scope.steps) + initial_height;
|
||||
scope.data.scope.current_scale = 1 / (1 + height);
|
||||
scope.data.scope.updatePanAndScale();
|
||||
scope.data.scope.$emit('awxNet-UpdateZoomWidget', scope.data.scope.current_scale, scope.data.updateZoomBoolean);
|
||||
}
|
||||
exports.scale_animation = scale_animation;
|
||||
|
||||
function pan_animation (scope) {
|
||||
var incr_x = (scope.data.x2 - scope.data.x1) / scope.steps;
|
||||
var incr_y = (scope.data.y2 - scope.data.y1) / scope.steps;
|
||||
var v_x = incr_x * scope.frame_number + scope.data.x1;
|
||||
var v_y = incr_y * scope.frame_number + scope.data.y1;
|
||||
var p = scope.data.scope.to_pan(v_x, v_y);
|
||||
scope.data.scope.panX = p.x + scope.data.scope.graph.width/2;
|
||||
scope.data.scope.panY = p.y + scope.data.scope.graph.height/2;
|
||||
scope.data.scope.first_channel.send("PanChanged", {});
|
||||
scope.data.scope.updatePanAndScale();
|
||||
}
|
||||
exports.pan_animation = pan_animation;
|
||||
107
awx/ui/client/src/network-ui/button.fsm.js
Normal file
@ -0,0 +1,107 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
function _Ready () {
|
||||
this.name = 'Ready';
|
||||
}
|
||||
inherits(_Ready, _State);
|
||||
var Ready = new _Ready();
|
||||
exports.Ready = Ready;
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Clicked () {
|
||||
this.name = 'Clicked';
|
||||
}
|
||||
inherits(_Clicked, _State);
|
||||
var Clicked = new _Clicked();
|
||||
exports.Clicked = Clicked;
|
||||
|
||||
function _Pressed () {
|
||||
this.name = 'Pressed';
|
||||
}
|
||||
inherits(_Pressed, _State);
|
||||
var Pressed = new _Pressed();
|
||||
exports.Pressed = Pressed;
|
||||
function _Disabled () {
|
||||
this.name = 'Disabled';
|
||||
}
|
||||
inherits(_Disabled, _State);
|
||||
var Disabled = new _Disabled();
|
||||
exports.Disabled = Disabled;
|
||||
|
||||
|
||||
|
||||
// Begin ready state
|
||||
_Ready.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Pressed);
|
||||
|
||||
};
|
||||
_Ready.prototype.onMouseDown.transitions = ['Pressed'];
|
||||
|
||||
_Ready.prototype.start = function (controller) {
|
||||
|
||||
controller.scope.enabled = true;
|
||||
|
||||
};
|
||||
|
||||
_Ready.prototype.onDisable = function (controller) {
|
||||
|
||||
controller.changeState(Disabled);
|
||||
|
||||
};
|
||||
_Ready.prototype.onDisable.transitions = ['Disabled'];
|
||||
// end ready state
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
_Clicked.prototype.start = function (controller) {
|
||||
|
||||
controller.scope.is_pressed = false;
|
||||
controller.changeState(Ready);
|
||||
controller.scope.callback(controller.scope);
|
||||
};
|
||||
_Clicked.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
_Pressed.prototype.start = function (controller) {
|
||||
controller.scope.is_pressed = true;
|
||||
};
|
||||
|
||||
_Pressed.prototype.onMouseUp = function (controller) {
|
||||
|
||||
controller.changeState(Clicked);
|
||||
|
||||
};
|
||||
_Pressed.prototype.onMouseUp.transitions = ['Clicked'];
|
||||
|
||||
_Disabled.prototype.onEnable = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Disabled.prototype.onEnable.transitions = ['Ready'];
|
||||
|
||||
_Disabled.prototype.start = function (controller) {
|
||||
|
||||
controller.scope.enabled = false;
|
||||
|
||||
};
|
||||
96
awx/ui/client/src/network-ui/buttons.fsm.js
Normal file
@ -0,0 +1,96 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
|
||||
function _Ready () {
|
||||
this.name = 'Ready';
|
||||
}
|
||||
inherits(_Ready, _State);
|
||||
var Ready = new _Ready();
|
||||
exports.Ready = Ready;
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _ButtonPressed () {
|
||||
this.name = 'ButtonPressed';
|
||||
}
|
||||
inherits(_ButtonPressed, _State);
|
||||
var ButtonPressed = new _ButtonPressed();
|
||||
exports.ButtonPressed = ButtonPressed;
|
||||
|
||||
|
||||
|
||||
|
||||
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
|
||||
|
||||
var i = 0;
|
||||
var buttons = controller.scope.all_buttons;
|
||||
var button = null;
|
||||
for (i = 0; i < buttons.length; i++) {
|
||||
button = buttons[i];
|
||||
if (button.is_selected(controller.scope.mouseX, controller.scope.mouseY)) {
|
||||
button.fsm.handle_message(msg_type, $event);
|
||||
controller.changeState(ButtonPressed);
|
||||
break;
|
||||
}
|
||||
button = null;
|
||||
}
|
||||
if (button === null) {
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
}
|
||||
|
||||
};
|
||||
_Ready.prototype.onMouseDown.transitions = ['ButtonPressed'];
|
||||
|
||||
_Ready.prototype.onMouseMove = function (controller, msg_type, $event) {
|
||||
|
||||
if (!controller.scope.hide_buttons) {
|
||||
|
||||
var i = 0;
|
||||
var buttons = controller.scope.all_buttons;
|
||||
var button = null;
|
||||
for (i = 0; i < buttons.length; i++) {
|
||||
button = buttons[i];
|
||||
button.mouse_over = false;
|
||||
if (button.is_selected(controller.scope.mouseX, controller.scope.mouseY)) {
|
||||
button.mouse_over = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
};
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
|
||||
_ButtonPressed.prototype.onMouseUp = function (controller, msg_type, $event) {
|
||||
|
||||
var i = 0;
|
||||
var buttons = controller.scope.all_buttons;
|
||||
var button = null;
|
||||
for (i = 0; i < buttons.length; i++) {
|
||||
button = buttons[i];
|
||||
button.fsm.handle_message(msg_type, $event);
|
||||
}
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_ButtonPressed.prototype.onMouseUp.transitions = ['Ready'];
|
||||
@ -0,0 +1,15 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/context_menu_button.partial.svg');
|
||||
|
||||
function contextMenuButton () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
templateUrl,
|
||||
scope: {
|
||||
contextMenuButton: '=',
|
||||
contextMenu: '='
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.contextMenuButton = contextMenuButton;
|
||||
14
awx/ui/client/src/network-ui/context.menu.directive.js
Normal file
@ -0,0 +1,14 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/context_menu.partial.svg');
|
||||
|
||||
function contextMenu () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
templateUrl,
|
||||
scope: {
|
||||
contextMenu: '='
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.contextMenu = contextMenu;
|
||||
23
awx/ui/client/src/network-ui/context_menu.partial.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
|
||||
<filter id="shadowFilter" x="0" y="0" width="102%" height="105%">
|
||||
<feOffset result="offOut" in="SourceGraphic" dx="20" dy="20" />
|
||||
<feColorMatrix result = "matrixOut" in = "offOut" type = "matrix" values = "0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0"/>
|
||||
<feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="10" />
|
||||
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
|
||||
</filter>
|
||||
<rect ng-attr-class="{{contextMenu.is_pressed ? 'NetworkUI__contextMenu--button-pressed' : contextMenu.mouse_over ? 'NetworkUI__contextMenu--button-hover' : 'NetworkUI__contextMenu'}}"
|
||||
x=0
|
||||
y=0
|
||||
ng-attr-width={{contextMenu.width}}
|
||||
ng-attr-height={{contextMenu.height}}
|
||||
rx=5 ry=5
|
||||
filter="url(#shadowFilter)">
|
||||
</rect>
|
||||
|
||||
<g>
|
||||
<g> <!-- context menu buttons -->
|
||||
<g ng-repeat="button in contextMenu.buttons">
|
||||
<g awx-net--context-menu-button context-menu-button="button" context-menu="contextMenu"></g>
|
||||
</g>
|
||||
</g> <!-- end context menu buttons -->
|
||||
19
awx/ui/client/src/network-ui/context_menu_button.partial.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
<rect ng-attr-class="{{contextMenuButton.is_pressed ? 'NetworkUI__contextMenuButton-pressed' : contextMenuButton.mouse_over ? 'NetworkUI__contextMenuButton-hover' : 'NetworkUI__contextMenuButton'}}"
|
||||
x= 1
|
||||
ng-attr-y="{{(contextMenuButton.height * $parent.$index) + 5}}"
|
||||
ng-attr-width={{contextMenuButton.width-2}}
|
||||
ng-attr-height={{contextMenuButton.height}}>
|
||||
</rect>
|
||||
<text ng-show="contextMenuButton.name !=='Remove'" ng-attr-class="{{contextMenuButton.is_pressed ? 'NetworkUI__contextMenuButtonText-pressed' : contextMenuButton.mouse_over ? 'NetworkUI__contextMenuButtonText-hover' : 'NetworkUI__contextMenuButtonText'}}"
|
||||
x=15
|
||||
ng-attr-y="{{(contextMenuButton.height * $parent.$index) + 18}}"
|
||||
dy=".3em"
|
||||
text-anchor="left">{{contextMenuButton.name}}
|
||||
</text>
|
||||
<text ng-show="contextMenuButton.name ==='Remove'" ng-attr-class="{{contextMenuButton.is_pressed ? 'NetworkUI__contextMenuRemoveButtonText-pressed' : contextMenuButton.mouse_over ? 'NetworkUI__contextMenuRemoveButtonText-hover' : 'NetworkUI__contextMenuRemoveButtonText'}}"
|
||||
x=15
|
||||
ng-attr-y="{{(contextMenuButton.height * $parent.$index) + 18}}"
|
||||
dy=".3em"
|
||||
text-anchor="left">{{contextMenuButton.name}}
|
||||
</text>
|
||||
8
awx/ui/client/src/network-ui/cursor.directive.js
Normal file
@ -0,0 +1,8 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/cursor.partial.svg');
|
||||
|
||||
function cursor () {
|
||||
return { restrict: 'A', templateUrl};
|
||||
}
|
||||
exports.cursor = cursor;
|
||||
5
awx/ui/client/src/network-ui/cursor.partial.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
<g ng-attr-transform="translate({{cursor.x}},{{cursor.y}})" ng-attr-class="{{cursor.hidden && 'NetworkUI--hidden' || ''}}" >
|
||||
<line x1="-15" y1="0" x2="15" y2="0" class="NetworkUI__cursor"/>
|
||||
<line x1="0" y1="-15" x2="0" y2="15" class="NetworkUI__cursor"/>
|
||||
</g>
|
||||
20
awx/ui/client/src/network-ui/debug.directive.js
Normal file
@ -0,0 +1,20 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/debug.partial.svg');
|
||||
|
||||
function debug () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
templateUrl,
|
||||
link: function(){
|
||||
$('.NetworkUI__debug-text').each(function(index, option){
|
||||
let startingY = 15;
|
||||
let offset = 20;
|
||||
let y = startingY + (index * offset);
|
||||
option.setAttribute('y', y);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.debug = debug;
|
||||
60
awx/ui/client/src/network-ui/debug.partial.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
|
||||
|
||||
<g ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || ''}}">
|
||||
<g transform="translate(0, 100)">
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">view_port.x: {{view_port.x}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">view_port.y: {{view_port.y}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">view_port.width: {{view_port.width}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">view_port.height: {{view_port.height}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">width: {{graph.width}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">height: {{graph.height}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">rc: {{graph.right_column}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse down: {{onMouseDownResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse up: {{onMouseUpResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse move: {{onMouseMoveResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse over: {{onMouseOverResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse enter: {{onMouseEnterResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse leave: {{onMouseLeaveResult}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Current scale: {{current_scale.toFixed(4)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Pan X: {{panX.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Pan Y: {{panY.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">View State: {{view_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse X: {{mouseX.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mouse Y: {{mouseY.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Scaled X: {{scaledX.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Scaled Y: {{scaledY.toFixed(2)}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Key: {{last_key}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Key Code: {{last_key_code}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Move State: {{move_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Move Readonly State: {{move_readonly_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Selected devices: {{selected_devices.length}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Selected links: {{selected_links.length}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Link State: {{link_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Buttons State: {{buttons_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Time State: {{time_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Time Pointer: {{time_pointer}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Group State: {{group_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Hotkeys State: {{hotkeys_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Mode State: {{mode_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Device Detail State: {{device_detail_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Site State: {{site_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Rack State: {{rack_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Stream State: {{stream_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">App Toolbox State: {{app_toolbox_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Inventory Toolbox State: {{inventory_toolbox_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Rack Toolbox State: {{rack_toolbox_controller.state.name}}</text>
|
||||
<text ng-attr-x="{{graph.right_column}}" class="NetworkUI__debug-text">Site Toolbox State: {{site_toolbox_controller.state.name}}</text>
|
||||
</g>
|
||||
<rect x=10 y=10 ng-attr-width="{{graph.width - 20}}" ng-attr-height="{{graph.height - 20}}" class="NetworkUI--debug"></rect>
|
||||
<line x1="-10000"
|
||||
ng-attr-y1="{{graph.height/2}}"
|
||||
x2="10000"
|
||||
ng-attr-y2="{{graph.height/2}}"
|
||||
ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || 'NetworkUI--debug'}}" />
|
||||
<line ng-attr-x1="{{graph.width/2}}"
|
||||
y1="-10000"
|
||||
ng-attr-x2="{{graph.width/2}}"
|
||||
y2="10000"
|
||||
ng-attr-class="{{debug.hidden && 'NetworkUI--hidden' || 'NetworkUI--debug'}}" />
|
||||
</g>
|
||||
8
awx/ui/client/src/network-ui/default.directive.js
Normal file
@ -0,0 +1,8 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/default.partial.svg');
|
||||
|
||||
function defaultd () {
|
||||
return { restrict: 'A', templateUrl};
|
||||
}
|
||||
exports.defaultd = defaultd;
|
||||
56
awx/ui/client/src/network-ui/default.partial.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
|
||||
<g transform="scale(0.75)">
|
||||
<g ng-if="item.moving">
|
||||
<line ng-attr-x1="{{-50 - 100}}"
|
||||
ng-attr-y1="0"
|
||||
ng-attr-x2="{{50 + 100}}"
|
||||
ng-attr-y2="0"
|
||||
class="NetworkUI--construction"></line>
|
||||
<line ng-attr-x1="0"
|
||||
ng-attr-y1="{{-50 - 100}}"
|
||||
ng-attr-x2="0"
|
||||
ng-attr-y2="{{50 + 100}}"
|
||||
class="NetworkUI--construction"></line>
|
||||
</g>
|
||||
|
||||
<g ng-if="!debug.hidden">
|
||||
<line ng-attr-x1="{{-50 - 10}}"
|
||||
ng-attr-y1="0"
|
||||
ng-attr-x2="{{50 + 10}}"
|
||||
ng-attr-y2="0"
|
||||
class="NetworkUI--debug"></rect>
|
||||
<line ng-attr-x1="0"
|
||||
ng-attr-y1="{{-50 - 10}}"
|
||||
ng-attr-x2="0"
|
||||
ng-attr-y2="{{50 + 10}}"
|
||||
class="NetworkUI--debug"></rect>
|
||||
<rect ng-attr-x="{{-50}}"
|
||||
ng-attr-y="{{-50}}"
|
||||
ng-attr-width="{{50 * 2}}"
|
||||
ng-attr-height="{{50 * 2}}"
|
||||
class="NetworkUI--debug"></rect>
|
||||
</g>
|
||||
<g class="NetworkUI__device">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
ng-attr-r="{{50 + 2}}"
|
||||
ng-attr-class="{{item.selected || item.remote_selected ? item.selected && item.remote_selected ? 'NetworkUI__device--selected-conflict' : item.selected ? 'NetworkUI__device--selected' : 'NetworkUI__device--remote-selected' : 'NetworkUI--hidden'}}">
|
||||
</circle>
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
ng-attr-r="{{50}}">
|
||||
</circle>
|
||||
</g>
|
||||
<g ng-show="item.icon || current_scale > 0.5">
|
||||
<text ng-attr-class="{{item.selected && ! item.edit_label ? 'NetworkUI__device-text--selected' : 'NetworkUI--hidden'}}"
|
||||
filter="url(#background)"
|
||||
text-anchor="middle"
|
||||
x="0"
|
||||
y="73"> {{item.name}}
|
||||
</text>
|
||||
<text class="NetworkUI__device-text" filter="url(#background)" text-anchor="middle" x="0" y="73">{{item.name}}{{item.edit_label?'_':''}}</text>
|
||||
</g>
|
||||
</g>
|
||||
161
awx/ui/client/src/network-ui/designs/README.md
Normal file
@ -0,0 +1,161 @@
|
||||
|
||||
Finite State Machine Designs
|
||||
============================
|
||||
|
||||
This directory contains the finite state machine designs that were used to
|
||||
generate the skeleton of the javascript implementations and can be used to
|
||||
check that the implementations still match the designs.
|
||||
|
||||
|
||||
**Machine Readable FSM Schema**
|
||||
|
||||
The machine readable FSM schema contains three top-level elements: `name`, `states`, and `transitions`.
|
||||
* The `name` element is a string.
|
||||
* The `states` element contains a list of `state` elements which have attributes `id`, `label`, and `x`, and `y`.
|
||||
* The `transitions` element contains a list of `transition` elements which have attributes `from_state`, `to_state`, and `label`.
|
||||
|
||||
|
||||
**Design Diagrams**
|
||||
|
||||
The diagrams below are visual representations of the finite state machine designs in this directory.
|
||||
The equivalent machine readable representations are linked as well.
|
||||
|
||||
---
|
||||
|
||||
**Null FSM**
|
||||
* See: null.yml
|
||||
|
||||
The null FSM is an FSM that ignores all events.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Button FSM**
|
||||
* See: button.yml
|
||||
|
||||
The button FSM describes how a button works. The key insight here is that a button is not
|
||||
clicked if the mouse is not over the button on both the `MouseDown` and `MouseUp` events. Moving
|
||||
the mouse off the button before `MouseUp` is not a click.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Buttons FSM**
|
||||
* See: buttons.yml
|
||||
|
||||
The buttons FSM distributes events to the buttons which each have their own FSM.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Device Detail FSM**
|
||||
* See: device_detail.yml
|
||||
|
||||
The device detail FSM describes interactions when zoomed into a device.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Group FSM**
|
||||
* See: group.yml
|
||||
|
||||
The group FSM describes how to organize multiple devices together in a group.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Hot Keys FSM**
|
||||
* See: hotkeys.yml
|
||||
|
||||
The hot keys FSM handles key events and generates new events like `NewLink` to implement
|
||||
hot keys.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Link FSM**
|
||||
* See: link.yml
|
||||
|
||||
The link FSM connects two devices together with a link.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Mode FSM**
|
||||
* See: mode.yml
|
||||
|
||||
The mode FSM controls the overall mode of the network UI application.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Move FSM**
|
||||
* See: move.yml
|
||||
|
||||
The move FSM controls placement of devices as well as editing the device labels.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Rack FSM**
|
||||
* See: rack.yml
|
||||
|
||||
The rack FSM controls organizing devices into a special group called a rack.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Site FSM**
|
||||
* See: site.yml
|
||||
|
||||
The site FSM controls organizing devices into a special group called a site.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Stream FSM**
|
||||
* See: stream.yml
|
||||
|
||||
The stream FSM controls how streams are defined between devices.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Time FSM**
|
||||
* See: time.yml
|
||||
|
||||
The time FSM controls undo/redo functionality of the network UI.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**Toolbox FSM**
|
||||
* See: toolbox.yml
|
||||
|
||||
The toolbox FSM controls the drag-and-drop toolboxes and allow placement of new devices, applications,
|
||||
racks, and sites onto the canvas.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**View FSM**
|
||||
* See: view.yml
|
||||
|
||||
The view FSM controls the panning and scaling of the the virtual canvas through clicking-and-dragging
|
||||
of the background and scrolling the mousewheel.
|
||||
|
||||

|
||||
29
awx/ui/client/src/network-ui/designs/animation.yml
Normal file
@ -0,0 +1,29 @@
|
||||
diagram_id: 58
|
||||
name: animation_fsm
|
||||
states:
|
||||
- id: 4
|
||||
label: Cancelled
|
||||
x: 590
|
||||
y: 602
|
||||
- id: 3
|
||||
label: Completed
|
||||
x: 225
|
||||
y: 604
|
||||
- id: 2
|
||||
label: Running
|
||||
x: 418
|
||||
y: 362
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 454
|
||||
y: 158
|
||||
transitions:
|
||||
- from_state: Running
|
||||
label: onAnimationCancelled
|
||||
to_state: Cancelled
|
||||
- from_state: Running
|
||||
label: onAnimationCompleted
|
||||
to_state: Completed
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Running
|
||||
BIN
awx/ui/client/src/network-ui/designs/button.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
43
awx/ui/client/src/network-ui/designs/button.yml
Normal file
@ -0,0 +1,43 @@
|
||||
diagram_id: 66
|
||||
name: 'button_fsm'
|
||||
finite_state_machine_id: 12
|
||||
states:
|
||||
- id: 3
|
||||
label: Clicked
|
||||
x: 331
|
||||
y: 568
|
||||
- id: 5
|
||||
label: Disabled
|
||||
x: 719
|
||||
y: 283
|
||||
- id: 4
|
||||
label: Pressed
|
||||
x: 606
|
||||
y: 563
|
||||
- id: 1
|
||||
label: Ready
|
||||
x: 471
|
||||
y: 376
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 468
|
||||
y: 170
|
||||
transitions:
|
||||
- from_state: Clicked
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Disabled
|
||||
label: onEnable
|
||||
to_state: Ready
|
||||
- from_state: Pressed
|
||||
label: onMouseUp
|
||||
to_state: Clicked
|
||||
- from_state: Ready
|
||||
label: onDisable
|
||||
to_state: Disabled
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Pressed
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/buttons.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
28
awx/ui/client/src/network-ui/designs/buttons.yml
Normal file
@ -0,0 +1,28 @@
|
||||
app: buttons_fsm
|
||||
finite_state_machine_id: 7
|
||||
panX: 133
|
||||
panY: 41
|
||||
scaleXY: 1
|
||||
states:
|
||||
- label: Start
|
||||
size: 100
|
||||
x: 392
|
||||
y: 88
|
||||
- label: Ready
|
||||
size: 100
|
||||
x: 392
|
||||
y: 281
|
||||
- label: ButtonPressed
|
||||
size: 100
|
||||
x: 394
|
||||
y: 491
|
||||
transitions:
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: ButtonPressed
|
||||
- from_state: ButtonPressed
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/details.panel.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
26
awx/ui/client/src/network-ui/designs/details.panel.yml
Normal file
@ -0,0 +1,26 @@
|
||||
diagram_id: 70
|
||||
finite_state_machine_id: 21
|
||||
name: diagram
|
||||
states:
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 590
|
||||
y: 233
|
||||
- id: 2
|
||||
label: Collapsed
|
||||
x: 594
|
||||
y: 490
|
||||
- id: 3
|
||||
label: Expanded
|
||||
x: 919
|
||||
y: 491
|
||||
transitions:
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Collapsed
|
||||
- from_state: Expanded
|
||||
label: onDetailsPanelClose
|
||||
to_state: Collapsed
|
||||
- from_state: Collapsed
|
||||
label: onDetailsPanel
|
||||
to_state: Expanded
|
||||
BIN
awx/ui/client/src/network-ui/designs/device.detail.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
19
awx/ui/client/src/network-ui/designs/device.detail.yml
Normal file
@ -0,0 +1,19 @@
|
||||
finite_state_machine_id: 19
|
||||
name: device_detail_fsm
|
||||
states:
|
||||
- id: 2
|
||||
label: Ready
|
||||
x: 517
|
||||
y: 588
|
||||
- id: 3
|
||||
label: Disable
|
||||
x: 770
|
||||
y: 455
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 507
|
||||
y: 336
|
||||
transitions:
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/group.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
119
awx/ui/client/src/network-ui/designs/group.yml
Normal file
@ -0,0 +1,119 @@
|
||||
diagram_id: 61
|
||||
finite_state_machine_id: 5
|
||||
name: group_fsm
|
||||
states:
|
||||
- id: 12
|
||||
label: ContextMenu
|
||||
x: 1228
|
||||
y: -74
|
||||
- id: 3
|
||||
label: CornerSelected
|
||||
x: 526
|
||||
y: 554
|
||||
- id: 8
|
||||
label: Disable
|
||||
x: 497
|
||||
y: 84
|
||||
- id: 9
|
||||
label: EditLabel
|
||||
x: 1130
|
||||
y: 112
|
||||
- id: 6
|
||||
label: Move
|
||||
x: 1297
|
||||
y: 786
|
||||
- id: 11
|
||||
label: Placing
|
||||
x: 299
|
||||
y: 300
|
||||
- id: 7
|
||||
label: Ready
|
||||
x: 733
|
||||
y: 304
|
||||
- id: 1
|
||||
label: Resize
|
||||
x: 571
|
||||
y: 911
|
||||
- id: 4
|
||||
label: Selected1
|
||||
x: 839
|
||||
y: 640
|
||||
- id: 10
|
||||
label: Selected2
|
||||
x: 1179
|
||||
y: 435
|
||||
- id: 5
|
||||
label: Selected3
|
||||
x: 1528
|
||||
y: 360
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 744
|
||||
y: 69
|
||||
transitions:
|
||||
- from_state: ContextMenu
|
||||
label: onLabelEdit
|
||||
to_state: EditLabel
|
||||
- from_state: ContextMenu
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: CornerSelected
|
||||
label: onMouseMove
|
||||
to_state: Resize
|
||||
- from_state: CornerSelected
|
||||
label: onMouseUp
|
||||
to_state: Selected1
|
||||
- from_state: EditLabel
|
||||
label: onKeyDown
|
||||
to_state: Selected2
|
||||
- from_state: EditLabel
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Move
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Placing
|
||||
label: onMouseDown
|
||||
to_state: Resize
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: CornerSelected
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Ready
|
||||
label: onNewGroup
|
||||
to_state: Placing
|
||||
- from_state: Resize
|
||||
label: onMouseUp
|
||||
to_state: Selected1
|
||||
- from_state: Selected1
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected1
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Selected2
|
||||
label: onKeyDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Selected3
|
||||
- from_state: Selected2
|
||||
label: onNewGroup
|
||||
to_state: Ready
|
||||
- from_state: Selected3
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected3
|
||||
label: onMouseUp
|
||||
to_state: ContextMenu
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/hotkeys.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
25
awx/ui/client/src/network-ui/designs/hotkeys.yml
Normal file
@ -0,0 +1,25 @@
|
||||
finite_state_machine_id: 1
|
||||
name: hotkeys_fsm
|
||||
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
|
||||
BIN
awx/ui/client/src/network-ui/designs/keybindings.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
38
awx/ui/client/src/network-ui/designs/keybindings.yml
Normal file
@ -0,0 +1,38 @@
|
||||
diagram_id: 68
|
||||
finite_state_machine_id: 18
|
||||
name: diagram
|
||||
states:
|
||||
- id: 1
|
||||
label: Enabled
|
||||
x: 842
|
||||
y: 533
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 839
|
||||
y: 270
|
||||
- id: 3
|
||||
label: Disabled
|
||||
x: 1412
|
||||
y: 522
|
||||
transitions:
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Enabled
|
||||
- from_state: Disabled
|
||||
label: onBindDocument
|
||||
to_state: Enabled
|
||||
- from_state: Enabled
|
||||
label: onUnbindDocument
|
||||
to_state: Disabled
|
||||
- from_state: Disabled
|
||||
label: onDetailsPanelClose
|
||||
to_state: Enabled
|
||||
- from_state: Enabled
|
||||
label: onDetailsPanel
|
||||
to_state: Disabled
|
||||
- from_state: Enabled
|
||||
label: onSearchDropdown
|
||||
to_state: Disabled
|
||||
- from_state: Disabled
|
||||
label: onSearchDropdownClose
|
||||
to_state: Enabled
|
||||
BIN
awx/ui/client/src/network-ui/designs/link.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
42
awx/ui/client/src/network-ui/designs/link.yml
Normal file
@ -0,0 +1,42 @@
|
||||
finite_state_machine_id: 4
|
||||
name: link_fsm
|
||||
states:
|
||||
- id: 5
|
||||
label: Selecting
|
||||
x: -429
|
||||
y: 63
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 15
|
||||
y: -221
|
||||
- id: 4
|
||||
label: Connecting
|
||||
x: -429
|
||||
y: 466
|
||||
- id: 3
|
||||
label: Connected
|
||||
x: 47
|
||||
y: 453
|
||||
- id: 1
|
||||
label: Ready
|
||||
x: 26
|
||||
y: 61
|
||||
transitions:
|
||||
- from_state: Ready
|
||||
label: onNewLink
|
||||
to_state: Selecting
|
||||
- from_state: Selecting
|
||||
label: onMouseUp
|
||||
to_state: Connecting
|
||||
- from_state: Connecting
|
||||
label: onMouseUp
|
||||
to_state: Connected
|
||||
- from_state: Connecting
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
- from_state: Connected
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/mode.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
96
awx/ui/client/src/network-ui/designs/mode.yml
Normal file
@ -0,0 +1,96 @@
|
||||
diagram_id: 68
|
||||
finite_state_machine_id: 9
|
||||
name: mode_fsm
|
||||
states:
|
||||
- id: 7
|
||||
label: Device
|
||||
x: 558
|
||||
y: 821
|
||||
- id: 2
|
||||
label: Interface
|
||||
x: 340
|
||||
y: 1053
|
||||
- id: 5
|
||||
label: MultiSite
|
||||
x: 569
|
||||
y: -88
|
||||
- id: 4
|
||||
label: Process
|
||||
x: 833
|
||||
y: 1051
|
||||
- id: 6
|
||||
label: Rack
|
||||
x: 571
|
||||
y: 486
|
||||
- id: 3
|
||||
label: Site
|
||||
x: 564
|
||||
y: 201
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 568
|
||||
y: -379
|
||||
transitions:
|
||||
- from_state: Device
|
||||
label: onMouseWheel
|
||||
to_state: Process
|
||||
- from_state: Device
|
||||
label: onMouseWheel
|
||||
to_state: Rack
|
||||
- from_state: Device
|
||||
label: onMouseWheel
|
||||
to_state: Interface
|
||||
- from_state: Device
|
||||
label: onScaleChanged
|
||||
to_state: Interface
|
||||
- from_state: Device
|
||||
label: onScaleChanged
|
||||
to_state: Rack
|
||||
- from_state: Device
|
||||
label: onScaleChanged
|
||||
to_state: Process
|
||||
- from_state: Interface
|
||||
label: onMouseWheel
|
||||
to_state: Device
|
||||
- from_state: Interface
|
||||
label: onScaleChanged
|
||||
to_state: Device
|
||||
- from_state: MultiSite
|
||||
label: onMouseWheel
|
||||
to_state: Site
|
||||
- from_state: MultiSite
|
||||
label: onScaleChanged
|
||||
to_state: Site
|
||||
- from_state: Process
|
||||
label: onMouseWheel
|
||||
to_state: Device
|
||||
- from_state: Process
|
||||
label: onScaleChanged
|
||||
to_state: Device
|
||||
- from_state: Rack
|
||||
label: onMouseWheel
|
||||
to_state: Site
|
||||
- from_state: Rack
|
||||
label: onMouseWheel
|
||||
to_state: Device
|
||||
- from_state: Rack
|
||||
label: onScaleChanged
|
||||
to_state: Site
|
||||
- from_state: Rack
|
||||
label: onScaleChanged
|
||||
to_state: Device
|
||||
- from_state: Site
|
||||
label: onMouseWheel
|
||||
to_state: MultiSite
|
||||
- from_state: Site
|
||||
label: onMouseWheel
|
||||
to_state: Rack
|
||||
- from_state: Site
|
||||
label: onScaleChanged
|
||||
to_state: MultiSite
|
||||
- from_state: Site
|
||||
label: onScaleChanged
|
||||
to_state: Rack
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: MultiSite
|
||||
BIN
awx/ui/client/src/network-ui/designs/move.png
Normal file
|
After Width: | Height: | Size: 329 KiB |
BIN
awx/ui/client/src/network-ui/designs/move.readonly.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
65
awx/ui/client/src/network-ui/designs/move.readonly.yml
Normal file
@ -0,0 +1,65 @@
|
||||
diagram_id: 91
|
||||
name: diagram
|
||||
states:
|
||||
- id: 0
|
||||
label: ContextMenu
|
||||
x: 826
|
||||
y: 1008
|
||||
- id: 1
|
||||
label: Disable
|
||||
x: 914
|
||||
y: 115
|
||||
- id: 5
|
||||
label: Ready
|
||||
x: 702
|
||||
y: 327
|
||||
- id: 6
|
||||
label: Selected1
|
||||
x: 397
|
||||
y: 332
|
||||
- id: 7
|
||||
label: Selected2
|
||||
x: 268
|
||||
y: 735
|
||||
- id: 8
|
||||
label: Selected3
|
||||
x: 225
|
||||
y: 1021
|
||||
- id: 9
|
||||
label: Start
|
||||
x: 704
|
||||
y: 128
|
||||
transitions:
|
||||
- from_state: ContextMenu
|
||||
label: onDetailsPanel
|
||||
to_state: Selected2
|
||||
- from_state: ContextMenu
|
||||
label: onMouseDown
|
||||
to_state: Selected2
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Selected1
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Selected2
|
||||
label: onKeyDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Selected3
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected3
|
||||
label: ''
|
||||
to_state: Selected3
|
||||
- from_state: Selected3
|
||||
label: onMouseMove
|
||||
to_state: Selected2
|
||||
- from_state: Selected3
|
||||
label: onMouseUp
|
||||
to_state: ContextMenu
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
107
awx/ui/client/src/network-ui/designs/move.yml
Normal file
@ -0,0 +1,107 @@
|
||||
diagram_id: 87
|
||||
name: move
|
||||
states:
|
||||
- id: 8
|
||||
label: ContextMenu
|
||||
x: 826
|
||||
y: 1008
|
||||
- id: 0
|
||||
label: Disable
|
||||
x: 914
|
||||
y: 115
|
||||
- id: 6
|
||||
label: EditLabel
|
||||
x: 765
|
||||
y: 684
|
||||
- id: 4
|
||||
label: Move
|
||||
x: 118
|
||||
y: 594
|
||||
- id: 5
|
||||
label: Placing
|
||||
x: 263
|
||||
y: 89
|
||||
- id: 2
|
||||
label: Ready
|
||||
x: 702
|
||||
y: 327
|
||||
- id: 3
|
||||
label: Selected1
|
||||
x: 397
|
||||
y: 332
|
||||
- id: 7
|
||||
label: Selected2
|
||||
x: 268
|
||||
y: 735
|
||||
- id: 9
|
||||
label: Selected3
|
||||
x: 361
|
||||
y: 961
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 704
|
||||
y: 128
|
||||
transitions:
|
||||
- from_state: ContextMenu
|
||||
label: onDetailsPanel
|
||||
to_state: Selected2
|
||||
- from_state: ContextMenu
|
||||
label: onLabelEdit
|
||||
to_state: EditLabel
|
||||
- from_state: ContextMenu
|
||||
label: onMouseDown
|
||||
to_state: Selected2
|
||||
- from_state: EditLabel
|
||||
label: onKeyDown
|
||||
to_state: Selected2
|
||||
- from_state: EditLabel
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Move
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Selected1
|
||||
- from_state: Placing
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Placing
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Ready
|
||||
label: onNewDevice
|
||||
to_state: Placing
|
||||
- from_state: Ready
|
||||
label: onPasteDevice
|
||||
to_state: Selected2
|
||||
- from_state: Selected1
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected1
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Selected2
|
||||
label: onKeyDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Selected3
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onNewDevice
|
||||
to_state: Ready
|
||||
- from_state: Selected3
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected3
|
||||
label: onMouseUp
|
||||
to_state: ContextMenu
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/null.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
15
awx/ui/client/src/network-ui/designs/null.yml
Normal file
@ -0,0 +1,15 @@
|
||||
finite_state_machine_id: 17
|
||||
name: null_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
|
||||
BIN
awx/ui/client/src/network-ui/designs/pipeline.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
233
awx/ui/client/src/network-ui/designs/pipeline.yml
Normal file
@ -0,0 +1,233 @@
|
||||
channels:
|
||||
- from_fsm: buttons_fsm
|
||||
from_fsm_id: 7
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: button_fsm
|
||||
to_fsm_id: 12
|
||||
type: ''
|
||||
- from_fsm: buttons_fsm
|
||||
from_fsm_id: 7
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: toolbox_fsm
|
||||
to_fsm_id: 14
|
||||
type: ''
|
||||
- from_fsm: details_panel_fsm
|
||||
from_fsm_id: 21
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: move_fsm
|
||||
to_fsm_id: 3
|
||||
type: ''
|
||||
- from_fsm: device_detail_fsm
|
||||
from_fsm_id: 19
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: view_fsm
|
||||
to_fsm_id: 2
|
||||
type: ''
|
||||
- from_fsm: group_fsm
|
||||
from_fsm_id: 5
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: stream_fsm
|
||||
to_fsm_id: 20
|
||||
type: ''
|
||||
- from_fsm: hotkeys_fsm
|
||||
from_fsm_id: 1
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: null_fsm
|
||||
to_fsm_id: 17
|
||||
type: ''
|
||||
- from_fsm: keybindings_fsm
|
||||
from_fsm_id: 18
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: hotkeys_fsm
|
||||
to_fsm_id: 1
|
||||
type: ''
|
||||
- from_fsm: link_fsm
|
||||
from_fsm_id: 4
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: details_panel_fsm
|
||||
to_fsm_id: 21
|
||||
type: ''
|
||||
- from_fsm: mode_fsm
|
||||
from_fsm_id: 9
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: time_fsm
|
||||
to_fsm_id: 8
|
||||
type: ''
|
||||
- from_fsm: move_fsm
|
||||
from_fsm_id: 3
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: device_detail_fsm
|
||||
to_fsm_id: 19
|
||||
type: ''
|
||||
- from_fsm: rack_fsm
|
||||
from_fsm_id: 6
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: group_fsm
|
||||
to_fsm_id: 5
|
||||
type: ''
|
||||
- from_fsm: site_fsm
|
||||
from_fsm_id: 22
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: rack_fsm
|
||||
to_fsm_id: 6
|
||||
type: ''
|
||||
- from_fsm: stream_fsm
|
||||
from_fsm_id: 20
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: link_fsm
|
||||
to_fsm_id: 4
|
||||
type: ''
|
||||
- from_fsm: test_fsm
|
||||
from_fsm_id: 23
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: mode_fsm
|
||||
to_fsm_id: 9
|
||||
type: ''
|
||||
- from_fsm: time_fsm
|
||||
from_fsm_id: 8
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: buttons_fsm
|
||||
to_fsm_id: 7
|
||||
type: ''
|
||||
- from_fsm: toolbox_fsm
|
||||
from_fsm_id: 14
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: site_fsm
|
||||
to_fsm_id: 22
|
||||
type: ''
|
||||
- from_fsm: view_fsm
|
||||
from_fsm_id: 2
|
||||
inbox: ''
|
||||
outbox: ''
|
||||
to_fsm: keybindings_fsm
|
||||
to_fsm_id: 18
|
||||
type: ''
|
||||
diagram_id: 85
|
||||
fsms:
|
||||
- id: 12
|
||||
name: button_fsm
|
||||
x1: -2438
|
||||
x2: -3026
|
||||
y1: -934
|
||||
y2: -1532
|
||||
- id: 7
|
||||
name: buttons_fsm
|
||||
x1: -2650
|
||||
x2: -2850
|
||||
y1: -16
|
||||
y2: -619
|
||||
- id: 21
|
||||
name: details_panel_fsm
|
||||
x1: 5669
|
||||
x2: 5140
|
||||
y1: -64
|
||||
y2: -521
|
||||
- id: 19
|
||||
name: device_detail_fsm
|
||||
x1: 7667
|
||||
x2: 7214
|
||||
y1: -110
|
||||
y2: -562
|
||||
- id: 5
|
||||
name: group_fsm
|
||||
x1: 3685
|
||||
x2: 2256
|
||||
y1: 278
|
||||
y2: -906
|
||||
- id: 1
|
||||
name: hotkeys_fsm
|
||||
x1: 9692
|
||||
x2: 9281
|
||||
y1: -124
|
||||
y2: -549
|
||||
- id: 18
|
||||
name: keybindings_fsm
|
||||
x1: 9223
|
||||
x2: 8370
|
||||
y1: -71
|
||||
y2: -614
|
||||
- id: 4
|
||||
name: link_fsm
|
||||
x1: 5080
|
||||
x2: 4436
|
||||
y1: 154
|
||||
y2: -732
|
||||
- id: 9
|
||||
name: mode_fsm
|
||||
x1: -3760
|
||||
x2: -4453
|
||||
y1: 192
|
||||
y2: -1439
|
||||
- id: 3
|
||||
name: move_fsm
|
||||
x1: 6968
|
||||
x2: 5813
|
||||
y1: 146
|
||||
y2: -935
|
||||
- id: 17
|
||||
name: null_fsm
|
||||
x1: 10125
|
||||
x2: 9925
|
||||
y1: -129
|
||||
y2: -543
|
||||
- id: 6
|
||||
name: rack_fsm
|
||||
x1: 2214
|
||||
x2: 1047
|
||||
y1: 127
|
||||
y2: -753
|
||||
- id: 22
|
||||
name: site_fsm
|
||||
x1: 964
|
||||
x2: -190
|
||||
y1: 128
|
||||
y2: -768
|
||||
- id: 20
|
||||
name: stream_fsm
|
||||
x1: 4376
|
||||
x2: 3868
|
||||
y1: 56
|
||||
y2: -643
|
||||
- id: 23
|
||||
name: test_fsm
|
||||
x1: -4569
|
||||
x2: -5140
|
||||
y1: 72
|
||||
y2: -863
|
||||
- id: 8
|
||||
name: time_fsm
|
||||
x1: -3122
|
||||
x2: -3693
|
||||
y1: -69
|
||||
y2: -553
|
||||
- id: 14
|
||||
name: toolbox_fsm
|
||||
x1: -680
|
||||
x2: -1722
|
||||
y1: 265
|
||||
y2: -904
|
||||
- id: 2
|
||||
name: view_fsm
|
||||
x1: 8311
|
||||
x2: 7734
|
||||
y1: -25
|
||||
y2: -684
|
||||
name: diagram
|
||||
states: []
|
||||
transitions: []
|
||||
BIN
awx/ui/client/src/network-ui/designs/rack.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
83
awx/ui/client/src/network-ui/designs/rack.yml
Normal file
@ -0,0 +1,83 @@
|
||||
diagram_id: 65
|
||||
finite_state_machine_id: 6
|
||||
name: rack_fsm
|
||||
states:
|
||||
- id: 9
|
||||
label: ContextMenu
|
||||
x: 898
|
||||
y: 1016
|
||||
- id: 2
|
||||
label: Disable
|
||||
x: 760
|
||||
y: 468
|
||||
- id: 7
|
||||
label: EditLabel
|
||||
x: 600
|
||||
y: 934
|
||||
- id: 8
|
||||
label: Move
|
||||
x: -69
|
||||
y: 861
|
||||
- id: 1
|
||||
label: Ready
|
||||
x: 532
|
||||
y: 560
|
||||
- id: 4
|
||||
label: Selected1
|
||||
x: 214
|
||||
y: 566
|
||||
- id: 5
|
||||
label: Selected2
|
||||
x: 220
|
||||
y: 810
|
||||
- id: 6
|
||||
label: Selected3
|
||||
x: 249
|
||||
y: 1047
|
||||
- id: 3
|
||||
label: Start
|
||||
x: 582
|
||||
y: 334
|
||||
transitions:
|
||||
- from_state: ContextMenu
|
||||
label: onLabelEdit
|
||||
to_state: EditLabel
|
||||
- from_state: ContextMenu
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: EditLabel
|
||||
label: onKeyDown
|
||||
to_state: Selected2
|
||||
- from_state: EditLabel
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Selected1
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected1
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Selected2
|
||||
label: onKeyDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Selected3
|
||||
- from_state: Selected3
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected3
|
||||
label: onMouseUp
|
||||
to_state: ContextMenu
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/site.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
83
awx/ui/client/src/network-ui/designs/site.yml
Normal file
@ -0,0 +1,83 @@
|
||||
diagram_id: 63
|
||||
finite_state_machine_id: 22
|
||||
name: site_fsm
|
||||
states:
|
||||
- id: 9
|
||||
label: ContextMenu
|
||||
x: 887
|
||||
y: 1031
|
||||
- id: 2
|
||||
label: Disable
|
||||
x: 760
|
||||
y: 468
|
||||
- id: 7
|
||||
label: EditLabel
|
||||
x: 600
|
||||
y: 934
|
||||
- id: 8
|
||||
label: Move
|
||||
x: -69
|
||||
y: 861
|
||||
- id: 1
|
||||
label: Ready
|
||||
x: 532
|
||||
y: 560
|
||||
- id: 4
|
||||
label: Selected1
|
||||
x: 214
|
||||
y: 566
|
||||
- id: 5
|
||||
label: Selected2
|
||||
x: 220
|
||||
y: 810
|
||||
- id: 6
|
||||
label: Selected3
|
||||
x: 249
|
||||
y: 1047
|
||||
- id: 3
|
||||
label: Start
|
||||
x: 582
|
||||
y: 334
|
||||
transitions:
|
||||
- from_state: ContextMenu
|
||||
label: onLabelEdit
|
||||
to_state: EditLabel
|
||||
- from_state: ContextMenu
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: EditLabel
|
||||
label: onKeyDown
|
||||
to_state: Selected2
|
||||
- from_state: EditLabel
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selected1
|
||||
- from_state: Selected1
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected1
|
||||
label: onMouseUp
|
||||
to_state: Selected2
|
||||
- from_state: Selected2
|
||||
label: onKeyDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected2
|
||||
label: onMouseDown
|
||||
to_state: Selected3
|
||||
- from_state: Selected3
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selected3
|
||||
label: onMouseUp
|
||||
to_state: ContextMenu
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
BIN
awx/ui/client/src/network-ui/designs/stream.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
42
awx/ui/client/src/network-ui/designs/stream.yml
Normal file
@ -0,0 +1,42 @@
|
||||
finite_state_machine_id: 20
|
||||
name: stream_fsm
|
||||
states:
|
||||
- id: 4
|
||||
label: Connecting
|
||||
x: 344
|
||||
y: 312
|
||||
- id: 5
|
||||
label: Selecting
|
||||
x: 311
|
||||
y: 23
|
||||
- id: 1
|
||||
label: Ready
|
||||
x: 36
|
||||
y: 28
|
||||
- id: 3
|
||||
label: Connected
|
||||
x: 55
|
||||
y: 317
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 43
|
||||
y: -188
|
||||
transitions:
|
||||
- from_state: Ready
|
||||
label: onNewStream
|
||||
to_state: Selecting
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Connected
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Connecting
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
- from_state: Connecting
|
||||
label: onMouseUp
|
||||
to_state: Connected
|
||||
- from_state: Selecting
|
||||
label: onMouseUp
|
||||
to_state: Connecting
|
||||
50
awx/ui/client/src/network-ui/designs/test.yml
Normal file
@ -0,0 +1,50 @@
|
||||
diagram_id: 69
|
||||
finite_state_machine_id: 23
|
||||
name: diagram
|
||||
states:
|
||||
- id: 1
|
||||
label: Disabled
|
||||
x: 895
|
||||
y: 344
|
||||
- id: 4
|
||||
label: Loading
|
||||
x: 524
|
||||
y: 710
|
||||
- id: 5
|
||||
label: Ready
|
||||
x: 722
|
||||
y: 509
|
||||
- id: 6
|
||||
label: Reporting
|
||||
x: 926
|
||||
y: 721
|
||||
- id: 3
|
||||
label: Running
|
||||
x: 720
|
||||
y: 922
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 702
|
||||
y: 186
|
||||
transitions:
|
||||
- from_state: Disabled
|
||||
label: onEnableTest
|
||||
to_state: Ready
|
||||
- from_state: Loading
|
||||
label: start
|
||||
to_state: Running
|
||||
- from_state: Ready
|
||||
label: onDisableTest
|
||||
to_state: Disabled
|
||||
- from_state: Ready
|
||||
label: start
|
||||
to_state: Loading
|
||||
- from_state: Reporting
|
||||
label: start
|
||||
to_state: Loading
|
||||
- from_state: Running
|
||||
label: onTestCompleted
|
||||
to_state: Reporting
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Disabled
|
||||
BIN
awx/ui/client/src/network-ui/designs/time.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
37
awx/ui/client/src/network-ui/designs/time.yml
Normal file
@ -0,0 +1,37 @@
|
||||
finite_state_machine_id: 8
|
||||
name: time_fsm
|
||||
states:
|
||||
- id: 1
|
||||
label: Present
|
||||
x: 256
|
||||
y: 123
|
||||
- id: 2
|
||||
label: Start
|
||||
x: 245
|
||||
y: -161
|
||||
- id: 3
|
||||
label: Past
|
||||
x: -115
|
||||
y: 129
|
||||
transitions:
|
||||
- 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: onUndo
|
||||
to_state: Past
|
||||
- from_state: Present
|
||||
label: onMouseWheel
|
||||
to_state: Past
|
||||
- from_state: Present
|
||||
label: onKeyDown
|
||||
to_state: Past
|
||||
BIN
awx/ui/client/src/network-ui/designs/toolbox.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
98
awx/ui/client/src/network-ui/designs/toolbox.yml
Normal file
@ -0,0 +1,98 @@
|
||||
finite_state_machine_id: 14
|
||||
name: toolbox_fsm
|
||||
states:
|
||||
- id: 9
|
||||
label: Disabled
|
||||
x: 885
|
||||
y: 141
|
||||
- id: 7
|
||||
label: OffScreen
|
||||
x: 1140
|
||||
y: 217
|
||||
- id: 1
|
||||
label: Selected
|
||||
x: 1180
|
||||
y: 959
|
||||
- id: 2
|
||||
label: Move
|
||||
x: 1409
|
||||
y: 741
|
||||
- id: 3
|
||||
label: Ready
|
||||
x: 892
|
||||
y: 429
|
||||
- id: 4
|
||||
label: Scrolling
|
||||
x: 567
|
||||
y: 431
|
||||
- id: 5
|
||||
label: Selecting
|
||||
x: 888
|
||||
y: 710
|
||||
- id: 6
|
||||
label: Dropping
|
||||
x: 1358
|
||||
y: 431
|
||||
- id: 8
|
||||
label: Start
|
||||
x: 672
|
||||
y: 196
|
||||
- id: 10
|
||||
label: OffScreen2
|
||||
x: 1115
|
||||
y: -12
|
||||
transitions:
|
||||
- from_state: Ready
|
||||
label: onDisable
|
||||
to_state: Disabled
|
||||
- from_state: OffScreen2
|
||||
label: onToggleToolbox
|
||||
to_state: Disabled
|
||||
- from_state: OffScreen2
|
||||
label: onEnable
|
||||
to_state: OffScreen
|
||||
- from_state: Ready
|
||||
label: onToggleToolbox
|
||||
to_state: OffScreen
|
||||
- from_state: Selecting
|
||||
label: onMouseDown
|
||||
to_state: Selected
|
||||
- from_state: Selected
|
||||
label: onMouseMove
|
||||
to_state: Move
|
||||
- from_state: Selecting
|
||||
label: onMouseDown
|
||||
to_state: Ready
|
||||
- from_state: Selected
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
- from_state: Dropping
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Scrolling
|
||||
label: onMouseWheel
|
||||
to_state: Ready
|
||||
- from_state: OffScreen
|
||||
label: onToggleToolbox
|
||||
to_state: Ready
|
||||
- from_state: Disabled
|
||||
label: onEnable
|
||||
to_state: Ready
|
||||
- from_state: Ready
|
||||
label: onMouseWheel
|
||||
to_state: Scrolling
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Selecting
|
||||
- from_state: Move
|
||||
label: onMouseUp
|
||||
to_state: Dropping
|
||||
- from_state: OffScreen
|
||||
label: onDisable
|
||||
to_state: OffScreen2
|
||||
- from_state: Disabled
|
||||
label: onToggleToolbox
|
||||
to_state: OffScreen2
|
||||
BIN
awx/ui/client/src/network-ui/designs/view.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
45
awx/ui/client/src/network-ui/designs/view.yml
Normal file
@ -0,0 +1,45 @@
|
||||
finite_state_machine_id: 2
|
||||
name: view_fsm
|
||||
states:
|
||||
- id: 1
|
||||
label: Start
|
||||
x: 498
|
||||
y: 175
|
||||
- id: 2
|
||||
label: Ready
|
||||
x: 506
|
||||
y: 395
|
||||
- id: 3
|
||||
label: Scale
|
||||
x: 310
|
||||
y: 626
|
||||
- id: 4
|
||||
label: Pan
|
||||
x: 741
|
||||
y: 631
|
||||
- id: 5
|
||||
label: Pressed
|
||||
x: 739
|
||||
y: 392
|
||||
transitions:
|
||||
- from_state: Scale
|
||||
label: onMouseWheel
|
||||
to_state: Ready
|
||||
- from_state: Start
|
||||
label: start
|
||||
to_state: Ready
|
||||
- from_state: Ready
|
||||
label: onMouseWheel
|
||||
to_state: Scale
|
||||
- from_state: Ready
|
||||
label: onMouseDown
|
||||
to_state: Pressed
|
||||
- from_state: Pressed
|
||||
label: onMouseMove
|
||||
to_state: Pan
|
||||
- from_state: Pressed
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
- from_state: Pan
|
||||
label: onMouseUp
|
||||
to_state: Ready
|
||||
62
awx/ui/client/src/network-ui/details.panel.fsm.js
Normal file
@ -0,0 +1,62 @@
|
||||
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 _Collapsed () {
|
||||
this.name = 'Collapsed';
|
||||
}
|
||||
inherits(_Collapsed, _State);
|
||||
var Collapsed = new _Collapsed();
|
||||
exports.Collapsed = Collapsed;
|
||||
|
||||
function _Expanded () {
|
||||
this.name = 'Expanded';
|
||||
}
|
||||
inherits(_Expanded, _State);
|
||||
var Expanded = new _Expanded();
|
||||
exports.Expanded = Expanded;
|
||||
|
||||
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller, msg_type, $event) {
|
||||
|
||||
controller.scope.$parent.vm.rightPanelIsExpanded = false;
|
||||
controller.changeState(Collapsed);
|
||||
controller.handle_message(msg_type, $event);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Collapsed'];
|
||||
|
||||
|
||||
|
||||
_Collapsed.prototype.onDetailsPanel = function (controller, msg_type, $event) {
|
||||
|
||||
controller.scope.$parent.vm.rightPanelIsExpanded = true;
|
||||
controller.changeState(Expanded);
|
||||
controller.handle_message(msg_type, $event);
|
||||
|
||||
};
|
||||
_Collapsed.prototype.onDetailsPanel.transitions = ['Expanded'];
|
||||
|
||||
|
||||
|
||||
_Expanded.prototype.onDetailsPanelClose = function (controller, msg_type, $event) {
|
||||
|
||||
controller.scope.$parent.vm.rightPanelIsExpanded = false;
|
||||
controller.scope.$parent.vm.keyPanelExpanded = false;
|
||||
controller.changeState(Collapsed);
|
||||
controller.handle_message(msg_type, $event);
|
||||
};
|
||||
_Expanded.prototype.onDetailsPanelClose.transitions = ['Collapsed'];
|
||||
47
awx/ui/client/src/network-ui/extract.js
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
var YAML = require('yamljs');
|
||||
|
||||
function Iterator(o){
|
||||
var k=Object.keys(o);
|
||||
return {
|
||||
next:function(){
|
||||
return k.shift();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
var implementation = require(myArgs[0]);
|
||||
var states = [];
|
||||
var transitions = [];
|
||||
var data = {states: states,
|
||||
transitions: transitions};
|
||||
|
||||
var state_iter = Iterator(implementation);
|
||||
var transition_iter = null;
|
||||
var next_state = state_iter.next();
|
||||
var next_transition = null;
|
||||
var state = null;
|
||||
var transition = null;
|
||||
var i = 0;
|
||||
while(next_state !== undefined) {
|
||||
state = implementation[next_state];
|
||||
transition_iter = Iterator(state.constructor.prototype);
|
||||
next_transition = transition_iter.next();
|
||||
while (next_transition !== undefined) {
|
||||
transition = state.constructor.prototype[next_transition];
|
||||
if (transition.transitions !== undefined) {
|
||||
for (i = 0; i < transition.transitions.length; i++) {
|
||||
transitions.push({from_state: next_state,
|
||||
to_state:transition.transitions[i],
|
||||
label:next_transition});
|
||||
}
|
||||
}
|
||||
next_transition = transition_iter.next();
|
||||
}
|
||||
states.push({label: state.name});
|
||||
next_state = state_iter.next();
|
||||
}
|
||||
|
||||
|
||||
console.log(YAML.stringify(data));
|
||||
57
awx/ui/client/src/network-ui/extract_messages.js
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
var YAML = require('yamljs');
|
||||
|
||||
function Iterator(o){
|
||||
var k=Object.keys(o);
|
||||
return {
|
||||
next:function(){
|
||||
return k.shift();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
var implementation = require(myArgs[0]);
|
||||
var messages = [];
|
||||
var data = {messages: messages};
|
||||
var message_iter = Iterator(implementation);
|
||||
var field_iter = null;
|
||||
var next_message = message_iter.next();
|
||||
var next_field = null;
|
||||
var message = null;
|
||||
var message_instance = null;
|
||||
var fields = null;
|
||||
// var field = null;
|
||||
// var i = 0;
|
||||
while(next_message !== undefined) {
|
||||
message = implementation[next_message];
|
||||
try {
|
||||
message_instance = new message();
|
||||
} catch(err) {
|
||||
next_message = message_iter.next();
|
||||
continue;
|
||||
}
|
||||
fields = [];
|
||||
field_iter = Iterator(message_instance);
|
||||
next_field = field_iter.next();
|
||||
while (next_field !== undefined) {
|
||||
fields.push(next_field);
|
||||
// field = message.constructor.prototype[next_field];
|
||||
// if (field.transitions !== undefined) {
|
||||
// for (i = 0; i < field.transitions.length; i++) {
|
||||
// transitions.push({from_message: next_message,
|
||||
// to_message:field.transitions[i],
|
||||
// label:next_field});
|
||||
// }
|
||||
// }
|
||||
next_field = field_iter.next();
|
||||
}
|
||||
if(message_instance.msg_type !== null && message_instance.msg_type !== undefined) {
|
||||
messages.push({msg_type: message_instance.msg_type,
|
||||
fields: fields});
|
||||
}
|
||||
next_message = message_iter.next();
|
||||
}
|
||||
|
||||
|
||||
console.log(YAML.stringify(data));
|
||||
90
awx/ui/client/src/network-ui/fsm.js
Normal file
@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var messages = require('./messages.js');
|
||||
|
||||
function Channel(from_controller, to_controller, tracer) {
|
||||
this.tracer = tracer;
|
||||
this.from_controller = from_controller;
|
||||
this.to_controller = to_controller;
|
||||
this.trace = false;
|
||||
}
|
||||
exports.Channel = Channel;
|
||||
|
||||
Channel.prototype.send = function(msg_type, message) {
|
||||
this.to_controller.handle_message(msg_type, message);
|
||||
};
|
||||
|
||||
function NullChannel(from_controller, tracer) {
|
||||
this.tracer = tracer;
|
||||
this.from_controller = from_controller;
|
||||
this.trace = false;
|
||||
}
|
||||
|
||||
NullChannel.prototype.send = function() {
|
||||
};
|
||||
|
||||
function FSMController (scope, name, initial_state, tracer) {
|
||||
this.scope = scope;
|
||||
this.name = name;
|
||||
this.state = initial_state;
|
||||
this.delegate_channel = new NullChannel(this, tracer);
|
||||
this.tracer = tracer;
|
||||
this.trace = true;
|
||||
this.handling_message_type = 'start';
|
||||
this.state.start(this);
|
||||
this.handling_message_type = null;
|
||||
}
|
||||
exports.FSMController = FSMController;
|
||||
|
||||
FSMController.prototype.changeState = function (state) {
|
||||
var old_handling_message_type;
|
||||
if(this.state !== null) {
|
||||
old_handling_message_type = this.handling_message_type;
|
||||
this.handling_message_type = 'end';
|
||||
this.state.end(this);
|
||||
this.handling_message_type = old_handling_message_type;
|
||||
}
|
||||
if (this.trace) {
|
||||
this.tracer.send_trace_message(new messages.FSMTrace(this.tracer.trace_order_seq(),
|
||||
this.name,
|
||||
this.state.name,
|
||||
state.name,
|
||||
this.handling_message_type));
|
||||
}
|
||||
this.state = state;
|
||||
if(state !== null) {
|
||||
old_handling_message_type = this.handling_message_type;
|
||||
this.handling_message_type = 'start';
|
||||
state.start(this);
|
||||
this.handling_message_type = old_handling_message_type;
|
||||
}
|
||||
};
|
||||
|
||||
FSMController.prototype.handle_message = function(msg_type, message) {
|
||||
|
||||
var old_handling_message_type = this.handling_message_type;
|
||||
this.handling_message_type = msg_type;
|
||||
var handler_name = 'on' + msg_type;
|
||||
if (typeof(this.state[handler_name]) !== "undefined") {
|
||||
this.state[handler_name](this, msg_type, message);
|
||||
} else {
|
||||
this.default_handler(msg_type, message);
|
||||
}
|
||||
this.handling_message_type = old_handling_message_type;
|
||||
};
|
||||
|
||||
FSMController.prototype.default_handler = function(msg_type, message) {
|
||||
this.delegate_channel.send(msg_type, message);
|
||||
};
|
||||
|
||||
function _State () {
|
||||
}
|
||||
|
||||
_State.prototype.start = function () {
|
||||
};
|
||||
|
||||
_State.prototype.end = function () {
|
||||
};
|
||||
|
||||
var State = new _State();
|
||||
exports.State = State;
|
||||
exports._State = _State;
|
||||
8
awx/ui/client/src/network-ui/host.directive.js
Normal file
@ -0,0 +1,8 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/host.partial.svg');
|
||||
|
||||
function host () {
|
||||
return { restrict: 'A', templateUrl};
|
||||
}
|
||||
exports.host = host;
|
||||
89
awx/ui/client/src/network-ui/host.partial.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
<g transform="scale(0.75)">
|
||||
<g ng-if="item.moving">
|
||||
<!--horizontal line -->
|
||||
<line x1="-150"
|
||||
y1="0"
|
||||
x2="150"
|
||||
y2="0"
|
||||
class="NetworkUI--construction">
|
||||
</line>
|
||||
<!-- end horizontal line -->
|
||||
|
||||
<!-- vertical line -->
|
||||
<line x1="0"
|
||||
y1="-150"
|
||||
x2="0"
|
||||
y2="150"
|
||||
class="NetworkUI--construction">
|
||||
</line>
|
||||
<!-- end vertical line -->
|
||||
</g>
|
||||
|
||||
<g ng-if="!debug.hidden">
|
||||
<!--horizontal line -->
|
||||
<line x1="-60"
|
||||
y1="0"
|
||||
x2="60"
|
||||
y2="0"
|
||||
class="NetworkUI--debug">
|
||||
</line>
|
||||
<!-- end horizontal line -->
|
||||
|
||||
<!-- vertical line -->
|
||||
<line x1="0"
|
||||
y1="-40"
|
||||
x2="0"
|
||||
y2="40"
|
||||
class="NetworkUI--debug">
|
||||
</line>
|
||||
<!-- end vertical line -->
|
||||
|
||||
<!-- debug rectangle -->
|
||||
<rect x="-50"
|
||||
y="-30"
|
||||
width="100"
|
||||
height="60"
|
||||
class="NetworkUI--debug">
|
||||
</rect>
|
||||
<!-- end debug rectangle -->
|
||||
</g>
|
||||
<g transform="translate(-50,-30)">
|
||||
<rect
|
||||
ry=20
|
||||
rx=20
|
||||
width=100
|
||||
height=60
|
||||
ng-attr-class="{{item.selected || item.remote_selected ? item.selected && item.remote_selected ? 'NetworkUI__host--selected-conflict' : item.selected ? 'NetworkUI__host--selected' : 'NetworkUI__host--remote-selected' : 'NetworkUI__host--background'}}">
|
||||
</rect>
|
||||
<g transform="scale(2)">
|
||||
<path
|
||||
class="NetworkUI__host"
|
||||
d="M17.8,14.7c-0.3,0-0.6,0.2-0.6,0.6c0,0.3,0.2,0.6,0.6,0.6c0.3,0,0.6-0.2,0.6-0.6S18.1,14.7,17.8,14.7z"/>
|
||||
<path
|
||||
class="NetworkUI__host"
|
||||
d="M40.8,0H9.2C4.2,0,0,4.2,0,9.2v11.5C0,25.8,4.2,30,9.2,30h31.5c5.1,0,9.2-4.2,9.2-9.2V9.2
|
||||
C50,4.2,45.8,0,40.8,0z M37.8,17.9c0,1-0.8,1.8-1.8,1.8H14c-1,0-1.8-0.8-1.8-1.8V13c0-1,0.8-1.8,1.8-1.8h22c1,0,1.8,0.8,1.8,1.8
|
||||
V17.9z"/>
|
||||
<path
|
||||
class="NetworkUI__host"
|
||||
d="M36,12.5H14c-0.3,0-0.4,0.2-0.4,0.4v4.9c0,0.3,0.2,0.4,0.4,0.4h22c0.3,0,0.4-0.2,0.4-0.4v-4.9
|
||||
C36.4,12.7,36.3,12.5,36,12.5z M17.8,17.2c-1.1,0-1.9-0.9-1.9-1.9c0-1.1,0.9-1.9,1.9-1.9s1.9,0.9,1.9,1.9S18.9,17.2,17.8,17.2z
|
||||
M28.2,17.1h-0.9c-0.3,0-0.6-0.2-0.6-0.6s0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6C28.8,16.8,28.6,17.1,28.2,17.1z M28.2,14.9
|
||||
h-0.9c-0.3,0-0.6-0.2-0.6-0.6c0-0.4,0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6C28.8,14.7,28.6,14.9,28.2,14.9z M30.9,17.1H30
|
||||
c-0.3,0-0.6-0.2-0.6-0.6s0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6C31.5,16.8,31.3,17.1,30.9,17.1z M30.9,14.9H30
|
||||
c-0.3,0-0.6-0.2-0.6-0.6c0-0.4,0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6S31.3,14.9,30.9,14.9z M33.6,17.1h-0.9
|
||||
c-0.4,0-0.6-0.2-0.6-0.6s0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6C34.2,16.8,33.9,17.1,33.6,17.1z M33.6,14.9h-0.9
|
||||
c-0.4,0-0.6-0.2-0.6-0.6c0-0.4,0.2-0.6,0.6-0.6h0.9c0.4,0,0.6,0.2,0.6,0.6C34.2,14.7,33.9,14.9,33.6,14.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<text ng-attr-class="{{item.selected && ! item.edit_label ? 'NetworkUI__host-text--selected' : 'NetworkUI--hidden'}}"
|
||||
filter="url(#background)"
|
||||
text-anchor="middle"
|
||||
x="0"
|
||||
y="50"> {{item.name}}
|
||||
</text>
|
||||
<text class="NetworkUI__host-text" filter="url(#background)" text-anchor="middle" x="0" y="50">{{item.name}}{{item.edit_label?'_':''}}</text>
|
||||
</g>
|
||||
</g>
|
||||
87
awx/ui/client/src/network-ui/hotkeys.fsm.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.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 === 'r' && ($event.ctrlKey || $event.metaKey)) {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
if ($event.key === 'd') {
|
||||
scope.debug.hidden = !scope.debug.hidden;
|
||||
return;
|
||||
}
|
||||
if ($event.key === 'i') {
|
||||
scope.hide_interfaces = !scope.hide_interfaces;
|
||||
return;
|
||||
}
|
||||
if($event.keyCode === 27){
|
||||
// 27 is the escape key
|
||||
scope.reset_fsm_state();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event.key === '0') {
|
||||
scope.jump_to_animation(0, 0, 1.0);
|
||||
}
|
||||
|
||||
controller.delegate_channel.send(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'];
|
||||
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/inventory_toolbox.partial.svg');
|
||||
|
||||
function inventoryToolbox () {
|
||||
return { restrict: 'A', templateUrl};
|
||||
}
|
||||
exports.inventoryToolbox = inventoryToolbox;
|
||||
95
awx/ui/client/src/network-ui/inventory_toolbox.partial.svg
Normal file
@ -0,0 +1,95 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
<g ng-if="toolbox.enabled">
|
||||
<rect class="NetworkUI__toolbox"
|
||||
ng-attr-x="{{toolbox.x}}"
|
||||
ng-attr-y="{{toolbox.y}}"
|
||||
ng-attr-width="{{toolbox.width}}"
|
||||
ng-attr-height="{{toolbox.height}}"
|
||||
rx=5></rect>
|
||||
<text
|
||||
class="NetworkUI__toolbox--title"
|
||||
ng-attr-transform="translate({{toolbox.title_coordinates.x}},{{toolbox.title_coordinates.y}})">
|
||||
{{toolbox.name}}
|
||||
</text>
|
||||
|
||||
<g clip-path="url(#inventory-toolbox-clip-path)">
|
||||
<g ng-attr-transform="translate({{toolbox.x}}, {{toolbox.y+20}})">
|
||||
<g ng-repeat="item in toolbox.items track by $index"
|
||||
ng-attr-transform="translate({{toolbox.width/2}},{{$index * toolbox.spacing + toolbox.spacing/2 + toolbox.scroll_offset}})"
|
||||
ng-attr-class="{{item.type}}"
|
||||
ng-switch on="item.type">
|
||||
<g ng-switch-when="router"><!-- begin router -->
|
||||
<g awx-net-router></g>
|
||||
</g> <!-- end router -->
|
||||
|
||||
<g ng-switch-when="switch"> <!-- begin switch -->
|
||||
<g awx-net-switch> </g>
|
||||
</g> <!-- end switch -->
|
||||
|
||||
<g ng-switch-when="host"> <!-- begin host -->
|
||||
<g awx-net-host> </g>
|
||||
|
||||
</g> <!-- end host -->
|
||||
|
||||
<g ng-switch-when="site"> <!-- begin site -->
|
||||
<g awx-net-site-icon> </g>
|
||||
</g> <!-- end site -->
|
||||
|
||||
<g ng-switch-when="rack"> <!-- begin rack -->
|
||||
<g awx-net-rack-icon> </g>
|
||||
</g> <!-- end rack -->
|
||||
|
||||
<g ng-switch-when="process"> <!-- begin site -->
|
||||
<g awx-net-process> </g>
|
||||
</g> <!-- end site -->
|
||||
|
||||
<g ng-switch-default> <!-- begin default -->
|
||||
<g awx-net-default></g>
|
||||
</g> <!-- end default -->
|
||||
</g> <!-- end devices -->
|
||||
</g> <!-- end transform -->
|
||||
</g> <!-- end clip path -->
|
||||
<rect class="NetworkUI__toolbox-bezel"
|
||||
ng-attr-x="{{toolbox.x}}"
|
||||
ng-attr-y="{{toolbox.y}}"
|
||||
ng-attr-width="{{toolbox.width}}"
|
||||
ng-attr-height="{{toolbox.height}}"
|
||||
rx=5></rect>
|
||||
<!-- selected item-->
|
||||
<g ng-if="toolbox.selected_item != null">
|
||||
<g ng-repeat="item in [toolbox.selected_item]"
|
||||
ng-attr-transform="translate({{item.x}}, {{item.y}})"
|
||||
ng-attr-class="{{item.type}}"
|
||||
ng-switch on="item.type">
|
||||
<g ng-switch-when="router"><!-- begin router -->
|
||||
<g awx-net-router></g>
|
||||
</g> <!-- end router -->
|
||||
|
||||
<g ng-switch-when="switch"> <!-- begin switch -->
|
||||
<g awx-net-switch> </g>
|
||||
</g> <!-- end switch -->
|
||||
|
||||
<g ng-switch-when="host"> <!-- begin host -->
|
||||
<g awx-net-host> </g>
|
||||
|
||||
</g> <!-- end host -->
|
||||
|
||||
<g ng-switch-when="site"> <!-- begin site -->
|
||||
<g awx-net-site-icon> </g>
|
||||
</g> <!-- end site -->
|
||||
|
||||
<g ng-switch-when="rack"> <!-- begin rack -->
|
||||
<g awx-net-rack-icon> </g>
|
||||
</g> <!-- end rack -->
|
||||
|
||||
<g ng-switch-when="process"> <!-- begin site -->
|
||||
<g awx-net-process> </g>
|
||||
</g> <!-- end site -->
|
||||
|
||||
<g ng-switch-default> <!-- begin default -->
|
||||
<g awx-net-default></g>
|
||||
</g> <!-- end default -->
|
||||
</g> <!-- end selected item -->
|
||||
</g> <!-- ng-if -->
|
||||
</g> <!-- ng-if toolbox.enabled -->
|
||||
</g> <!-- ng-if !hide_menus -->
|
||||
93
awx/ui/client/src/network-ui/keybindings.fsm.js
Normal file
@ -0,0 +1,93 @@
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
|
||||
function _Disabled () {
|
||||
this.name = 'Disabled';
|
||||
}
|
||||
inherits(_Disabled, _State);
|
||||
var Disabled = new _Disabled();
|
||||
exports.Disabled = Disabled;
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Enabled () {
|
||||
this.name = 'Enabled';
|
||||
}
|
||||
inherits(_Enabled, _State);
|
||||
var Enabled = new _Enabled();
|
||||
exports.Enabled = Enabled;
|
||||
|
||||
|
||||
|
||||
|
||||
_Disabled.prototype.onBindDocument = function (controller) {
|
||||
|
||||
$(document).bind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Enabled);
|
||||
|
||||
};
|
||||
_Disabled.prototype.onBindDocument.transitions = ['Enabled'];
|
||||
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
$(document).bind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Enabled);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Enabled'];
|
||||
|
||||
|
||||
|
||||
_Enabled.prototype.onUnbindDocument = function (controller) {
|
||||
|
||||
$(document).unbind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Disabled);
|
||||
|
||||
};
|
||||
_Enabled.prototype.onUnbindDocument.transitions = ['Disabled'];
|
||||
|
||||
_Disabled.prototype.onDetailsPanelClose = function (controller) {
|
||||
|
||||
$(document).bind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Enabled);
|
||||
|
||||
};
|
||||
_Disabled.prototype.onDetailsPanelClose.transitions = ['Enabled'];
|
||||
|
||||
_Disabled.prototype.onSearchDropdownClose = function (controller) {
|
||||
|
||||
$(document).bind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Enabled);
|
||||
|
||||
};
|
||||
_Disabled.prototype.onSearchDropdownClose.transitions = ['Enabled'];
|
||||
|
||||
|
||||
|
||||
_Enabled.prototype.onDetailsPanel = function (controller) {
|
||||
|
||||
$(document).unbind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Disabled);
|
||||
|
||||
};
|
||||
_Enabled.prototype.onDetailsPanel.transitions = ['Disabled'];
|
||||
|
||||
_Enabled.prototype.onSearchDropdown = function (controller) {
|
||||
|
||||
$(document).unbind("keydown", controller.scope.onKeyDown);
|
||||
controller.changeState(Disabled);
|
||||
|
||||
};
|
||||
_Enabled.prototype.onSearchDropdown.transitions = ['Disabled'];
|
||||
8
awx/ui/client/src/network-ui/link.directive.js
Normal file
@ -0,0 +1,8 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
const templateUrl = require('~network-ui/link.partial.svg');
|
||||
|
||||
function link () {
|
||||
return { restrict: 'A', templateUrl};
|
||||
}
|
||||
exports.link = link;
|
||||
128
awx/ui/client/src/network-ui/link.partial.svg
Normal file
@ -0,0 +1,128 @@
|
||||
<!-- Copyright (c) 2017 Red Hat, Inc. -->
|
||||
<line ng-attr-x1="{{link.from_device.x}}"
|
||||
ng-attr-y1="{{link.from_device.y}}"
|
||||
ng-attr-x2="{{link.to_device !== null ? link.to_device.x : scaledX}}"
|
||||
ng-attr-y2="{{link.to_device !== null ? link.to_device.y : scaledY}}"
|
||||
ng-attr-class="{{link.selected && 'NetworkUI__link--selected' || 'NetworkUI--hidden'}}"/>
|
||||
<line ng-attr-x1="{{link.from_device.x}}"
|
||||
ng-attr-y1="{{link.from_device.y}}"
|
||||
ng-attr-x2="{{link.to_device !== null ? link.to_device.x : scaledX}}"
|
||||
ng-attr-y2="{{link.to_device !== null ? link.to_device.y : scaledY}}"
|
||||
class="{{link.status === null ? 'NetworkUI__link' : link.status ? 'NetworkUI__link--link-pass' : 'NetworkUI__link--link-fail'}}"/>
|
||||
<g ng-if="!debug.hidden && current_scale > 0.5">
|
||||
<line ng-if="link.to_device !== null && link.plength(scaledX, scaledY) < 100"
|
||||
ng-attr-x1="{{link.pDistanceLine(scaledX, scaledY).x2}}"
|
||||
ng-attr-y1="{{link.pDistanceLine(scaledX, scaledY).y2}}"
|
||||
ng-attr-x2="{{scaledX}}"
|
||||
ng-attr-y2="{{scaledY}}"
|
||||
ng-attr-class="NetworkUI__link--debug" />
|
||||
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
|
||||
{{link.to_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{link.length()/2}}, 0)">
|
||||
<circle ng-attr-cx="0"
|
||||
ng-attr-cy="0"
|
||||
r=10
|
||||
class="NetworkUI__circle-debug" ></circle>
|
||||
</g>
|
||||
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
|
||||
{{link.to_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{link.length()/2}}, 0)">
|
||||
<line x1="0" y1=-20 x2=0 y2=20 class="NetworkUI__link--debug"/>
|
||||
</g>
|
||||
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
|
||||
{{link.to_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{link.to_device.size}}, 0)">
|
||||
<circle ng-attr-cx="0"
|
||||
ng-attr-cy="0"
|
||||
r=10
|
||||
class="NetworkUI__circle-debug" ></circle>
|
||||
</g>
|
||||
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.from_device.x}},
|
||||
{{link.from_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{-link.from_device.size}}, 0)">
|
||||
<circle ng-attr-cx="0"
|
||||
ng-attr-cy="0"
|
||||
r=10
|
||||
class="NetworkUI__circle-debug" ></circle>
|
||||
</g>
|
||||
</g>
|
||||
<g ng-if="link.to_device !== null" ng-attr-transform="translate({{link.to_device.x}},
|
||||
{{link.to_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{link.to_device.size}}, 0)
|
||||
rotate(180)
|
||||
translate(-19, -9)
|
||||
">
|
||||
</g>
|
||||
|
||||
<g ng-if="(!hide_interfaces && selected_links.length === 0 && selected_interfaces.length === 0) || link.selected || link.to_interface.selected || link.from_interface.selected">
|
||||
<g ng-if="current_scale > 1.0 && link.to_device !== null"
|
||||
ng-attr-transform="translate({{link.from_device.x}},
|
||||
{{link.from_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{-link.length()/2}}, 0)
|
||||
rotate({{-link.slope()}})
|
||||
translate(0, -5)
|
||||
">
|
||||
<text ng-attr-class="{{link.selected && ! link.edit_label ? 'NetworkUI__interface-text--selected' : 'NetworkUI--hidden'}}"
|
||||
filter="url(#background)"
|
||||
text-anchor="middle"
|
||||
font-size="8"
|
||||
x="0"
|
||||
y="0"> {{link.name}}</text>
|
||||
<text class="NetworkUI__link-text" filter="url(#background)" text-anchor="middle" x="0" y="0">{{link.name}}{{link.edit_label?'_':''}}</text>
|
||||
</g>
|
||||
|
||||
<g ng-if="current_scale > 1.0 && link.to_device !== null"
|
||||
ng-attr-transform="translate({{link.from_device.x}},
|
||||
{{link.from_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{-link.from_interface.dot_d - 25}}, 0)
|
||||
rotate({{-link.slope()}})
|
||||
">
|
||||
<text ng-attr-class="{{link.from_interface.selected && ! link.from_interface.edit_label ? 'NetworkUI__interface-text--selected' : 'NetworkUI--hidden'}}"
|
||||
filter="url(#background)"
|
||||
text-anchor="middle"
|
||||
font-size="8"
|
||||
x="0"
|
||||
y="0"> {{link.from_interface.name}}</text>
|
||||
<text class="NetworkUI__interface-text" filter="url(#background)" text-anchor="middle" x="0" y="0">{{link.from_interface.name}}{{link.from_interface.edit_label ?'_':''}}</text>
|
||||
</g>
|
||||
|
||||
<g ng-if="current_scale > 1.0 && link.to_device !== null"
|
||||
ng-attr-transform="translate({{link.from_device.x}},
|
||||
{{link.from_device.y}})
|
||||
rotate({{link.slope()}})
|
||||
translate({{-link.length() + link.to_interface.dot_d + 25}}, 0)
|
||||
rotate({{-link.slope()}})
|
||||
">
|
||||
<text ng-attr-class="{{link.to_interface.selected && ! link.to_interface.edit_label ? 'NetworkUI__interface-text--selected' : 'NetworkUI--hidden'}}"
|
||||
filter="url(#background)"
|
||||
text-anchor="middle"
|
||||
x="0"
|
||||
y="0"> {{link.to_interface.name}}</text>
|
||||
<text class="NetworkUI__interface-text" filter="url(#background)" text-anchor="middle" x="0" y="0">{{link.to_interface.name}}{{link.to_interface.edit_label?'_':''}}</text>
|
||||
</g>
|
||||
<g ng-if="current_scale > 1.0 && link.to_device !== null">
|
||||
<circle ng-attr-cx="{{link.from_interface.dot_x}}"
|
||||
ng-attr-cy="{{link.from_interface.dot_y}}"
|
||||
r=14
|
||||
ng-attr-class="{{link.from_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" ></circle>
|
||||
<circle ng-attr-cx="{{link.from_interface.dot_x}}"
|
||||
ng-attr-cy="{{link.from_interface.dot_y}}"
|
||||
r=10
|
||||
class="NetworkUI__interface" ></circle>
|
||||
<circle ng-attr-cx="{{link.to_interface.dot_x}}"
|
||||
ng-attr-cy="{{link.to_interface.dot_y}}"
|
||||
r=14
|
||||
ng-attr-class="{{link.to_interface.selected ? 'NetworkUI__interface--selected' : 'NetworkUI--hidden'}}" ></circle>
|
||||
<circle ng-attr-cx="{{link.to_interface.dot_x}}"
|
||||
ng-attr-cy="{{link.to_interface.dot_y}}"
|
||||
r=10
|
||||
class="NetworkUI__interface" ></circle>
|
||||
</g>
|
||||
</g> <!-- end hide_interfaces -->
|
||||
5
awx/ui/client/src/network-ui/main.js
Normal file
@ -0,0 +1,5 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var networkUI = require('./network.ui.app.js');
|
||||
var tower = require('./tower.app.js');
|
||||
exports.networkUI = networkUI.networkUI;
|
||||
exports.tower = tower.tower;
|
||||
240
awx/ui/client/src/network-ui/messages.js
Normal file
@ -0,0 +1,240 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
|
||||
|
||||
function serialize(message) {
|
||||
return JSON.stringify([message.constructor.name, message]);
|
||||
}
|
||||
exports.serialize = serialize;
|
||||
|
||||
function DeviceMove(sender, id, x, y, previous_x, previous_y) {
|
||||
this.msg_type = "DeviceMove";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.previous_x = previous_x;
|
||||
this.previous_y = previous_y;
|
||||
}
|
||||
exports.DeviceMove = DeviceMove;
|
||||
|
||||
function DeviceCreate(sender, id, x, y, name, type, host_id) {
|
||||
this.msg_type = "DeviceCreate";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.host_id = host_id;
|
||||
}
|
||||
exports.DeviceCreate = DeviceCreate;
|
||||
|
||||
function DeviceDestroy(sender, id, previous_x, previous_y, previous_name, previous_type, previous_host_id) {
|
||||
this.msg_type = "DeviceDestroy";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
this.previous_x = previous_x;
|
||||
this.previous_y = previous_y;
|
||||
this.previous_name = previous_name;
|
||||
this.previous_type = previous_type;
|
||||
this.previous_host_id = previous_host_id;
|
||||
}
|
||||
exports.DeviceDestroy = DeviceDestroy;
|
||||
|
||||
function DeviceSelected(sender, id) {
|
||||
this.msg_type = "DeviceSelected";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
}
|
||||
exports.DeviceSelected = DeviceSelected;
|
||||
|
||||
function DeviceUnSelected(sender, id) {
|
||||
this.msg_type = "DeviceUnSelected";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
}
|
||||
exports.DeviceUnSelected = DeviceUnSelected;
|
||||
|
||||
function InterfaceCreate(sender, device_id, id, name) {
|
||||
this.msg_type = "InterfaceCreate";
|
||||
this.sender = sender;
|
||||
this.device_id = device_id;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
exports.InterfaceCreate = InterfaceCreate;
|
||||
|
||||
function LinkCreate(sender, id, from_device_id, to_device_id, from_interface_id, to_interface_id) {
|
||||
this.msg_type = "LinkCreate";
|
||||
this.id = id;
|
||||
this.sender = sender;
|
||||
this.name = '';
|
||||
this.from_device_id = from_device_id;
|
||||
this.to_device_id = to_device_id;
|
||||
this.from_interface_id = from_interface_id;
|
||||
this.to_interface_id = to_interface_id;
|
||||
}
|
||||
exports.LinkCreate = LinkCreate;
|
||||
|
||||
function LinkDestroy(sender, id, from_device_id, to_device_id, from_interface_id, to_interface_id, name) {
|
||||
this.msg_type = "LinkDestroy";
|
||||
this.id = id;
|
||||
this.sender = sender;
|
||||
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;
|
||||
|
||||
function LinkSelected(sender, id) {
|
||||
this.msg_type = "LinkSelected";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
}
|
||||
exports.LinkSelected = LinkSelected;
|
||||
|
||||
function LinkUnSelected(sender, id) {
|
||||
this.msg_type = "LinkUnSelected";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
}
|
||||
exports.LinkUnSelected = LinkUnSelected;
|
||||
|
||||
function MultipleMessage(sender, messages) {
|
||||
this.msg_type = "MultipleMessage";
|
||||
this.sender = sender;
|
||||
this.messages = messages;
|
||||
}
|
||||
exports.MultipleMessage = MultipleMessage;
|
||||
|
||||
|
||||
function MouseEvent(sender, x, y, type, trace_id) {
|
||||
this.msg_type = "MouseEvent";
|
||||
this.sender = sender;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.type = type;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.MouseEvent = MouseEvent;
|
||||
|
||||
function MouseWheelEvent(sender, delta, deltaX, deltaY, type, metaKey, trace_id) {
|
||||
this.msg_type = "MouseWheelEvent";
|
||||
this.sender = sender;
|
||||
this.delta = delta;
|
||||
this.deltaX = deltaX;
|
||||
this.deltaY = deltaY;
|
||||
this.type = type;
|
||||
this.originalEvent = {metaKey: metaKey};
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.MouseWheelEvent = MouseWheelEvent;
|
||||
|
||||
function KeyEvent(sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey, trace_id) {
|
||||
this.msg_type = "KeyEvent";
|
||||
this.sender = sender;
|
||||
this.key = key;
|
||||
this.keyCode = keyCode;
|
||||
this.type = type;
|
||||
this.altKey = altKey;
|
||||
this.shiftKey = shiftKey;
|
||||
this.ctrlKey = ctrlKey;
|
||||
this.metaKey = metaKey;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.KeyEvent = KeyEvent;
|
||||
|
||||
function StartRecording(sender, trace_id) {
|
||||
this.msg_type = "StartRecording";
|
||||
this.sender = sender;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.StartRecording = StartRecording;
|
||||
|
||||
function StopRecording(sender, trace_id) {
|
||||
this.msg_type = "StopRecording";
|
||||
this.sender = sender;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.StopRecording = StopRecording;
|
||||
|
||||
function ViewPort(sender, scale, panX, panY, graph_width, graph_height, trace_id) {
|
||||
this.msg_type = "ViewPort";
|
||||
this.sender = sender;
|
||||
this.scale = scale;
|
||||
this.panX = panX;
|
||||
this.panY = panY;
|
||||
this.graph_width = graph_width;
|
||||
this.graph_height = graph_height;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.ViewPort = ViewPort;
|
||||
|
||||
function PasteDevice(device) {
|
||||
this.device = device;
|
||||
}
|
||||
exports.PasteDevice = PasteDevice;
|
||||
|
||||
function FSMTrace(order, fsm_name, from_state, to_state, recv_message_type) {
|
||||
this.msg_type = 'FSMTrace';
|
||||
this.order = order;
|
||||
this.sender = 0;
|
||||
this.trace_id = 0;
|
||||
this.fsm_name = fsm_name;
|
||||
this.from_state = from_state;
|
||||
this.to_state = to_state;
|
||||
this.recv_message_type = recv_message_type;
|
||||
}
|
||||
exports.FSMTrace = FSMTrace;
|
||||
|
||||
function Snapshot(sender, devices, links, inventory_toolbox, order, trace_id) {
|
||||
this.msg_type = 'Snapshot';
|
||||
this.sender = 0;
|
||||
this.devices = devices;
|
||||
this.links = links;
|
||||
this.inventory_toolbox = inventory_toolbox;
|
||||
this.order = order;
|
||||
this.trace_id = trace_id;
|
||||
}
|
||||
exports.Snapshot = Snapshot;
|
||||
|
||||
function EnableTest() {
|
||||
this.msg_type = "EnableTest";
|
||||
}
|
||||
exports.EnableTest = EnableTest;
|
||||
|
||||
function DisableTest() {
|
||||
this.msg_type = "DisableTest";
|
||||
}
|
||||
exports.DisableTest = DisableTest;
|
||||
|
||||
function StartTest() {
|
||||
this.msg_type = "StartTest";
|
||||
}
|
||||
exports.StartTest = StartTest;
|
||||
|
||||
function TestCompleted() {
|
||||
this.msg_type = "TestCompleted";
|
||||
}
|
||||
exports.TestCompleted = TestCompleted;
|
||||
|
||||
function TestResult(sender, id, name, result, date, code_under_test) {
|
||||
this.msg_type = "TestResult";
|
||||
this.sender = sender;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.result = result;
|
||||
this.date = date;
|
||||
this.code_under_test = code_under_test;
|
||||
}
|
||||
exports.TestResult = TestResult;
|
||||
|
||||
function Coverage(sender, coverage, result_id) {
|
||||
this.msg_type = "Coverage";
|
||||
this.sender = sender;
|
||||
this.coverage = coverage;
|
||||
this.result_id = result_id;
|
||||
}
|
||||
exports.Coverage = Coverage;
|
||||
38
awx/ui/client/src/network-ui/mode.fsm.js
Normal file
@ -0,0 +1,38 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
var move = require('./move.fsm.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Rack () {
|
||||
this.name = 'Rack';
|
||||
}
|
||||
inherits(_Rack, _State);
|
||||
var Rack = new _Rack();
|
||||
exports.Rack = Rack;
|
||||
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.scope.inventory_toolbox_controller.handle_message('Disable', {});
|
||||
controller.changeState(Rack);
|
||||
};
|
||||
_Start.prototype.start.transitions = ['MultiSite'];
|
||||
|
||||
|
||||
_Rack.prototype.start = function (controller) {
|
||||
controller.scope.current_mode = controller.state.name;
|
||||
controller.scope.inventory_toolbox_controller.handle_message('Enable', {});
|
||||
controller.scope.move_controller.changeState(move.Ready);
|
||||
};
|
||||
380
awx/ui/client/src/network-ui/models.js
Normal file
@ -0,0 +1,380 @@
|
||||
/* Copyright (c) 2017-2018 Red Hat, Inc. */
|
||||
var fsm = require('./fsm.js');
|
||||
var button = require('./button.fsm.js');
|
||||
var util = require('./util.js');
|
||||
var animation_fsm = require('./animation.fsm.js');
|
||||
|
||||
function Device(id, name, x, y, type, host_id) {
|
||||
this.id = id;
|
||||
this.host_id = host_id ? host_id: 0;
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.height = type === "host" ? 20 : 37.5;
|
||||
this.width = 37.5;
|
||||
this.size = 37.5;
|
||||
this.type = type;
|
||||
this.selected = false;
|
||||
this.remote_selected = false;
|
||||
this.moving = false;
|
||||
this.icon = false;
|
||||
this.tasks = [];
|
||||
this.shape = type === "router" ? "circular" : "rectangular";
|
||||
this.interface_seq = util.natural_numbers(0);
|
||||
this.interfaces = [];
|
||||
this.interfaces_by_name = {};
|
||||
this.variables = {};
|
||||
}
|
||||
exports.Device = Device;
|
||||
|
||||
Device.prototype.toJSON = function () {
|
||||
return {id: this.id,
|
||||
name: this.name,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
type: this.type,
|
||||
interfaces: this.interfaces.map(function (x) {
|
||||
return x.toJSON();
|
||||
}),
|
||||
variables: this.variables
|
||||
};
|
||||
};
|
||||
|
||||
Device.prototype.is_selected = function (x, y) {
|
||||
|
||||
return (x > this.x - this.width &&
|
||||
x < this.x + this.width &&
|
||||
y > this.y - this.height &&
|
||||
y < this.y + this.height);
|
||||
|
||||
};
|
||||
|
||||
Device.prototype.describeArc = util.describeArc;
|
||||
|
||||
|
||||
function Interface(id, name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.link = null;
|
||||
this.device = null;
|
||||
this.edit_label = false;
|
||||
this.dot_x = null;
|
||||
this.dot_y = null;
|
||||
}
|
||||
exports.Interface = Interface;
|
||||
|
||||
Interface.prototype.toJSON = function () {
|
||||
|
||||
return {id: this.id,
|
||||
name: this.name};
|
||||
};
|
||||
|
||||
Interface.prototype.is_selected = function (x, y) {
|
||||
|
||||
if (this.link === null || this.device === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var d = Math.sqrt(Math.pow(x - this.device.x, 2) + Math.pow(y - this.device.y, 2));
|
||||
return this.link.is_selected(x, y) && (d < this.dot_d + 30);
|
||||
};
|
||||
|
||||
Interface.prototype.dot_distance = function () {
|
||||
this.dot_d = Math.sqrt(Math.pow(this.device.x - this.dot_x, 2) + Math.pow(this.device.y - this.dot_y, 2));
|
||||
};
|
||||
|
||||
Interface.prototype.dot = function () {
|
||||
if (this.link === null || this.device === null) {
|
||||
return;
|
||||
}
|
||||
if (this.link.to_device === null || this.link.from_device === null) {
|
||||
return;
|
||||
}
|
||||
var p;
|
||||
if (this.device.shape === "circular") {
|
||||
|
||||
var theta = this.link.slope_rads();
|
||||
if (this.link.from_interface === this) {
|
||||
theta = theta + Math.PI;
|
||||
}
|
||||
p = {x: this.device.x - this.device.size * Math.cos(theta),
|
||||
y: this.device.y - this.device.size * Math.sin(theta)};
|
||||
this.dot_x = p.x;
|
||||
this.dot_y = p.y;
|
||||
this.dot_distance();
|
||||
return;
|
||||
}
|
||||
|
||||
var x1;
|
||||
var y1;
|
||||
var x2;
|
||||
var y2;
|
||||
var x3;
|
||||
var y3;
|
||||
var x4;
|
||||
var y4;
|
||||
var param1;
|
||||
var param2;
|
||||
|
||||
x3 = this.link.to_device.x;
|
||||
y3 = this.link.to_device.y;
|
||||
x4 = this.link.from_device.x;
|
||||
y4 = this.link.from_device.y;
|
||||
|
||||
x1 = this.device.x - this.device.width;
|
||||
y1 = this.device.y - this.device.height;
|
||||
x2 = this.device.x + this.device.width;
|
||||
y2 = this.device.y - this.device.height;
|
||||
|
||||
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
|
||||
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
|
||||
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
|
||||
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
|
||||
this.dot_x = p.x;
|
||||
this.dot_y = p.y;
|
||||
this.dot_distance();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
x1 = this.device.x - this.device.width;
|
||||
y1 = this.device.y + this.device.height;
|
||||
x2 = this.device.x + this.device.width;
|
||||
y2 = this.device.y + this.device.height;
|
||||
|
||||
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
|
||||
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
|
||||
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
|
||||
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
|
||||
this.dot_x = p.x;
|
||||
this.dot_y = p.y;
|
||||
this.dot_distance();
|
||||
return;
|
||||
}
|
||||
|
||||
x1 = this.device.x + this.device.width;
|
||||
y1 = this.device.y - this.device.height;
|
||||
x2 = this.device.x + this.device.width;
|
||||
y2 = this.device.y + this.device.height;
|
||||
|
||||
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
|
||||
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
|
||||
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
|
||||
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
|
||||
this.dot_x = p.x;
|
||||
this.dot_y = p.y;
|
||||
this.dot_distance();
|
||||
return;
|
||||
}
|
||||
|
||||
x1 = this.device.x - this.device.width;
|
||||
y1 = this.device.y - this.device.height;
|
||||
x2 = this.device.x - this.device.width;
|
||||
y2 = this.device.y + this.device.height;
|
||||
|
||||
p = util.intersection(x3, y3, x4, y4, x1, y1, x2, y2);
|
||||
param1 = util.pCase(p.x, p.y, x1, y1, x2, y2);
|
||||
param2 = util.pCase(p.x, p.y, x3, y3, x4, y4);
|
||||
if (param1 >= 0 && param1 <= 1 && param2 >= 0 && param2 <= 1) {
|
||||
this.dot_x = p.x;
|
||||
this.dot_y = p.y;
|
||||
this.dot_distance();
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function Link(id, from_device, to_device, from_interface, to_interface) {
|
||||
this.id = id;
|
||||
this.from_device = from_device;
|
||||
this.to_device = to_device;
|
||||
this.from_interface = from_interface;
|
||||
this.to_interface = to_interface;
|
||||
this.selected = false;
|
||||
this.remote_selected = false;
|
||||
this.status = null;
|
||||
this.edit_label = false;
|
||||
this.name = "";
|
||||
}
|
||||
exports.Link = Link;
|
||||
|
||||
Link.prototype.toJSON = function () {
|
||||
|
||||
return {from_device_id: this.from_device.id,
|
||||
to_device_id: this.to_device.id,
|
||||
from_interface_id: this.from_interface.id,
|
||||
to_interface_id: this.to_interface.id,
|
||||
name: this.name};
|
||||
};
|
||||
|
||||
Link.prototype.is_selected = function (x, y) {
|
||||
// Is the distance to the mouse location less than 25 if on the label side
|
||||
// or 5 on the other from the shortest line to the link?
|
||||
|
||||
if (this.to_device === null) {
|
||||
return false;
|
||||
}
|
||||
var d = util.pDistance(x,
|
||||
y,
|
||||
this.from_device.x,
|
||||
this.from_device.y,
|
||||
this.to_device.x,
|
||||
this.to_device.y);
|
||||
if (util.cross_z_pos(x,
|
||||
y,
|
||||
this.from_device.x,
|
||||
this.from_device.y,
|
||||
this.to_device.x,
|
||||
this.to_device.y)) {
|
||||
return d < 10;
|
||||
} else {
|
||||
return d < 10;
|
||||
}
|
||||
};
|
||||
|
||||
Link.prototype.slope_rads = function () {
|
||||
//Return the slope in degrees for this link.
|
||||
var x1 = this.from_device.x;
|
||||
var y1 = this.from_device.y;
|
||||
var x2 = this.to_device.x;
|
||||
var y2 = this.to_device.y;
|
||||
return Math.atan2(y2 - y1, x2 - x1);
|
||||
};
|
||||
|
||||
Link.prototype.slope = function () {
|
||||
//Return the slope in degrees for this link.
|
||||
var x1 = this.from_device.x;
|
||||
var y1 = this.from_device.y;
|
||||
var x2 = this.to_device.x;
|
||||
var y2 = this.to_device.y;
|
||||
return Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI + 180;
|
||||
};
|
||||
|
||||
Link.prototype.pDistanceLine = function (x, y) {
|
||||
|
||||
var x1 = this.from_device.x;
|
||||
var y1 = this.from_device.y;
|
||||
var x2 = this.to_device.x;
|
||||
var y2 = this.to_device.y;
|
||||
return util.pDistanceLine(x, y, x1, y1, x2, y2);
|
||||
};
|
||||
|
||||
|
||||
Link.prototype.length = function () {
|
||||
//Return the length of this link.
|
||||
var x1 = this.from_device.x;
|
||||
var y1 = this.from_device.y;
|
||||
var x2 = this.to_device.x;
|
||||
var y2 = this.to_device.y;
|
||||
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
|
||||
};
|
||||
|
||||
Link.prototype.plength = function (x, y) {
|
||||
//Return the length of this link.
|
||||
var x1 = this.from_device.x;
|
||||
var y1 = this.from_device.y;
|
||||
var x2 = this.to_device.x;
|
||||
var y2 = this.to_device.y;
|
||||
return util.pDistance(x, y, x1, y1, x2, y2);
|
||||
};
|
||||
|
||||
function ContextMenu(name, x, y, width, height, callback, enabled, buttons, tracer) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.callback = callback;
|
||||
this.is_pressed = false;
|
||||
this.mouse_over = false;
|
||||
this.enabled = false;
|
||||
this.buttons = buttons;
|
||||
this.fsm = new fsm.FSMController(this, "button_fsm", enabled ? button.Start : button.Disabled, tracer);
|
||||
}
|
||||
exports.ContextMenu = ContextMenu;
|
||||
|
||||
|
||||
ContextMenu.prototype.is_selected = function (x, y) {
|
||||
|
||||
return (x > this.x &&
|
||||
x < this.x + this.width &&
|
||||
y > this.y &&
|
||||
y < this.y + this.height);
|
||||
|
||||
};
|
||||
|
||||
function ContextMenuButton(name, x, y, width, height, callback, tracer) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.callback = callback;
|
||||
this.is_pressed = false;
|
||||
this.mouse_over = false;
|
||||
this.enabled = true;
|
||||
this.fsm = new fsm.FSMController(this, "button_fsm", button.Start, tracer);
|
||||
}
|
||||
exports.ContextMenuButton = ContextMenuButton;
|
||||
|
||||
|
||||
ContextMenuButton.prototype.is_selected = function (x, y) {
|
||||
|
||||
return (x > this.x &&
|
||||
x < this.x + this.width &&
|
||||
y > this.y &&
|
||||
y < this.y + this.height);
|
||||
|
||||
};
|
||||
|
||||
|
||||
function ToolBox(id, name, type, x, y, width, height) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.items = [];
|
||||
this.spacing = 200;
|
||||
this.scroll_offset = 0;
|
||||
this.selected_item = null;
|
||||
this.enabled = true;
|
||||
}
|
||||
exports.ToolBox = ToolBox;
|
||||
|
||||
function Test(name, event_trace, fsm_trace, pre_test_snapshot, post_test_snapshot) {
|
||||
this.name = name;
|
||||
this.event_trace = event_trace;
|
||||
this.fsm_trace = fsm_trace;
|
||||
this.pre_test_snapshot = pre_test_snapshot;
|
||||
this.post_test_snapshot = post_test_snapshot;
|
||||
}
|
||||
exports.Test = Test;
|
||||
|
||||
function TestResult(id, name, result, date, code_under_test) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.result = result;
|
||||
this.date = date;
|
||||
this.code_under_test = code_under_test;
|
||||
}
|
||||
exports.TestResult = TestResult;
|
||||
|
||||
function Animation(id, steps, data, scope, tracer, callback) {
|
||||
|
||||
this.id = id;
|
||||
this.steps = steps;
|
||||
this.active = true;
|
||||
this.frame_number_seq = util.natural_numbers(-1);
|
||||
this.frame_number = 0;
|
||||
this.data = data;
|
||||
this.data.updateZoomBoolean = data.updateZoomBoolean !== undefined ? data.updateZoomBoolean : true;
|
||||
this.callback = callback;
|
||||
this.scope = scope;
|
||||
this.interval = null;
|
||||
this.frame_delay = 17;
|
||||
this.fsm = new fsm.FSMController(this, "animation_fsm", animation_fsm.Start, tracer);
|
||||
}
|
||||
exports.Animation = Animation;
|
||||
397
awx/ui/client/src/network-ui/move.fsm.js
Normal file
@ -0,0 +1,397 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
var models = require('./models.js');
|
||||
var messages = require('./messages.js');
|
||||
var util = require('./util.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
function _Ready () {
|
||||
this.name = 'Ready';
|
||||
}
|
||||
inherits(_Ready, _State);
|
||||
var Ready = new _Ready();
|
||||
exports.Ready = Ready;
|
||||
|
||||
function _Disable () {
|
||||
this.name = 'Disable';
|
||||
}
|
||||
inherits(_Disable, _State);
|
||||
var Disable = new _Disable();
|
||||
exports.Disable = Disable;
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Selected2 () {
|
||||
this.name = 'Selected2';
|
||||
}
|
||||
inherits(_Selected2, _State);
|
||||
var Selected2 = new _Selected2();
|
||||
exports.Selected2 = Selected2;
|
||||
|
||||
function _Selected3 () {
|
||||
this.name = 'Selected3';
|
||||
}
|
||||
inherits(_Selected3, _State);
|
||||
var Selected3 = new _Selected3();
|
||||
exports.Selected3 = Selected3;
|
||||
|
||||
function _Move () {
|
||||
this.name = 'Move';
|
||||
}
|
||||
inherits(_Move, _State);
|
||||
var Move = new _Move();
|
||||
exports.Move = Move;
|
||||
|
||||
function _Selected1 () {
|
||||
this.name = 'Selected1';
|
||||
}
|
||||
inherits(_Selected1, _State);
|
||||
var Selected1 = new _Selected1();
|
||||
exports.Selected1 = Selected1;
|
||||
|
||||
function _Placing () {
|
||||
this.name = 'Placing';
|
||||
}
|
||||
inherits(_Placing, _State);
|
||||
var Placing = new _Placing();
|
||||
exports.Placing = Placing;
|
||||
|
||||
|
||||
function _ContextMenu () {
|
||||
this.name = 'ContextMenu';
|
||||
}
|
||||
inherits(_ContextMenu, _State);
|
||||
var ContextMenu = new _ContextMenu();
|
||||
exports.ContextMenu = ContextMenu;
|
||||
|
||||
|
||||
_State.prototype.onUnselectAll = function (controller, msg_type, $event) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
};
|
||||
|
||||
_Ready.prototype.onPasteDevice = function (controller, msg_type, message) {
|
||||
|
||||
var scope = controller.scope;
|
||||
var device = null;
|
||||
var remote_device = null;
|
||||
var intf = null;
|
||||
var link = null;
|
||||
var new_link = null;
|
||||
var i = 0;
|
||||
var c_messages = [];
|
||||
|
||||
scope.pressedX = scope.mouseX;
|
||||
scope.pressedY = scope.mouseY;
|
||||
scope.pressedScaledX = scope.scaledX;
|
||||
scope.pressedScaledY = scope.scaledY;
|
||||
|
||||
device = new models.Device(controller.scope.device_id_seq(),
|
||||
message.device.name,
|
||||
scope.scaledX,
|
||||
scope.scaledY,
|
||||
message.device.type,
|
||||
message.device.host_id);
|
||||
device.variables = message.device.variables;
|
||||
scope.update_links_in_vars_by_device(device.name, device.variables);
|
||||
scope.devices.push(device);
|
||||
scope.devices_by_name[message.device.name] = device;
|
||||
c_messages.push(new messages.DeviceCreate(scope.client_id,
|
||||
device.id,
|
||||
device.x,
|
||||
device.y,
|
||||
device.name,
|
||||
device.type,
|
||||
device.host_id));
|
||||
for (i=0; i < message.device.interfaces.length; i++) {
|
||||
intf = new models.Interface(message.device.interfaces[i].id, message.device.interfaces[i].name);
|
||||
device.interfaces.push(intf);
|
||||
device.interfaces_by_name[message.device.interfaces[i].name] = intf;
|
||||
intf.device = device;
|
||||
c_messages.push(new messages.InterfaceCreate(controller.scope.client_id,
|
||||
device.id,
|
||||
intf.id,
|
||||
intf.name));
|
||||
}
|
||||
if (scope.links_in_vars_by_device[device.name] !== undefined) {
|
||||
for (i=0; i < scope.links_in_vars_by_device[device.name].length; i++) {
|
||||
link = scope.links_in_vars_by_device[device.name][i];
|
||||
if (device.interfaces_by_name[link.from_interface] === undefined) {
|
||||
intf = new models.Interface(device.interface_seq(), link.from_interface);
|
||||
device.interfaces.push(intf);
|
||||
device.interfaces_by_name[link.from_interface] = intf;
|
||||
intf.device = device;
|
||||
c_messages.push(new messages.InterfaceCreate(controller.scope.client_id,
|
||||
device.id,
|
||||
intf.id,
|
||||
intf.name));
|
||||
}
|
||||
if (scope.devices_by_name[link.to_device] !== undefined) {
|
||||
remote_device = scope.devices_by_name[link.to_device];
|
||||
if (remote_device.interfaces_by_name[link.to_interface] === undefined) {
|
||||
intf = new models.Interface(remote_device.interface_seq(), link.to_interface);
|
||||
remote_device.interfaces.push(intf);
|
||||
remote_device.interfaces_by_name[link.to_interface] = intf;
|
||||
intf.device = remote_device;
|
||||
c_messages.push(new messages.InterfaceCreate(controller.scope.client_id,
|
||||
remote_device.id,
|
||||
intf.id,
|
||||
intf.name));
|
||||
}
|
||||
}
|
||||
if (scope.devices_by_name[link.to_device] === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (scope.devices_by_name[link.to_device].interfaces_by_name[link.to_interface] === undefined) {
|
||||
continue;
|
||||
}
|
||||
new_link = new models.Link(scope.link_id_seq(),
|
||||
device,
|
||||
scope.devices_by_name[link.to_device],
|
||||
device.interfaces_by_name[link.from_interface],
|
||||
scope.devices_by_name[link.to_device].interfaces_by_name[link.to_interface]);
|
||||
c_messages.push(new messages.LinkCreate(controller.scope.client_id,
|
||||
new_link.id,
|
||||
new_link.from_device.id,
|
||||
new_link.to_device.id,
|
||||
new_link.from_interface.id,
|
||||
new_link.to_interface.id));
|
||||
device.interfaces_by_name[link.from_interface].link = new_link;
|
||||
scope.devices_by_name[link.to_device].interfaces_by_name[link.to_interface].link = new_link;
|
||||
scope.links.push(new_link);
|
||||
scope.updateInterfaceDots();
|
||||
}
|
||||
}
|
||||
scope.selected_devices.push(device);
|
||||
device.selected = true;
|
||||
console.log(c_messages);
|
||||
scope.$emit('awxNet-addSearchOption', device);
|
||||
scope.send_control_message(new messages.MultipleMessage(controller.scope.client_id, c_messages));
|
||||
controller.changeState(Selected2);
|
||||
};
|
||||
_Ready.prototype.onPasteDevice.transitions = ['Selected2'];
|
||||
|
||||
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
|
||||
|
||||
var last_selected = controller.scope.select_items($event.shiftKey);
|
||||
|
||||
if (last_selected.last_selected_device !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else if (last_selected.last_selected_link !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else if (last_selected.last_selected_interface !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else {
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
}
|
||||
};
|
||||
_Ready.prototype.onMouseDown.transitions = ['Selected1'];
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
_Selected2.prototype.onMouseDown = function (controller, msg_type, $event) {
|
||||
|
||||
var last_selected = null;
|
||||
|
||||
if (controller.scope.selected_devices.length === 1) {
|
||||
var current_selected_device = controller.scope.selected_devices[0];
|
||||
var last_selected_device = controller.scope.select_items($event.shiftKey).last_selected_device;
|
||||
if (current_selected_device === last_selected_device) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller.scope.selected_links.length === 1) {
|
||||
var current_selected_link = controller.scope.selected_links[0];
|
||||
last_selected = controller.scope.select_items($event.shiftKey);
|
||||
if (current_selected_link === last_selected.last_selected_link) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller.scope.selected_interfaces.length === 1) {
|
||||
var current_selected_interface = controller.scope.selected_interfaces[0];
|
||||
last_selected = controller.scope.select_items($event.shiftKey);
|
||||
if (current_selected_interface === last_selected.last_selected_interface) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
controller.scope.first_channel.send('BindDocument', {});
|
||||
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.scope.deleteDevice();
|
||||
}
|
||||
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
};
|
||||
_Selected2.prototype.onKeyDown.transitions = ['Ready'];
|
||||
|
||||
_Selected1.prototype.onMouseMove = function (controller) {
|
||||
|
||||
controller.changeState(Move);
|
||||
|
||||
};
|
||||
_Selected1.prototype.onMouseMove.transitions = ['Move'];
|
||||
|
||||
_Selected1.prototype.onMouseUp = function (controller) {
|
||||
|
||||
if(controller.scope.$parent.vm.rightPanelIsExpanded){
|
||||
controller.scope.onDetailsContextButton();
|
||||
}
|
||||
controller.changeState(Selected2);
|
||||
|
||||
};
|
||||
_Selected1.prototype.onMouseUp.transitions = ['Selected2'];
|
||||
|
||||
_Selected1.prototype.onMouseDown = util.noop;
|
||||
|
||||
_Move.prototype.start = function (controller) {
|
||||
|
||||
var devices = controller.scope.selected_devices;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
for (i = 0; i < devices.length; i++) {
|
||||
devices[i].moving = true;
|
||||
for (j = 0; j < controller.scope.devices.length; j++) {
|
||||
if ((Math.pow(devices[i].x - controller.scope.devices[j].x, 2) +
|
||||
Math.pow(devices[i].y - controller.scope.devices[j].y, 2)) < 160000) {
|
||||
controller.scope.devices[j].moving = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_Move.prototype.end = function (controller) {
|
||||
|
||||
var devices = controller.scope.devices;
|
||||
var i = 0;
|
||||
for (i = 0; i < devices.length; i++) {
|
||||
devices[i].moving = false;
|
||||
}
|
||||
};
|
||||
|
||||
_Move.prototype.onMouseMove = function (controller) {
|
||||
|
||||
var devices = controller.scope.selected_devices;
|
||||
|
||||
var diffX = controller.scope.scaledX - controller.scope.pressedScaledX;
|
||||
var diffY = controller.scope.scaledY - controller.scope.pressedScaledY;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var previous_x, previous_y;
|
||||
for (i = 0; i < devices.length; i++) {
|
||||
previous_x = devices[i].x;
|
||||
previous_y = devices[i].y;
|
||||
devices[i].x = devices[i].x + diffX;
|
||||
devices[i].y = devices[i].y + diffY;
|
||||
for (j = 0; j < devices[i].interfaces.length; j++) {
|
||||
devices[i].interfaces[j].dot();
|
||||
if (devices[i].interfaces[j].link !== null) {
|
||||
devices[i].interfaces[j].link.to_interface.dot();
|
||||
devices[i].interfaces[j].link.from_interface.dot();
|
||||
}
|
||||
}
|
||||
controller.scope.send_control_message(new messages.DeviceMove(controller.scope.client_id,
|
||||
devices[i].id,
|
||||
devices[i].x,
|
||||
devices[i].y,
|
||||
previous_x,
|
||||
previous_y));
|
||||
}
|
||||
controller.scope.pressedScaledX = controller.scope.scaledX;
|
||||
controller.scope.pressedScaledY = controller.scope.scaledY;
|
||||
|
||||
};
|
||||
|
||||
_Move.prototype.onMouseUp = function (controller, msg_type, $event) {
|
||||
|
||||
controller.changeState(Selected1);
|
||||
controller.handle_message(msg_type, $event);
|
||||
};
|
||||
_Move.prototype.onMouseUp.transitions = ['Selected1'];
|
||||
|
||||
_Move.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Selected1);
|
||||
};
|
||||
_Move.prototype.onMouseDown.transitions = ['Selected1'];
|
||||
|
||||
_Selected3.prototype.onMouseUp = function (controller, msg_type, $event) {
|
||||
let context_menu = controller.scope.context_menus[0];
|
||||
context_menu.enabled = true;
|
||||
context_menu.x = $event.x;
|
||||
context_menu.y = $event.y;
|
||||
context_menu.buttons.forEach(function(button, index){
|
||||
button.x = $event.x;
|
||||
let menuPaddingTop = 5;
|
||||
button.y = $event.y + menuPaddingTop + (button.height * index);
|
||||
});
|
||||
|
||||
controller.changeState(ContextMenu);
|
||||
};
|
||||
_Selected3.prototype.onMouseUp.transitions = ['ContextMenu'];
|
||||
|
||||
_Selected3.prototype.onMouseMove = function (controller) {
|
||||
controller.changeState(Move);
|
||||
};
|
||||
_Selected3.prototype.onMouseMove.transitions = ['Move'];
|
||||
|
||||
_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'];
|
||||
|
||||
|
||||
_ContextMenu.prototype.end = function (controller) {
|
||||
|
||||
controller.scope.removeContextMenu();
|
||||
};
|
||||
|
||||
_ContextMenu.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Selected2);
|
||||
|
||||
};
|
||||
_ContextMenu.prototype.onMouseDown.transitions = ['Selected2'];
|
||||
|
||||
_ContextMenu.prototype.onDetailsPanel = function (controller, msg_type, $event) {
|
||||
|
||||
controller.changeState(Selected2);
|
||||
controller.handle_message(msg_type, $event);
|
||||
};
|
||||
_ContextMenu.prototype.onDetailsPanel.transitions = ['Selected2'];
|
||||
177
awx/ui/client/src/network-ui/move.readonly.fsm.js
Normal file
@ -0,0 +1,177 @@
|
||||
/* Copyright (c) 2017 Red Hat, Inc. */
|
||||
var inherits = require('inherits');
|
||||
var fsm = require('./fsm.js');
|
||||
var util = require('./util.js');
|
||||
|
||||
function _State () {
|
||||
}
|
||||
inherits(_State, fsm._State);
|
||||
|
||||
function _Ready () {
|
||||
this.name = 'Ready';
|
||||
}
|
||||
inherits(_Ready, _State);
|
||||
var Ready = new _Ready();
|
||||
exports.Ready = Ready;
|
||||
|
||||
function _Disable () {
|
||||
this.name = 'Disable';
|
||||
}
|
||||
inherits(_Disable, _State);
|
||||
var Disable = new _Disable();
|
||||
exports.Disable = Disable;
|
||||
|
||||
function _Start () {
|
||||
this.name = 'Start';
|
||||
}
|
||||
inherits(_Start, _State);
|
||||
var Start = new _Start();
|
||||
exports.Start = Start;
|
||||
|
||||
function _Selected2 () {
|
||||
this.name = 'Selected2';
|
||||
}
|
||||
inherits(_Selected2, _State);
|
||||
var Selected2 = new _Selected2();
|
||||
exports.Selected2 = Selected2;
|
||||
|
||||
function _Selected3 () {
|
||||
this.name = 'Selected3';
|
||||
}
|
||||
inherits(_Selected3, _State);
|
||||
var Selected3 = new _Selected3();
|
||||
exports.Selected3 = Selected3;
|
||||
|
||||
function _Selected1 () {
|
||||
this.name = 'Selected1';
|
||||
}
|
||||
inherits(_Selected1, _State);
|
||||
var Selected1 = new _Selected1();
|
||||
exports.Selected1 = Selected1;
|
||||
|
||||
function _ContextMenu () {
|
||||
this.name = 'ContextMenu';
|
||||
}
|
||||
inherits(_ContextMenu, _State);
|
||||
var ContextMenu = new _ContextMenu();
|
||||
exports.ContextMenu = ContextMenu;
|
||||
|
||||
|
||||
_State.prototype.onUnselectAll = function (controller, msg_type, $event) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
};
|
||||
|
||||
|
||||
_Ready.prototype.onMouseDown = function (controller, msg_type, $event) {
|
||||
|
||||
var last_selected = controller.scope.select_items($event.shiftKey);
|
||||
|
||||
if (last_selected.last_selected_device !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else if (last_selected.last_selected_link !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else if (last_selected.last_selected_interface !== null) {
|
||||
controller.changeState(Selected1);
|
||||
} else {
|
||||
controller.delegate_channel.send(msg_type, $event);
|
||||
}
|
||||
};
|
||||
_Ready.prototype.onMouseDown.transitions = ['Selected1'];
|
||||
|
||||
_Start.prototype.start = function (controller) {
|
||||
|
||||
controller.changeState(Ready);
|
||||
|
||||
};
|
||||
_Start.prototype.start.transitions = ['Ready'];
|
||||
|
||||
|
||||
_Selected2.prototype.onMouseDown = function (controller, msg_type, $event) {
|
||||
|
||||
var last_selected = null;
|
||||
|
||||
if (controller.scope.selected_devices.length === 1) {
|
||||
var current_selected_device = controller.scope.selected_devices[0];
|
||||
var last_selected_device = controller.scope.select_items($event.shiftKey).last_selected_device;
|
||||
if (current_selected_device === last_selected_device) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller.scope.selected_links.length === 1) {
|
||||
var current_selected_link = controller.scope.selected_links[0];
|
||||
last_selected = controller.scope.select_items($event.shiftKey);
|
||||
if (current_selected_link === last_selected.last_selected_link) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller.scope.selected_interfaces.length === 1) {
|
||||
var current_selected_interface = controller.scope.selected_interfaces[0];
|
||||
last_selected = controller.scope.select_items($event.shiftKey);
|
||||
if (current_selected_interface === last_selected.last_selected_interface) {
|
||||
controller.changeState(Selected3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
controller.scope.first_channel.send('BindDocument', {});
|
||||
controller.changeState(Ready);
|
||||
controller.handle_message(msg_type, $event);
|
||||
};
|
||||
_Selected2.prototype.onMouseDown.transitions = ['Ready', 'Selected3'];
|
||||
|
||||
|
||||
_Selected1.prototype.onMouseUp = function (controller) {
|
||||
|
||||
if(controller.scope.$parent.vm.rightPanelIsExpanded){
|
||||
controller.scope.onDetailsContextButton();
|
||||
}
|
||||
controller.changeState(Selected2);
|
||||
|
||||
};
|
||||
_Selected1.prototype.onMouseUp.transitions = ['Selected2'];
|
||||
|
||||
_Selected1.prototype.onMouseDown = util.noop;
|
||||
|
||||
_Selected3.prototype.onMouseUp = function (controller, msg_type, $event) {
|
||||
let context_menu = controller.scope.context_menus[0];
|
||||
context_menu.enabled = true;
|
||||
context_menu.x = $event.x;
|
||||
context_menu.y = $event.y;
|
||||
context_menu.buttons.forEach(function(button, index){
|
||||
button.x = $event.x;
|
||||
let menuPaddingTop = 5;
|
||||
button.y = $event.y + menuPaddingTop + (button.height * index);
|
||||
});
|
||||
|
||||
controller.changeState(ContextMenu);
|
||||
};
|
||||
_Selected3.prototype.onMouseUp.transitions = ['ContextMenu'];
|
||||
|
||||
_Selected3.prototype.onMouseMove = function (controller) {
|
||||
controller.changeState(Selected2);
|
||||
};
|
||||
_Selected3.prototype.onMouseMove.transitions = ['Selected2'];
|
||||
|
||||
_ContextMenu.prototype.end = function (controller) {
|
||||
|
||||
controller.scope.removeContextMenu();
|
||||
};
|
||||
|
||||
_ContextMenu.prototype.onMouseDown = function (controller) {
|
||||
|
||||
controller.changeState(Selected2);
|
||||
|
||||
};
|
||||
_ContextMenu.prototype.onMouseDown.transitions = ['Selected2'];
|
||||
|
||||
_ContextMenu.prototype.onDetailsPanel = function (controller, msg_type, $event) {
|
||||
|
||||
controller.changeState(Selected2);
|
||||
controller.handle_message(msg_type, $event);
|
||||
};
|
||||
_ContextMenu.prototype.onDetailsPanel.transitions = ['Selected2'];
|
||||