3c275ddb5db30edd64bae36fd6968ebb4b34c293
[osm/LW-UI.git] / static / topology3D / js / model_graph_editor.js
1 /*
2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 if (typeof dreamer === 'undefined') {
17 var dreamer = {};
18 }
19 var level = {}
20
21 dreamer.ModelGraphEditor = (function (global) {
22 'use strict';
23
24 var DEBUG = true;
25 var SHIFT_BUTTON = 16;
26 var IMAGE_PATH = "/static/assets/img/";
27 var GUI_VERSION = "v1";
28
29
30 ModelGraphEditor.prototype = new dreamer.GraphEditor();
31 ModelGraphEditor.prototype.constructor = ModelGraphEditor;
32 ModelGraphEditor.prototype.parent = dreamer.GraphEditor.prototype;
33
34 /**
35 * Constructor
36 */
37 function ModelGraphEditor(args) {
38
39 log("Constructor");
40
41 }
42
43
44 ModelGraphEditor.prototype.init = function (args) {
45 this.parent.init.call(this, args);
46
47 if (args.gui_properties[GUI_VERSION] != undefined) {
48 args.gui_properties = args.gui_properties[GUI_VERSION];
49 }
50
51 this.desc_id = args.desc_id || undefined;
52 this.type_property = {};
53 this.type_property["unrecognized"] = args.gui_properties["default"];
54 this.type_property["unrecognized"]["default_node_label_color"] = args.gui_properties["default"]["label_color"];
55 //this.type_property["unrecognized"]["shape"] = d3.symbolCross;
56 this._edit_mode = (args.edit_mode != undefined) ? args.edit_mode : this._edit_mode;
57
58 Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) {
59 this.type_property[key] = args.gui_properties["nodes"][key];
60 if ( this.type_property[key]['property'] != undefined){
61 for(var c_prop in this.type_property[key]){
62 if(c_prop != 'property'){
63
64 this.type_property[key][c_prop]['shape'] = this.parent.get_d3_symbol(this.type_property[key][c_prop]['shape']);
65 if(this.type_property[key][c_prop]["image"] != undefined){
66 this.type_property[key][c_prop]["image"] = IMAGE_PATH + this.type_property[key][c_prop]["image"]
67 }
68 }
69 }
70 }
71 else{
72 this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]);
73 if (this.type_property[key]["image"] != undefined) {
74 this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"];
75 }
76 }
77
78
79
80 }, this);
81 if(args.gui_properties["edges"]){
82 this.type_property_link = args.gui_properties["edges"];
83 var self = this;
84 var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link))
85 this.defs.selectAll("marker")
86 .data(link_types)
87 .enter()
88 .append("svg:marker") // This section adds in the arrows
89 .attr("id", function(d){
90 return d;
91 })
92 .attr("viewBox", "-5 -5 10 10")
93 .attr("refX", 13) /*must be smarter way to calculate shift*/
94 .attr("refY", 0)
95 .attr("markerUnits", "userSpaceOnUse")
96 .attr("markerWidth", 12)
97 .attr("markerHeight", 12)
98 .attr("orient", "auto")
99 .append("path")
100 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
101 .attr('fill', function(d){
102 return self.type_property_link[d].color;
103 });
104 }
105
106 this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined;
107
108 var self = this;
109 var data_url = (args.data_url) ? args.data_url : "graph_data/";
110 if (!args.graph_data) {
111 d3.json(data_url, function (error, data) {
112 //console.log(JSON.stringify(data))
113 self.d3_graph.nodes = data.vertices;
114 self.d3_graph.links = data.edges;
115 self.d3_graph.graph_parameters = data.graph_parameters;
116 self.model = data.model;
117 self.refreshGraphParameters(self.d3_graph.graph_parameters);
118 self.refresh();
119 self.startForce();
120 //if(args.filter_base != undefined)
121
122 setTimeout(function () {
123 //self.handleForce(self.forceSimulationActive);
124 //var f_t = {"node":{"type":[],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":[""]}}
125 //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"]}}
126 self.handleFiltersParams(args.filter_base);
127 //self.handleFiltersParams(f_t);
128 //console.log(JSON.stringify(args.filter_base))
129 //console.log(self.d3_graph.nodes.length)
130 //console.log(JSON.stringify(self.d3_graph.nodes))
131 //self.d3_graph.nodes.forEach(function(key, index){
132 //console.log(key, index);
133 //})
134 }, 500);
135
136 });
137 } else {
138 this.updateData(args)
139 }
140 }
141
142 /**
143 * Update data of the graph.
144 * @param {Object} Required. An object that specifies tha data of the new node.
145 * @returns {boolean}
146 */
147 ModelGraphEditor.prototype.updateData = function (args) {
148 console.log("updateData")
149 this.d3_graph.nodes = args.graph_data.vertices;
150 this.d3_graph.links = args.graph_data.edges;
151 this.d3_graph.graph_parameters = args.graph_parameters;
152 this.model = args.model;
153 this.refreshGraphParameters(this.d3_graph.graph_parameters);
154 this.refresh();
155 this.startForce();
156 //if(args.filter_base != undefined)
157
158 //if(args.filter_base){
159 var self = this;
160 setTimeout(function () {
161 self.handleForce(true);
162 self.handleFiltersParams(args.filter_base);
163 }, 500);
164 //}
165 }
166
167 /**
168 * Add a new node to the graph.
169 * @param {Object} Required. An object that specifies tha data of the new node.
170 * @returns {boolean}
171 */
172 ModelGraphEditor.prototype.addNode = function (node, success, error) {
173 var self = this;
174 var current_layer = self.getCurrentView();
175 var node_type = node.info.type;
176
177 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) {
178 if (self.model.layer[current_layer].nodes[node_type].addable.callback) {
179 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class;
180 var controller = new dreamer[c]();
181 controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function () {
182 self.parent.addNode.call(self, node);
183 success && success();
184 }, error);
185
186 } else {
187
188 log('addNode: callback undefined in model spec.');
189 error && error("You can't add a " + node.info.type + ", callback undefined.");
190 }
191 } else {
192 //FIXME Error handling????
193 log("You can't add a " + node.info.type + " in a current layer " + current_layer);
194 error && error("You can't add a " + node.info.type + " in a current layer " + current_layer);
195 }
196 };
197
198
199
200 /**
201 * Update the data properties of the node
202 * @param {Object} Required. An object that specifies tha data of the node.
203 * @returns {boolean}
204 */
205 ModelGraphEditor.prototype.updateDataNode = function (args) {
206 //FIXME updating a node properties need commit to server side!
207 this.parent.updateDataNode.call(this, args);
208 };
209
210 /**
211 * Remove a node from graph and related links.
212 * @param {String} Required. Id of node to remove.
213 * @returns {boolean}
214 */
215 ModelGraphEditor.prototype.removeNode = function (node, success, error) {
216 console.log('removeNode', JSON.stringify(node))
217 var self = this;
218 var current_layer = self.getCurrentView();
219 var node_type = node.info.type;
220 if (node.info.desc_id == undefined){
221 node.info.desc_id = self.desc_id;
222 }
223 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
224 if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
225 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
226 var controller = new dreamer[c]();
227 controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () {
228 self.parent.removeNode.call(self, node);
229 success && success();
230 }, error);
231 } else {
232
233 log('removeNode: callback undefined in model spec.');
234 error && error("You can't remove a " + node.info.type + ", callback undefined.");
235 }
236 } else {
237 //FIXME we need to manage alert in a different way: FAILBACK
238 log("You can't remove a " + node.info.type);
239 error && error("You can't remove a " + node.info.type);
240 }
241 };
242
243 /**
244 * Add a new link to graph.
245 * @param {Object} Required. An object that specifies tha data of the new Link.
246 * @returns {boolean}
247 */
248 ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
249 var self = this;
250 var source_id = s.id;
251 var target_id = d.id;
252 var source_type = s.info.type;
253 var destination_type = d.info.type;
254 var link = {
255 source: s,
256 target: d,
257 view: this.filter_parameters.link.view[0],
258 group: this.filter_parameters.link.group,
259 desc_id: this.desc_id
260 };
261 log("addLink: " + JSON.stringify(link))
262 var current_layer = self.getCurrentView()
263 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]) {
264
265 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
266 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
267 console.log(callback, self.model.callback)
268 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;
269 link.directed_edge = direct_edge;
270 var c = self.model.callback[callback].class;
271 var controller = new dreamer[c]();
272 controller[callback](self, link, function () {
273 self._deselectAllNodes();
274 self.parent.addLink.call(self, link);
275 if (success)
276 success();
277 }, error);
278 } else {
279 log('addLink: callback undefined in model spec.');
280 error && error("You can't add a link, callback undefined.");
281 }
282
283 } else {
284 //FIXME we need to manage alert in a different way: FAILBACK
285 log("You can't link a " + source_type + " with a " + destination_type);
286
287 error && error("You can't link a " + source_type + " with a " + destination_type);
288 }
289 };
290
291 /**
292 * Remove a link from graph.
293 * @param {String} Required. The identifier of link to remove.
294 * @returns {boolean}
295 */
296 ModelGraphEditor.prototype.removeLink = function (link, success, error) {
297 var self = this;
298 var s = link.source;
299 var d = link.target;
300 var source_type = s.info.type;
301 var destination_type = d.info.type;
302 var current_layer = self.getCurrentView()
303 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] &&
304 self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
305 ) {
306 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
307 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
308 var c = self.model.callback[callback].class;
309 var controller = new dreamer[c]();
310 controller[callback](self, link, function () {
311 self._deselectAllNodes();
312 self._deselectAllLinks();
313 self.parent.removeLink.call(self, link.index);
314 success && success();
315 }, error);
316 } else {
317 log('removeLink: callback undefined in model spec.');
318 error && error("You can't remove a link, callback undefined.");
319 }
320
321 } else {
322 //FIXME we need to manage alert in a different way: FAILBACK
323 log("You can't delete the link");
324 error && error("You can't delete the link");
325 }
326
327
328 };
329
330
331 ModelGraphEditor.prototype.savePositions = function (data) {
332 var vertices = {}
333 this.node.each(function (d) {
334 vertices[d.id] = {}
335 vertices[d.id]['x'] = d.x;
336 vertices[d.id]['y'] = d.y;
337 });
338 new dreamer.GraphRequests().savePositions({
339 'vertices': vertices
340 });
341
342 }
343
344 /**
345 * Internal functions
346 */
347
348 /**
349 *
350 *
351 */
352
353 ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
354
355 var self = this;
356 var contextMenuLinksAction = [{
357 title: 'Delete Link',
358 action: function (elm, link, i) {
359 self.removeLink(link, null, showAlert);
360 },
361 edit_mode: true
362 }];
363 var contextMenuNodesAction = [{
364 title: 'Edit',
365 action: function (elm, d, i) {
366 if (d.info.type != undefined) {
367 self.eventHandler.fire("edit_descriptor", self.project_id, d);
368 }
369 },
370 nodes: [],
371 edit_mode: true
372 },
373 {
374 title: 'Delete',
375 action: function (elm, d, i) {
376 self.removeNode(d, null, showAlert);
377 },
378 edit_mode: true
379 },
380
381 ];
382 if(this.customBehavioursOnEvents){
383 contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
384 }
385
386
387 if ( self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
388 for (var i in self.model.layer[layer].action.node) {
389 var action = self.model.layer[layer].action.node[i]
390 contextMenuNodesAction.push({
391 title: action.title,
392 action: function (elm, d, i) {
393 var callback = action.callback;
394 var c = self.model.callback[callback].class;
395 var controller = new dreamer[c]();
396 var args = {
397 elm: elm,
398 d: d,
399 i: i
400 }
401
402 controller[callback](self, args);
403 },
404 edit_mode: (action.edit_mode != undefined) ? action.edit_mode: undefined
405 });
406 }
407 }
408
409 this.behavioursOnEvents = {
410 'nodes': {
411 'click': function (d) {
412
413 d3.event.preventDefault();
414
415 if (self._edit_mode && self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) {
416 self.addLink(self._selected_node, d, null, showAlert);
417 } else {
418 self._selectNodeExclusive(this, d);
419 }
420
421 },
422 'mouseover': function (d) {
423 self.link.style('stroke-width', function (l) {
424 if (d === l.source || d === l.target)
425 return 4;
426 else
427 return 2;
428 });
429 },
430 'mouseout': function (d) {
431 self.link.style('stroke-width', 2);
432 },
433 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
434 'edit_mode': self._edit_mode,
435 'layer': layer,
436 'type_object': 'node'
437 })
438 },
439 'links': {
440 'click': function (d) {
441 self._selectLinkExclusive(this, d);
442
443 },
444 'dblclick': function (event) {
445
446 },
447 'mouseover': function (d) {
448 d3.select(this).style('stroke-width', 4);
449 },
450 'mouseout': function (d) {
451 if (d != self._selected_link)
452 d3.select(this).style('stroke-width', 2);
453 },
454 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
455 'edit_mode': self._edit_mode,
456 'layer': layer,
457 'type_object': 'link'
458 })
459 }
460 }
461 };
462
463 ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
464
465 this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
466 this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
467 };
468
469 ModelGraphEditor.prototype.getAvailableNodes = function () {
470 log('getAvailableNodes');
471 log(this.model)
472 if (this.model && this.model.layer[this.getCurrentView()] != undefined)
473 return this.model.layer[this.getCurrentView()].nodes;
474 return [];
475 }
476
477
478 ModelGraphEditor.prototype.exploreLayer = function (args) {
479
480 };
481
482 ModelGraphEditor.prototype.getTypeProperty = function () {
483 return this.type_property;
484 };
485
486 ModelGraphEditor.prototype.getCurrentGroup = function () {
487 return this.filter_parameters.node.group[0];
488
489 }
490
491 ModelGraphEditor.prototype.getCurrentView = function () {
492 return this.filter_parameters.link.view[0];
493
494 }
495 /**
496 * Log utility
497 */
498 function log(text) {
499 if (DEBUG)
500 console.log("::ModelGraphEditor::", text);
501 }
502
503
504
505 return ModelGraphEditor;
506
507
508 }(this));
509
510 if (typeof module === 'object') {
511 module.exports = dreamer.ModelGraphEditor;
512 }