Adds devserver support

* Adds support for webpack devserver
* Enable istanbul on network UI
* Enable capture and replay tests on the network ui
* Normalize mouse wheel events
* Fix missing trailing slash on hosts API
* Add Export YAML button
This commit is contained in:
Ben Thomasson 2017-11-14 13:44:47 -05:00
parent 2713ec2dd5
commit 809eafe9a9
No known key found for this signature in database
GPG Key ID: 5818EF4CC895D5F5
9 changed files with 130 additions and 108 deletions

View File

@ -23,6 +23,7 @@ from awx.network_ui.utils import transform_dict
import dpath.util
from pprint import pformat
import os
import json
import time
# Connected to websocket.connect
@ -40,7 +41,6 @@ HISTORY_MESSAGE_IGNORE_TYPES = ['DeviceSelected',
SPACING = 200
RACK_SPACING = 50
settings.RECORDING = False
logger = logging.getLogger("awx.network_ui.consumers")
@ -430,15 +430,6 @@ class _Persistence(object):
else:
logger.warning("Unsupported message %s", message['msg_type'])
def onDeploy(self, message_value, topology_id, client_id):
DeviceGroup("workers").send({"text": json.dumps(["Deploy", topology_id, yaml_serialize_topology(topology_id)])})
def onDestroy(self, message_value, topology_id, client_id):
DeviceGroup("workers").send({"text": json.dumps(["Destroy", topology_id])})
def onDiscover(self, message_value, topology_id, client_id):
DeviceGroup("workers").send({"text": json.dumps(["Discover", topology_id, yaml_serialize_topology(topology_id)])})
def onLayout(self, message_value, topology_id, client_id):
# circular_layout(topology_id)
# grid_layout(topology_id)
@ -448,18 +439,18 @@ class _Persistence(object):
pass
def onCoverage(self, coverage, topology_id, client_id):
with open("coverage/coverage{0}.json".format(int(time.time())), "w") as f:
with open(os.path.abspath("coverage/coverage{0}.json".format(int(time.time()))), "w") as f:
f.write(json.dumps(coverage['coverage']))
def onStartRecording(self, recording, topology_id, client_id):
settings.RECORDING = True
pass
def onStopRecording(self, recording, topology_id, client_id):
settings.RECORDING = False
pass
def write_event(self, event, topology_id, client_id):
if settings.RECORDING and event.get('save', True):
with open("recording.log", "a") as f:
if event.get('save', True):
with open(os.path.abspath("recording/recording_{0}.log".format(topology_id)), "a") as f:
f.write(json.dumps(event))
f.write("\n")
@ -896,25 +887,6 @@ def ws_disconnect(message):
def console_printer(message):
print message['text'] # pragma: no cover
# Worker channel events
@channel_session
def worker_connect(message):
Group("workers").add(message.reply_channel)
@channel_session
def worker_message(message):
# Channel('console_printer').send({"text": message['text']})
pass
@channel_session
def worker_disconnect(message):
pass
# Tester channel events
@channel_session

View File

@ -16,22 +16,22 @@ class Command(BaseCommand):
parser.add_argument('topology_id', type=int)
parser.add_argument('recording')
parser.add_argument('--time-scale', dest="time_scale", default=1.0, type=float)
parser.add_argument('--delete-topology-at-start', dest="delete_topolgy", action="store_true", default=False)
parser.add_argument('--delete-topology-at-start', dest="delete_topology", action="store_true", default=False)
def handle(self, *args, **options):
print options['topology_id']
print options['recording']
topology_id = options['topology_id']
if options['delete_topolgy'] is True:
if options['delete_topology'] is True:
TopologyHistory.objects.filter(topology_id=topology_id).delete()
Device.objects.filter(topology_id=topology_id).delete()
time.scale = options.get('time_scale', 1.0)
ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id={0}".format(options['topology_id'])))
ui = MessageHandler(create_connection("ws://localhost:8013/network_ui/topology?topology_id={0}".format(options['topology_id'])))
ui.recv()
ui.recv()
ui.send('StopRecording')
ui.send('StartReplay')
if options['delete_topolgy'] is True:
if options['delete_topology'] is True:
ui.send_message(['History', []])
ui.send('Snapshot', sender=ui.client_id, devices=[], links=[])
messages = []

View File

@ -39,7 +39,7 @@ class Command(BaseCommand):
TestRedoPersistence,
TestPersistence,
TestViews,
TestWorkerWebSocket,
#TestWorkerWebSocket,
TestAnsibleWebSocket,
TestInvalidValues]
if options.get('suites'):
@ -49,7 +49,7 @@ class Command(BaseCommand):
verbosity=0 if options.get('quiet') else 2 if options.get('verbose') else 1,
buffer=options.get('buffer')).run(unittest.TestSuite(tests))
ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
ui = MessageHandler(create_connection("ws://localhost:8013/network_ui/topology?topology_id=143"))
ui.recv()
ui.recv()
ui.send('CoverageRequest')
@ -59,7 +59,7 @@ class Command(BaseCommand):
class TestViews(unittest.TestCase):
def test_index(self):
requests.get("http://localhost:8001/network_ui")
requests.get("http://localhost:8013/network_ui")
class MessageHandler(object):
@ -104,28 +104,10 @@ class MessageHandler(object):
self.ws.close()
class TestWorkerWebSocket(unittest.TestCase):
def test(self):
self.worker = MessageHandler(create_connection("ws://localhost:8001/network_ui/worker?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ui.recv()
self.ui.recv()
self.ui.send("Deploy")
self.assertTrue(self.worker.recv())
self.ui.send("Destroy")
self.assertTrue(self.worker.recv())
self.worker.send("Hi")
def tearDown(self):
self.worker.close()
self.ui.close()
class TestAnsibleWebSocket(unittest.TestCase):
def test(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/ansible?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/ansible?topology_id=143"))
self.ws.send('Facts', foo=5)
def tearDown(self):
@ -135,7 +117,7 @@ class TestAnsibleWebSocket(unittest.TestCase):
class TestPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
@ -213,7 +195,7 @@ class TestPersistence(unittest.TestCase):
class TestUndoPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
@ -305,7 +287,7 @@ class TestUndoPersistence(unittest.TestCase):
class TestRedoPersistence(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
@ -332,7 +314,7 @@ class TestRedoPersistence(unittest.TestCase):
class TestUIWebSocket(unittest.TestCase):
def test(self):
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8013/network_ui/topology?topology_id=143"))
self.ui.recv()
self.ui.recv()
self.ui.send("Hello")
@ -344,8 +326,8 @@ class TestUIWebSocket(unittest.TestCase):
class TestUI(unittest.TestCase):
def setUp(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8001/network_ui/topology?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ui = MessageHandler(create_connection("ws://localhost:8013/network_ui/topology?topology_id=143"))
self.ws.recv()
self.ws.recv()
self.ui.recv()
@ -515,22 +497,22 @@ topology_id: 143
class TestInvalidValues(unittest.TestCase):
def test_bad_topology_id1(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=0"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=0"))
self.ws.close()
def test_bad_topology_id2(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=foo"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=foo"))
self.ws.close()
def test_bad_sender(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ws.ws.send(json.dumps(['DeviceCreate', dict(sender=-1, name="TestSwitchA", x=100, y=100, type="switch", id=100)]))
self.ws.ws.send(json.dumps(['DeviceDestroy', dict(sender=-1, previous_name="TestSwitchA",
previous_x=100, previous_y=100, previous_type="switch", id=100)]))
self.ws.close()
def test_unsupported_command(self):
self.ws = MessageHandler(create_connection("ws://localhost:8001/network_ui/tester?topology_id=143"))
self.ws = MessageHandler(create_connection("ws://localhost:8013/network_ui/tester?topology_id=143"))
self.ws.recv()
self.ws.recv()
self.ws.send("NotSupported")

View File

@ -2,7 +2,6 @@
from channels.routing import route
from awx.network_ui.consumers import ws_connect, ws_message, ws_disconnect, console_printer, persistence, discovery
from awx.network_ui.consumers import ansible_connect, ansible_message, ansible_disconnect
from awx.network_ui.consumers import worker_connect, worker_message, worker_disconnect
from awx.network_ui.consumers import tester_connect, tester_message, tester_disconnect
from awx.network_ui.consumers import tables_connect, tables_message, tables_disconnect
@ -13,9 +12,6 @@ channel_routing = [
route("websocket.connect", ansible_connect, path=r"^/network_ui/ansible"),
route("websocket.receive", ansible_message, path=r"^/network_ui/ansible"),
route("websocket.disconnect", ansible_disconnect, path=r"^/network_ui/ansible"),
route("websocket.connect", worker_connect, path=r"^/network_ui/worker"),
route("websocket.receive", worker_message, path=r"^/network_ui/worker"),
route("websocket.disconnect", worker_disconnect, path=r"^/network_ui/worker"),
route("websocket.connect", tester_connect, path=r"^/network_ui/tester"),
route("websocket.receive", tester_message, path=r"^/network_ui/tester"),
route("websocket.disconnect", tester_disconnect, path=r"^/network_ui/tester"),

View File

@ -20,11 +20,11 @@ lint:
lessc:
lessc src/style.less css/style.css
istanbul:
istanbul: main
istanbul instrument --output src-instrumented src
webpack src-instrumented/main.js js/bundle-instrumented.js
webpack --config webpack-instrumented.config.js
cp index.html index-instrumented.html
sed -i "s/bundle.js/bundle-instrumented.js/g" index-instrumented.html
sed -i "s/\/bundle.js/\/bundle-instrumented.js/g" index-instrumented.html
cp vendor/*.js js/
@ -35,6 +35,8 @@ simple-server: lint main lessc
deploy: main
rsync -av src/ ../../../../awx/ui/client/src/network_ui/
deploy-instrumented: istanbul
rsync -av src-instrumented/ ../../../../awx/ui/client/src/network_ui/
extract:
mkdir -p extracted

View File

@ -22,7 +22,8 @@
"reconnectingwebsocket": "^1.0.0",
"require": "",
"webpack": "",
"titlecase": ""
"titlecase": "",
"lodash": ""
},
"devDependencies": {
"eslint": "^3.17.1",

View File

@ -1,4 +1,5 @@
/* Copyright (c) 2017 Red Hat, Inc. */
var _ = require('lodash');
var angular = require('angular');
var fsm = require('./fsm.js');
var null_fsm = require('./null.fsm.js');
@ -21,21 +22,23 @@ var messages = require('./messages.js');
var svg_crowbar = require('./svg-crowbar.js');
var ReconnectingWebSocket = require('reconnectingwebsocket');
var NetworkUIController = function($scope, $document, $location, $window, $http) {
var NetworkUIController = function($scope, $document, $location, $window, $http, $q) {
window.scope = $scope;
var i = 0;
$scope.http = $http;
$scope.api_token = '';
$scope.disconnected = true;
$scope.disconnected = false;
$scope.topology_id = $location.search().topology_id || 0;
// Create a web socket to connect to the backend server
//
$scope.inventory_id = $location.search().inventory_id || 0;
$scope.inventory_id = $location.search().inventory_id || 1;
if (!$scope.disconnected) {
$scope.control_socket = new ReconnectingWebSocket("ws://" + window.location.host + "/network_ui/topology?topology_id=" + $scope.topology_id,
$scope.control_socket = new ReconnectingWebSocket("wss://" + window.location.host + "/network_ui/topology?topology_id=" + $scope.topology_id,
null,
{debug: false, reconnectInterval: 300});
} else {
@ -43,6 +46,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
on_message: util.noop
};
}
$scope.my_location = $location.protocol() + "://" + $location.host() + ':' + $location.port();
$scope.history = [];
$scope.client_id = 0;
$scope.onMouseDownResult = "";
@ -143,23 +147,39 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
//Inventory Toolbox Setup
$scope.inventory_toolbox = new models.ToolBox(0, 'Inventory', 'device', 10, 200, 150, $scope.graph.height - 200 - 100);
if (!$scope.disconnected) {
console.log($location.protocol() + "://" + $location.host() + ':' + $location.port());
console.log($scope.my_location);
function add_host (host) {
console.log(host);
var device = new models.Device(0, host.data.name, 0, 0, host.data.type);
device.icon = true;
$scope.inventory_toolbox.items.push(device);
}
$http.get('/api/v2/inventories/' + $scope.inventory_id + '/hosts/?format=json')
.then(function(response) {
console.log(response);
.then(function(inventory) {
console.log(inventory);
console.log(inventory.headers());
var host = null;
var i = 0;
function add_host (response) {
console.log(response);
var device = new models.Device(0, response.data.name, 0, 0, response.data.type);
device.icon = true;
$scope.inventory_toolbox.items.push(device);
}
for (i=0; i<response.data.results.length;i++) {
host = response.data.results[i];
$http.get('/api/v2/hosts/'+ host.id + '/variable_data?format=json')
.then(add_host);
var httpGets = [];
for (i=0; i < inventory.data.results.length;i++) {
host = inventory.data.results[i];
console.log($location.protocol() + "://" + $location.host() + ':' + $location.port());
console.log($scope.my_location);
httpGets.push($http.get('/api/v2/hosts/'+ host.id + '/variable_data/?format=json'));
}
return httpGets;
})
.then(function(httpGets) {
console.log(httpGets);
$q.all(httpGets).then(function (results) {
var i = 0;
for (i=0; i < results.length; i++) {
add_host(results[i]);
}
console.log(['done', x]);
});
});
}
$scope.inventory_toolbox.spacing = 150;
@ -352,8 +372,24 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
// Event Handlers
$scope.normalize_mouse_event = function ($event) {
$event.x = $event.pageX;
$event.y = $event.pageY;
if ($event.pageX !== undefined) {
$event.x = $event.pageX;
}
if ($event.pageY !== undefined) {
$event.y = $event.pageY;
}
if ($event.originalEvent !== undefined) {
var originalEvent = $event.originalEvent;
if (originalEvent.wheelDelta !== undefined) {
$event.delta = $event.originalEvent.wheelDelta;
}
if (originalEvent.wheelDeltaX !== undefined) {
$event.deltaX = $event.originalEvent.wheelDeltaX;
}
if (originalEvent.wheelDeltaY !== undefined) {
$event.deltaY = $event.originalEvent.wheelDeltaY;
}
}
};
$scope.onMouseDown = function ($event) {
@ -418,9 +454,10 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
$scope.onMouseEnter = $scope.onMouseOver;
$scope.onMouseWheel = function ($event) {
var delta = $event.originalEvent.wheelDelta;
var deltaX = $event.originalEvent.wheelDeltaX;
var deltaY = $event.originalEvent.wheelDeltaY;
$scope.normalize_mouse_event($event);
var delta = $event.delta;
var deltaX = $event.deltaX;
var deltaY = $event.deltaY;
console.log([$event, delta, deltaX, deltaY]);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey));
@ -609,20 +646,24 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
$scope.group_controller.changeState(group.Ready);
};
$scope.onExportYamlButton = function (button) {
console.log(button);
$window.open('/network_ui/topology.yaml?topology_id=' + $scope.topology_id , '_blank');
};
// Buttons
$scope.buttons = [
new models.Button("DEPLOY", 10, 10, 70, 30, $scope.onDeployButton),
new models.Button("DESTROY", 90, 10, 80, 30, $scope.onDestroyButton),
new models.Button("RECORD", 180, 10, 80, 30, $scope.onRecordButton),
new models.Button("EXPORT", 270, 10, 70, 30, $scope.onExportButton),
new models.Button("DISCOVER", 350, 10, 80, 30, $scope.onDiscoverButton),
new models.Button("LAYOUT", 440, 10, 70, 30, $scope.onLayoutButton),
new models.Button("CONFIGURE", 520, 10, 90, 30, $scope.onConfigureButton)
new models.Button("DEPLOY", 10, 48, 70, 30, $scope.onDeployButton),
new models.Button("DESTROY", 90, 48, 80, 30, $scope.onDestroyButton),
new models.Button("RECORD", 180, 48, 80, 30, $scope.onRecordButton),
new models.Button("EXPORT", 270, 48, 70, 30, $scope.onExportButton),
new models.Button("DISCOVER", 350, 48, 80, 30, $scope.onDiscoverButton),
new models.Button("LAYOUT", 440, 48, 70, 30, $scope.onLayoutButton),
new models.Button("CONFIGURE", 520, 48, 90, 30, $scope.onConfigureButton),
new models.Button("EXPORT YAML", 620, 48, 120, 30, $scope.onExportYamlButton),
];
$scope.buttons = [];
var LAYERS_X = 160;
$scope.layers = [
@ -644,6 +685,8 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
true)
];
$scope.layers = [];
$scope.all_buttons = [];
$scope.all_buttons.extend($scope.buttons);
$scope.all_buttons.extend($scope.layers);

View File

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

View File

@ -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
}
}
}