From: lombardofr Date: Sun, 28 Oct 2018 18:43:46 +0000 (+0100) Subject: [wip] NS instance topology view X-Git-Tag: v5.0.0~20 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F74%2F6774%2F2;p=osm%2FLW-UI.git [wip] NS instance topology view Change-Id: I69dd246298d04860414bc0ad60ec3272426d0141 Signed-off-by: lombardofr --- diff --git a/instancehandler/template/instance_list.html b/instancehandler/template/instance_list.html index de47e62..97eac57 100644 --- a/instancehandler/template/instance_list.html +++ b/instancehandler/template/instance_list.html @@ -131,41 +131,54 @@ }, "targets": 5 },{ - // "width": "10%", + "width": "20%", "render": function (data, type, row) { - return '
\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + + var template = '
' + + ' '; + }else{ + template += ' '; + } + template += ' ' + + ' ' + + ' ' + '
'; + return template; }, "targets": 5 }, @@ -203,12 +216,12 @@ }, { "render": function (data, type, row) { - return '
\n' + - ' \n' + + return '
' + + ' ' + '
'; }, "targets": 5 diff --git a/instancehandler/template/instance_topology_view.html b/instancehandler/template/instance_topology_view.html index fb67fbc..d1183f5 100644 --- a/instancehandler/template/instance_topology_view.html +++ b/instancehandler/template/instance_topology_view.html @@ -6,12 +6,11 @@ {% block head_block %} {{ block.super }} - - - - - + + + + {% endblock %} {% block title_header_big %} @@ -31,21 +30,25 @@ {% block content_body %} {{ block.super }} {% csrf_token %} - -
-
- {% block topology_toolbar %} - {{ block.super }} - {% include 'topology_toolbar_instance.html' %} - {% endblock %} -
+
+
+
+
+ + +
+
+
+
+
- - {% endblock %} {% block resource_block %} @@ -53,20 +56,15 @@ - - - - - - - - - - - - + + + + + + + + diff --git a/instancehandler/views.py b/instancehandler/views.py index 45120e2..e38e586 100644 --- a/instancehandler/views.py +++ b/instancehandler/views.py @@ -21,6 +21,7 @@ import yaml import json import logging from lib.osm.osmclient.clientv2 import Client +from lib.osm.osm_rdcl_parser import OsmParser import authosm.utils as osmutils from sf_t3d.decorators import login_required @@ -154,156 +155,33 @@ def delete(request, instance_id=None, type=None): print result return __response_handler(request, result, 'instances:list', to_redirect=True, type='ns') - +@login_required def show_topology(request, instance_id=None, type=None): user = osmutils.get_user(request) project_id = user.project_id raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',') if 'application/json' in raw_content_types: - result = {'vertices': [ - {"info": {"type": "vnf", "property": {"custom_label": ""}, - "group": []}, "id": "ping"}, - {"info": {"type": "vnf", "property": {"custom_label": ""}, - "group": []}, "id": "pong"}, - {"info": {"type": "vdu", "property": {"custom_label": ""}, - "group": ['pong']}, "id": "pong/ubuntu"}, - {"info": {"type": "vdu", "property": {"custom_label": ""}, - "group": ['ping']}, "id": "ping/ubuntu"}, - {"info": {"type": "cp", "property": {"custom_label": ""}, - "group": ['ping']}, "id": "ping/cp0"}, - {"info": {"type": "cp", "property": {"custom_label": ""}, - "group": ['ping']}, "id": "ping/cp1"}, - {"info": {"type": "cp", "property": {"custom_label": ""}, - "group": ['pong']}, "id": "pong/cp0"}, - {"info": {"type": "cp", "property": {"custom_label": ""}, - "group": ['pong']}, "id": "pong/cp1"}, - {"info": {"type": "ns_vl", "property": {"custom_label": ""}, - "group": []}, "id": "mgmt_vl"}, - ], - 'edges': [ - # {"source": "ping", "group": [], "target": "ping/cp0", "view": "Data"}, - {"source": "pong/ubuntu", "group": ['pong'], "target": "pong/cp0", "view": "vnf"}, - {"source": "ping/ubuntu", "group": ['ping'], "target": "ping/cp0", "view": "vnf"}, - {"source": "pong/ubuntu", "group": ['pong'], "target": "pong/cp1", "view": "vnf"}, - {"source": "ping/ubuntu", "group": ['ping'], "target": "ping/cp1", "view": "vnf"}, - {"source": "pong", "group": [], "target": "mgmt_vl", "view": "ns"}, - {"source": "ping", "group": [], "target": "mgmt_vl", "view": "ns"}, - ], 'graph_parameters': [], - 'model': { - "layer": { - - "ns": { - "nodes": { - "vnf": { - "addable": { - "callback": "addNode" - }, - "removable": { - "callback": "removeNode" - }, - "expands": "vnf" - }, - "ns_vl": { - "addable": { - "callback": "addNode" - }, - "removable": { - "callback": "removeNode" - } - }, - - }, - "allowed_edges": { - "ns_vl": { - "destination": { - "vnf": { - "callback": "addLink", - "direct_edge": False, - "removable": { - "callback": "removeLink" - } - } - } - }, - "vnf": { - "destination": { - "ns_vl": { - "callback": "addLink", - "direct_edge": False, - "removable": { - "callback": "removeLink" - } - }, - - } - } - - } - }, - "vnf": { - "nodes": { - "vdu": { - "addable": { - "callback": "addNode" - }, - "removable": { - "callback": "removeNode" - } - }, - "cp": { - "addable": { - "callback": "addNode" - }, - "removable": { - "callback": "removeNode" - } - }, - - }, - "allowed_edges": { - "vdu": { - "destination": { - "cp": { - "callback": "addLink", - "direct_edge": False, - "removable": { - "callback": "removeLink" - } - } - } - }, - "cp": { - "destination": { - "vdu": { - "callback": "addLink", - "direct_edge": False, - "removable": { - "callback": "removeLink" - } - } - } - } - } - }, - "name": "OSM", - "version": 1, - "nodes": { - "vnf": { - "label": "vnf" - }, - "ns_vl": { - "label": "vl" - }, - "cp": { - "label": "cp" - }, - "vdu": { - "label": "vdu" - } - }, - "description": "osm" - } - }} + client = Client() + nsr_object = {'nsr': {}, 'vnfr': {}, 'vnfd': {}} + if type == 'ns': + + nsr_resp = client.ns_get(user.get_token(), instance_id) + nsr_object['nsr'] = nsr_resp['data'] + if 'constituent-vnfr-ref' in nsr_object['nsr'] : + for vnfr_id in nsr_object['nsr']['constituent-vnfr-ref']: + vnfr_resp = client.vnf_get(user.get_token(), vnfr_id) + vnfr = vnfr_resp['data'] + nsr_object['vnfr'][vnfr['id']] = vnfr + if vnfr['vnfd-id'] not in nsr_object['vnfd']: + vnfd_resp = client.vnfd_get(user.get_token(), vnfr['vnfd-id']) + nsr_object['vnfd'][vnfr['vnfd-id']] = vnfd_resp['vnfd:vnfd-catalog']['vnfd'][0] + + + + test = OsmParser() + #print nsr_object + + result = test.nsr_to_graph(nsr_object) return __response_handler(request, result) else: result = {'type': type, 'project_id': project_id, 'instance_id': instance_id} diff --git a/lib/osm/osm_rdcl_parser.py b/lib/osm/osm_rdcl_parser.py index e9fbecd..0ba27aa 100644 --- a/lib/osm/osm_rdcl_parser.py +++ b/lib/osm/osm_rdcl_parser.py @@ -14,21 +14,254 @@ # limitations under the License. # -import json -import pyaml -import yaml -from lib.util import Util -from lib.parser import Parser + import logging -import traceback -import glob -import os +# from lib.rdcl_graph import RdclGraph +import copy logging.basicConfig(level=logging.DEBUG) log = logging.getLogger('OsmParser') -class OsmParser(Parser): - """Parser methods for osm project type +class RdclGraph(object): + """ Operates on the graph representation used for the GUI graph views """ + node_ids = [] + node_t3d_base = { + 'info': { + 'property': { + 'custom_label': '', + }, + 'type': '', + 'group': [] + } + } + + def __init__(self): + pass + + def add_link(self, source, target, view, group, graph_object, optional={}): + if (source is None or source not in self.node_ids) or (target is None or target not in self.node_ids): + return + edge_obj = { + 'source': source, + 'target': target, + 'view': view, + 'group': [group], + } + + edge_obj.update(optional) + if edge_obj not in graph_object['edges']: + graph_object['edges'].append(edge_obj) + + def add_node(self, id, type, group, positions, graph_object, optional={}): + if id is None: + return + node = next((x for x in graph_object['vertices'] if x['id'] == id), None) + if node is not None: + node['info']['group'].append(group) + else: + node = copy.deepcopy(self.node_t3d_base) + node['id'] = id + node['info']['type'] = type + if group is not None: + node['info']['group'].append(group) + if positions and id in positions['vertices'] and 'x' in positions['vertices'][id] and 'y' in \ + positions['vertices'][id]: + node['fx'] = positions['vertices'][id]['x'] + node['fy'] = positions['vertices'][id]['y'] + node['info'].update(optional) + graph_object['vertices'].append(node) + self.node_ids.append(id) + + def is_directed_edge(self, source_type=None, target_type=None, layer=None, model={}): + if source_type is None or target_type is None or layer is None: + return None + if layer in model['layer'] and 'allowed_edges' in model['layer'][layer]: + if source_type in model['layer'][layer]['allowed_edges'] and target_type in \ + model['layer'][layer]['allowed_edges'][source_type]['destination']: + edge_pro = model['layer'][layer]['allowed_edges'][source_type]['destination'][target_type] + return edge_pro['direct_edge'] if 'direct_edge' in edge_pro else False + + return None + + +class OsmParser(RdclGraph): + """ Operates on the graph representation used for the GUI graph views """ + + def nsr_to_graph(self, nsr_full): + + graph = {'vertices': [], 'edges': [], 'model': { + "layer": { + + "nsr": { + "nodes": { + "vnfr": { + "addable": {}, + "removable": {}, + "expands": "vnfr" + }, + "ns_vl": { + "addable": {}, + "removable": {} + }, + "ns_cp": { + "addable": {}, + "removable": {} + }, + + }, + "allowed_edges": { + "ns_vl": { + "destination": { + "vnfr": { + "callback": "addLink", + "direct_edge": False, + "removable": {} + } + } + }, + "vnfr": { + "destination": { + "ns_vl": { + "callback": "addLink", + "direct_edge": False, + "removable": {} + }, + + } + } + + } + }, + + "vnfr": { + "nodes": { + "vdur": {}, + "cp": {}, + "int_cp": {}, + "vnf_vl": {} + + }, + "allowed_edges": { + "vdur": { + "destination": { + "cp": { + "direct_edge": False, + }, + "int_cp": { + "direct_edge": False, + }, + "vnf_vl": { + "direct_edge": False, + } + } + }, + "cp": { + "destination": { + "vdur": { + "direct_edge": False, + } + } + }, + "int_cp": { + "destination": { + "vdur": { + "direct_edge": False, + }, + "vnf_vl": { + "direct_edge": False, + } + } + }, + "vnf_vl": { + "destination": { + "vdur": { + "direct_edge": False + } + } + } + } + }, + "name": "OSM", + "version": 1, + "description": "osm" + } + }, 'graph_parameters': {'view': {'nsr': {}, 'vnfr': {}}}} + + nsr = nsr_full['nsr'] + + graph['graph_parameters']['view']['nsr'] = {} + nsr_graph_param = graph['graph_parameters']['view']['nsr'] + nsr_graph_param['id'] = nsr['_id'] if '_id' in nsr else None + nsr_graph_param['nsdId'] = nsr['nsdId'] if 'nsdId' in nsr else None + nsr_graph_param['name-ref'] = nsr['name-ref'] if 'name-ref' in nsr else None + nsr_graph_param['operational-status'] = nsr['operational-status'] if 'operational-status' in nsr else None + nsr_graph_param['config-status'] = nsr['config-status'] if 'config-status' in nsr else None + nsr_graph_param['detailed-status'] = nsr['detailed-status'] if 'detailed-status' in nsr else None + nsr_graph_param['create-time'] = nsr['create-time'] if 'create-time' in nsr else None + nsr_graph_param['instantiate_params'] = nsr['instantiate_params'] if 'instantiate_params' in nsr else None + + map_vnf_index_to_id = {} + for vnfr_id in nsr['constituent-vnfr-ref']: + current_vnfr = nsr_full['vnfr'][vnfr_id] + + graph['graph_parameters']['view']['vnfr'][vnfr_id] = {} + vnfr_graph_param = graph['graph_parameters']['view']['vnfr'][vnfr_id] + vnfr_graph_param['id'] = vnfr_id + vnfr_graph_param['vnfd-id'] = current_vnfr['vnfd-id'] + vnfr_graph_param['vnfd-ref'] = current_vnfr['vnfd-ref'] + vnfr_graph_param['member-vnf-index-ref'] = current_vnfr['member-vnf-index-ref'] + vnfr_graph_param['vim-account-id'] = current_vnfr['vim-account-id'] + vnfr_graph_param['created-time'] = current_vnfr['created-time'] + + vnfr_label = current_vnfr['vnfd-ref'] + ':' + current_vnfr['member-vnf-index-ref'] + map_vnf_index_to_id[current_vnfr['member-vnf-index-ref']] = vnfr_id + self.add_node(vnfr_id, 'vnfr', None, None, graph, + {'property': {'custom_label': vnfr_label}, 'osm': current_vnfr}) + + for cp in current_vnfr['connection-point']: + if cp['id']: + cp_id = vnfr_label + ':' + cp['id'] + self.add_node(cp_id, 'cp', vnfr_id, None, graph, {'osm': cp}) + + for vdur in current_vnfr['vdur']: + vdur_id = vnfr_label + ':' + vdur['vdu-id-ref'] + self.add_node(vdur_id, 'vdur', vnfr_id, None, graph, {'osm': vdur}) + if current_vnfr['vnfd-id'] in nsr_full['vnfd']: + for vdu in nsr_full['vnfd'][current_vnfr['vnfd-id']]['vdu']: + if vdu['id'] == vdur['vdu-id-ref']: + if 'internal-connection-point' in vdu: + for int_cp in vdu['internal-connection-point']: + cp_id = vnfr_label + ':' + int_cp['id'] + self.add_node(cp_id, 'int_cp', vnfr_id, None, graph, {'osm': int_cp}) + for interface in vdu['interface']: + if interface['type'] == "EXTERNAL": + cp_id = vnfr_label + ':' + interface['external-connection-point-ref'] + self.add_link(cp_id, vdur_id, 'vnfr', vnfr_id, graph) + elif interface['type'] == "INTERNAL": + cp_id = vnfr_label + ':' + interface['internal-connection-point-ref'] + self.add_link(cp_id, vdur_id, 'vnfr', vnfr_id, graph) + + if current_vnfr['vnfd-id'] in nsr_full['vnfd'] and 'internal-vld' in nsr_full['vnfd'][ + current_vnfr['vnfd-id']]: + for vnfd_vld in nsr_full['vnfd'][current_vnfr['vnfd-id']]['internal-vld']: + vld_id = vnfr_label + ':' + vnfd_vld['id'] + self.add_node(vld_id, 'vnf_vl', vnfr_id, None, graph, {'osm': vnfd_vld}) + if vnfd_vld['internal-connection-point']: + for int_cp in vnfd_vld['internal-connection-point']: + int_cp_id = vnfr_label + ':' + int_cp['id-ref'] + self.add_link(vld_id, int_cp_id, 'vnfr', vnfr_id, graph) + + for ns_vld in nsr['nsd']['vld']: + self.add_node(ns_vld['id'], 'ns_vl', None, None, graph, + {'property': {'custom_label': ns_vld['name']}, 'osm': ns_vld}) + for cp_ref in ns_vld['vnfd-connection-point-ref']: + self.add_link(map_vnf_index_to_id[str(cp_ref['member-vnf-index-ref'])], ns_vld['id'], 'nsr', None, + graph) + + return graph + - """ +if __name__ == '__main__': + parser = OsmParser() + print parser.nsr_to_graph({}) diff --git a/projecthandler/template/project/osm/osm_project_descriptors.html b/projecthandler/template/project/osm/osm_project_descriptors.html index 877e7f2..83a8b38 100644 --- a/projecthandler/template/project/osm/osm_project_descriptors.html +++ b/projecthandler/template/project/osm/osm_project_descriptors.html @@ -55,7 +55,7 @@ var descr_list_url = '{% url "projects:list_descriptors" descriptor_type=descriptor_type%}'; var vim_list_url = '{% url "vims:list" %}'; var new_desc_url ="{% url 'projects:new_descriptor' descriptor_type=descriptor_type %}"; - var descriptor_type = '{{ descriptor_type }}'; + var descriptor_type = '{{ descriptor_type | safe }}'; var project_id = '{{ project_id }}'; var table; @@ -78,9 +78,7 @@ } }, "error": function (hxr, error, thrown) { - console.log(hxr) - console.log(thrown) - console.log(error); + } }, diff --git a/static/TopologyComposer/css/composer.css b/static/TopologyComposer/css/composer.css new file mode 100644 index 0000000..ac19770 --- /dev/null +++ b/static/TopologyComposer/css/composer.css @@ -0,0 +1,177 @@ + +.node_path { + opacity: 1; + stroke: #2F3550; + stroke-width: 1; +} + +.node_selected { + opacity: 1 !important; + stroke: #2F3550 !important; + stroke-width: 3 !important; +} + +.node_text { + font-family: Lucida Console; + text-anchor: middle; + user-select: none; +} + +.hidden_circle { + opacity: 0; + stroke: #FF0000 !important; + stroke-width: 2 !important; +} + +.hidden_circle:hover { + opacity: 1; + stroke: #FF0000 !important; + stroke-width: 2; +} + +.node_path:hover { + opacity: 1; + stroke: #2F3550; + stroke-width: 3; +} + +.node_path:hover text { + opacity: 0.4; + +} + +.link { + fill: none; +} + +.link path { + stroke-width: 2; + stroke: lightgray; +} + +.matted { + opacity: 0.3; +} + +.invisible { + visibility: hidden; +} + +.left-tool-bar-monitoring { + position: absolute; + top: 20px; + left: 20px; + z-index: 1; + text-align: center; + font-size: 12px; + + cursor: default; + padding: 0 6px; + line-height: 22px; + position: absolute; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.container-fluid { + min-height: 100vh !important; + background-color: white; +} + +#main { + height: 100%; + display: flex; +} + +#graph_editor_container { + position: relative; + min-height: 100vh; + border-right: 1px solid #AFAFAF; + flex: 1 1 auto; + display: flex; + align-items: center; + justify-content: center; +} + +#legenda { + position: absolute; + top: 20px; + right: 20px; + border: 1px solid #cdcdcd; + border-radius: 2px; + padding: 15px; +} + +#legenda > .node { + margin-top: 10px; + font-size: 13px; + display: flex; + align-items: center; +} + +#legenda > .node:first-child { + margin-top: 0; +} + +#legenda > .node > .icon { + width: 20px; + height: 20px; + border-radius: 1px; + margin-right: 10px; +} + +#side { + flex: 0 0 auto; + max-width: 500px; + min-width: 300px; + padding: 20px 30px; + display: flex; + flex-direction: column; +} + +.section { + font-size: 16px; + line-height: 40px; + letter-spacing: 0.4px; + border-bottom: 1px solid #ccc; + margin-bottom: 10px; + margin-top: 20px; + display: flex; + justify-content: space-between; +} + +.section > .status { + display: flex; + align-items: center; + font-size: 12px +} + +.section > .status > .indicator { + width: 8px; + height: 8px; + background-color: red; + border-radius: 50%; + display: block; + margin-right: 7px; +} + +.section > .status.active > .indicator { + background-color: green; +} + +.children td { + padding: 5px 7px; + font-size: 13px; + line-height: 15px; +} + +.children td:first-child { + padding-left: 0; + font-weight: 500; + text-align: right +} \ No newline at end of file diff --git a/static/TopologyComposer/css/d3-context-menu.css b/static/TopologyComposer/css/d3-context-menu.css new file mode 100644 index 0000000..dc47e62 --- /dev/null +++ b/static/TopologyComposer/css/d3-context-menu.css @@ -0,0 +1,78 @@ +.d3-context-menu { + position: absolute; + display: none; + background-color: #f2f2f2; + border-radius: 4px; + + font-family: Arial, sans-serif; + font-size: 14px; + min-width: 150px; + border: 1px solid #d4d4d4; + + z-index:1200; +} + +.d3-context-menu ul { + list-style-type: none; + margin: 4px 0px; + padding: 0px; + cursor: default; +} + +.d3-context-menu ul li { + padding: 4px 16px; + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; +} + +.d3-context-menu ul li:hover { + background-color: #4677f8; + color: #fefefe; +} + +/* + Header +*/ + +.d3-context-menu ul li.is-header, +.d3-context-menu ul li.is-header:hover { + background-color: #f2f2f2; + color: #444; + font-weight: bold; + font-style: italic; +} + +/* + Disabled +*/ + +.d3-context-menu ul li.is-disabled, +.d3-context-menu ul li.is-disabled:hover { + background-color: #f2f2f2; + color: #888; + cursor: not-allowed; +} + +/* + Divider +*/ + +.d3-context-menu ul li.is-divider { + padding: 0px 0px; +} + +.d3-context-menu ul li.is-divider:hover { + background-color: #f2f2f2; +} + +.d3-context-menu ul hr { + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); +} diff --git a/static/TopologyComposer/js/d3-context-menu.js b/static/TopologyComposer/js/d3-context-menu.js new file mode 100644 index 0000000..6f77042 --- /dev/null +++ b/static/TopologyComposer/js/d3-context-menu.js @@ -0,0 +1,131 @@ +(function (root, factory) { + if (typeof module === 'object' && module.exports) { + module.exports = function (d3) { + d3.contextMenu = factory(d3); + return d3.contextMenu; + }; + } else if (typeof define === 'function' && define.amd) { + try { + var d3 = require('d3'); + } catch (e) { + d3 = root.d3; + } + + d3.contextMenu = factory(d3); + define([], function () { + return d3.contextMenu; + }); + } else if (root.d3) { + root.d3.contextMenu = factory(root.d3); + } +}(this, + function (d3) { + return function (menu, opts) { + var openCallback, + closeCallback; + + if (typeof opts === 'function') { + openCallback = opts; + } else { + opts = opts || {}; + openCallback = opts.onOpen; + closeCallback = opts.onClose; + } + + // create the div element that will hold the context menu + d3.selectAll('.d3-context-menu').data([1]) + .enter() + .append('div') + .attr('class', 'd3-context-menu'); + + // close menu + d3.select('body').on('click.d3-context-menu', function () { + d3.select('.d3-context-menu').style('display', 'none'); + if (closeCallback) { + closeCallback(); + } + }); + + // this gets executed when a contextmenu event occurs + return function (data, index) { + var elm = this; + d3.selectAll('.d3-context-menu').html(''); + var list = d3.selectAll('.d3-context-menu') + .on('contextmenu', function (d) { + d3.select('.d3-context-menu').style('display', 'none'); + d3.event.preventDefault(); + d3.event.stopPropagation(); + }) + .append('ul'); + list.selectAll('li').data(typeof menu === 'function' ? menu(data) : menu).enter() + .filter(function (d) { + if(opts.type_object == 'node'){ + if (opts.edit_mode || opts.edit_mode == d.edit_mode ) { + if ((d.nodes == undefined || d.nodes.length == 0) || + (d.nodes != undefined && d.nodes.length > 0 && d.nodes.indexOf(data.info.type) > -1)) + return true + } + } + if(opts.type_object == 'link'){ + if (opts.edit_mode == d.edit_mode) { + + return true + } + } + return false; + }) + .append('li') + .attr('class', function (d) { + var ret = ''; + if (d.divider) { + ret += ' is-divider'; + } + if (d.disabled) { + ret += ' is-disabled'; + } + if (!d.action) { + ret += ' is-header'; + } + //if() + return ret; + }) + .html(function (d) { + if (d.divider) { + return '
'; + } + if (!d.title) { + console.error('No title attribute set. Check the spelling of your options.'); + } + return (typeof d.title === 'string') ? d.title : d.title(data); + }) + .on('click', function (d, i) { + if (d.disabled) return; // do nothing if disabled + if (!d.action) return; // headers have no "action" + d.action(elm, data, index); + d3.select('.d3-context-menu').style('display', 'none'); + + if (closeCallback) { + closeCallback(); + } + }); + + // the openCallback allows an action to fire before the menu is displayed + // an example usage would be closing a tooltip + if (openCallback) { + if (openCallback(data, index) === false) { + return; + } + } + + // display context menu + d3.select('.d3-context-menu') + .style('left', (d3.event.pageX - 2) + 'px') + .style('top', (d3.event.pageY - 2) + 'px') + .style('display', 'block'); + + d3.event.preventDefault(); + d3.event.stopPropagation(); + }; + }; + } +)); \ No newline at end of file diff --git a/static/TopologyComposer/js/event.js b/static/TopologyComposer/js/event.js new file mode 100755 index 0000000..6519b7d --- /dev/null +++ b/static/TopologyComposer/js/event.js @@ -0,0 +1,74 @@ +/* + Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni + Copyright 2018 EveryUP srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +if (typeof TCD3 === 'undefined') { + var TCD3 = {}; +} + +TCD3.Event = (function () { + 'use strict'; + + function Event () { + this._listeners = {}; + } + + Event.prototype.addL = function (type, listener) { + if (typeof this._listeners[type] === "undefined"){ + this._listeners[type] = []; + } + this._listeners[type].push(listener); + }; + + Event.prototype.fire = function (event, args) { + if (typeof event === "string"){ + event = { type: event }; + } + if (!event.target){ + event.target = this; + } + + if (!event.type){ + throw new Error("Event object missing 'type' property."); + } + + if (this._listeners[event.type] instanceof Array){ + var listeners = this._listeners[event.type]; + for (var i=0, len=listeners.length; i < len; i++){ + listeners[i].call(this, event, args); + } + } + }; + + /*Event.prototype.addListener = function (type, listener) { + if (this._listeners[type] instanceof Array){ + var listeners = this._listeners[type]; + for (var i=0, len=listeners.length; i < len; i++){ + if (listeners[i] === listener){ + listeners.splice(i, 1); + break; + } + } + } + + };*/ + + return Event; +}()); + +if (typeof module === 'object') { + module.exports = TCD3.Event; +} diff --git a/static/TopologyComposer/js/graph_editor.js b/static/TopologyComposer/js/graph_editor.js new file mode 100755 index 0000000..4cc8663 --- /dev/null +++ b/static/TopologyComposer/js/graph_editor.js @@ -0,0 +1,1055 @@ +/* + Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni + Copyright 2018 EveryUP srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +if (typeof TCD3 === 'undefined') { + var TCD3 = {}; +} + +TCD3.GraphEditor = (function () { + 'use strict'; + + var DEBUG = true; + var SHIFT_BUTTON = 16; + var CANC_BUTTON = 46; + var nominal_text_size = 14; + var nominal_stroke = 1.5; + var EventHandler = TCD3.Event; + + + /** + * Constructor + */ + function GraphEditor(args) { + log("Constructor"); + this.eventHandler = new EventHandler(); + this.lastKeyDown = -1; + this._selected_node = undefined; + this._selected_link = undefined; + this._edit_mode = true; + this.filter_parameters = { + node: { + type: [], + group: [], + }, + link: { + group: [], + view: [], + } + }; + this.current_view_id = ''; + // graph data initailization + this.d3_graph = { + nodes: [], + links: [], + graph_parameters: {} + + }; + + + } + + + GraphEditor.prototype.init = function (args) { + args = args || {}; + var self = this; + this.width = args.width || 1500; + this.height = args.height || 1500; + this.forceSimulationActive = false; + + var min_zoom = 0.1; + var max_zoom = 7; + this._setupBehaviorsOnEvents(); + this._setupFiltersBehaviors(args); + + this.type_property = { + "unrecognized": { + "shape": d3.symbolCircle, + "color": "#fff", + "node_label_color": "#000", + "size": 15 + }, + }; + + this.type_property_link = { + "unrecognized": { + "color": "lightgray", + }, + }; + + this.force = d3.forceSimulation() + .force("charge", d3.forceManyBody()) + .force("collide", d3.forceCollide().radius(80)) + // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) { + .force("link", d3.forceLink().distance(100).id(function (d) { + return d.id; + })) + .force("center", d3.forceCenter(this.width / 2, this.height / 2)); + + var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom]); + + var size = d3.scalePow().exponent(2) + .domain([1, 100]) + .range([8, 24]); + + this.svg = d3.select("#graph_editor_container").append("svg") + .attr("id", "graph_svg") + .attr("preserveAspectRatio", "xMinYMid") + .attr("width", this.width) + .attr("height", this.height); + + //End Arrow style + this.defs = this.svg.append("svg:defs"); + + this.defs.selectAll("marker") + .data(["unrecognized"]) // Different link/path types can be defined here + .enter().append("svg:marker") // This section adds in the arrows + .attr("id", String) + .attr("viewBox", "-5 -5 10 10") + .attr("refX", 13) //must be smarter way to calculate shift + .attr("refY", 0) + .attr("markerUnits", "userSpaceOnUse") + .attr("markerWidth", 12) + .attr("markerHeight", 12) + .attr("orient", "auto") + .append("path") + .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z") + .attr('fill', this.type_property_link['unrecognized']['color']); + + d3.select(window) + .on('keydown', function () { + log('keydown ' + d3.event.keyCode); + //d3.event.preventDefault(); + if (self.lastKeyDown !== -1) return; + self.lastKeyDown = d3.event.keyCode; + if (self.lastKeyDown === CANC_BUTTON && self._selected_node !== undefined) { + self.removeNode(self._selected_node, null, showAlert); + } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link !== undefined) { + self.removeLink(self._selected_link, null, showAlert); + } + + }) + .on('keyup', function () { + log('keyup' + self.lastKeyDown); + self.lastKeyDown = -1; + }); + var popup = this.svg.append("g") + .attr("id", "popup") + .attr("class", "popup") + .attr("opacity", "0") + .attr("transform", "translate(1 1)") + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); + + function dragstarted(d) { + //d3.select(this).raise().classed("active", true); + } + + function dragged(d) { + //console.log(JSON.stringify(d)) + d3.select(this).attr("transform", function () { + return "translate(" + d3.event.x + "," + d3.event.y + ")"; + + }) + } + + function dragended(d) { + //d3.select(this).classed("active", false); + } + + var chart = $("#graph_svg"); + this.aspect = chart.width() / chart.height(); + this.container = $("#graph_editor_container"); + $(window).on("resize", function () { + + self.width = self.container.width(); + self.height = self.container.height(); + chart.attr("width", self.container.width()); + chart.attr("height", self.container.height()); + }).trigger("resize"); + + } + + + GraphEditor.prototype.get_d3_symbol = + function (myString) { + + switch (myString) { + case "circle": + return d3.symbolCircle; + case "square": + return d3.symbolSquare; + case "diamond": + return d3.symbolDiamond; + case "triangle": + return d3.symbolTriangle; + case "star": + return d3.symbolStar; + case "cross": + return d3.symbolCross; + default: + // if the string is not recognized + return d3.symbolCross; + } + + }; + + GraphEditor.prototype.get_name_from_d3_symbol = + function (mySymbol) { + switch (mySymbol) { + case d3.symbolCircle: + return "circle"; + case d3.symbolSquare: + return "square"; + case d3.symbolDiamond: + return "diamond"; + case d3.symbolTriangle: + return "triangle"; + case d3.symbolStar: + return "star"; + case d3.symbolCross: + return "cross"; + default: + // if the string is not recognized + return "unknown"; + //return d3.symbolCircleUnknown; + } + + }; + + /** + * Start or Stop force layout + * @param {boolean} Required. Value true: start, false: stop + * @returns {boolean} + */ + GraphEditor.prototype.handleForce = function (start) { + if (start) + this.force.stop(); + this.forceSimulationActive = start; + this.node.each(function (d) { + d.fx = (start) ? null : d.x; + d.fy = (start) ? null : d.y; + }); + + if (start) + this.force.restart(); + + this.eventHandler.fire("force_status_changed_on", start); + }; + + /** + * Handle the parameters of basic filters: node type, view, group + * @param {Object} Required. + * + */ + GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) { + this.filter_parameters = (filtersParams !== undefined) ? filtersParams : this.filter_parameters; + this.current_view_id = (this.filter_parameters !== undefined && this.filter_parameters.link.view[0] !== undefined) ? this.filter_parameters.link.view[0] : this.current_view_id + this.cleanAll(); + this.refresh(); + this.startForce(); + this.force.restart(); + this._deselectAllNodes(); + this.handleForce(this.forceSimulationActive); + if (!notFireEvent) + this.eventHandler.fire("filters_changed", filtersParams); + + }; + + /** + * Add a new node to the graph. + * @param {Object} Required. An object that specifies tha data of the new node. + * @returns {boolean} + */ + GraphEditor.prototype.addNode = function (args) { + if (args.id && args.info && args.info.type) { + args.fixed = true; + this.force.stop(); + this.cleanAll(); + this.d3_graph.nodes.push(args); + this.refresh(); + this.startForce(); + this.force.restart(); + this.handleForce(this.forceSimulationActive); + return true; + } + + return false; + + }; + + /** + * Update the data properties of the node + * @param {Object} Required. An object that specifies tha data of the node. + * @returns {boolean} + */ + GraphEditor.prototype.updateDataNode = function (args) { + + }; + + /** + * Remove a node from graph and related links. + * @param {String} Required. Id of node to remove. + * @returns {boolean} + */ + GraphEditor.prototype.removeNode = function (node) { + if (node != undefined) { + var node_id = node.id; + this.d3_graph['nodes'].forEach(function (n, index, object) { + if (n.id === node_id) { + object.splice(index, 1); + } + }); + + var self = this; + var links_to_remove = []; + this.d3_graph['links'].forEach(function (l, index, object) { + if (node_id === l.source.id || node_id === l.target.id) { + links_to_remove.push(index); + } + }); + var links_removed = 0; + links_to_remove.forEach(function (l_index) { + self.d3_graph['links'].splice(l_index - links_removed, 1); + links_removed++; + }); + this.cleanAll(); + this.refresh(); + this.startForce(); + this.force.restart(); + + return true; + } + return false; + }; + + + /** + * Add a new link to graph. + * @param {Object} Required. An object that specifies tha data of the new Link. + * @returns {boolean} + */ + GraphEditor.prototype.addLink = function (link) { + console.log("addLink" + JSON.stringify(link)); + if (link.source && link.target) { + this.force.stop(); + this.cleanAll(); + this.d3_graph.links.push(link); + this.refresh(); + this.startForce(); + this.force.restart(); + return true; + } + + return false; + }; + + /** + * Remove a link from graph. + * @param {String} Required. The identifier of link to remove. + * @returns {boolean} + */ + GraphEditor.prototype.removeLink = function (link_id) { + var self = this; + if (link_id !== 'undefined') { + this.d3_graph['links'].forEach(function (l, index, object) { + if (link_id === l.index) { + object.splice(index, 1); + + self.cleanAll(); + self.refresh(); + self.startForce(); + self.force.restart(); + return true; + } + + }); + } + + return false; + }; + + + /** + * Force a refresh of GraphView + * @returns {} + */ + GraphEditor.prototype.refresh = function () { + + //log(data) + var self = this; + + this.link = this.svg + .selectAll() + .data(self.d3_graph.links + .filter(this.link_filter_cb) + ) + .enter().append("g") + .attr("class", "link cleanable") + .append("path") + .attr("class", "link") + .attr("class", "cleanable") + .style("stroke-width", nominal_stroke) + .style("stroke", function (d) { + return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color"); + }) + .attr("marker-end", function (d) { + if (!d.directed_edge) + return ''; + + var marker_url = (d.type_link) ? d.type_link : "unrecognized" + return (d.directed_edge ? "url(#" + marker_url + ")" : ''); + }); + + this.nodeContainer = this.svg + .selectAll() + .data(self.d3_graph.nodes + .filter(this.node_filter_cb)) + .enter() + .append("g") + // .attr("class", "nodosdads") + .attr("class", "node cleanable"); + + this.svg.selectAll('.node') + .data(self.d3_graph.nodes + .filter(this.node_filter_cb)) + + .filter(function (d) { + return (d.info.type === undefined) || (self._node_property_by_type(d.info.type, 'image', d) === undefined) + }) + + .append("svg:path") + .attr("d", d3.symbol() + .size(function (d) { + return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4; + }) + .type(function (d) { + // console.log(d.info.type, 'shape', self.current_view_id) + return (self._node_property_by_type(d.info.type, 'shape', d)); + }) + ) + .style("fill", function (d) { + return self._node_property_by_type(d.info.type, 'color', d); + }) + .attr("transform", function () { + return "rotate(-45)"; + + }) + .attr("stroke-width", 2.4) + + .attr("class", "node_path") + .attr("id", function (d) { + return "path_" + d.id; + }) + + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); + + var figure_node = this.svg.selectAll('.node') + .data(self.d3_graph.nodes + .filter(this.node_filter_cb)) + + .filter(function (d) { + return self._node_property_by_type(d.info.type, 'image', d) != undefined + }); + + figure_node.append("svg:image") + .attr("xlink:href", function (d) { + return self._node_property_by_type(d.info.type, 'image', d) + }) + .attr("x", function (d) { + return -self._node_property_by_type(d.info.type, 'size', d) / 2 + }) + .attr("y", function (d) { + return -self._node_property_by_type(d.info.type, 'size', d) / 2 + }) + .attr("width", function (d) { + return self._node_property_by_type(d.info.type, 'size', d) + }) + .attr("height", function (d) { + return self._node_property_by_type(d.info.type, 'size', d) + }) + .style("stroke", "black") + .style("stroke-width", "1px") + + .attr("class", "node_path") + .attr("id", function (d) { + return "path_" + d.id; + }) + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); + + figure_node.append("svg:path") + .attr("d", d3.symbol() + .size(function (d) { + return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4; + }) + .type(function (d) { + return (self.get_d3_symbol('circle')); + }) + ) + .style("fill", 'transparent') + .attr("transform", function () { + return "rotate(-45)"; + + }) + .attr("stroke-width", 2.4) + + .attr("class", "hidden_circle") + .attr("id", function (d) { + return "path_" + d.id; + }) + + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); + + + this.node = this.svg.selectAll('.node') + .data(self.d3_graph.nodes + .filter(this.node_filter_cb)).selectAll("image, path, circle"); + + + this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"]) + .on("mouseover", self.behavioursOnEvents.nodes["mouseover"]) + .on("mouseout", self.behavioursOnEvents.nodes["mouseout"]) + .on('click', self.behavioursOnEvents.nodes["click"]) + .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]); + + this.link + .on("contextmenu", self.behavioursOnEvents.links["contextmenu"]) + .on("mouseover", self.behavioursOnEvents.links["mouseover"]) + .on('click', self.behavioursOnEvents.links["click"]) + .on("mouseout", self.behavioursOnEvents.links["mouseout"]); + + + this.text = this.svg.selectAll(".node") + .data(self.d3_graph.nodes + .filter(this.node_filter_cb)) + .append("svg:text") + .attr("class", "node_text cleanable") + .attr("dy", function (d) { + return "-5"; + }) + .attr("pointer-events", "none") + .style("font-size", nominal_text_size + "px") + .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif") + .style("fill", function (d) { + return self._node_property_by_type(d.info.type, 'node_label_color', d); + }) + //.style("text-anchor", "middle") + .text(function (d) { + if(d.info && d.info.property.custom_label && d.info.property.custom_label !==''){ + return d.info.property.custom_label + } else + return d.id; + }); + + + function dragstarted(d) { + d.draggednode = true; + if (!d3.event.active) self.force.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + + } + + function dragged(d) { + d.fx = d3.event.x; + d.fy = d3.event.y; + } + + function dragended(d) { + d.draggednode = false; + if (!d3.event.active) self.force.alphaTarget(0); + if (self.forceSimulationActive) { + d.fx = null; + d.fy = null; + } else { + d.fx = d.x; + d.fy = d.y; + self.force.stop(); + self.forceSimulationActive = false; + } + } + + + }; + + /** + * Start force layout on Graph. + * + */ + GraphEditor.prototype.startForce = function () { + this.force.stop(); + var self = this; + this.force + .nodes(this.d3_graph.nodes) + .on("tick", ticked); + + + this.force + .force("link") + .links(this.d3_graph.links); + + function ticked() { + self.node.attr("cx", function (d) { + return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x)); + }) + .attr("cy", function (d) { + return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y)); + }); + + self.link.attr("d", function (d) { + var dx = d.target.x - d.source.x, + dy = d.target.y - d.source.y, + dr = Math.sqrt(dx * dx + dy * dy); + return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y; + }); + + self.node.attr("transform", function (d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + self.text.attr("transform", function (d) { + var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d)/2 +nominal_text_size; + return "translate(" + d.x + "," + label_pos_y + ")"; + }); + } + + + }; + + /** + * This method attaches an event handler. + * @param {String} Required. A String that specifies the name of the event. + * @param {Function} Required. Specifies the function to run when the event occurs. + * @returns {} + */ + GraphEditor.prototype.addListener = function (event_name, cb) { + this.eventHandler.addL(event_name, cb); + }; + + /** + * This method removes an event handler that has been attached with the addListener() method. + * @param {String} Required. A String that specifies the name of the event to remove. + * @param {Function} Required. Specifies the function to remove. + * @returns {} + */ + GraphEditor.prototype.removeListener = function (event_name, cb) { + + }; + + + GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) { + log("setNodeClass"); + var self = this; + this.svg.selectAll('.node').classed(class_name, false); + this.svg.selectAll('.node') + .classed(class_name, filter_cb); + }; + + GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) { + log("setLinkClass"); + var self = this; + this.svg.selectAll('.link').classed(class_name, false); + this.svg.selectAll('.link') + .classed(class_name, filter_cb); + }; + + GraphEditor.prototype.showNodeInfo = function (args) { + this.addLinesToPopup(args['node_info'], "Info about node selected") + this.handlePopupVisibility(true, 'right') + }; + + GraphEditor.prototype.addLinesToPopup = function (data, title) { + var self = this; + var index = 1; + var translate_y = 0; + var width_popup = 400; + var height_popup = 0; + + d3.selectAll(".popupcleanable").remove(); // clean + + var popupbg = d3.select(".popup").append("rect") + .attr("id", "popupbg") + .attr("class", "popup bg popupcleanable cleanable") + .attr("width", "400") + .attr("height", "0") + .attr("rx", 10) // set the x corner curve radius + .attr("ry", 10); // set the y corner curve radius + + + d3.select(".popup").append("svg:path") + .attr("d", d3.symbol() + .size(function (d) { + return 80 + }) + .type(function (d) { + console.log("popup") + return (self.get_d3_symbol()); + }) + ) + .style("fill", 'red') + .attr("transform", function () { + return "translate(380,15) rotate(-45)"; + + }) + .attr("stroke-width", 2.4) + .attr("id", "close_popup") + .attr("class", "popupcleanable cleanable") + .on("click", function (d) { + self.handlePopupVisibility(false); + }); + + d3.select(".popup").append("text") + .attr("class", "popup title popupcleanable cleanable") + .attr("x", "10") + .attr("y", "20") + .text(title); + + for (var i in data) { + //console.log(i, data, data[i]) + //var typeofvalue = typeof data[i]; + var record = data[i]; + index = this._addRecordToPopup(i, record, index) + + } + + }; + + GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) { + //console.log("_addRecordToPopup", key, record, index) + var translate_y = 23 * index; + var summary = d3.select(".popup").append("g") + .attr("class", "popup summary d popupcleanable cleanable") + .attr("transform", "translate(10 " + translate_y + ")"); + if (Object.prototype.toString.call(record) !== '[object Array]') { //is a record simple key:value + //console.log(key, record) + var summary_g = summary.append("g"); + summary_g.append("rect") + .attr("class", "popup summary bg popupcleanable cleanable") + .attr("width", "380") + .attr("height", "20"); + + summary_g.append("text") + .attr("class", "popup summary popupcleanable cleanable") + .attr("x", (tab) ? tab : 10) + .attr("y", "17") + .attr("width", "100") + .text(function (d) { + return key.toUpperCase() + ":"; + }); + + summary_g.append("text") + .attr("class", "popup summary popupcleanable cleanable") + .attr("x", "370") + .attr("y", "17") + .attr("text-anchor", "end") + .text(function (d) { + return record + }); + } + else {//is a record simple complex: have a list of sub record key:value + //index ++; + this._addRecordToPopup(key, "", index) + for (var r in record) { + //console.log(i, r, record, record[r]) + for (var k in record[r]) { + //console.log(i, r, k, record[r][k]) + var curr_key = k; + var recordValue = record[r][k] + + index++; + this._addRecordToPopup(curr_key, recordValue, index, 20) + } + } + + } + + translate_y = 30 * index++; + d3.select('#popupbg').attr("height", translate_y); + return index; + }; + + + /** + * Remove all the graph objects from the view + */ + GraphEditor.prototype.cleanAll = function () { + this.svg.selectAll('.cleanable').remove(); + }; + + /** + * Internal functions + */ + + GraphEditor.prototype._node_property_by_type = function (type, property, node) { + //console.log(type, property, layer, group) + var unrecognized = function (ui_prop, property) { + return ui_prop['unrecognized'][property] + }; + + //type recognized + if (this.type_property[type]) { + + if (this.type_property[type]['property']) { + var filt_property = this.type_property[type]['property'] + return this.type_property[type][node.info[filt_property]][property] + } else { // type without property spec + + return this.type_property[type][property] + + } + + } else { //type unrecognized + return unrecognized(this.type_property, property) + } + + }; + + GraphEditor.prototype._link_property_by_type = function (type, property) { + //log(type + "-" + property) + if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) { + //if(property == "shape") + // log("dentro" + this.type_property[type][property]) + return this.type_property_link[type][property]; + } else { + return this.type_property_link['unrecognized'][property]; + } + + } + + + /** + * + * + * + */ + GraphEditor.prototype._setupFiltersBehaviors = function (args) { + + var self = this; + + this.node_filter_cb = args.node_filter_cb || function (d) { + + var cond_view = true, + cond_group = true; + //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group) + // check filter by node type + if (self.filter_parameters.node.type.length > 0) { + + if (self.filter_parameters.node.type.indexOf(d.info.type) < 0) + cond_view = false; + } + + // check filter by group + if (self.filter_parameters.node.group.length > 0) { + self.filter_parameters.node.group.forEach(function (group) { + if (d.info.group.indexOf(group) < 0) + cond_group = false; + }); + + + } + + + return cond_view && cond_group; + }; + + this.link_filter_cb = args.link_filter_cb || function (d) { + var cond_view = true, + cond_group = true; + + // check filter by view + if (self.filter_parameters.link.view.length > 0) { + self.filter_parameters.link.view.forEach(function (view) { + if (d.view.indexOf(view) < 0) + cond_view = false; + }); + } + + // check filter by group + if (self.filter_parameters.link.group.length > 0) { + self.filter_parameters.link.group.forEach(function (group) { + if (d.group.indexOf(group) < 0) + cond_group = false; + }); + } + return cond_view && cond_group; + }; + + }; + + /** + * + * + */ + GraphEditor.prototype._setupBehaviorsOnEvents = function () { + log("_setupBehaviorsOnEvents"); + var self = this; + this.behavioursOnEvents = { + 'nodes': { + 'click': function (d) { + d3.event.preventDefault(); + log('click', d); + if (self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) { + var source_id = self._selected_node.id; + var target_id = d.id; + log("--" + JSON.stringify(self.filter_parameters.link.view)); + var new_link = { + source: source_id, + target: target_id, + view: self.filter_parameters.link.view[0], + group: self.filter_parameters.link.group[0], + }; + self.addLink(new_link); + self._deselectAllNodes(); + } else { + self._selectNodeExclusive(this, d); + } + + }, + 'mouseover': function (d) { + + }, + 'mouseout': function (d) { + }, + 'dblclick': function (d) { + d3.event.preventDefault(); + log('dblclick'); + }, + 'contextmenu': function (d, i) { + d3.event.preventDefault(); + log("contextmenu node"); + self.eventHandler.fire("right_click_node", d); + } + }, + 'links': { + 'click': function (event) { + + }, + 'dblclick': function (event) { + + } + } + }; + }; + + /** + * Deselect previously selected nodes + * + */ + GraphEditor.prototype._deselectAllNodes = function () { + log("_deselectAllNodes"); + this.node.classed("node_selected", false); + this._selected_node = undefined; + }; + + GraphEditor.prototype._deselectAllLinks = function () { + log("_deselectAllLinks"); + this.link.classed("link_selected", false).style('stroke-width', 2); + this._selected_link = undefined; + }; + /** + * Select node in exclusive mode + * @param {Object} Required. Element selected on click event + */ + GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) { + log("_selectNodeExclusive "); + var activeClass = "node_selected"; + var alreadyIsActive = d3.select(node_instance).classed(activeClass); + this._deselectAllNodes(); + this._deselectAllLinks(); + d3.select(node_instance).classed(activeClass, !alreadyIsActive); + this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__; + if(this._selected_node){ + this.eventHandler.fire("node:selected", this._selected_node) + } else { + this.eventHandler.fire("node:deselected", this._selected_node) + } + }; + + /** + * Select node in exclusive mode + * @param {Object} Required. Element selected on click event + */ + GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) { + log("_selectLinkExclusive "); + var activeClass = "link_selected"; + var alreadyIsActive = d3.select(link_instance).classed(activeClass); + this._deselectAllNodes(); + this._deselectAllLinks(); + d3.select(link_instance).classed(activeClass, !alreadyIsActive); + d3.select(link_instance).style('stroke-width', 4) + this._selected_link = link_instance.__data__; + }; + + /** + * Callback to resize SVG element on window resize + */ + GraphEditor.prototype.resizeSvg = function (width, height) { + log("resizeSvg"); + //log(event); + this.width = width || this.width; + this.height = height || this.height; + this.svg.attr('width', width); + this.svg.attr('height', height); + + } + + GraphEditor.prototype.handlePopupVisibility = function (visible, side) { + var opacity = (visible) ? 1 : 0; + + var translate_op = (side === "left") ? "translate(50 50)" : "translate(" + (this.width - 450).toString() + " 50)"; + + if (!visible) { + d3.selectAll(".popupcleanable").remove(); + d3.select(".popup") + .attr("transform", "translate(-1 -1)"); + } else { + d3.select(".popup") + .attr("transform", translate_op); + } + d3.select(".popup").attr("opacity", opacity); + }; + + GraphEditor.prototype.refreshGraphParameters = function (graphParameters) { + this.eventHandler.fire("refresh_graph_parameters", graphParameters); + }; + + /** + * Log utility + */ + function log(text) { + if (DEBUG) + console.log("::GraphEditor::", text); + } + + + return GraphEditor; + + +}(this)); + +if (typeof module === 'object') { + module.exports = TCD3.GraphEditor; +} diff --git a/static/TopologyComposer/js/model_graph_editor.js b/static/TopologyComposer/js/model_graph_editor.js new file mode 100644 index 0000000..2f5f6c8 --- /dev/null +++ b/static/TopologyComposer/js/model_graph_editor.js @@ -0,0 +1,482 @@ +/* + Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni + Copyright 2018 EveryUP srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +if (typeof TCD3 === 'undefined') { + var TCD3 = {}; +} + +TCD3.ModelGraphEditor = (function () { + 'use strict'; + + var DEBUG = true; + var SHIFT_BUTTON = 16; + var IMAGE_PATH = "/static/assets/img/"; + + + ModelGraphEditor.prototype = new TCD3.GraphEditor(); + ModelGraphEditor.prototype.constructor = ModelGraphEditor; + ModelGraphEditor.prototype.parent = TCD3.GraphEditor.prototype; + + /** + * Constructor + */ + function ModelGraphEditor(args) { + + log("Constructor"); + + } + + + ModelGraphEditor.prototype.init = function (args) { + this.parent.init.call(this, args); + var self = this; + this.desc_id = args.desc_id || undefined; //TODO remove it + + this.type_property = {}; + this.type_property["unrecognized"] = args.gui_properties["nodes"]["default"]; + + this._edit_mode = args.edit_mode || false; + + Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) { + + this.type_property[key] = args.gui_properties["nodes"][key]; + + this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]); + if (this.type_property[key]["image"] !== undefined) { + this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"]; + } + + }, this); + + if (args.gui_properties["edges"]) { + this.type_property_link = args.gui_properties["edges"]; + var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link)) + this.defs.selectAll("marker") + .data(link_types) + .enter() + .append("svg:marker") // This section adds in the arrows + .attr("id", function (d) { + return d; + }) + .attr("viewBox", "-5 -5 10 10") + .attr("refX", 13) /*must be smarter way to calculate shift*/ + .attr("refY", 0) + .attr("markerUnits", "userSpaceOnUse") + .attr("markerWidth", 12) + .attr("markerHeight", 12) + .attr("orient", "auto") + .append("path") + .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z") + .attr('fill', function (d) { + return self.type_property_link[d].color; + }); + } + + this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined; + + + var data_url = args.data_url || undefined; + if (!args.graph_data && args.data_url) { + d3.json(data_url, function (error, data) { + //console.log(JSON.stringify(data)) + self.d3_graph.nodes = data.vertices; + self.d3_graph.links = data.edges; + self.d3_graph.graph_parameters = data.graph_parameters; + self.model = data.model; + self.refreshGraphParameters(self.d3_graph.graph_parameters); + self.refresh(); + self.startForce(); + //if(args.filter_base != undefined) + + setTimeout(function () { + self.handleForce(true); + self.handleFiltersParams(args.filter_base); + }, 500); + + }); + } else { + this.updateData(args) + } + } + + /** + * Update data of the graph. + * @param {Object} Required. An object that specifies tha data of the new node. + * @returns {boolean} + */ + ModelGraphEditor.prototype.updateData = function (args) { + console.log("updateData") + this.d3_graph.nodes = args.graph_data.vertices; + this.d3_graph.links = args.graph_data.edges; + this.d3_graph.graph_parameters = args.graph_parameters; + this.model = args.model; + this.refreshGraphParameters(this.d3_graph.graph_parameters); + this.refresh(); + this.startForce(); + //if(args.filter_base != undefined) + + //if(args.filter_base){ + var self = this; + setTimeout(function () { + self.handleForce(true); + self.handleFiltersParams(args.filter_base); + }, 500); + //} + } + + /** + * Add a new node to the graph. + * @param {Object} Required. An object that specifies tha data of the new node. + * @returns {boolean} + */ + ModelGraphEditor.prototype.addNode = function (node, success, error) { + var self = this; + var current_layer = self.getCurrentView(); + var node_type = node.info.type; + + if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) { + if (self.model.layer[current_layer].nodes[node_type].addable.callback) { + var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class; + var controller = new TCD3[c](); + controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function () { + self.parent.addNode.call(self, node); + success && success(); + }, error); + + } else { + + log('addNode: callback undefined in model spec.'); + error && error("You can't add a " + node.info.type + ", callback undefined."); + } + } else { + //FIXME Error handling???? + log("You can't add a " + node.info.type + " in a current layer " + current_layer); + error && error("You can't add a " + node.info.type + " in a current layer " + current_layer); + } + }; + + + /** + * Update the data properties of the node + * @param {Object} Required. An object that specifies tha data of the node. + * @returns {boolean} + */ + ModelGraphEditor.prototype.updateDataNode = function (args) { + //FIXME updating a node properties need commit to server side! + this.parent.updateDataNode.call(this, args); + }; + + /** + * Remove a node from graph and related links. + * @param {String} Required. Id of node to remove. + * @returns {boolean} + */ + ModelGraphEditor.prototype.removeNode = function (node, success, error) { + console.log('removeNode', JSON.stringify(node)) + var self = this; + var current_layer = self.getCurrentView(); + var node_type = node.info.type; + if (node.info.desc_id == undefined) { + node.info.desc_id = self.desc_id; + } + if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) { + if (self.model.layer[current_layer].nodes[node_type].removable.callback) { + var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class; + var controller = new TCD3[c](); + controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () { + self.parent.removeNode.call(self, node); + success && success(); + }, error); + } else { + + log('removeNode: callback undefined in model spec.'); + error && error("You can't remove a " + node.info.type + ", callback undefined."); + } + } else { + //FIXME we need to manage alert in a different way: FAILBACK + log("You can't remove a " + node.info.type); + error && error("You can't remove a " + node.info.type); + } + }; + + /** + * Add a new link to graph. + * @param {Object} Required. An object that specifies tha data of the new Link. + * @returns {boolean} + */ + ModelGraphEditor.prototype.addLink = function (s, d, success, error) { + var self = this; + var source_id = s.id; + var target_id = d.id; + var source_type = s.info.type; + var destination_type = d.info.type; + var link = { + source: s, + target: d, + view: this.filter_parameters.link.view[0], + group: this.filter_parameters.link.group, + desc_id: this.desc_id + }; + log("addLink: " + JSON.stringify(link)) + var current_layer = self.getCurrentView() + if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) { + + if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) { + var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback; + console.log(callback, self.model.callback) + var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false; + link.directed_edge = direct_edge; + var c = self.model.callback[callback].class; + var controller = new TCD3[c](); + controller[callback](self, link, function () { + self._deselectAllNodes(); + self.parent.addLink.call(self, link); + if (success) + success(); + }, error); + } else { + log('addLink: callback undefined in model spec.'); + error && error("You can't add a link, callback undefined."); + } + + } else { + //FIXME we need to manage alert in a different way: FAILBACK + log("You can't link a " + source_type + " with a " + destination_type); + + error && error("You can't link a " + source_type + " with a " + destination_type); + } + }; + + /** + * Remove a link from graph. + * @param {String} Required. The identifier of link to remove. + * @returns {boolean} + */ + ModelGraphEditor.prototype.removeLink = function (link, success, error) { + var self = this; + var s = link.source; + var d = link.target; + var source_type = s.info.type; + var destination_type = d.info.type; + var current_layer = self.getCurrentView() + if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] && + self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable + ) { + if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) { + var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback; + var c = self.model.callback[callback].class; + var controller = new TCD3[c](); + controller[callback](self, link, function () { + self._deselectAllNodes(); + self._deselectAllLinks(); + self.parent.removeLink.call(self, link.index); + success && success(); + }, error); + } else { + log('removeLink: callback undefined in model spec.'); + error && error("You can't remove a link, callback undefined."); + } + + } else { + //FIXME we need to manage alert in a different way: FAILBACK + log("You can't delete the link"); + error && error("You can't delete the link"); + } + + + }; + + + ModelGraphEditor.prototype.savePositions = function (data) { + var vertices = {}; + this.node.each(function (d) { + vertices[d.id] = {}; + vertices[d.id]['x'] = d.x; + vertices[d.id]['y'] = d.y; + }); + new TCD3.GraphRequests().savePositions({ + 'vertices': vertices + }); + + }; + + /** + * Internal functions + */ + + ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) { + + var self = this; + var contextMenuLinksAction = [{ + title: 'Delete Link', + action: function (elm, link, i) { + self.removeLink(link, null, showAlert); + }, + edit_mode: true + }]; + var contextMenuNodesAction = [{ + title: 'Edit', + action: function (elm, d, i) { + if (d.info.type != undefined) { + self.eventHandler.fire("edit_descriptor", self.project_id, d); + } + }, + nodes: [], + edit_mode: true + }, + { + title: 'Delete', + action: function (elm, d, i) { + self.removeNode(d, null, showAlert); + }, + edit_mode: true + } + + ]; + if (this.customBehavioursOnEvents) { + contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes); + } + + + if (self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) { + for (var i in self.model.layer[layer].action.node) { + var action = self.model.layer[layer].action.node[i] + contextMenuNodesAction.push({ + title: action.title, + action: function (elm, d, i) { + var callback = action.callback; + var c = self.model.callback[callback].class; + var controller = new TCD3[c](); + var args = { + elm: elm, + d: d, + i: i + }; + + controller[callback](self, args); + }, + edit_mode: (action.edit_mode !== undefined) ? action.edit_mode : undefined + }); + } + } + + this.behavioursOnEvents = { + 'nodes': { + 'click': function (d) { + + d3.event.preventDefault(); + + if (self._edit_mode && self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) { + self.addLink(self._selected_node, d, null, showAlert); + } else { + self._selectNodeExclusive(this, d); + } + + }, + 'mouseover': function (d) { + self.link.style('stroke-width', function (l) { + if (d === l.source || d === l.target) + return 4; + else + return 2; + }); + }, + 'mouseout': function (d) { + self.link.style('stroke-width', 2); + }, + 'contextmenu': d3.contextMenu(contextMenuNodesAction, { + 'edit_mode': self._edit_mode, + 'layer': layer, + 'type_object': 'node' + }) + }, + 'links': { + 'click': function (d) { + self._selectLinkExclusive(this, d); + + }, + 'dblclick': function (event) { + + }, + 'mouseover': function (d) { + d3.select(this).style('stroke-width', 4); + }, + 'mouseout': function (d) { + if (d !== self._selected_link) + d3.select(this).style('stroke-width', 2); + }, + 'contextmenu': d3.contextMenu(contextMenuLinksAction, { + 'edit_mode': self._edit_mode, + 'layer': layer, + 'type_object': 'link' + }) + } + } + }; + + ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) { + + this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent); + if (filtersParams && filtersParams.link && filtersParams.link.view) + this._setupBehaviorsOnEvents(filtersParams.link.view[0]); + }; + + ModelGraphEditor.prototype.getAvailableNodes = function () { + log('getAvailableNodes'); + log(this.model); + if (this.model && this.model.layer[this.getCurrentView()] !== undefined) + return this.model.layer[this.getCurrentView()].nodes; + return []; + }; + + + ModelGraphEditor.prototype.getTypeProperty = function () { + return this.type_property; + }; + + ModelGraphEditor.prototype.getCurrentGroup = function () { + return this.filter_parameters.node.group[0]; + + }; + + ModelGraphEditor.prototype.getCurrentView = function () { + return this.filter_parameters.link.view[0]; + }; + ModelGraphEditor.prototype.getCurrentFilters = function () { + return this.filter_parameters; + }; + + ModelGraphEditor.prototype.getGraphParams = function () { + return this.d3_graph.graph_parameters; + }; + + /** + * Log utility + */ + function log(text) { + if (DEBUG) + console.log("::ModelGraphEditor::", text); + } + + + return ModelGraphEditor; + + +}(this)); + +if (typeof module === 'object') { + module.exports = TCD3.ModelGraphEditor; +} diff --git a/static/TopologyComposer/js/settings.js b/static/TopologyComposer/js/settings.js new file mode 100644 index 0000000..5893dc4 --- /dev/null +++ b/static/TopologyComposer/js/settings.js @@ -0,0 +1,32 @@ +/* + Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni + Copyright 2018 EveryUP srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +if (typeof TCD3 === 'undefined') { + var TCD3 = {}; +} + + +TCD3.settings = (function () { + 'use strict'; + + + return Event; +}()); + +if (typeof module === 'object') { + module.exports = TCD3.Event; +} diff --git a/static/src/instancehandler/instance_list.js b/static/src/instancehandler/instance_list.js index fa9bef4..a2c2003 100644 --- a/static/src/instancehandler/instance_list.js +++ b/static/src/instancehandler/instance_list.js @@ -87,6 +87,11 @@ var removeFormGroup = function (event) { $formGroup.remove(); }; +function showTopology(type, instance_id) { + var url = '/instances/'+type+'/'+instance_id+'/topology'; + window.location = url; +} + function showInstanceDetails(type, instance_id) { var url_info = '/instances/'+type+'/'+instance_id; var dialog = bootbox.dialog({ diff --git a/static/src/instancehandler/instance_topology_view.js b/static/src/instancehandler/instance_topology_view.js index f306443..1193b18 100644 --- a/static/src/instancehandler/instance_topology_view.js +++ b/static/src/instancehandler/instance_topology_view.js @@ -1,159 +1,69 @@ //GraphEditor instance -var graph_editor = new dreamer.ModelGraphEditor(); -var selected_vnffgId = null; -var show_all = null; - -// Enable Drop Action on the Graph -initDropOnGraph(); +var graph_editor = new TCD3.ModelGraphEditor(); var type_view = { - "ns": ["vnf", "ns_vl"], - "vnf": ["vdu", "cp"] + "nsr": ["vnfr", "ns_vl"], + "vnfr": ["vdur", "cp", "vnf_vl"] }; var params = { - node: { - type: type_view['ns'], - group: [] - }, - link: { - group: [], - view: ['ns'] - } - }; -$(document).ready(function() { + node: { + type: type_view['nsr'], + group: [] + }, + link: { + group: [], + view: ['nsr'] + } +}; + +$(document).ready(function () { graph_editor.addListener("filters_changed", changeFilter); - graph_editor.addListener("refresh_graph_parameters", refreshGraphParameters); + graph_editor.addListener("node:selected", refreshElementInfo); + graph_editor.addListener("node:deselected", refreshElementInfo); - console.log(osm_gui_properties) // graph_editor initialization graph_editor.init({ - width: $('#graph_ed_container').width(), - height: $('#graph_ed_container').height(), + width: $('#graph_editor_container').width(), + height: $('#graph_editor_container').height(), data_url: window.location.href, - desc_id: getUrlParameter('id'), + //desc_id: getUrlParameter('id'), gui_properties: osm_gui_properties, edit_mode: false, - behaviorsOnEvents:{ + behaviorsOnEvents: { viewBased: false, behaviors: buildBehaviorsOnEvents() } }); - // this will filter in the different views, excluding the node types that are not listed in params graph_editor.handleFiltersParams(params); }); -var filters = function(e, params) { - graph_editor.handleFiltersParams(params); - $('#' + e).nextAll('li').remove(); -} - -function initDropOnGraph() { - - var dropZone = document.getElementById('graph_ed_container'); - dropZone.ondrop = function(e) { - var group = graph_editor.getCurrentGroup() - e.preventDefault(); - var elemet_id = e.dataTransfer.getData("text/plain"); - var nodetype = $('#'+elemet_id).attr('type-name'); - console.log(nodetype); - if (nodetype) { - var type_name = graph_editor.getTypeProperty()[nodetype].name; - $('#div_chose_id').show(); - $('#input_choose_node_id').val(nodetype + "_" + generateUID()); - $('#modal_chooser_title_add_node').text('Add ' + type_name); - $('#save_choose_node_id').off('click').on('click', function() { - var name = $('#input_choose_node_id').val(); - var node_information = { - 'id': name, - 'info': { - 'type': nodetype, - 'group': [group], - 'desc_id': getUrlParameter('id'), - }, - 'x': e.layerX, - 'y': e.layerY - } - console.log(JSON.stringify(node_information)) - graph_editor.addNode(node_information, function() { - $('#modal_choose_node_id').modal('hide'); - }, function(error){ - showAlert(error) - }); - }); - $('#modal_choose_node_id').modal('show'); - - - } - - } - - dropZone.ondragover = function(ev) { - console.log("ondragover"); - return false; - } - - dropZone.ondragleave = function() { - console.log("ondragleave"); - return false; - } -} function handleForce(el) { - if (el.id == "topology_play") { - $("#topology_pause").removeClass('active'); - $("#topology_play").addClass('active'); - } else { - $("#topology_pause").addClass('active'); - $("#topology_play").removeClass('active'); - } - - graph_editor.handleForce((el.id == "topology_play") ? true : false); - + graph_editor.handleForce((el.getAttribute('aria-pressed') === "true")); } function changeFilter(e, c) { - - console.log("changeFilter", JSON.stringify(c)); - //$("#title_header").text("OSHI Graph Editor"); - //updateNodeDraggable({type_property: type_property, nodes_layer: graph_editor.getAvailableNodes()}) - if(c) - new dreamer.GraphRequests().getAvailableNodes({layer: c.link.view[0]}, buildPalette, showAlert); - -} - -function refreshGraphParameters(e, graphParameters) { - var self = $(this); - if (graphParameters == null) return; - + if (c && c.link && c.link.view[0]) { + updateLegend(c.link.view[0]); + } + layerDetails(graph_editor.getCurrentFilters()) } -function resetFilters(){ +function resetFilters() { graph_editor.handleFiltersParams(params); } function buildBehaviorsOnEvents() { var self = this; - var contextmenuNodesAction = [{ - title: 'Show info', - action: function (elm, d, i) { - // console.log('Show NodeInfo', elm, d, i); - var nodeData = { - "node": { - "id": d.id - } - }; - }, - edit_mode: false - - }, + var contextmenuNodesAction = [ { title: 'Explore', action: function (elm, c_node, i) { - if (c_node.info.type != undefined) { + if (c_node.info.type !== undefined) { var current_layer_nodes = Object.keys(graph_editor.model.layer[graph_editor.getCurrentView()].nodes); if (current_layer_nodes.indexOf(c_node.info.type) >= 0) { if (graph_editor.model.layer[graph_editor.getCurrentView()].nodes[c_node.info.type].expands) { @@ -178,10 +88,161 @@ function buildBehaviorsOnEvents() { }, edit_mode: false }]; - var behavioursOnEvents = { - 'nodes': contextmenuNodesAction + return { + 'nodes': contextmenuNodesAction }; - return behavioursOnEvents; +} + +function refreshElementInfo(event, element) { + if (event.type === 'node:selected') { + switch (element.info.type) { + case 'vnfr': + vnfrDetails(element.info.osm); + break; + case 'vdur': + vdurDetails(element.info.osm); + break; + case 'int_cp': + case 'cp': + cpDetails(element.info.osm); + break; + case 'vnf_vl': + case 'ns_vl': + vlDetails(element.info.osm); + break; + } + } + else if (event.type === 'node:deselected') { + layerDetails(graph_editor.getCurrentFilters()) + } +} + +function layerDetails(filters) { + var side = $('#side'); + var graph_parameters = graph_editor.getGraphParams(); + var layer_template = ''; + if(graph_parameters['view'] && filters.link.view.length >0 && filters.link.view[0]){ + if(filters.link.view[0] === 'nsr') { + layer_template = getMainSection('NS View'); + layer_template += getChildrenTable(graph_parameters['view']['nsr']); + } + else if(filters.link.view[0] === 'vnfr') { + layer_template = getMainSection('VNF View'); + var vnfr_id = filters.link.group[0]; + layer_template += getChildrenTable(graph_parameters['view']['vnfr'][vnfr_id]); + } + } + + side.empty(); + side.append(layer_template) +} + +function updateLegend(view) { + var legend = $('#legenda'); + var nodes = type_view[view]; + var legend_template = ''; + var nodes_properties = osm_gui_properties['nodes']; + for (var n in nodes){ + var node = nodes[n]; + if(nodes_properties[node]){ + legend_template += '
' + + '
' + + '
' +nodes_properties[node].name + '
'; + } + } + + legend.empty(); + legend.append(legend_template) + +} + +var map = { + 'ip-address': 'IP', 'vnfd-id': 'Vnfd Id', 'vnfd-ref': 'Vnfd Ref', 'vim-account-id': 'Vim Id', + 'member-vnf-index-ref': 'Member index', 'created-time': 'Created', 'id': 'Id', 'mgmt-network': 'Mgmt network', + 'name': 'Name', 'type': 'Type', 'vim-network-name': 'Vim network name', 'connection-point-id': 'Cp Id', + 'vdu-id-ref': 'Vdu Id', 'nsr-id-ref': 'Nsr Id' +}; + +function vnfrDetails(vnfr) { + var side = $('#side'); + var vnfr_template = getMainSection('VNFR'); + + vnfr_template += getChildrenTable(vnfr); + side.empty(); + side.append(vnfr_template) +} + +function vdurDetails(vdur) { + var side = $('#side'); + var vdur_template = getMainSectionWithStatus('VDUR', vdur['status'] === 'ACTIVE'); + vdur_template += getChildrenTable(vdur); + + if (vdur['interfaces'].length > 0) { + vdur_template += getSubSection('Interfaces:'); + vdur_template += ''; + + for (var i = 0; i < vdur['interfaces'].length; ++i) { + var interface = vdur['interfaces'][i]; + var interface_template = '' + + '' + + ''; + vdur_template += interface_template; + } + vdur_template += '
' + interface['name'] + 'IP:' + interface['ip-address'] + 'MAC:' + interface['mac-address'] + '
'; + } + + side.empty(); + side.append(vdur_template) +} + +function cpDetails(cp) { + var side = $('#side'); + var cp_template = getMainSection('Connection Point'); + + cp_template += getChildrenTable(cp); + side.empty(); + side.append(cp_template); +} + +function vlDetails(vl) { + var side = $('#side'); + var vl_template = getMainSection('Virtual Link'); + + vl_template += getChildrenTable(vl); + side.empty(); + side.append(vl_template); +} + + +function getMainSection(title) { + return '
' + title + '
'; +} + +function getSubSection(title) { + return '
' + title + '
'; +} + +function getMainSectionWithStatus(title, status) { + var template = '
' + title + ''; + if (status) + template += '
ACTIVE
'; + else + template += '
NO ACTIVE
'; + template += '
'; + return template; +} + +function getChildrenTable(data) { + var template = ''; + + for (var key in data) { + if (typeof data[key] === 'string') { + var key_map = (map[key]) ? map[key] : key; + template += ''; + } + } + template += '
' + key_map + '' + data[key] + '
'; + return template; } \ No newline at end of file diff --git a/static/src/osm_gui_properties.js b/static/src/osm_gui_properties.js index 25a6aba..45abaf7 100644 --- a/static/src/osm_gui_properties.js +++ b/static/src/osm_gui_properties.js @@ -1,50 +1,70 @@ var osm_gui_properties = { - "v1": { - "default": { - "shape": "cross", - "color": "#42f44e", - "label_color": "black", - "size": 15 - }, "nodes": { - "ns_vl": { - "image": "vl-80.png", - // "shape": "triangle", - "color": "#196B90", - "size": 20, - "name": "VL" - }, - "cp": { - "image": "cp-80.png", - // "shape": "circle", - "color": "#F27220", - "size": 20, - "name": "CP" + "default": { + "shape": "square", + "color": "#36c2f3", + "label_color": "black", + "default_node_label_color": "#000", + "size": 15 }, + "vnf": { - "image": "vnf-100.png", - // "shape": "square", + //"image": "vnf-100.png", + "shape": "square", "color": "#54A698", "size": 35, + "width": 40, + "height": 20, "name": "VNF" }, - "vnf_vl": { - "shape": "triangle", - //"color": "#5FC9DB", - "color": "#196B90", - "size": 11, - "name": "IntVL" - }, - "vdu": { "shape": "square", //"color": "#50A7CC", "color": "#54A698", "size": 30, + "width": 40, + "height": 20, "name": "VDU" - } + }, + "cp": { + //"image": "cp-80.png", + "shape": "square", + "color": "#c6b63f", + "size": 40, + "name": "CP" + }, + "int_cp": { + //"image": "cp-80.png", + "shape": "square", + "color": "#e3bfad", + "size": 40, + "name": "CP" + }, + "ns_vl": { + "shape": "triangle", + "color": "#5ba56e", + "size": 30, + "name": "VL" + }, + "vnf_vl": { + "shape": "triangle", + "color": "#f8a800", + "size": 30, + "name": "IntVL" + }, + "vdur": { + "shape": "square", + "color": "#cf1c24", + "size": 60, + "name": "VDUR" + }, + "vnfr": { + "shape": "square", + "color": "#605ca8", + "size": 60, + "name": "VNFR" + }, + }, "graphs": null - - } -} \ No newline at end of file + }; \ No newline at end of file diff --git a/static/src/utils.js b/static/src/utils.js index 6763f78..79d88c2 100644 --- a/static/src/utils.js +++ b/static/src/utils.js @@ -29,20 +29,19 @@ function savePositions(el) { function buildPalette(args) { $("#paletteContainer").empty(); var type_property = graph_editor.getTypeProperty(); + if (args.length > 0) { - args.forEach(function (category) { + $('#paletteContainer').append('
Legenda
'); + args.forEach(function (category) { var category_id = "category_" + category.category_name.replace(/[\s.*+?^${}()\/|[\]\\]/g, "_");//.replace(/\s/g, ''); var content_id = "palette-content-" + category.category_name.replace(/[\s.*+?^${}()\/|[\]\\]/g, "_");//.replace(/\s/g, ''); - $("#paletteContainer").append('
' + - '' + - ' ' + category.category_name + '' + - '
' + + $("#paletteContainer").append('
' + '
' + - '
' + '
'); + category.types.forEach(function (type) { console.log(graph_editor.get_name_from_d3_symbol(d3.symbolCircle)) var type_id = type.id.replace(/[\s.*+?^${}()|[\]\\]/g, "_"); @@ -66,7 +65,7 @@ function buildPalette(args) { '
' + palette_node_icon + '
' + - '
' + '
'; $("#" + content_id).append(html_to_append); }); @@ -74,12 +73,11 @@ function buildPalette(args) { } togglePaletteSpinner(true); - } function handlePaletteCat(item) { console.log("handlePaletteContainer") - var category_id = $(item).attr("category_id") + var category_id = $(item).attr("category_id"); $('#' + category_id).toggleClass("palette-close"); } @@ -90,11 +88,11 @@ function togglePaletteSpinner(addOrRemove) { function showAlert(msg) { // modal_alert_text - var alert_msg = "" - if (typeof msg == "string") - alert_msg = msg + var alert_msg = ""; + if (typeof msg === "string") + alert_msg = msg; else - alert_msg = JSON.stringify(msg) + alert_msg = JSON.stringify(msg); $('#modal_alert_text').text(alert_msg); $('#modal_alert').modal('show'); } @@ -113,25 +111,17 @@ function buildHtmlShape(args) { switch (mySymbol) { case d3.symbolCircle: return '
'; - break; case d3.symbolSquare: return '
'; - break; case d3.symbolDiamond: return '
'; - ; - break; + case d3.symbolTriangle: return '
'; - break; case d3.symbolStar: return '
'; - ; - break; case d3.symbolCross: return '
'; - ; - break; default: // if the string is not recognized return "unknown"; @@ -145,7 +135,7 @@ if (!String.format) { String.format = function (format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { - return typeof args[number] != 'undefined' ? + return typeof args[number] !== 'undefined' ? args[number] : match; }); diff --git a/static/topology3D/css/d3-context-menu.css b/static/topology3D/css/d3-context-menu.css deleted file mode 100644 index 334513c..0000000 --- a/static/topology3D/css/d3-context-menu.css +++ /dev/null @@ -1,78 +0,0 @@ -.d3-context-menu { - position: absolute; - display: none; - background-color: #f2f2f2; - border-radius: 4px; - - font-family: Arial, sans-serif; - font-size: 14px; - min-width: 150px; - border: 1px solid #d4d4d4; - - z-index:1200; -} - -.d3-context-menu ul { - list-style-type: none; - margin: 4px 0px; - padding: 0px; - cursor: default; -} - -.d3-context-menu ul li { - padding: 4px 16px; - - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; -} - -.d3-context-menu ul li:hover { - background-color: #4677f8; - color: #fefefe; -} - -/* - Header -*/ - -.d3-context-menu ul li.is-header, -.d3-context-menu ul li.is-header:hover { - background-color: #f2f2f2; - color: #444; - font-weight: bold; - font-style: italic; -} - -/* - Disabled -*/ - -.d3-context-menu ul li.is-disabled, -.d3-context-menu ul li.is-disabled:hover { - background-color: #f2f2f2; - color: #888; - cursor: not-allowed; -} - -/* - Divider -*/ - -.d3-context-menu ul li.is-divider { - padding: 0px 0px; -} - -.d3-context-menu ul li.is-divider:hover { - background-color: #f2f2f2; -} - -.d3-context-menu ul hr { - border: 0; - height: 0; - border-top: 1px solid rgba(0, 0, 0, 0.1); - border-bottom: 1px solid rgba(255, 255, 255, 0.3); -} \ No newline at end of file diff --git a/static/topology3D/css/graph_editor_d3js.css b/static/topology3D/css/graph_editor_d3js.css deleted file mode 100755 index 0433236..0000000 --- a/static/topology3D/css/graph_editor_d3js.css +++ /dev/null @@ -1,415 +0,0 @@ - -.node_path { - opacity: 1; - stroke: #2F3550; - stroke-width: 1; -} - -.node_selected { - opacity: 1 !important; - stroke: #2F3550 !important; - stroke-width: 3 !important; -} - -.hidden_circle { - opacity: 0; - stroke: #FF0000 !important; - stroke-width: 2 !important; -} - -.hidden_circle:hover { - opacity: 1; - stroke: #FF0000 !important; - stroke-width: 2; -} - -.node_path:hover { - opacity: 1; - stroke: #2F3550; - stroke-width: 3; -} - -.node_path:hover text { - opacity: 0.4; - -} - -.link { - fill: none; -} - -.link path { - stroke-width: 2; -} - -.matted { - opacity: 0.3; -} - -.invisible { - visibility: hidden; -} - -.drag_box { - padding: 0 40px 0 0; - margin-bottom: 0px !important; - padding-top: 0px; - top: 14px; - /* right: 25px !important; */ - /* width: 15%; */ - position: absolute; - z-index: 1; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - right: 0; - cursor: move; -} - -.selector_box { - padding: 0 40px 0 0; - margin-bottom: 0px !important; - padding-top: 0px; - top: 70px; - /* right: 25px !important; */ - /* width: 15%; */ - position: absolute; - z-index: 1; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - right: 0; - cursor: move; -} - -.drag_button { - cursor: move !important; - /*border-style: dashed;*/ - border-width: thin; - border-color: black; - color: white; - border-radius: 50% !important; - height: 50px !important; - width: 50px !important; - margin-right: 5px; -} - -.drag_button p { - font-family: "Lucida Console", Monaco, monospace;; - font-size: 0.6em; - font-weight: bold; - text-decoration: none; - color: black; - text-align: center; - padding-top: 50%; - padding-bottom: 50%; - width: 100%; -} - -.left-tool-bar { - top: 14px; - cursor: default; - line-height: 22px; - position: absolute; - - padding: 0px 6px; - - /* top: 7px; */ - /* right: 6px; */ - z-index: 1; - text-align: center; - font-size: 12px; - /*color: #777; - /* border-radius: 20px; */ - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.left-tool-bar-monitoring { - - z-index: 1; - text-align: center; - font-size: 12px; - - top: 64px; - cursor: default; - padding: 0 6px; - line-height: 22px; - position: absolute; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.vnffg-box { - top: 80px; - left: 100px; - cursor: default; - position: absolute; - width: 130px; - - padding-top: 0px; - padding-bottom: 0px; - - /* top: 7px; */ - /* right: 6px; */ - z-index: 1; - text-align: center; - font-size: 12px; - /*color: #777; - /* border-radius: 20px; */ - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -textarea { - font-family: "Lucida Console", Monaco, monospace; - height: 350px !important; - resize: none; -} - -.form-group { - margin-bottom: 0px !important; - margin-top: 0px !important; -} - -.modal-body { - overflow-y: auto; -} - -[draggable=true] { - -khtml-user-drag: element; - -webkit-user-drag: element; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.help-key { - border: 1px solid #ddd; - padding: 4px; - border-radius: 3px; - background: #f6f6f6; - font-family: Courier, monospace; - box-shadow: #999 1px 1px 1px; -} - -/* -Palette section -*/ -#palette { - position: absolute; - top: 20px; - bottom: 20px; - right: 22px; - background: #f3f3f3; - width: 170px; - text-align: center; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid #bbb; - box-sizing: border-box; - padding: 0 0 0 0; -} - -.palette-status-indicator { - top: 50%; - position: relative; - justify-content: center; -} - -.palette-status-hidden > .palette-status-indicator { - top: 50%; - position: relative; - justify-content: center; - display: none; -} - -.palette-container { - #display: none; - position: absolute; - top: 0px; - right: 0; - bottom: 25px; - left: 0; - padding: 0; - overflow-y: auto; - box-sizing: border-box; -} - -.palette-category { - - border-bottom: 1px solid #ccc; -} - -.palette-hide { - display: none; -} - -.palette-header { - position: relative; - background: #f3f3f3; - cursor: pointer; - text-align: left; - padding: 1px; - font-weight: bold; - font-size: 16px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.palette-content { - - background: #fff; - padding: 3px; -} - -.palette-close > .palette-content { - background: #fff; - padding: 3px; - display: none; -} - -.palette-close > .palette-header > i.fa.fa-chevron-down { - - filter: none; - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; - -webkit-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); -} - -.palette-node { - cursor: move; - margin: 3px auto; - height: 35px; - border-radius: 5px; - border: 1px solid #999; - background: #ddd no-repeat 5% 50%; - width: 80%; - background-size: contain; - position: relative; -} - -.palette-node-label { - font-size: 13px; - font-weight: bold; - padding-left: 30px; - margin-left: auto; - margin-right: auto; - position: relative; - top: 50%; - transform: translateY(-50%); -} - -.palette-node-icon-container { - position: absolute; - text-align: center; - top: 0; - bottom: 0; - left: 0; - width: 30px; - border-right: 1px solid rgba(0, 0, 0, 0.1); - background-color: rgba(0, 0, 0, 0.05); -} - -.palette-node-icon { - /* display: inline-block;*/ - width: 30px; - height: 100%; - background-position: 50% 50%; - background-size: contain; - background-repeat: no-repeat; -} - -.palette-node-circle { - display: inline-block; - margin-top: 2px; - width: 28px; - height: 28px; - -webkit-border-radius: 14px; - -moz-border-radius: 14px; - border-radius: 14px; - background: red; -} - -.palette-node-square { - display: inline-block; - margin-top: 4px; - padding-top: 1px; - width: 26px; - height: 26px; - background: red; -} - -.palette-node-triangle { - padding-top: 1px; - width: 0px; - height: 0px; - border-left: 15px solid transparent; - border-right: 15px solid transparent; - border-bottom: 30px solid #2f2f2f; -} - -/* -END Palette section -*/ - -/* -Popup section -*/ -.popup.bg { - fill: black; - opacity: 0.21; - border-radius: 3px; -} - -.popup.summary.bg { - fill: white; - opacity: 1; -} - -.popup.summary.counter { - overflow: hidden; - text-overflow: ellipsis; - font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.popup.summary.countervalue { - overflow: hidden; - text-overflow: ellipsis; - font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.popup.title { - fill: black; - font-size: 16px; - font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -/* -Popup section -*/ \ No newline at end of file diff --git a/static/topology3D/js/d3-context-menu.js b/static/topology3D/js/d3-context-menu.js deleted file mode 100644 index 6f77042..0000000 --- a/static/topology3D/js/d3-context-menu.js +++ /dev/null @@ -1,131 +0,0 @@ -(function (root, factory) { - if (typeof module === 'object' && module.exports) { - module.exports = function (d3) { - d3.contextMenu = factory(d3); - return d3.contextMenu; - }; - } else if (typeof define === 'function' && define.amd) { - try { - var d3 = require('d3'); - } catch (e) { - d3 = root.d3; - } - - d3.contextMenu = factory(d3); - define([], function () { - return d3.contextMenu; - }); - } else if (root.d3) { - root.d3.contextMenu = factory(root.d3); - } -}(this, - function (d3) { - return function (menu, opts) { - var openCallback, - closeCallback; - - if (typeof opts === 'function') { - openCallback = opts; - } else { - opts = opts || {}; - openCallback = opts.onOpen; - closeCallback = opts.onClose; - } - - // create the div element that will hold the context menu - d3.selectAll('.d3-context-menu').data([1]) - .enter() - .append('div') - .attr('class', 'd3-context-menu'); - - // close menu - d3.select('body').on('click.d3-context-menu', function () { - d3.select('.d3-context-menu').style('display', 'none'); - if (closeCallback) { - closeCallback(); - } - }); - - // this gets executed when a contextmenu event occurs - return function (data, index) { - var elm = this; - d3.selectAll('.d3-context-menu').html(''); - var list = d3.selectAll('.d3-context-menu') - .on('contextmenu', function (d) { - d3.select('.d3-context-menu').style('display', 'none'); - d3.event.preventDefault(); - d3.event.stopPropagation(); - }) - .append('ul'); - list.selectAll('li').data(typeof menu === 'function' ? menu(data) : menu).enter() - .filter(function (d) { - if(opts.type_object == 'node'){ - if (opts.edit_mode || opts.edit_mode == d.edit_mode ) { - if ((d.nodes == undefined || d.nodes.length == 0) || - (d.nodes != undefined && d.nodes.length > 0 && d.nodes.indexOf(data.info.type) > -1)) - return true - } - } - if(opts.type_object == 'link'){ - if (opts.edit_mode == d.edit_mode) { - - return true - } - } - return false; - }) - .append('li') - .attr('class', function (d) { - var ret = ''; - if (d.divider) { - ret += ' is-divider'; - } - if (d.disabled) { - ret += ' is-disabled'; - } - if (!d.action) { - ret += ' is-header'; - } - //if() - return ret; - }) - .html(function (d) { - if (d.divider) { - return '
'; - } - if (!d.title) { - console.error('No title attribute set. Check the spelling of your options.'); - } - return (typeof d.title === 'string') ? d.title : d.title(data); - }) - .on('click', function (d, i) { - if (d.disabled) return; // do nothing if disabled - if (!d.action) return; // headers have no "action" - d.action(elm, data, index); - d3.select('.d3-context-menu').style('display', 'none'); - - if (closeCallback) { - closeCallback(); - } - }); - - // the openCallback allows an action to fire before the menu is displayed - // an example usage would be closing a tooltip - if (openCallback) { - if (openCallback(data, index) === false) { - return; - } - } - - // display context menu - d3.select('.d3-context-menu') - .style('left', (d3.event.pageX - 2) + 'px') - .style('top', (d3.event.pageY - 2) + 'px') - .style('display', 'block'); - - d3.event.preventDefault(); - d3.event.stopPropagation(); - }; - }; - } -)); \ No newline at end of file diff --git a/static/topology3D/js/event.js b/static/topology3D/js/event.js deleted file mode 100755 index cfe815c..0000000 --- a/static/topology3D/js/event.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -if (typeof dreamer === 'undefined') { - var dreamer = {}; -} - -dreamer.Event = (function (global) { - 'use strict'; - - function Event () { - this._listeners = {}; - } - - Event.prototype.addL = function (type, listener) { - if (typeof this._listeners[type] == "undefined"){ - this._listeners[type] = []; - } - this._listeners[type].push(listener); - }; - - Event.prototype.fire = function (event, args) { - if (typeof event == "string"){ - event = { type: event }; - } - if (!event.target){ - event.target = this; - } - - if (!event.type){ //falsy - throw new Error("Event object missing 'type' property."); - } - - if (this._listeners[event.type] instanceof Array){ - var listeners = this._listeners[event.type]; - for (var i=0, len=listeners.length; i < len; i++){ - listeners[i].call(this, event, args); - } - } - }; - - Event.prototype.addListener = function (type, listener) { - if (this._listeners[type] instanceof Array){ - var listeners = this._listeners[type]; - for (var i=0, len=listeners.length; i < len; i++){ - if (listeners[i] === listener){ - listeners.splice(i, 1); - break; - } - } - } - - }; - - return Event; -}()); - -if (typeof module === 'object') { - module.exports = dreamer.Event; -} diff --git a/static/topology3D/js/graph_editor.js b/static/topology3D/js/graph_editor.js deleted file mode 100755 index 8343f05..0000000 --- a/static/topology3D/js/graph_editor.js +++ /dev/null @@ -1,1084 +0,0 @@ -/* - Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -if (typeof dreamer === 'undefined') { - var dreamer = {}; -} -var level = {} - -dreamer.GraphEditor = (function (global) { - 'use strict'; - - var DEBUG = true; - var SHIFT_BUTTON = 16; - var CANC_BUTTON = 46; - var default_link_color = "#888"; - var nominal_text_size = 15; - var nominal_stroke = 1.5; - var EventHandler = dreamer.Event; - // var IMAGE_PATH = "/static/assets/img/"; - - - - /** - * Constructor - */ - function GraphEditor(args) { - log("Constructor"); - this.eventHandler = new EventHandler(); - this.lastKeyDown = -1; - this._selected_node = undefined; - this._selected_link = undefined; - this._edit_mode = true; - this.filter_parameters = { - node: { - type: [], - group: [], - }, - link: { - group: [], - view: [], - } - }; - this.current_view_id = ''; - // graph data initailization - this.d3_graph = { - nodes: [], - links: [], - graph_parameters: {} - - }; - - - } - - - - GraphEditor.prototype.init = function (args) { - args = args || {} - var self = this; - this.width = 550//args.width || 500; - this.height = 550// args.height || 500; - this.forceSimulationActive = false; - - //FixMe - this.width = this.width - this.width * 0.007; - this.height = this.height - this.height * 0.07; - - //console.log("this.width", this.width, "this.height", this.height); - var min_zoom = 0.1; - var max_zoom = 7; - this._setupBehaviorsOnEvents(); - this._setupFiltersBehaviors(args); - - this.type_property = { - "unrecognized": { - "shape": d3.symbolCircle, - "color": "white", - "node_label_color": "black", - "size": 15 - }, - }; - - this.type_property_link = { - "unrecognized": { - "color": "#888", - //"color": "red", - }, - }; - - this.force = d3.forceSimulation() - .force("collide", d3.forceCollide().radius(40)) - .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) { - return d.id; - })) - .force("center", d3.forceCenter(this.width / 2, this.height / 2)); - - var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom]) - - var size = d3.scalePow().exponent(2) - .domain([1, 100]) - .range([8, 24]); - - this.svg = d3.select("#graph_ed_container").append("svg") - .attr("id", "graph_svg") - .attr("perserveAspectRatio", "xMinYMid") - .attr("width", this.width) - .attr("height", this.height); - - //End Arrow style - this.defs = this.svg.append("svg:defs"); - - this.defs.selectAll("marker") - .data(["unrecognized"]) // Different link/path types can be defined here - .enter().append("svg:marker") // This section adds in the arrows - .attr("id", String) - .attr("viewBox", "-5 -5 10 10") - .attr("refX", 13) //must be smarter way to calculate shift - .attr("refY", 0) - .attr("markerUnits", "userSpaceOnUse") - .attr("markerWidth", 12) - .attr("markerHeight", 12) - .attr("orient", "auto") - .append("path") - .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z") - .attr('fill', this.type_property_link['unrecognized']['color']); - - d3.select(window) - .on('keydown', function () { - log('keydown ' + d3.event.keyCode); - //d3.event.preventDefault(); - if (self.lastKeyDown !== -1) return; - self.lastKeyDown = d3.event.keyCode; - if (self.lastKeyDown === CANC_BUTTON && self._selected_node != undefined) { - self.removeNode(self._selected_node, null, showAlert); - } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link != undefined) { - self.removeLink(self._selected_link, null, showAlert); - } - - }) - .on('keyup', function () { - log('keyup' + self.lastKeyDown); - self.lastKeyDown = -1; - }); - var popup = this.svg.append("g") - .attr("id", "popup") - .attr("class", "popup") - .attr("opacity", "0") - .attr("transform", "translate(1 1)") - .call(d3.drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended)); - - function dragstarted(d) { - //d3.select(this).raise().classed("active", true); - } - - function dragged(d) { - //console.log(JSON.stringify(d)) - d3.select(this).attr("transform", function () { - return "translate("+d3.event.x+","+d3.event.y+")"; - - }) - } - - function dragended(d) { - //d3.select(this).classed("active", false); - } - - var chart = $("#graph_svg"); - this.aspect = chart.width() / chart.height(); - this.container = chart.parent(); - $(window).on("resize", function() { - - var palette_width = ($("#palette").length > 0) ? $("#palette").width() : 0; - var working_width = self.container.width() - palette_width; - self.width = (working_width < 0) ? 0 : working_width; - self.height = self.container.height(); - chart.attr("width", self.width); - chart.attr("height", self.height); - }).trigger("resize"); - - } - - - GraphEditor.prototype.get_d3_symbol = - function (myString) { - - switch (myString) { - case "circle": - return d3.symbolCircle; - break; - case "square": - return d3.symbolSquare; - break; - case "diamond": - return d3.symbolDiamond; - break; - case "triangle": - return d3.symbolTriangle; - break; - case "star": - return d3.symbolStar; - break; - case "cross": - return d3.symbolCross; - break; - default: - // if the string is not recognized - return d3.symbolCross; - //return d3.symbolCircleUnknown; - } - - } - - GraphEditor.prototype.get_name_from_d3_symbol = - function (mySymbol) { - switch (mySymbol) { - case d3.symbolCircle: - return "circle"; - break; - case d3.symbolSquare: - return "square"; - break; - case d3.symbolDiamond: - return "diamond"; - break; - case d3.symbolTriangle: - return "triangle"; - break; - case d3.symbolStar: - return "star"; - break; - case d3.symbolCross: - return "cross"; - break; - default: - // if the string is not recognized - return "unknown"; - //return d3.symbolCircleUnknown; - } - - } - - /** - * Start or Stop force layout - * @param {boolean} Required. Value true: start, false: stop - * @returns {boolean} - */ - GraphEditor.prototype.handleForce = function (start) { - if (start) - this.force.stop(); - this.forceSimulationActive = start; - this.node.each(function (d) { - d.fx = (start) ? null : d.x; - d.fy = (start) ? null : d.y; - }); - - if (start) - this.force.restart(); - - this.eventHandler.fire("force_status_changed_on", start); - }; - - /** - * Handle the parameters of basic filters: node type, view, group - * @param {Object} Required. - * - */ - GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) { - console.log("handleFiltersParams", filtersParams) - this.filter_parameters = (filtersParams != undefined) ? filtersParams : this.filter_parameters; - this.current_view_id = (this.filter_parameters != undefined && this.filter_parameters.link.view[0] != undefined) ? this.filter_parameters.link.view[0] : this.current_view_id - this.cleanAll(); - this.refresh(); - this.startForce(); - this.force.restart(); - this._deselectAllNodes(); - this.handleForce(this.forceSimulationActive); - if (!notFireEvent) - this.eventHandler.fire("filters_changed", filtersParams); - - }; - - /** - * Add a new node to the graph. - * @param {Object} Required. An object that specifies tha data of the new node. - * @returns {boolean} - */ - GraphEditor.prototype.addNode = function (args) { - if (args.id && args.info && args.info.type) { - args.fixed = true; - this.force.stop(); - this.cleanAll(); - this.d3_graph.nodes.push(args); - this.refresh(); - this.startForce(); - this.force.restart(); - this.handleForce(this.forceSimulationActive); - return true; - } - - return false; - - }; - - /** - * Update the data properties of the node - * @param {Object} Required. An object that specifies tha data of the node. - * @returns {boolean} - */ - GraphEditor.prototype.updateDataNode = function (args) { - - }; - - /** - * Remove a node from graph and related links. - * @param {String} Required. Id of node to remove. - * @returns {boolean} - */ - GraphEditor.prototype.removeNode = function (node) { - if (node != undefined) { - var node_id = node.id; - this.d3_graph['nodes'].forEach(function (n, index, object) { - if (n.id == node_id) { - object.splice(index, 1); - - } - - }); - //TODO trovare una metodo piu efficace - var self = this; - var links_to_remove = []; - this.d3_graph['links'].forEach(function (l, index, object) { - if (node_id === l.source.id || node_id === l.target.id) { - links_to_remove.push(index); - } - - }); - var links_removed = 0; - links_to_remove.forEach(function (l_index) { - self.d3_graph['links'].splice(l_index - links_removed, 1); - links_removed++; - }); - this.cleanAll(); - this.refresh(); - this.startForce(); - this.force.restart(); - - return true; - } - return false; - }; - - - /** - * Add a new link to graph. - * @param {Object} Required. An object that specifies tha data of the new Link. - * @returns {boolean} - */ - GraphEditor.prototype.addLink = function (link) { - console.log("addLink" + JSON.stringify(link)) - if (link.source && link.target) { - this.force.stop(); - this.cleanAll(); - this.d3_graph.links.push(link); - this.refresh(); - this.startForce(); - this.force.restart(); - return true; - } - - return false; - }; - - /** - * Remove a link from graph. - * @param {String} Required. The identifier of link to remove. - * @returns {boolean} - */ - GraphEditor.prototype.removeLink = function (link_id) { - var self = this; - if (link_id !== 'undefined') { - this.d3_graph['links'].forEach(function (l, index, object) { - if (link_id === l.index) { - object.splice(index, 1); - - self.cleanAll(); - self.refresh(); - self.startForce(); - self.force.restart(); - return true; - } - - }); - } - - return false; - }; - - - /** - * Force a refresh of GraphView - * @returns {} - */ - GraphEditor.prototype.refresh = function () { - - //log(data) - var self = this; - - this.link = this.svg - .selectAll() - .data(self.d3_graph.links - .filter(this.link_filter_cb) - ) - .enter().append("g") - .attr("class", "link cleanable") - .append("path") - .attr("class", "link") - .attr("class", "cleanable") - .style("stroke-width", nominal_stroke) - .style("stroke", function (d) { - return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color"); - }) - .attr("marker-end", function (d) { - if (!d.directed_edge) - return ''; - - var marker_url = (d.type_link) ? d.type_link : "unrecognized" - return (d.directed_edge ? "url(#" + marker_url + ")" : ''); - }); - - this.nodeContainer = this.svg - .selectAll() - .data(self.d3_graph.nodes - .filter(this.node_filter_cb)) - .enter() - .append("g") - // .attr("class", "nodosdads") - .attr("class", "node cleanable"); - - this.svg.selectAll('.node') - .data(self.d3_graph.nodes - .filter(this.node_filter_cb)) - - .filter(function (d) { - return (d.info.type == undefined) || (self._node_property_by_type(d.info.type, 'image', d) == undefined) - }) - - .append("svg:path") - .attr("d", d3.symbol() - .size(function (d) { - return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4; - }) - .type(function (d) { - // console.log(d.info.type, 'shape', self.current_view_id) - return (self._node_property_by_type(d.info.type, 'shape', d)); - }) - ) - .style("fill", function (d) { - return self._node_property_by_type(d.info.type, 'color', d); - }) - .attr("transform", function () { - return "rotate(-45)"; - - }) - .attr("stroke-width", 2.4) - - .attr("class", "node_path") - .attr("id", function (d) { - return "path_" + d.id; - }) - - .call(d3.drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended)); - - var figure_node = this.svg.selectAll('.node') - .data(self.d3_graph.nodes - .filter(this.node_filter_cb)) - - .filter(function (d) { - return self._node_property_by_type(d.info.type, 'image', d) != undefined - }); - - figure_node.append("svg:image") - .attr("xlink:href", function (d) { - return self._node_property_by_type(d.info.type, 'image', d) - }) - .attr("x", function (d) { - return -self._node_property_by_type(d.info.type, 'size', d) / 2 - }) - .attr("y", function (d) { - return -self._node_property_by_type(d.info.type, 'size', d) / 2 - }) - .attr("width", function (d) { - return self._node_property_by_type(d.info.type, 'size', d) - }) - .attr("height", function (d) { - return self._node_property_by_type(d.info.type, 'size', d) - }) - .style("stroke", "black") - .style("stroke-width", "1px") - - .attr("class", "node_path") - .attr("id", function (d) { - return "path_" + d.id; - }) - .call(d3.drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended)); - - figure_node.append("svg:path") - .attr("d", d3.symbol() - .size(function (d) { - return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4; - }) - .type(function (d) { - return (self.get_d3_symbol('circle')); - }) - ) - .style("fill", 'transparent') - .attr("transform", function () { - return "rotate(-45)"; - - }) - .attr("stroke-width", 2.4) - - .attr("class", "hidden_circle") - .attr("id", function (d) { - return "path_" + d.id; - }) - - .call(d3.drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended)); - - - - this.node = this.svg.selectAll('.node') - .data(self.d3_graph.nodes - .filter(this.node_filter_cb)).selectAll("image, path, circle"); - - - - this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"]) - .on("mouseover", self.behavioursOnEvents.nodes["mouseover"]) - .on("mouseout", self.behavioursOnEvents.nodes["mouseout"]) - .on('click', self.behavioursOnEvents.nodes["click"]) - .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]); - - this.link - .on("contextmenu", self.behavioursOnEvents.links["contextmenu"]) - .on("mouseover", self.behavioursOnEvents.links["mouseover"]) - .on('click', self.behavioursOnEvents.links["click"]) - .on("mouseout", self.behavioursOnEvents.links["mouseout"]); - - - - this.text = this.svg.selectAll(".node") - .data(self.d3_graph.nodes - .filter(this.node_filter_cb)) - .append("svg:text") - .attr("class", "nodetext") - .attr("class", "cleanable") - .attr("dy", function(d) { - if (self._node_property_by_type(d.info.type, 'image', d) == undefined) { - //shape - return "-5" - } - else { - //image - return (-self._node_property_by_type(d.info.type, 'size', d)/2).toString() - } - }) - .attr("pointer-events", "none") - .style("font-size", nominal_text_size + "px") - .style("font-family", "Lucida Console") - .style("fill", function (d) { - return self._node_property_by_type(d.info.type, 'node_label_color', d); - }) - .style("text-anchor", "middle") - .text(function (d) { - return d.id; - }); - - - - function dragstarted(d) { - d.draggednode = true; - if (!d3.event.active) self.force.alphaTarget(0.3).restart(); - d.fx = d.x; - d.fy = d.y; - - } - - function dragged(d) { - d.fx = d3.event.x; - d.fy = d3.event.y; - } - - function dragended(d) { - d.draggednode = false; - if (!d3.event.active) self.force.alphaTarget(0); - if (self.forceSimulationActive) { - d.fx = null; - d.fy = null; - } else { - d.fx = d.x; - d.fy = d.y; - self.force.stop(); - self.forceSimulationActive = false; - } - } - - - }; - - /** - * Start force layout on Graph. - * - */ - GraphEditor.prototype.startForce = function () { - //this.force.stop(); - var self = this - this.force - .nodes(this.d3_graph.nodes) - .on("tick", ticked); - - - this.force - .force("link") - .links(this.d3_graph.links); - - function ticked() { - self.node.attr("cx", function (d) { - return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x)); - }) - .attr("cy", function (d) { - return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y)); - }); - - self.link.attr("d", function (d) { - var dx = d.target.x - d.source.x, - dy = d.target.y - d.source.y, - dr = Math.sqrt(dx * dx + dy * dy); - return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y; - }); - - self.node.attr("transform", function (d) { - return "translate(" + d.x + "," + d.y + ")"; - }); - self.text.attr("transform", function (d) { - var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d) + 10; - return "translate(" + d.x + "," + label_pos_y + ")"; - }); - }; - - - - }; - - /** - * This method attaches an event handler. - * @param {String} Required. A String that specifies the name of the event. - * @param {Function} Required. Specifies the function to run when the event occurs. - * @returns {} - */ - GraphEditor.prototype.addListener = function (event_name, cb) { - this.eventHandler.addL(event_name, cb); - } - - /** - * This method removes an event handler that has been attached with the addListener() method. - * @param {String} Required. A String that specifies the name of the event to remove. - * @param {Function} Required. Specifies the function to remove. - * @returns {} - */ - GraphEditor.prototype.removeListener = function (event_name, cb) { - - } - - - GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) { - log("setNodeClass"); - var self = this; - this.svg.selectAll('.node').classed(class_name, false); - this.svg.selectAll('.node') - .classed(class_name, filter_cb); - } - - GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) { - log("setLinkClass"); - var self = this; - this.svg.selectAll('.link').classed(class_name, false); - this.svg.selectAll('.link') - .classed(class_name, filter_cb); - } - - GraphEditor.prototype.showNodeInfo = function(args){ - this.addLinesToPopup(args['node_info'], "Info about node selected") - this.handlePopupVisibility(true, 'right') - } - GraphEditor.prototype.addLinesToPopup = function(data, title) { - var self = this; - var index = 1; - var translate_y = 0; - var width_popup = 400; - var height_popup = 0; - - d3.selectAll(".popupcleanable").remove(); // clean - - var popupbg = d3.select(".popup").append("rect") - .attr("id", "popupbg") - .attr("class", "popup bg popupcleanable cleanable") - .attr("width", "400") - .attr("height", "0") - .attr("rx", 10) // set the x corner curve radius - .attr("ry", 10); // set the y corner curve radius - - - d3.select(".popup").append("svg:path") - .attr("d", d3.symbol() - .size(function (d) { - return 80 - }) - .type(function (d) { - console.log("popiup") - return (self.get_d3_symbol()); - }) - ) - .style("fill", 'red') - .attr("transform", function () { - return "translate(380,15) rotate(-45)"; - - }) - .attr("stroke-width", 2.4) - .attr("id", "close_popup") - .attr("class", "popupcleanable cleanable") - .on("click", function(d) { - self.handlePopupVisibility(false); - }); - - d3.select(".popup").append("text") - .attr("class", "popup title popupcleanable cleanable") - .attr("x", "10") - .attr("y", "20") - .text(title); - - for (var i in data) { - //console.log(i, data, data[i]) - //var typeofvalue = typeof data[i]; - var record = data[i]; - index = this._addRecordToPopup(i, record,index) - - } - - }; - - GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) { - //console.log("_addRecordToPopup", key, record, index) - var translate_y = 23 * index; - var summary = d3.select(".popup").append("g") - .attr("class", "popup summary d popupcleanable cleanable") - .attr("transform", "translate(10 " + translate_y + ")"); - if(Object.prototype.toString.call( record ) !== '[object Array]'){ //is a record simple key:value - //console.log(key, record) - var summary_g = summary.append("g"); - summary_g.append("rect") - .attr("class", "popup summary bg popupcleanable cleanable") - .attr("width", "380") - .attr("height", "20"); - - summary_g.append("text") - .attr("class", "popup summary popupcleanable cleanable") - .attr("x", (tab)? tab: 10) - .attr("y", "17") - .attr("width", "100") - .text(function(d){ - return key.toUpperCase() + ":"; - }); - - summary_g.append("text") - .attr("class", "popup summary popupcleanable cleanable") - .attr("x", "370") - .attr("y", "17") - .attr("text-anchor", "end") - .text(function(d){return record}); - } - else {//is a record simple complex: have a list of sub record key:value - //index ++; - this._addRecordToPopup(key, "", index) - for(var r in record){ - //console.log(i, r, record, record[r]) - for(var k in record[r]){ - //console.log(i, r, k, record[r][k]) - var curr_key = k; - var recordValue = record[r][k] - - index ++; - this._addRecordToPopup(curr_key, recordValue, index, 20) - } - } - - } - - translate_y = 30 * index++; - d3.select('#popupbg').attr("height", translate_y); - return index; - }; - - - - /** - * Remove all the graph objects from the view - */ - GraphEditor.prototype.cleanAll = function () { - this.svg.selectAll('.cleanable').remove(); - }; - - /** - * Internal functions - */ - - GraphEditor.prototype._node_property_by_type = function (type, property, node) { - //console.log(type, property, layer, group) - var unrecognized = function (ui_prop, property) { - return ui_prop['unrecognized'][property] - }; - - //type recognized - if (this.type_property[type]) { - - if (this.type_property[type]['property']) { - var filt_property = this.type_property[type]['property'] - return this.type_property[type][node.info[filt_property]][property] - } else { // type without property spec - - return this.type_property[type][property] - - } - - } else { //type unrecognized - return unrecognized(this.type_property, property) - } - - }; - - GraphEditor.prototype._link_property_by_type = function (type, property) { - //log(type + "-" + property) - if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) { - //if(property == "shape") - // log("dentro" + this.type_property[type][property]) - return this.type_property_link[type][property]; - } else { - return this.type_property_link['unrecognized'][property]; - } - - } - - - /** - * - * - * - */ - GraphEditor.prototype._setupFiltersBehaviors = function (args) { - - var self = this; - - this.node_filter_cb = args.node_filter_cb || function (d) { - - var cond_view = true, - cond_group = true; - //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group) - // check filter by node type - if (self.filter_parameters.node.type.length > 0) { - - if (self.filter_parameters.node.type.indexOf(d.info.type) < 0) - cond_view = false; - } - - // check filter by group - if (self.filter_parameters.node.group.length > 0) { - self.filter_parameters.node.group.forEach(function (group) { - if (d.info.group.indexOf(group) < 0) - cond_group = false; - }); - - - } - - - return cond_view && cond_group; - }; - - this.link_filter_cb = args.link_filter_cb || function (d) { - var cond_view = true, - cond_group = true; - - // check filter by view - if (self.filter_parameters.link.view.length > 0) { - self.filter_parameters.link.view.forEach(function (view) { - if (d.view.indexOf(view) < 0) - cond_view = false; - }); - } - - // check filter by group - if (self.filter_parameters.link.group.length > 0) { - self.filter_parameters.link.group.forEach(function (group) { - if (d.group.indexOf(group) < 0) - cond_group = false; - }); - } - return cond_view && cond_group; - }; - - }; - - /** - * - * - */ - GraphEditor.prototype._setupBehaviorsOnEvents = function () { - log("_setupBehaviorsOnEvents"); - var self = this; - this.behavioursOnEvents = { - 'nodes': { - 'click': function (d) { - d3.event.preventDefault(); - log('click', d); - if (self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) { - var source_id = self._selected_node.id; - var target_id = d.id; - log("--" + JSON.stringify(self.filter_parameters.link.view)); - var new_link = { - source: source_id, - target: target_id, - view: self.filter_parameters.link.view[0], - group: self.filter_parameters.link.group[0], - }; - self.addLink(new_link); - self._deselectAllNodes(); - } else { - self._selectNodeExclusive(this, d); - } - - }, - 'mouseover': function (d) { - - }, - 'mouseout': function (d) {}, - 'dblclick': function (d) { - d3.event.preventDefault(); - log('dblclick'); - }, - 'contextmenu': function (d, i) { - d3.event.preventDefault(); - log("contextmenu node"); - self.eventHandler.fire("right_click_node", d); - } - }, - 'links': { - 'click': function (event) { - - }, - 'dblclick': function (event) { - - } - } - }; - }; - - /** - * Deselect previously selected nodes - * - */ - GraphEditor.prototype._deselectAllNodes = function () { - log("_deselectAllNodes"); - this.node.classed("node_selected", false); - this._selected_node = undefined; - }; - - GraphEditor.prototype._deselectAllLinks = function () { - log("_deselectAllLinks"); - this.link.classed("link_selected", false).style('stroke-width', 2); - this._selected_link = undefined; - }; - /** - * Select node in exclusive mode - * @param {Object} Required. Element selected on click event - */ - GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) { - log("_selectNodeExclusive "); - var activeClass = "node_selected"; - var alreadyIsActive = d3.select(node_instance).classed(activeClass); - this._deselectAllNodes(); - this._deselectAllLinks(); - d3.select(node_instance).classed(activeClass, !alreadyIsActive); - this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__; - }; - - /** - * Select node in exclusive mode - * @param {Object} Required. Element selected on click event - */ - GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) { - log("_selectLinkExclusive "); - var activeClass = "link_selected"; - var alreadyIsActive = d3.select(link_instance).classed(activeClass); - this._deselectAllNodes(); - this._deselectAllLinks(); - d3.select(link_instance).classed(activeClass, !alreadyIsActive); - d3.select(link_instance).style('stroke-width', 4) - this._selected_link = link_instance.__data__; - }; - - /** - * Callback to resize SVG element on window resize - */ - GraphEditor.prototype.resizeSvg = function (width, height) { - log("resizeSvg"); - //log(event); - this.width = width || this.width; - this.height = height || this.height; - this.svg.attr('width', width); - this.svg.attr('height', height); - - } - - GraphEditor.prototype.handlePopupVisibility = function(visible, side) { - var opacity = (visible) ? 1 : 0; - - var translate_op = (side == "left") ? "translate(50 50)" : "translate("+(this.width - 450).toString()+" 50)"; - - if (!visible) { - d3.selectAll(".popupcleanable").remove(); - d3.select(".popup") - .attr("transform", "translate(-1 -1)"); - } else { - d3.select(".popup") - .attr("transform", translate_op); - } - d3.select(".popup").attr("opacity", opacity); - }; - - GraphEditor.prototype.refreshGraphParameters = function (graphParameters) { - this.eventHandler.fire("refresh_graph_parameters", graphParameters); - } - - /** - * Log utility - */ - function log(text) { - if (DEBUG) - console.log("::GraphEditor::", text); - } - - - - return GraphEditor; - - -}(this)); - -if (typeof module === 'object') { - module.exports = dreamer.GraphEditor; -} \ No newline at end of file diff --git a/static/topology3D/js/graph_request.js b/static/topology3D/js/graph_request.js deleted file mode 100644 index 055fc1c..0000000 --- a/static/topology3D/js/graph_request.js +++ /dev/null @@ -1,318 +0,0 @@ -/* - Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -if (typeof dreamer === 'undefined') { - var dreamer = {}; -} -var level = {} - -dreamer.GraphRequests = (function(global) { - 'use strict'; - - var DEBUG = true; - - GraphRequests.prototype.constructor = GraphRequests; - - /** - * Constructor - */ - function GraphRequests(args) { - - - } - - GraphRequests.prototype.addNode = function(args, choice, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - - data = args_to_formdata(args, data); - - //FIXME questo metodo dovrebbere essere generico - if(args.existing_element) - data.append('existing_element', args.existing_element ? args.existing_element : false) - //if (choice) - // data.append('choice', choice); - $.ajax({ - url: "addelement", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - GraphRequests.prototype.removeNode = function(args, choice, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - - data = args_to_formdata(args, data); - - $.ajax({ - url: "removeelement", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(); - }, - error: function(result) { - if (error) - error(result); - } - }); - }; - - GraphRequests.prototype.getNodeOverview = function(args, success, error) { - - var params = jQuery.param(args) - console.log("params", params) - $.ajax({ - url: "overviewelement?"+params, - type: 'GET', - success: function(result) { - if (success) - success(result); - }, - error: function(result) { - if (error) - error(result); - } - }); - }; - - GraphRequests.prototype.addLink = function(args, choice, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - data = args_to_formdata(args, data); - - //data.append('destination', JSON.stringify(destination)); - if (choice) - data.append('choice', choice); - //if(link.desc_id) - // data.append('element_desc_id', link.desc_id || ''); - $.ajax({ - url: "addlink", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - GraphRequests.prototype.removeLink = function(args, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - data = args_to_formdata(args, data); - - $.ajax({ - url: "removelink", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - // - GraphRequests.prototype.getAvailableNodes = function(args, success, error){ - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - $.ajax({ - url: "availablenodes?layer="+args.layer, - type: 'GET', - success: function(result) { - if (success) - success(result); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - } - - GraphRequests.prototype.savePositions = function(positions, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - data.append('positions', JSON.stringify(positions)); - $.ajax({ - url: "positions", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - /* START ETSI methods */ - GraphRequests.prototype.addVnffg = function(args, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - /* data.append('group_id', args.info.group[0]); - data.append('element_id', args.id); - data.append('element_type', args.info.type);*/ - data = args_to_formdata(args, data); - $.ajax({ - url: "addelement", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(result); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - GraphRequests.prototype.addNodeToVnffg = function(args, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - /* data.append('group_id', args.info.group[0]); - data.append('element_id', args.id); - data.append('element_type', args.info.type); - data.append('vnffg_id', args.vnffgId);*/ - data = args_to_formdata(args, data); - - $.ajax({ - url: "addnodetovnffg", - type: 'POST', - data: data, - cache: false, - contentType: false, - processData: false, - success: function(result) { - if (success) - success(result); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - }; - - GraphRequests.prototype.getUnusedVnf = function(nsd_id, success, error) { - var data = new FormData(); - data.append('csrfmiddlewaretoken', this.getCookie('csrftoken')); - $.ajax({ - url: "unusedvnf/" + nsd_id, - type: 'GET', - success: function(result) { - if (success) - success(result); - }, - error: function(result) { - if (error) - error(result); - log("some error: " + result); - } - }); - - }; - /* END ETSI methods */ - - GraphRequests.prototype.getCookie = function(name) { - var cookieValue = null; - if (document.cookie && document.cookie !== '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) === (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; - }; - - function args_to_formdata(args, form_data){ - for ( var key in args ) { - form_data.append(key, args[key]); - } - return form_data; - }; - - - /** - * Log utility - */ - function log(text) { - if (DEBUG) - console.log("::GraphRequests::", text); - } - - return GraphRequests; - - -}(this)); - -if (typeof module === 'object') { - module.exports = dreamer.GraphRequests; -} diff --git a/static/topology3D/js/model_graph_editor.js b/static/topology3D/js/model_graph_editor.js deleted file mode 100644 index be092a4..0000000 --- a/static/topology3D/js/model_graph_editor.js +++ /dev/null @@ -1,514 +0,0 @@ -/* - Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -if (typeof dreamer === 'undefined') { - var dreamer = {}; -} -var level = {} - -dreamer.ModelGraphEditor = (function (global) { - 'use strict'; - - var DEBUG = true; - var SHIFT_BUTTON = 16; - var IMAGE_PATH = "/static/assets/img/"; - var GUI_VERSION = "v1"; - - - ModelGraphEditor.prototype = new dreamer.GraphEditor(); - ModelGraphEditor.prototype.constructor = ModelGraphEditor; - ModelGraphEditor.prototype.parent = dreamer.GraphEditor.prototype; - - /** - * Constructor - */ - function ModelGraphEditor(args) { - - log("Constructor"); - - } - - - ModelGraphEditor.prototype.init = function (args) { - this.parent.init.call(this, args); - - if (args.gui_properties[GUI_VERSION] != undefined) { - args.gui_properties = args.gui_properties[GUI_VERSION]; - } - - this.desc_id = args.desc_id || undefined; - this.type_property = {}; - this.type_property["unrecognized"] = args.gui_properties["default"]; - this.type_property["unrecognized"]["default_node_label_color"] = args.gui_properties["default"]["label_color"]; - //this.type_property["unrecognized"]["shape"] = d3.symbolCross; - this._edit_mode = (args.edit_mode != undefined) ? args.edit_mode : this._edit_mode; - - Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) { - console.log(key + " ####") - this.type_property[key] = args.gui_properties["nodes"][key]; - if ( this.type_property[key]['property'] != undefined){ - for(var c_prop in this.type_property[key]){ - if(c_prop != 'property'){ - - this.type_property[key][c_prop]['shape'] = this.parent.get_d3_symbol(this.type_property[key][c_prop]['shape']); - if(this.type_property[key][c_prop]["image"] != undefined){ - this.type_property[key][c_prop]["image"] = IMAGE_PATH + this.type_property[key][c_prop]["image"] - } - } - } - } - else{ - - this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]); - if (this.type_property[key]["image"] != undefined) { - this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"]; - } - } - - - - }, this); - if(args.gui_properties["edges"]){ - this.type_property_link = args.gui_properties["edges"]; - var self = this; - var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link)) - this.defs.selectAll("marker") - .data(link_types) - .enter() - .append("svg:marker") // This section adds in the arrows - .attr("id", function(d){ - return d; - }) - .attr("viewBox", "-5 -5 10 10") - .attr("refX", 13) /*must be smarter way to calculate shift*/ - .attr("refY", 0) - .attr("markerUnits", "userSpaceOnUse") - .attr("markerWidth", 12) - .attr("markerHeight", 12) - .attr("orient", "auto") - .append("path") - .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z") - .attr('fill', function(d){ - return self.type_property_link[d].color; - }); - } - - this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined; - - var self = this; - var data_url = (args.data_url) ? args.data_url : "graph_data/"; - if (!args.graph_data) { - d3.json(data_url, function (error, data) { - //console.log(JSON.stringify(data)) - self.d3_graph.nodes = data.vertices; - self.d3_graph.links = data.edges; - self.d3_graph.graph_parameters = data.graph_parameters; - self.model = data.model; - self.refreshGraphParameters(self.d3_graph.graph_parameters); - self.refresh(); - self.startForce(); - //if(args.filter_base != undefined) - - setTimeout(function () { - //self.handleForce(self.forceSimulationActive); - //var f_t = {"node":{"type":[],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":[""]}} - //var f_t ={"node":{"type":["vnf_vl","vnf_ext_cp","vnf_vdu_cp","vnf_vdu","vnf_click_vdu"],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":["vnf"]}} - self.handleFiltersParams(args.filter_base); - //self.handleFiltersParams(f_t); - //console.log(JSON.stringify(args.filter_base)) - //console.log(self.d3_graph.nodes.length) - //console.log(JSON.stringify(self.d3_graph.nodes)) - //self.d3_graph.nodes.forEach(function(key, index){ - //console.log(key, index); - //}) - }, 500); - - }); - } else { - this.updateData(args) - } - } - - /** - * Update data of the graph. - * @param {Object} Required. An object that specifies tha data of the new node. - * @returns {boolean} - */ - ModelGraphEditor.prototype.updateData = function (args) { - console.log("updateData") - this.d3_graph.nodes = args.graph_data.vertices; - this.d3_graph.links = args.graph_data.edges; - this.d3_graph.graph_parameters = args.graph_parameters; - this.model = args.model; - this.refreshGraphParameters(this.d3_graph.graph_parameters); - this.refresh(); - this.startForce(); - //if(args.filter_base != undefined) - - //if(args.filter_base){ - var self = this; - setTimeout(function () { - self.handleForce(true); - self.handleFiltersParams(args.filter_base); - }, 500); - //} - } - - /** - * Add a new node to the graph. - * @param {Object} Required. An object that specifies tha data of the new node. - * @returns {boolean} - */ - ModelGraphEditor.prototype.addNode = function (node, success, error) { - var self = this; - var current_layer = self.getCurrentView(); - var node_type = node.info.type; - - if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) { - if (self.model.layer[current_layer].nodes[node_type].addable.callback) { - var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class; - var controller = new dreamer[c](); - controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function () { - self.parent.addNode.call(self, node); - success && success(); - }, error); - - } else { - - log('addNode: callback undefined in model spec.'); - error && error("You can't add a " + node.info.type + ", callback undefined."); - } - } else { - //FIXME Error handling???? - log("You can't add a " + node.info.type + " in a current layer " + current_layer); - error && error("You can't add a " + node.info.type + " in a current layer " + current_layer); - } - }; - - - - /** - * Update the data properties of the node - * @param {Object} Required. An object that specifies tha data of the node. - * @returns {boolean} - */ - ModelGraphEditor.prototype.updateDataNode = function (args) { - //FIXME updating a node properties need commit to server side! - this.parent.updateDataNode.call(this, args); - }; - - /** - * Remove a node from graph and related links. - * @param {String} Required. Id of node to remove. - * @returns {boolean} - */ - ModelGraphEditor.prototype.removeNode = function (node, success, error) { - console.log('removeNode', JSON.stringify(node)) - var self = this; - var current_layer = self.getCurrentView(); - var node_type = node.info.type; - if (node.info.desc_id == undefined){ - node.info.desc_id = self.desc_id; - } - if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) { - if (self.model.layer[current_layer].nodes[node_type].removable.callback) { - var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class; - var controller = new dreamer[c](); - controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () { - self.parent.removeNode.call(self, node); - success && success(); - }, error); - } else { - - log('removeNode: callback undefined in model spec.'); - error && error("You can't remove a " + node.info.type + ", callback undefined."); - } - } else { - //FIXME we need to manage alert in a different way: FAILBACK - log("You can't remove a " + node.info.type); - error && error("You can't remove a " + node.info.type); - } - }; - - /** - * Add a new link to graph. - * @param {Object} Required. An object that specifies tha data of the new Link. - * @returns {boolean} - */ - ModelGraphEditor.prototype.addLink = function (s, d, success, error) { - var self = this; - var source_id = s.id; - var target_id = d.id; - var source_type = s.info.type; - var destination_type = d.info.type; - var link = { - source: s, - target: d, - view: this.filter_parameters.link.view[0], - group: this.filter_parameters.link.group, - desc_id: this.desc_id - }; - log("addLink: " + JSON.stringify(link)) - var current_layer = self.getCurrentView() - if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) { - - if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) { - var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback; - console.log(callback, self.model.callback) - var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false; - link.directed_edge = direct_edge; - var c = self.model.callback[callback].class; - var controller = new dreamer[c](); - controller[callback](self, link, function () { - self._deselectAllNodes(); - self.parent.addLink.call(self, link); - if (success) - success(); - }, error); - } else { - log('addLink: callback undefined in model spec.'); - error && error("You can't add a link, callback undefined."); - } - - } else { - //FIXME we need to manage alert in a different way: FAILBACK - log("You can't link a " + source_type + " with a " + destination_type); - - error && error("You can't link a " + source_type + " with a " + destination_type); - } - }; - - /** - * Remove a link from graph. - * @param {String} Required. The identifier of link to remove. - * @returns {boolean} - */ - ModelGraphEditor.prototype.removeLink = function (link, success, error) { - var self = this; - var s = link.source; - var d = link.target; - var source_type = s.info.type; - var destination_type = d.info.type; - var current_layer = self.getCurrentView() - if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] && - self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable - ) { - if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) { - var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback; - var c = self.model.callback[callback].class; - var controller = new dreamer[c](); - controller[callback](self, link, function () { - self._deselectAllNodes(); - self._deselectAllLinks(); - self.parent.removeLink.call(self, link.index); - success && success(); - }, error); - } else { - log('removeLink: callback undefined in model spec.'); - error && error("You can't remove a link, callback undefined."); - } - - } else { - //FIXME we need to manage alert in a different way: FAILBACK - log("You can't delete the link"); - error && error("You can't delete the link"); - } - - - }; - - - ModelGraphEditor.prototype.savePositions = function (data) { - var vertices = {} - this.node.each(function (d) { - vertices[d.id] = {}; - vertices[d.id]['x'] = d.x; - vertices[d.id]['y'] = d.y; - }); - new dreamer.GraphRequests().savePositions({ - 'vertices': vertices - }); - - }; - - /** - * Internal functions - */ - - /** - * - * - */ - - ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) { - - var self = this; - var contextMenuLinksAction = [{ - title: 'Delete Link', - action: function (elm, link, i) { - self.removeLink(link, null, showAlert); - }, - edit_mode: true - }]; - var contextMenuNodesAction = [{ - title: 'Edit', - action: function (elm, d, i) { - if (d.info.type != undefined) { - self.eventHandler.fire("edit_descriptor", self.project_id, d); - } - }, - nodes: [], - edit_mode: true - }, - { - title: 'Delete', - action: function (elm, d, i) { - self.removeNode(d, null, showAlert); - }, - edit_mode: true - } - - ]; - if(this.customBehavioursOnEvents){ - contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes); - } - - - if ( self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) { - for (var i in self.model.layer[layer].action.node) { - var action = self.model.layer[layer].action.node[i] - contextMenuNodesAction.push({ - title: action.title, - action: function (elm, d, i) { - var callback = action.callback; - var c = self.model.callback[callback].class; - var controller = new dreamer[c](); - var args = { - elm: elm, - d: d, - i: i - } - - controller[callback](self, args); - }, - edit_mode: (action.edit_mode != undefined) ? action.edit_mode: undefined - }); - } - } - - this.behavioursOnEvents = { - 'nodes': { - 'click': function (d) { - - d3.event.preventDefault(); - - if (self._edit_mode && self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) { - self.addLink(self._selected_node, d, null, showAlert); - } else { - self._selectNodeExclusive(this, d); - } - - }, - 'mouseover': function (d) { - self.link.style('stroke-width', function (l) { - if (d === l.source || d === l.target) - return 4; - else - return 2; - }); - }, - 'mouseout': function (d) { - self.link.style('stroke-width', 2); - }, - 'contextmenu': d3.contextMenu(contextMenuNodesAction, { - 'edit_mode': self._edit_mode, - 'layer': layer, - 'type_object': 'node' - }) - }, - 'links': { - 'click': function (d) { - self._selectLinkExclusive(this, d); - - }, - 'dblclick': function (event) { - - }, - 'mouseover': function (d) { - d3.select(this).style('stroke-width', 4); - }, - 'mouseout': function (d) { - if (d != self._selected_link) - d3.select(this).style('stroke-width', 2); - }, - 'contextmenu': d3.contextMenu(contextMenuLinksAction, { - 'edit_mode': self._edit_mode, - 'layer': layer, - 'type_object': 'link' - }) - } - } - }; - - ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) { - - this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent); - this._setupBehaviorsOnEvents(filtersParams.link.view[0]); - }; - - ModelGraphEditor.prototype.getAvailableNodes = function () { - log('getAvailableNodes'); - log(this.model) - if (this.model && this.model.layer[this.getCurrentView()] != undefined) - return this.model.layer[this.getCurrentView()].nodes; - return []; - } - - - ModelGraphEditor.prototype.exploreLayer = function (args) { - - }; - - ModelGraphEditor.prototype.getTypeProperty = function () { - return this.type_property; - }; - - ModelGraphEditor.prototype.getCurrentGroup = function () { - return this.filter_parameters.node.group[0]; - - } - - ModelGraphEditor.prototype.getCurrentView = function () { - return this.filter_parameters.link.view[0]; - - } - /** - * Log utility - */ - function log(text) { - if (DEBUG) - console.log("::ModelGraphEditor::", text); - } - - - - return ModelGraphEditor; - - -}(this)); - -if (typeof module === 'object') { - module.exports = dreamer.ModelGraphEditor; -} \ No newline at end of file