diff --git a/awx/network_ui/admin.py b/awx/network_ui/admin.py index 5a371ca476..ed3fe8e577 100644 --- a/awx/network_ui/admin.py +++ b/awx/network_ui/admin.py @@ -18,6 +18,12 @@ from awx.network_ui.models import Group from awx.network_ui.models import GroupDevice +from awx.network_ui.models import DataBinding + +from awx.network_ui.models import DataType + +from awx.network_ui.models import DataSheet + class DeviceAdmin(admin.ModelAdmin): fields = ('topology', 'name', 'x', 'y', 'id', 'type', 'interface_id_seq',) @@ -37,7 +43,7 @@ admin.site.register(Link, LinkAdmin) class TopologyAdmin(admin.ModelAdmin): fields = ('name', 'scale', 'panX', 'panY', 'device_id_seq', 'link_id_seq', 'group_id_seq',) - raw_id_fields = ('device_id_seq',) + raw_id_fields = () admin.site.register(Topology, TopologyAdmin) @@ -77,7 +83,7 @@ admin.site.register(Interface, InterfaceAdmin) class GroupAdmin(admin.ModelAdmin): fields = ('id', 'name', 'x1', 'y1', 'x2', 'y2', 'topology',) - raw_id_fields = ('id', 'y1', 'x2', 'topology',) + raw_id_fields = ('topology',) admin.site.register(Group, GroupAdmin) @@ -89,3 +95,27 @@ class GroupDeviceAdmin(admin.ModelAdmin): admin.site.register(GroupDevice, GroupDeviceAdmin) + + +class DataBindingAdmin(admin.ModelAdmin): + fields = ('column', 'row', 'table', 'primary_key_id', 'field', 'data_type', 'sheet',) + raw_id_fields = ('data_type', 'sheet',) + + +admin.site.register(DataBinding, DataBindingAdmin) + + +class DataTypeAdmin(admin.ModelAdmin): + fields = ('type_name',) + raw_id_fields = () + + +admin.site.register(DataType, DataTypeAdmin) + + +class DataSheetAdmin(admin.ModelAdmin): + fields = ('name', 'topology', 'client',) + raw_id_fields = ('topology', 'client',) + + +admin.site.register(DataSheet, DataSheetAdmin) diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py index eeadc12f84..646a886ec0 100644 --- a/awx/network_ui/consumers.py +++ b/awx/network_ui/consumers.py @@ -4,6 +4,7 @@ from channels.sessions import channel_session from awx.network_ui.models import Topology, Device, Link, Client, TopologyHistory, MessageType, Interface from awx.network_ui.models import Group as DeviceGroup from awx.network_ui.models import GroupDevice as GroupDeviceMap +from awx.network_ui.models import DataSheet, DataBinding, DataType from awx.network_ui.serializers import yaml_serialize_topology import urlparse from django.db.models import Q @@ -270,7 +271,7 @@ class _Persistence(object): if handler is not None: handler(message_value, topology_id, client_id) else: - print "Unsupported message ", message_type + logger.warning("Unsupported message %s", message_type) def get_handler(self, message_type): return getattr(self, "on{0}".format(message_type), None) @@ -298,6 +299,23 @@ class _Persistence(object): def onDeviceLabelEdit(self, device, topology_id, client_id): Device.objects.filter(topology_id=topology_id, id=device['id']).update(name=device['name']) + for pk in Device.objects.filter(topology_id=topology_id, id=device['id']).values_list('pk', flat=True): + for db in DataBinding.objects.filter(primary_key_id=pk, + table="Device", + field="name").values('sheet__client_id', + 'sheet__name', + 'column', + 'row'): + message = ['TableCellEdit', dict(sender=0, + msg_type="TableCellEdit", + sheet=db['sheet__name'], + col=db['column'] + 1, + row=db['row'] + 2, + new_value=device['name'], + old_value=device['previous_name'])] + logger.info("Sending message %r", message) + Group("topology-%s-client-%s" % (topology_id, db['sheet__client_id'])).send({"text": json.dumps(message)}) + def onInterfaceLabelEdit(self, interface, topology_id, client_id): (Interface.objects @@ -376,7 +394,7 @@ class _Persistence(object): if handler is not None: handler(message, topology_id, client_id) else: - print "Unsupported message ", message['msg_type'] + 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)])}) @@ -480,7 +498,7 @@ class _UndoPersistence(object): if handler is not None: handler(message_value, topology_id, client_id) else: - print "Unsupported undo message ", message_type + logger.warnding("Unsupported undo message %s", message_type) def onSnapshot(self, snapshot, topology_id, client_id): pass @@ -541,7 +559,7 @@ class _RedoPersistence(object): if handler is not None: handler(message_value, topology_id, client_id) else: - print "Unsupported redo message ", message_type + logger.warning("Unsupported redo message %s", message_type) def onDeviceSelected(self, message_value, topology_id, client_id): 'Ignore DeviceSelected messages' @@ -574,7 +592,7 @@ class _Discovery(object): if handler is not None: handler(message_value, topology_id) else: - print "Unsupported message ", message_type + logger.warning("Unsupported message %s", message_type) def get_handler(self, message_type): return getattr(self, "on{0}".format(message_type), None) @@ -851,3 +869,174 @@ def tester_message(message): @channel_session def tester_disconnect(message): pass + +# Tables UI channel events + + +def make_sheet(data, column_headers=[]): + + sheet = [] + + n_columns = max([len(x) for x in data]) - 1 + + row_i = 0 + sheet.append([dict(value=x, editable=False) for x in list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")[0:n_columns]]) + row_i += 1 + if column_headers: + sheet.append([dict(value=row_i, editable=False)] + [dict(value=x, editable=False) for x in column_headers]) + row_i += 1 + for row in data: + sheet_row = [dict(value=row_i, editable=False)] + row_i += 1 + sheet_row.extend([dict(value=x, editable=True, col=i, row=row_i) for i, x in enumerate(row[1:])]) + sheet.append(sheet_row) + return sheet + + + +def make_bindings(sheet_id, klass, filter_q, values_list, order_by): + + values_list = ['pk'] + values_list + data = list(klass.objects.filter(**filter_q).values_list(*values_list).order_by(*order_by)) + + data_types = set() + + for row in data: + for cell in row: + data_types.add(type(cell).__name__) + + data_type_map = dict() + + logger.info(repr(data_types)) + + for dt in list(data_types): + data_type_map[dt] = DataType.objects.get_or_create(type_name=dt)[0].pk + + logger.info(repr(data_type_map)) + + bindings = [] + + for row_i, row in enumerate(data): + pk = row[0] + for col_i, cell in enumerate(row[1:]): + field = values_list[col_i + 1] + if '__' in field: + continue + logger.info("make_bindings %s %s %s %s %s %s %s", sheet_id, klass.__name__, pk, col_i, row_i, field, data_type_map[type(cell).__name__]) + bindings.append(DataBinding.objects.get_or_create(sheet_id=sheet_id, + column=col_i, + row=row_i, + table=klass.__name__, + primary_key_id=pk, + field=field, + data_type_id=data_type_map[type(cell).__name__])[0]) + return data + + + +@channel_session +def tables_connect(message): + data = urlparse.parse_qs(message.content['query_string']) + topology_id = parse_topology_id(data) + message.channel_session['topology_id'] = topology_id + client = Client() + client.save() + Group("topology-%s-client-%s" % (topology_id, client.pk)).add(message.reply_channel) + message.channel_session['client_id'] = client.pk + message.reply_channel.send({"text": json.dumps(["id", client.pk])}) + message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])}) + + device_sheet, _ = DataSheet.objects.get_or_create(topology_id=topology_id, client_id=client.pk, name="Devices") + data = make_bindings(device_sheet.pk, Device, dict(topology_id=topology_id), ['name'], ['name']) + message.reply_channel.send({"text": json.dumps(["sheet", dict(name="Devices", data=make_sheet(data, ['Device Name']))])}) + + interface_sheet, _ = DataSheet.objects.get_or_create(topology_id=topology_id, client_id=client.pk, name="Interfaces") + data = make_bindings(interface_sheet.pk, Interface, dict(device__topology_id=topology_id), ['device__name', 'name'], ['device__name', 'name']) + message.reply_channel.send({"text": json.dumps(["sheet", dict(name="Interfaces", data=make_sheet(data, ['Device Name', 'Interface Name']))])}) + + group_sheet, _ = DataSheet.objects.get_or_create(topology_id=topology_id, client_id=client.pk, name="Groups") + data = make_bindings(group_sheet.pk, DeviceGroup, dict(topology_id=topology_id), ['name'], ['name']) + message.reply_channel.send({"text": json.dumps(["sheet", dict(name="Groups", data=make_sheet(data, ['Group Name']))])}) + + +def device_label_edit(o): + d = transform_dict(dict(name='name', + id='id', + old_value='previous_name'), o.__dict__) + d['msg_type'] = 'DeviceLabelEdit' + return ['DeviceLabelEdit', d] + + +def group_label_edit(o): + d = transform_dict(dict(name='name', + id='id', + old_value='previous_name'), o.__dict__) + d['msg_type'] = 'GroupLabelEdit' + return ['GroupLabelEdit', d] + + +def interface_label_edit(o): + d = o.__dict__ + d['device_id'] = o.device.id + d = transform_dict(dict(name='name', + id='id', + device_id='device_id', + old_value='previous_name'), o.__dict__) + d['msg_type'] = 'InterfaceLabelEdit' + return ['InterfaceLabelEdit', d] + + +@channel_session +def tables_message(message): + data = json.loads(message['text']) + logger.info(data[0]) + logger.info(data[1]) + + data_type_mapping = {'unicode': unicode, + 'int': int} + + + table_mapping = {'Device': Device, + 'Interface': Interface, + 'Group': DeviceGroup} + + + transformation_mapping = {('Device', 'name'): device_label_edit, + ('Interface', 'name'): interface_label_edit, + ('Group', 'name'): group_label_edit} + + + if data[0] == "TableCellEdit": + + topology_id = message.channel_session['topology_id'] + group_channel = Group("topology-%s" % topology_id) + client_id = message.channel_session['client_id'] + data_sheet = DataSheet.objects.get(topology_id=topology_id, client_id=client_id, name=data[1]['sheet']).pk + logger.info("DataSheet %s", data_sheet) + + data_bindings = DataBinding.objects.filter(sheet_id=data_sheet, + column=data[1]['col'] - 1, + row=data[1]['row'] - 2) + + logger.info("Found %s bindings", data_bindings.count()) + logger.info(repr(data_bindings.values('table', 'data_type__type_name', 'field', 'primary_key_id'))) + + for table, data_type, field, pk in data_bindings.values_list('table', 'data_type__type_name', 'field', 'primary_key_id'): + new_value = data_type_mapping[data_type](data[1]['new_value']) + old_value = data_type_mapping[data_type](data[1]['old_value']) + logger.info("Updating %s", table_mapping[table].objects.filter(pk=pk).values()) + table_mapping[table].objects.filter(pk=pk).update(**{field: new_value}) + logger.info("Updated %s", table_mapping[table].objects.filter(pk=pk).count()) + + for o in table_mapping[table].objects.filter(pk=pk): + o.old_value = old_value + message = transformation_mapping[(table, field)](o) + message[1]['sender'] = 0 + logger.info("Sending %r", message) + group_channel.send({"text": json.dumps(message)}) + + + +@channel_session +def tables_disconnect(message): + pass diff --git a/awx/network_ui/designs/models.yml b/awx/network_ui/designs/models.yml index 3a52a4a23f..8f1442382e 100644 --- a/awx/network_ui/designs/models.yml +++ b/awx/network_ui/designs/models.yml @@ -76,8 +76,6 @@ models: type: FloatField - default: 0 name: device_id_seq - ref: Topology - ref_field: device_id_seq type: IntegerField - default: 0 name: link_id_seq @@ -154,8 +152,6 @@ models: pk: true type: AutoField - name: id - ref: Group - ref_field: id type: IntegerField - len: 200 name: name @@ -163,12 +159,8 @@ models: - name: x1 type: IntegerField - name: y1 - ref: Group - ref_field: y1 type: IntegerField - name: x2 - ref: Group - ref_field: x2 type: IntegerField - name: y2 type: IntegerField @@ -177,23 +169,78 @@ models: ref_field: topology_id type: ForeignKey name: Group - x: 907 - y: 520 + x: 407 + y: -379 - fields: - name: group_device_id pk: true type: AutoField - name: group - ref: GroupDevice - ref_field: group + ref: Group + ref_field: group_id type: ForeignKey - name: device ref: Device ref_field: device_id type: ForeignKey name: GroupDevice - x: 631 - y: 561 + x: 739 + y: -234 +- fields: + - name: data_binding_id + pk: true + type: AutoField + - name: column + type: IntegerField + - name: row + type: IntegerField + - len: 200 + name: table + type: CharField + - name: primary_key_id + type: IntegerField + - len: 200 + name: field + type: CharField + - name: data_type + ref: DataType + ref_field: data_type_id + type: ForeignKey + - name: sheet + ref: DataSheet + ref_field: data_sheet_id + type: ForeignKey + name: DataBinding + x: -515 + y: -370 +- fields: + - name: data_type_id + pk: true + type: AutoField + - len: 200 + name: type_name + type: CharField + name: DataType + x: -782 + y: -172 +- fields: + - name: data_sheet_id + pk: true + type: AutoField + - len: 200 + name: name + type: CharField + - name: topology + ref: Topology + ref_field: topology_id + type: ForeignKey + - name: client + ref: Client + ref_field: client_id + type: ForeignKey + name: DataSheet + x: -207 + y: -282 modules: [] view: panX: 213.72955551921206 diff --git a/awx/network_ui/migrations/0016_auto_20170717_1520.py b/awx/network_ui/migrations/0016_auto_20170717_1520.py new file mode 100644 index 0000000000..1e4304083e --- /dev/null +++ b/awx/network_ui/migrations/0016_auto_20170717_1520.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0015_auto_20170710_1937'), + ] + + operations = [ + migrations.CreateModel( + name='DataBinding', + fields=[ + ('data_binding_id', models.AutoField(serialize=False, verbose_name=b'DataBinding', primary_key=True)), + ('column', models.IntegerField()), + ('row', models.IntegerField()), + ('table', models.CharField(max_length=200)), + ('primary_key_id', models.IntegerField()), + ('field', models.CharField(max_length=200)), + ], + ), + migrations.CreateModel( + name='DataSheet', + fields=[ + ('data_sheet_id', models.AutoField(serialize=False, primary_key=True)), + ('name', models.CharField(max_length=200)), + ('topology', models.ForeignKey(to='network_ui.Topology')), + ], + ), + migrations.CreateModel( + name='DataType', + fields=[ + ('data_type_id', models.AutoField(serialize=False, primary_key=True)), + ('type_name', models.CharField(max_length=200)), + ], + ), + migrations.AddField( + model_name='databinding', + name='data_type', + field=models.ForeignKey(to='network_ui.DataType'), + ), + migrations.AddField( + model_name='databinding', + name='sheet', + field=models.ForeignKey(to='network_ui.DataSheet'), + ), + ] diff --git a/awx/network_ui/migrations/0017_auto_20170717_1813.py b/awx/network_ui/migrations/0017_auto_20170717_1813.py new file mode 100644 index 0000000000..ab941ef82f --- /dev/null +++ b/awx/network_ui/migrations/0017_auto_20170717_1813.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0016_auto_20170717_1520'), + ] + + operations = [ + migrations.AddField( + model_name='datasheet', + name='client', + field=models.ForeignKey(default=1, to='network_ui.Client'), + preserve_default=False, + ), + migrations.AlterField( + model_name='databinding', + name='data_binding_id', + field=models.AutoField(serialize=False, primary_key=True), + ), + migrations.AlterField( + model_name='group', + name='id', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='group', + name='x2', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='group', + name='y1', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='topology', + name='device_id_seq', + field=models.IntegerField(default=0), + ), + ] diff --git a/awx/network_ui/models.py b/awx/network_ui/models.py index 72420130fb..5f91d49358 100644 --- a/awx/network_ui/models.py +++ b/awx/network_ui/models.py @@ -34,7 +34,7 @@ class Topology(models.Model): scale = models.FloatField() panX = models.FloatField() panY = models.FloatField() - device_id_seq = models.IntegerField('Topology', default=0) + device_id_seq = models.IntegerField(default=0) link_id_seq = models.IntegerField(default=0) group_id_seq = models.IntegerField(default=0) @@ -81,11 +81,11 @@ class Interface(models.Model): class Group(models.Model): group_id = models.AutoField(primary_key=True,) - id = models.IntegerField('Group',) + id = models.IntegerField() name = models.CharField(max_length=200,) x1 = models.IntegerField() - y1 = models.IntegerField('Group',) - x2 = models.IntegerField('Group',) + y1 = models.IntegerField() + x2 = models.IntegerField() y2 = models.IntegerField() topology = models.ForeignKey('Topology',) @@ -95,3 +95,29 @@ class GroupDevice(models.Model): group_device_id = models.AutoField(primary_key=True,) group = models.ForeignKey('Group',) device = models.ForeignKey('Device',) + + +class DataBinding(models.Model): + + data_binding_id = models.AutoField(primary_key=True,) + column = models.IntegerField() + row = models.IntegerField() + table = models.CharField(max_length=200,) + primary_key_id = models.IntegerField() + field = models.CharField(max_length=200,) + data_type = models.ForeignKey('DataType',) + sheet = models.ForeignKey('DataSheet',) + + +class DataType(models.Model): + + data_type_id = models.AutoField(primary_key=True,) + type_name = models.CharField(max_length=200,) + + +class DataSheet(models.Model): + + data_sheet_id = models.AutoField(primary_key=True,) + name = models.CharField(max_length=200,) + topology = models.ForeignKey('Topology',) + client = models.ForeignKey('Client',) diff --git a/awx/network_ui/routing.py b/awx/network_ui/routing.py index c57d79d221..97091ef69d 100644 --- a/awx/network_ui/routing.py +++ b/awx/network_ui/routing.py @@ -3,6 +3,7 @@ from awx.network_ui.consumers import ws_connect, ws_message, ws_disconnect, cons 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 channel_routing = [ route("websocket.connect", ws_connect, path=r"^/network_ui/topology"), @@ -17,6 +18,9 @@ channel_routing = [ route("websocket.connect", tester_connect, path=r"^/network_ui/tester"), route("websocket.receive", tester_message, path=r"^/network_ui/tester"), route("websocket.disconnect", tester_disconnect, path=r"^/network_ui/tester"), + route("websocket.connect", tables_connect, path=r"^/network_ui/tables"), + route("websocket.receive", tables_message, path=r"^/network_ui/tables"), + route("websocket.disconnect", tables_disconnect, path=r"^/network_ui/tables"), route("console_printer", console_printer), route("persistence", persistence.handle), route("discovery", discovery.handle), diff --git a/awx/network_ui/static/network_ui/index2.html b/awx/network_ui/static/network_ui/index2.html index 0fc5f099c9..998ed3cc96 100644 --- a/awx/network_ui/static/network_ui/index2.html +++ b/awx/network_ui/static/network_ui/index2.html @@ -1,7 +1,9 @@ + + diff --git a/awx/network_ui/static/network_ui/package.json b/awx/network_ui/static/network_ui/package.json index 02379a0b65..080d9b9e41 100644 --- a/awx/network_ui/static/network_ui/package.json +++ b/awx/network_ui/static/network_ui/package.json @@ -10,17 +10,18 @@ "license": "ISC", "dependencies": { "angular": "~1.6.2", + "angular-mousewheel": "~1.0.5", "angular-ui-router": "", - "webpack": "", + "angular-xeditable": "~0.8.0", "browserify": "", + "hamsterjs": "~1.1.2", "inherits": "", - "require": "", "jshint": "", - "less": "", + "less": "^2.7.2", "mathjs": "", "reconnectingwebsocket": "^1.0.0", - "hamsterjs": "~1.1.2", - "angular-mousewheel": "~1.0.5" + "require": "", + "webpack": "" }, "devDependencies": { "eslint": "^3.17.1", diff --git a/awx/network_ui/static/network_ui/src/main.js b/awx/network_ui/static/network_ui/src/main.js index 6a37717da7..1222f6b011 100644 --- a/awx/network_ui/static/network_ui/src/main.js +++ b/awx/network_ui/static/network_ui/src/main.js @@ -1,4 +1,6 @@ var networkUI = require('./network.ui.app.js'); +var tablesUI = require('./tables.ui.app.js'); var tower = require('./tower.app.js'); exports.networkUI = networkUI.networkUI; ++exports.tablesUI = tablesUI.tablesUI; exports.tower = tower.tower; diff --git a/awx/network_ui/static/network_ui/src/messages.js b/awx/network_ui/static/network_ui/src/messages.js index 6a40b8eac5..5b9e098c4a 100644 --- a/awx/network_ui/static/network_ui/src/messages.js +++ b/awx/network_ui/static/network_ui/src/messages.js @@ -317,3 +317,14 @@ function GroupMembership(sender, id, members) { this.members = members; } exports.GroupMembership = GroupMembership; + +function TableCellEdit(sender, sheet, col, row, old_value, new_value) { + this.msg_type = "TableCellEdit"; + this.sender = sender; + this.sheet = sheet; + this.col = col; + this.row = row; + this.old_value = old_value; + this.new_value = new_value; +} +exports.TableCellEdit = TableCellEdit; diff --git a/awx/network_ui/static/network_ui/src/tables.ui.app.js b/awx/network_ui/static/network_ui/src/tables.ui.app.js new file mode 100644 index 0000000000..764dec6ea4 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/tables.ui.app.js @@ -0,0 +1,15 @@ + +//console.log = function () { }; +var angular = require('angular'); +var TablesUIController = require('./tables.ui.controller.js'); +var awxTablesUI = require('./tables.ui.directive.js'); + +var tablesUI = angular.module('tablesUI', ['xeditable']) + .controller('TablesUIController', TablesUIController.TablesUIController) + .directive('awxTablesUi', awxTablesUI.awxTablesUI) + .run(function(editableOptions) { + editableOptions.theme = 'bs3'; + editableOptions.activate = 'select'; + }); + +exports.tablesUI = tablesUI; diff --git a/awx/network_ui/static/network_ui/src/tables.ui.controller.js b/awx/network_ui/static/network_ui/src/tables.ui.controller.js new file mode 100644 index 0000000000..709c9580ba --- /dev/null +++ b/awx/network_ui/static/network_ui/src/tables.ui.controller.js @@ -0,0 +1,152 @@ + +var util = require('./util.js'); +var messages = require('./messages.js'); +var ReconnectingWebSocket = require('reconnectingwebsocket'); + +var TablesUIController = function($scope, $window, $location, $timeout) { + + $window.scope = $scope; + $scope.disconnected = false; + + $scope.topology_id = $location.search().topology_id || 0; + if (!$scope.disconnected) { + $scope.control_socket = new ReconnectingWebSocket("ws://" + window.location.host + "/network_ui/tables?topology_id=" + $scope.topology_id, + null, + {debug: false, reconnectInterval: 300}); + } else { + $scope.control_socket = { + on_message: util.noop + }; + } + + $scope.client_id = 0; + $scope.message_id_seq = util.natural_numbers(0); + + $scope.onClientId = function(data) { + $scope.client_id = data; + }; + + $scope.control_socket.onmessage = function(message) { + var type_data = JSON.parse(message.data); + var type = type_data[0]; + var data = type_data[1]; + $scope.handle_message(type, data); + $scope.$apply(); + }; + + $scope.control_socket.onopen = function() { + //Ignore + }; + + // Call onopen directly if $scope.control_socket is already open + if ($scope.control_socket.readyState === WebSocket.OPEN) { + $scope.control_socket.onopen(); + } + + $scope.send_control_message = function (message) { + var i = 0; + message.sender = $scope.client_id; + message.message_id = $scope.message_id_seq(); + if (message.constructor.name === "MultipleMessage") { + for (i=0; i < message.messages.length; i++) { + message.messages[i].message_id = $scope.message_id_seq(); + } + } + var data = messages.serialize(message); + if (!$scope.disconnected) { + $scope.control_socket.send(data); + console.log("Sent message"); + } else { + console.log(data); + } + }; + + $scope.handle_message = function(msg_type, message) { + + var handler_name = 'on' + msg_type; + if (typeof(this[handler_name]) !== "undefined") { + this[handler_name](msg_type, message); + } else { + this.default_handler(msg_type, message); + } + }; + + $scope.default_handler = function(msg_type, message) { + console.log([msg_type, message]); + }; + + + // End web socket + // + // + + + $scope.onid = function(msg_type, message) { + console.log(["Set client_id to" , message]); + $scope.client_id = message; + }; + + $scope.ontopology_id = function(msg_type, message) { + console.log(["Set topology_id to" , message]); + $scope.topology_id = message; + $location.search({topology_id: message}); + }; + + $scope.onsheet = function(msg_type, message) { + console.log("Update sheet"); + console.log(message); + $scope.data = message.data; + $scope.name = message.name; + $scope.sheets.push(message.name); + $scope.sheets_by_name[message.name] = message.data; + }; + + $scope.onTableCellEdit = function(msg_type, message) { + if (message.sender === $scope.client_id) { + return; + } + console.log(["Updating data", message.sheet, message.row, message.col, message.new_value]); + + $scope.sheets_by_name[message.sheet][message.row][message.col].value = message.new_value; + $scope.$apply(); + }; + + $scope.user = { + name: 'world' + }; + + $scope.data = []; + $scope.sheets = []; + $scope.sheets_by_name = {}; + + console.log("Tables UI started"); + + $scope.$on('$destroy', function () { + console.log("Tables UI stopping"); + }); + + $scope.updateData = function (old_data, new_data, column_index, row_index, column_name, row_name) { + console.log(['updateData', $scope.name, old_data, new_data, column_index, row_index, column_name, row_name]); + $scope.send_control_message(new messages.TableCellEdit($scope.client_id, + $scope.name, + column_index, + row_index, + old_data, + new_data)); + + $timeout(function () { + var q = document.querySelectorAll("#" + $scope.name + "_" + column_index + "_" + (row_index + 1)); + if (q.length > 0) { + q[0].click(); + } + }); + }; + + $scope.changeSheet = function(sheet) { + $scope.name = sheet; + $scope.data = $scope.sheets_by_name[sheet]; + }; +}; + +exports.TablesUIController = TablesUIController; +console.log("Tables UI loaded"); diff --git a/awx/network_ui/static/network_ui/src/tables.ui.directive.js b/awx/network_ui/static/network_ui/src/tables.ui.directive.js new file mode 100644 index 0000000000..a18c2b0dcc --- /dev/null +++ b/awx/network_ui/static/network_ui/src/tables.ui.directive.js @@ -0,0 +1,5 @@ + +function awxTablesUI () { + return { restrict: 'E', templateUrl: '/static/network_ui/widgets/tables_ui.html' }; +} +exports.awxTablesUI = awxTablesUI; diff --git a/awx/network_ui/static/network_ui/src/tower.app.js b/awx/network_ui/static/network_ui/src/tower.app.js index 44890e9805..6f648783df 100644 --- a/awx/network_ui/static/network_ui/src/tower.app.js +++ b/awx/network_ui/static/network_ui/src/tower.app.js @@ -2,7 +2,7 @@ var angular = require('angular'); var ui_router = require('angular-ui-router'); -var tower = angular.module('tower', ['networkUI', 'ui.router']); +var tower = angular.module('tower', ['tablesUI', 'networkUI', 'ui.router']); tower.config(function($stateProvider, $urlRouterProvider) { @@ -12,7 +12,7 @@ tower.config(function($stateProvider, $urlRouterProvider) { .state({ name: 'index', url: '/index', - template: 'Topology' + template: '' }); $stateProvider @@ -21,6 +21,13 @@ tower.config(function($stateProvider, $urlRouterProvider) { url: '/topology', template: "" }); + + $stateProvider + .state({ + name: 'tables', + url: '/tables', + template: "" + }); }); exports.tower = tower; diff --git a/awx/network_ui/static/network_ui/webpack.config.js b/awx/network_ui/static/network_ui/webpack.config.js index 2b1e8399da..04a5b34a5b 100644 --- a/awx/network_ui/static/network_ui/webpack.config.js +++ b/awx/network_ui/static/network_ui/webpack.config.js @@ -6,7 +6,8 @@ module.exports = { "angular-ui-router", "hamsterjs", "angular-mousewheel", - "reconnectingwebsocket"], + "reconnectingwebsocket", + "angular-xeditable"] }, output: { path: __dirname + "/js", diff --git a/awx/network_ui/static/network_ui/widgets/tables_ui.html b/awx/network_ui/static/network_ui/widgets/tables_ui.html new file mode 100644 index 0000000000..0ce85a699b --- /dev/null +++ b/awx/network_ui/static/network_ui/widgets/tables_ui.html @@ -0,0 +1,29 @@ +
+ + + + + + + + + + + + + + + + + +
+ + {{column_header.value}} +
+ {{row[0].value}} + + {{cell.value}} +
+