2018-03-21 17:24:52 +01:00
|
|
|
/*=============
|
|
|
|
* GLOBAL VARS
|
|
|
|
* ============*/
|
|
|
|
var eventGraph;
|
|
|
|
var dataHandler;
|
|
|
|
var mispInteraction;
|
2018-03-19 09:44:25 +01:00
|
|
|
var nodes = new vis.DataSet();
|
|
|
|
var edges = new vis.DataSet();
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
var typeaheadData;
|
|
|
|
var scope_id = $('#eventgraph_network').data('event-id');
|
|
|
|
var container = document.getElementById('eventgraph_network');
|
|
|
|
var user_manipulation = $('#eventgraph_network').data('user-manipulation');
|
2018-03-26 12:18:56 +02:00
|
|
|
var root_id_attr = "rootNode:attribute"
|
|
|
|
var root_id_object = "rootNode:object"
|
|
|
|
var root_node_x_pos = 800;
|
|
|
|
var cluster_expand_threshold = 100;
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
/*=========
|
|
|
|
* CLASSES
|
|
|
|
* ========*/
|
|
|
|
// network class (handle the event graph manipulation and events)
|
|
|
|
class EventGraph {
|
|
|
|
constructor(network_options, nodes, edges) {
|
2018-03-21 17:55:14 +01:00
|
|
|
// FIXME: Do the mapping between meta-catory and fa-icons.
|
|
|
|
// Should be replaced later on.
|
2018-03-21 17:24:52 +01:00
|
|
|
this.mapping_meta_fa = new Map();
|
|
|
|
this.mapping_meta_fa.set('file', {"meta-category": "file","fa_text": "file","fa-hex": "f15b"});
|
|
|
|
this.mapping_meta_fa.set('financial', {"meta-category": "financial","fa_text": "money-bil-alt","fa-hex": "f3d1"});
|
2018-03-26 12:18:56 +02:00
|
|
|
this.mapping_meta_fa.set('network', {"meta-category": "network","fa_text": "server","fa-hex": "f233"});
|
2018-03-21 17:24:52 +01:00
|
|
|
this.mapping_meta_fa.set('misc', {"meta-category": "misc","fa_text": "cube","fa-hex": "f1b2"});
|
|
|
|
// FIXME
|
2018-03-26 12:18:56 +02:00
|
|
|
this.first_draw = true;
|
2018-03-21 17:24:52 +01:00
|
|
|
this.nodes = nodes;
|
|
|
|
this.edges = edges;
|
|
|
|
var data = { // empty
|
|
|
|
nodes: this.nodes,
|
|
|
|
edges: this.edges
|
|
|
|
};
|
2018-03-26 12:18:56 +02:00
|
|
|
this.cluster_index = 0; // use to get uniq cluster ID
|
|
|
|
this.clusters = [];
|
|
|
|
this.unreferenced_nodes = new Map();
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
this.network = new vis.Network(container, data, network_options);
|
2018-03-26 12:18:56 +02:00
|
|
|
this.add_unreferenced_root_node();
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
var that = this;
|
|
|
|
this.network.on("selectNode", function (params) {
|
|
|
|
that.network.moveTo({
|
|
|
|
position: {
|
|
|
|
x: params.pointer.canvas.x,
|
|
|
|
y: params.pointer.canvas.y
|
|
|
|
},
|
|
|
|
animation: true,
|
|
|
|
});
|
|
|
|
});
|
2018-03-22 16:53:53 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
// Util
|
|
|
|
get_node_color(uuid) {
|
|
|
|
return this.nodes.get(uuid).icon.color;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Graph interaction
|
2018-03-26 12:18:56 +02:00
|
|
|
|
|
|
|
// Clusterize the specified node with its connected childs
|
|
|
|
clusterize(nodeID) {
|
|
|
|
var that = eventGraph;
|
|
|
|
var clusterOptionsByData = {
|
|
|
|
processProperties: global_processProperties,
|
|
|
|
clusterNodeProperties: {borderWidth: 3, shape: 'database', font: {size: 30}}
|
|
|
|
|
|
|
|
};
|
|
|
|
that.network.clusterByConnection(nodeID, clusterOptionsByData);
|
|
|
|
}
|
|
|
|
|
|
|
|
init_clusterize() {
|
|
|
|
var that = eventGraph;
|
|
|
|
var clusterOptionsByData = {
|
|
|
|
processProperties: global_processProperties,
|
|
|
|
clusterNodeProperties: {borderWidth: 3, shape: 'database', font: {size: 30}},
|
|
|
|
joinCondition: function(nodeOptions) {
|
|
|
|
var is_unref = that.unreferenced_nodes.get(nodeOptions.id) === undefined ? false : true;
|
|
|
|
return is_unref;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
this.network.clusterOutliers(clusterOptionsByData);
|
|
|
|
}
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
reset_graphs() {
|
|
|
|
this.nodes.clear();
|
|
|
|
this.edges.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
update_graph(data) {
|
2018-03-22 10:46:29 +01:00
|
|
|
this.network_loading(true, loadingText_creating);
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
// New nodes will be automatically added
|
|
|
|
// removed references will be deleted
|
|
|
|
var node_conf;
|
|
|
|
var newNodes = [];
|
|
|
|
var newNodeIDs = [];
|
|
|
|
for(var node of data.items) {
|
|
|
|
var group, label;
|
|
|
|
if ( node.node_type == 'object' ) {
|
|
|
|
group = 'object';
|
|
|
|
label = node.type;
|
|
|
|
var striped_value = label.substring(0, max_displayed_char) + (label.length < max_displayed_char ? "" : "[...]");
|
|
|
|
node_conf = {
|
|
|
|
id: node.id,
|
|
|
|
label: striped_value,
|
|
|
|
title: label,
|
|
|
|
group: group,
|
|
|
|
mass: 5,
|
|
|
|
icon: {
|
|
|
|
color: getRandomColor(),
|
|
|
|
face: 'FontAwesome',
|
|
|
|
code: String.fromCharCode(parseInt(this.mapping_meta_fa.get(node['meta-category'])['fa-hex'], 16)),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
dataHandler.mapping_value_to_nodeID.set(striped_value, node.id);
|
|
|
|
} else {
|
|
|
|
group = 'attribute';
|
|
|
|
label = node.type + ': ' + node.val;
|
|
|
|
var striped_value = label.substring(0, max_displayed_char) + (label.length < max_displayed_char ? "" : "[...]");
|
|
|
|
node_conf = {
|
|
|
|
id: node.id,
|
|
|
|
label: striped_value,
|
|
|
|
title: label,
|
|
|
|
group: group,
|
|
|
|
mass: 5,
|
|
|
|
};
|
|
|
|
dataHandler.mapping_value_to_nodeID.set(striped_value, node.id);
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
newNodes.push(node_conf);
|
|
|
|
newNodeIDs.push(node.id);
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
// check if nodes got deleted
|
|
|
|
var old_node_ids = this.nodes.getIds();
|
|
|
|
for (var old_id of old_node_ids) {
|
2018-03-26 12:18:56 +02:00
|
|
|
// Ignore root node
|
|
|
|
if (old_id == "rootNode:attribute" || old_id == "rootNode:object") {
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
// This old node got removed
|
|
|
|
if (newNodeIDs.indexOf(old_id) == -1) {
|
|
|
|
this.nodes.remove(old_id);
|
2018-03-19 11:40:27 +01:00
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
this.nodes.update(newNodes);
|
|
|
|
|
|
|
|
// New relations will be automatically added
|
|
|
|
// removed references will be deleted
|
|
|
|
var newRelations = [];
|
|
|
|
var newRelationIDs = [];
|
|
|
|
for(var rel of data.relations) {
|
|
|
|
var rel = {
|
2018-03-22 16:53:53 +01:00
|
|
|
id: rel.id,
|
2018-03-21 17:24:52 +01:00
|
|
|
from: rel.from,
|
|
|
|
to: rel.to,
|
|
|
|
label: rel.type,
|
|
|
|
title: rel.comment,
|
|
|
|
color: {
|
2018-03-26 12:18:56 +02:00
|
|
|
opacity: 1.0,
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
newRelations.push(rel);
|
2018-03-22 16:53:53 +01:00
|
|
|
newRelationIDs.push(rel.id);
|
2018-03-21 13:20:58 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
// check if nodes got deleted
|
|
|
|
var old_rel_ids = this.edges.getIds();
|
|
|
|
for (var old_id of old_rel_ids) {
|
|
|
|
// This old node got removed
|
|
|
|
if (newRelationIDs.indexOf(old_id) == -1) {
|
|
|
|
this.edges.remove(old_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.edges.update(newRelations);
|
2018-03-26 12:18:56 +02:00
|
|
|
|
|
|
|
// links unreferenced attributes and object to root nodes
|
|
|
|
if (this.first_draw) {
|
|
|
|
this.link_not_referenced_nodes();
|
|
|
|
this.first_draw = !this.first_draw
|
|
|
|
}
|
|
|
|
|
2018-03-22 10:46:29 +01:00
|
|
|
this.network_loading(false, "");
|
2018-03-21 13:20:58 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
reset_view() {
|
|
|
|
this.network.fit({animation: true });
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
reset_view_on_stabilized() {
|
2018-03-26 12:18:56 +02:00
|
|
|
var that = eventGraph;
|
2018-03-22 10:58:08 +01:00
|
|
|
this.network.once("stabilized", function(params) {
|
2018-03-21 17:24:52 +01:00
|
|
|
that.network.fit({ animation: true });
|
|
|
|
});
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
|
|
|
|
2018-03-26 13:22:40 +02:00
|
|
|
focus_on_stabilized(nodeID) {
|
|
|
|
this.network.once("stabilized", function(params) {
|
|
|
|
eventGraph.network.focus(nodeID, {animation: true, scale: 1});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-22 10:46:29 +01:00
|
|
|
// state true: loading
|
|
|
|
// state false: finished
|
|
|
|
network_loading(state, message) {
|
|
|
|
if(state) {
|
2018-03-21 17:24:52 +01:00
|
|
|
$('.loading-network-div').show();
|
2018-03-22 10:46:29 +01:00
|
|
|
$('.loadingText-network').text(message);
|
|
|
|
} else {
|
2018-03-21 17:24:52 +01:00
|
|
|
setTimeout(function() {
|
|
|
|
$('.loading-network-div').hide();
|
2018-03-22 10:46:29 +01:00
|
|
|
}, 500)
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
collapse_node(parent_id) {
|
2018-03-26 12:18:56 +02:00
|
|
|
if(parent_id === undefined) { return; }
|
|
|
|
|
|
|
|
if (!(parent_id == root_id_attr || parent_id == root_id_object)) { // Is not a root node
|
|
|
|
var node_group = this.nodes.get(parent_id).group;
|
|
|
|
if (parent_id === undefined || node_group != 'object') { // No node selected or collapse not permitted
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var connected_nodes = this.network.getConnectedNodes(parent_id);
|
|
|
|
var connected_edges = this.network.getConnectedEdges(parent_id);
|
|
|
|
// Remove nodes
|
|
|
|
for (var nodeID of connected_nodes) {
|
|
|
|
// Object's attribute are in UUID format (while other object or in simple integer)
|
|
|
|
if (nodeID.length > 10) {
|
|
|
|
this.nodes.remove(nodeID);
|
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
2018-03-26 12:18:56 +02:00
|
|
|
// Remove edges
|
|
|
|
for (var edgeID of connected_edges) {
|
|
|
|
// Object's attribute (edge) are in UUID format (while other object or in simple integer)
|
|
|
|
if (edgeID.length > 10) {
|
|
|
|
this.edges.remove(edgeID);
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-26 12:18:56 +02:00
|
|
|
} else { // Is a root node
|
|
|
|
this.clusterize(parent_id);
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
expand_node(parent_id) {
|
2018-03-26 12:18:56 +02:00
|
|
|
if (!this.network.isCluster(parent_id)) {
|
|
|
|
|
|
|
|
if (parent_id === undefined // Node node selected
|
|
|
|
|| this.nodes.get(parent_id).group != "object") { // Cannot expand attribute
|
|
|
|
return;
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
2018-03-26 12:18:56 +02:00
|
|
|
var newNodes = [];
|
|
|
|
var newRelations = [];
|
2018-03-21 17:24:52 +01:00
|
|
|
|
2018-03-26 12:18:56 +02:00
|
|
|
var parent_pos = this.network.getPositions([parent_id])[parent_id];
|
|
|
|
for(var attr of dataHandler.mapping_all_obj_relation.get(parent_id)) {
|
|
|
|
var parent_color = eventGraph.get_node_color(parent_id);
|
|
|
|
|
|
|
|
// Ensure unicity of nodes
|
|
|
|
if (this.nodes.get(attr.uuid) !== null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var striped_value = attr.value.substring(0, max_displayed_char) + (attr.value.length < max_displayed_char ? "" : "[...]");
|
|
|
|
var node = {
|
|
|
|
id: attr.uuid,
|
|
|
|
x: parent_pos.x,
|
|
|
|
y: parent_pos.y,
|
|
|
|
label: attr.type + ': ' + striped_value,
|
|
|
|
title: attr.type + ': ' + attr.value,
|
|
|
|
group: 'obj_relation',
|
|
|
|
color: {
|
|
|
|
background: parent_color
|
|
|
|
},
|
|
|
|
font: {
|
|
|
|
color: getTextColour(parent_color)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
newNodes.push(node);
|
|
|
|
|
|
|
|
var rel = {
|
|
|
|
from: parent_id,
|
|
|
|
to: attr.uuid,
|
|
|
|
arrows: '',
|
|
|
|
color: {
|
|
|
|
opacity: 0.5,
|
|
|
|
color: parent_color
|
|
|
|
},
|
|
|
|
length: 40
|
|
|
|
};
|
|
|
|
newRelations.push(rel);
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-26 12:18:56 +02:00
|
|
|
|
|
|
|
this.nodes.add(newNodes);
|
|
|
|
this.edges.add(newRelations);
|
|
|
|
|
|
|
|
} else { // is a cluster
|
|
|
|
if(this.network.getNodesInCluster(parent_id).length > cluster_expand_threshold) {
|
|
|
|
if(!confirm("The cluster contains lots of nodes. Are you sure you want to expand it?")) {
|
|
|
|
return;
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-26 12:18:56 +02:00
|
|
|
}
|
|
|
|
// expand cluster
|
|
|
|
this.network.openCluster(parent_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
link_not_referenced_nodes() {
|
|
|
|
var newEdges = [];
|
|
|
|
var that = this;
|
|
|
|
this.nodes.forEach(function(nodeData) {
|
|
|
|
var cur_id = nodeData.id;
|
|
|
|
var cur_group = nodeData.group;
|
|
|
|
|
|
|
|
// Do not link already connected nodes
|
|
|
|
if (that.network.getConnectedEdges(cur_id).length > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var new_edge = {
|
|
|
|
to: cur_id,
|
2018-03-21 17:24:52 +01:00
|
|
|
arrows: '',
|
|
|
|
color: {
|
2018-03-26 12:18:56 +02:00
|
|
|
opacity: 0.7,
|
|
|
|
color: '#d9d9d9'
|
2018-03-21 17:24:52 +01:00
|
|
|
},
|
2018-03-26 12:18:56 +02:00
|
|
|
length: 150
|
|
|
|
}
|
|
|
|
if (cur_group == 'attribute') {
|
|
|
|
new_edge.from = root_id_attr;
|
|
|
|
} else if (cur_group == 'object') {
|
|
|
|
new_edge.from = root_id_object;
|
|
|
|
}
|
|
|
|
that.unreferenced_nodes.set(cur_id, '')
|
|
|
|
newEdges.push(new_edge);
|
|
|
|
});
|
|
|
|
this.edges.add(newEdges);
|
|
|
|
this.init_clusterize();
|
|
|
|
}
|
|
|
|
|
|
|
|
add_unreferenced_root_node() {
|
|
|
|
var root_node_attr = {
|
|
|
|
id: root_id_attr,
|
|
|
|
fixed: true,
|
|
|
|
x: -root_node_x_pos,
|
|
|
|
y: 0,
|
|
|
|
label: 'Unreferenced Attributes',
|
|
|
|
title: 'All Attributes not being referenced',
|
|
|
|
group: 'rootNodeAttribute'
|
|
|
|
};
|
|
|
|
var root_node_obj = {
|
|
|
|
id: root_id_object,
|
|
|
|
fixed: true,
|
|
|
|
x: root_node_x_pos,
|
|
|
|
y: 0,
|
|
|
|
label: 'Unreferenced Objects',
|
|
|
|
title: 'All Objects not being referenced',
|
|
|
|
group: 'rootNodeObject'
|
|
|
|
};
|
|
|
|
this.nodes.add([root_node_attr, root_node_obj]);
|
2018-03-21 13:56:20 +01:00
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 13:56:20 +01:00
|
|
|
}
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
// data class (handle data)
|
|
|
|
class DataHandler {
|
|
|
|
constructor(network) {
|
|
|
|
this.network = network;
|
|
|
|
this.mapping_value_to_nodeID = new Map();
|
2018-03-22 16:53:53 +01:00
|
|
|
this.mapping_attr_id_to_uuid = new Map();
|
2018-03-21 17:24:52 +01:00
|
|
|
this.mapping_all_obj_relation = new Map();
|
2018-03-22 16:53:53 +01:00
|
|
|
this.mapping_rel_id_to_uuid = new Map();
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-19 15:07:51 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
extract_references(data) {
|
|
|
|
var items = [];
|
|
|
|
var relations = [];
|
|
|
|
|
|
|
|
if (data.Attribute !== undefined) {
|
|
|
|
for (var attr of data.Attribute) {
|
2018-03-22 16:53:53 +01:00
|
|
|
this.mapping_attr_id_to_uuid.set(attr.id, attr.uuid);
|
2018-03-21 17:24:52 +01:00
|
|
|
items.push({
|
|
|
|
'id': attr.id,
|
|
|
|
'type': attr.type,
|
|
|
|
'val': attr.value,
|
|
|
|
'node_type': 'attribute'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.Object !== undefined) {
|
|
|
|
for (var obj of data.Object) {
|
2018-03-22 16:53:53 +01:00
|
|
|
this.mapping_attr_id_to_uuid.set(obj.id, obj.uuid);
|
2018-03-21 17:24:52 +01:00
|
|
|
this.mapping_all_obj_relation.set(obj.id, obj.Attribute);
|
|
|
|
items.push({
|
|
|
|
'id': obj.id,
|
|
|
|
'type': obj.name,
|
|
|
|
'val': obj.value,
|
|
|
|
'node_type': 'object',
|
|
|
|
'meta-category': obj['meta-category']
|
|
|
|
});
|
|
|
|
|
|
|
|
for (var rel of obj.ObjectReference) {
|
2018-03-22 16:53:53 +01:00
|
|
|
this.mapping_rel_id_to_uuid.set(rel.id, rel.uuid);
|
2018-03-21 17:24:52 +01:00
|
|
|
relations.push({
|
2018-03-22 16:53:53 +01:00
|
|
|
'id': rel.id,
|
2018-03-21 17:24:52 +01:00
|
|
|
'from': obj.id,
|
|
|
|
'to': rel.referenced_id,
|
|
|
|
'type': rel.relationship_type,
|
|
|
|
'comment': rel.comment != "" ? rel.comment : "[Comment not set]"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
items: items,
|
|
|
|
relations: relations
|
2018-03-19 16:19:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-22 16:53:53 +01:00
|
|
|
fetch_data_and_update(stabilize) {
|
2018-03-22 10:46:29 +01:00
|
|
|
eventGraph.network_loading(true, loadingText_fetching);
|
2018-03-21 17:24:52 +01:00
|
|
|
$.getJSON( "/events/getReferences/"+scope_id+"/event.json", function( data ) {
|
|
|
|
var extracted = dataHandler.extract_references(data);
|
|
|
|
eventGraph.update_graph(extracted);
|
2018-03-22 16:53:53 +01:00
|
|
|
if ( stabilize === undefined || stabilize) {
|
|
|
|
eventGraph.reset_view_on_stabilized();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fetch_reference_data(rel_uuid, callback) {
|
|
|
|
$.getJSON( "/events/getReferenceData/"+rel_uuid+"/reference.json", function( data ) {
|
|
|
|
callback(data);
|
2018-03-21 17:24:52 +01:00
|
|
|
});
|
|
|
|
}
|
2018-03-20 17:29:26 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
get_typeaheadData() {
|
|
|
|
var to_ret = []
|
|
|
|
for( var entry of this.mapping_value_to_nodeID) {
|
|
|
|
var value = entry[0];
|
|
|
|
to_ret.push(value);
|
|
|
|
}
|
|
|
|
return to_ret;
|
2018-03-19 16:19:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-22 09:46:54 +01:00
|
|
|
// MISP interaction class (handle interaction with misp)
|
2018-03-21 17:24:52 +01:00
|
|
|
class MispInteraction {
|
|
|
|
constructor(nodes, edges) {
|
|
|
|
this.nodes = nodes;
|
|
|
|
this.edges = edges;
|
2018-03-22 16:53:53 +01:00
|
|
|
// Dirty way to know what modif was successful as the callback gives no information
|
|
|
|
// May be changed in the futur
|
|
|
|
this.callback_to_be_called = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
register_callback(callback) {
|
|
|
|
this.callback_to_be_called = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
apply_callback() {
|
|
|
|
var that = mispInteraction;
|
|
|
|
if (that.callback_to_be_called !== null) {
|
|
|
|
that.callback_to_be_called(that.callback_data);
|
|
|
|
}
|
|
|
|
that.callback_to_be_called = null;
|
|
|
|
that.callback_data = null;
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
remove_reference(edgeData, callback) {
|
2018-03-22 16:53:53 +01:00
|
|
|
var that = mispInteraction;
|
2018-03-22 09:46:54 +01:00
|
|
|
var edge_id = edgeData.edges[0];
|
2018-03-22 16:53:53 +01:00
|
|
|
var relation_id = edge_id;
|
2018-03-21 17:24:52 +01:00
|
|
|
deleteObject('object_references', 'delete', relation_id, scope_id);
|
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
add_reference(edgeData, callback) {
|
|
|
|
var that = mispInteraction;
|
2018-03-22 16:53:53 +01:00
|
|
|
var uuid = dataHandler.mapping_attr_id_to_uuid.get(edgeData.to);
|
2018-03-21 17:24:52 +01:00
|
|
|
if (!that.can_create_reference(edgeData.from) || !that.can_be_referenced(edgeData.to)) {
|
|
|
|
return;
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
genericPopup('/objectReferences/add/'+edgeData.from, '#popover_form', function() {
|
|
|
|
$('#targetSelect').val(uuid);
|
|
|
|
$('option[value='+uuid+']').click()
|
|
|
|
});
|
2018-03-20 14:43:05 +01:00
|
|
|
}
|
2018-03-22 16:53:53 +01:00
|
|
|
|
|
|
|
edit_reference(edgeData, callback) {
|
2018-03-22 17:19:57 +01:00
|
|
|
if (callback !== undefined) {
|
|
|
|
callback();
|
|
|
|
}
|
2018-03-22 16:53:53 +01:00
|
|
|
var that = mispInteraction;
|
|
|
|
var rel_id = edgeData.id;
|
|
|
|
var rel_uuid = dataHandler.mapping_rel_id_to_uuid.get(rel_id);
|
|
|
|
|
|
|
|
that.register_callback(function() {
|
|
|
|
var relation_id = edgeData.id;
|
|
|
|
submitDeletion(scope_id, 'delete', 'object_references', relation_id);
|
|
|
|
});
|
|
|
|
|
|
|
|
dataHandler.fetch_reference_data(rel_uuid, function(data) {
|
|
|
|
data = data[0].ObjectReference;
|
|
|
|
var uuid = data.referenced_uuid;
|
|
|
|
genericPopup('/objectReferences/add/'+data.object_id, '#popover_form', function() {
|
|
|
|
$('#targetSelect').val(uuid);
|
|
|
|
$('#ObjectReferenceComment').val(data.comment);
|
|
|
|
$('#ObjectReferenceRelationshipTypeSelect').val(data.relationship_type);
|
|
|
|
$('option[value='+uuid+']').click();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
can_create_reference(id) {
|
|
|
|
return this.nodes.get(id).group == "object";
|
|
|
|
}
|
|
|
|
|
|
|
|
can_be_referenced(id) {
|
|
|
|
var res;
|
|
|
|
if (this.nodes.get(id).group == "object") {
|
|
|
|
res = true;
|
|
|
|
} else if (this.nodes.get(id).group == "attribute") {
|
|
|
|
res = true;
|
|
|
|
} else {
|
|
|
|
res = false;
|
2018-03-20 14:43:05 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
return res;
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-20 14:43:05 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
add_item(nodeData, callback) {
|
2018-03-22 16:53:53 +01:00
|
|
|
var that = mispInteraction;
|
2018-03-21 17:24:52 +01:00
|
|
|
choicePopup("Add an element", [
|
|
|
|
{
|
|
|
|
text: "Add an Object",
|
|
|
|
onclick: "getPopup('"+scope_id+"', 'objectTemplates', 'objectChoice');"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: "Add an Attribute",
|
|
|
|
onclick: "simplePopup('/attributes/add/"+scope_id+"');"
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
delete_item(nodeData, callback) {
|
|
|
|
var selected_nodes = nodeData.nodes;
|
|
|
|
for (var nodeID of selected_nodes) {
|
|
|
|
var node = this.nodes.get(nodeID)
|
|
|
|
if (node.group == "attribute") {
|
|
|
|
deleteObject('attributes', 'delete', nodeID, scope_id);
|
|
|
|
} else if (node.group == "object") {
|
|
|
|
deleteObject('objects', 'delete', nodeID, scope_id);
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-20 14:43:05 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
edit_item(nodeData, callback) {
|
2018-03-22 16:53:53 +01:00
|
|
|
var that = mispInteraction;
|
2018-03-21 17:24:52 +01:00
|
|
|
var id = nodeData.id
|
2018-03-22 16:53:53 +01:00
|
|
|
var group = nodes.get(id).group;
|
|
|
|
if (group == 'attribute') {
|
|
|
|
simplePopup('/attributes/edit/'+id);
|
|
|
|
} else if (group == 'object') {
|
|
|
|
window.location = '/objects/edit/'+id;
|
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 10:57:26 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
/*=========
|
|
|
|
* UTILS
|
|
|
|
* ========*/
|
|
|
|
function getRandomColor() {
|
|
|
|
var letters = '0123456789ABCDEF';
|
|
|
|
var color = '#';
|
|
|
|
for (var i = 0; i < 6; i++) {
|
|
|
|
color += letters[Math.floor(Math.random() * 16)];
|
|
|
|
}
|
|
|
|
return color;
|
2018-03-20 14:43:05 +01:00
|
|
|
}
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
function getTextColour(hex) {
|
|
|
|
hex = hex.slice(1);
|
|
|
|
var r = parseInt(hex.substring(0,2), 16);
|
|
|
|
var g = parseInt(hex.substring(2,4), 16);
|
|
|
|
var b = parseInt(hex.substring(4,6), 16);
|
|
|
|
var avg = ((2 * r) + b + (3 * g))/6;
|
|
|
|
if (avg < 128) {
|
|
|
|
return 'white';
|
|
|
|
} else {
|
|
|
|
return 'black';
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
function genericPopupCallback(result) {
|
2018-03-22 16:53:53 +01:00
|
|
|
// sucess and eventgraph is enabled
|
|
|
|
if (result == "success" && dataHandler !== undefined) {
|
|
|
|
mispInteraction.apply_callback();
|
|
|
|
dataHandler.fetch_data_and_update(false);
|
2018-03-19 09:44:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-20 14:43:05 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
// Called when the user click on the 'Event graph' toggle
|
2018-03-20 14:43:05 +01:00
|
|
|
function enable_interactive_graph() {
|
|
|
|
// unregister onclick
|
2018-03-21 17:24:52 +01:00
|
|
|
$('#eventgraph_toggle').removeAttr('onclick');
|
2018-03-20 14:43:05 +01:00
|
|
|
|
|
|
|
// Defer the loading of the network to let some time for the DIV to appear
|
|
|
|
setTimeout(function() {
|
|
|
|
$('.shortcut-help').popover({
|
|
|
|
container: 'body',
|
|
|
|
title: 'Shortcuts',
|
|
|
|
content: shortcut_text,
|
|
|
|
placement: 'left',
|
|
|
|
trigger: 'hover',
|
|
|
|
html: true,
|
2018-03-19 09:44:25 +01:00
|
|
|
});
|
2018-03-21 13:20:58 +01:00
|
|
|
$('.fullscreen-btn').click(function() {
|
2018-03-21 17:24:52 +01:00
|
|
|
var network_div = $('#eventgraph_div');
|
2018-03-21 13:20:58 +01:00
|
|
|
var fullscreen_enabled = !network_div.data('fullscreen');
|
|
|
|
network_div.data('fullscreen', fullscreen_enabled);
|
|
|
|
var height_val = fullscreen_enabled == true ? "calc(100vh - 42px - 42px - 10px)" : "500px";
|
|
|
|
|
|
|
|
network_div.css("height", height_val);
|
|
|
|
network_div[0].scrollIntoView({
|
|
|
|
behavior: "smooth",
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
$('#network-typeahead').typeahead(typeaheadOption);
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-21 17:24:52 +01:00
|
|
|
eventGraph = new EventGraph(network_options, nodes, edges);
|
|
|
|
dataHandler = new DataHandler(eventGraph.network);
|
2018-03-20 14:43:05 +01:00
|
|
|
|
|
|
|
$(document).on("keydown", function(evt) {
|
2018-03-22 17:12:50 +01:00
|
|
|
if($('#network-typeahead').is(":focus")) {
|
|
|
|
return;
|
|
|
|
}
|
2018-03-20 14:43:05 +01:00
|
|
|
switch(evt.keyCode) {
|
|
|
|
case 88: // x
|
2018-03-21 17:24:52 +01:00
|
|
|
var selected_id = eventGraph.network.getSelectedNodes()[0];
|
|
|
|
eventGraph.expand_node(selected_id);
|
2018-03-19 09:44:25 +01:00
|
|
|
break;
|
|
|
|
|
2018-03-20 14:43:05 +01:00
|
|
|
case 67: // c
|
2018-03-21 17:24:52 +01:00
|
|
|
var selected_id = eventGraph.network.getSelectedNodes()[0];
|
|
|
|
eventGraph.collapse_node(selected_id);
|
2018-03-20 14:43:05 +01:00
|
|
|
break;
|
|
|
|
case 86: // v
|
2018-03-21 17:24:52 +01:00
|
|
|
eventGraph.reset_view();
|
2018-03-19 09:44:25 +01:00
|
|
|
break;
|
2018-03-20 17:29:26 +01:00
|
|
|
|
|
|
|
case 69: // e
|
|
|
|
if (evt.shiftKey) {
|
2018-03-21 17:24:52 +01:00
|
|
|
var selected_id = eventGraph.network.getSelectedNodes()[0];
|
2018-03-22 16:53:53 +01:00
|
|
|
if (selected_id !== undefined) { // A node is selected
|
|
|
|
var data = { id: selected_id };
|
|
|
|
mispInteraction.edit_item(data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
selected_id = eventGraph.network.getSelectedEdges()[0];
|
|
|
|
if (selected_id !== undefined) { // A edge is selected
|
|
|
|
var data = { id: selected_id };
|
|
|
|
mispInteraction.edit_reference(data);
|
|
|
|
break;
|
|
|
|
}
|
2018-03-20 17:29:26 +01:00
|
|
|
}
|
|
|
|
break;
|
2018-03-21 13:20:58 +01:00
|
|
|
|
|
|
|
case 70: // f
|
|
|
|
if (evt.shiftKey) {
|
|
|
|
// set focus to search input
|
2018-03-21 17:24:52 +01:00
|
|
|
eventGraph.network.disableEditMode(); // un-toggle edit mode
|
2018-03-21 13:20:58 +01:00
|
|
|
$('#network-typeahead').focus();
|
|
|
|
$('#network-typeahead').text('');
|
|
|
|
evt.preventDefault(); // avoid writting a 'F' in the input field
|
|
|
|
}
|
|
|
|
break;
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-20 14:43:05 +01:00
|
|
|
case 16: // <SHIFT>
|
|
|
|
if (!user_manipulation) { // user can't modify references
|
|
|
|
break;
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
eventGraph.network.addEdgeMode(); // toggle edit mode
|
2018-03-19 09:44:25 +01:00
|
|
|
break;
|
|
|
|
|
2018-03-20 14:43:05 +01:00
|
|
|
case 46: // <Delete>
|
|
|
|
if (!user_manipulation) { // user can't modify references
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// References
|
2018-03-21 17:24:52 +01:00
|
|
|
var selected_ids = eventGraph.network.getSelectedEdges();
|
2018-03-20 14:43:05 +01:00
|
|
|
for (var selected_id of selected_ids) {
|
|
|
|
var edge = { edges: [selected_id] }; // trick to use the same function
|
2018-03-21 17:24:52 +01:00
|
|
|
mispInteraction.remove_reference(edge);
|
2018-03-20 14:43:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Objects or Attributes
|
2018-03-21 17:24:52 +01:00
|
|
|
selected_ids = eventGraph.network.getSelectedNodes();
|
2018-03-20 14:43:05 +01:00
|
|
|
data = { nodes: selected_ids };
|
2018-03-21 17:24:52 +01:00
|
|
|
mispInteraction.delete_item(data);
|
2018-03-20 14:43:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$(document).on("keyup", function(evt) {
|
|
|
|
switch(evt.keyCode) {
|
|
|
|
case 16: // <SHIFT>
|
|
|
|
if (!user_manipulation) { // user can't modify references
|
|
|
|
break;
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
eventGraph.network.disableEditMode(); // un-toggle edit mode
|
2018-03-20 14:43:05 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-03-19 09:44:25 +01:00
|
|
|
|
2018-03-20 14:43:05 +01:00
|
|
|
|
|
|
|
});
|
|
|
|
|
2018-03-22 17:05:49 +01:00
|
|
|
dataHandler.fetch_data_and_update();
|
2018-03-20 14:43:05 +01:00
|
|
|
}, 1);
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
|
|
|
|
/*=========
|
|
|
|
* OPTIONS
|
|
|
|
* ========*/
|
|
|
|
mispInteraction = new MispInteraction(nodes, edges);
|
|
|
|
|
|
|
|
var network_options = {
|
|
|
|
interaction: {
|
|
|
|
hover: true
|
|
|
|
},
|
2018-03-26 12:18:56 +02:00
|
|
|
layout: {
|
|
|
|
randomSeed: 321453
|
|
|
|
},
|
2018-03-21 17:24:52 +01:00
|
|
|
manipulation: {
|
|
|
|
enabled: user_manipulation,
|
|
|
|
initiallyActive: false,
|
|
|
|
addEdge: mispInteraction.add_reference,
|
2018-03-22 16:53:53 +01:00
|
|
|
editEdge: { editWithoutDrag: mispInteraction.edit_reference },
|
2018-03-21 17:24:52 +01:00
|
|
|
addNode: mispInteraction.add_item,
|
|
|
|
editNode: mispInteraction.edit_item,
|
|
|
|
deleteNode: mispInteraction.delete_item,
|
|
|
|
deleteEdge: mispInteraction.remove_reference
|
|
|
|
},
|
|
|
|
physics: {
|
|
|
|
enabled: true,
|
|
|
|
barnesHut: {
|
|
|
|
gravitationalConstant: -10000,
|
|
|
|
centralGravity: 5,
|
|
|
|
springLength: 150,
|
|
|
|
springConstant: 0.24,
|
|
|
|
damping: 1.0,
|
|
|
|
|
|
|
|
},
|
2018-03-26 12:18:56 +02:00
|
|
|
minVelocity: 3.0,
|
2018-03-21 17:24:52 +01:00
|
|
|
},
|
|
|
|
edges: {
|
|
|
|
width: 3,
|
|
|
|
arrows: 'to'
|
|
|
|
},
|
|
|
|
nodes: {
|
|
|
|
chosen: {
|
|
|
|
node: function(values, id, selected, hovering) {
|
|
|
|
values.shadow = true;
|
|
|
|
values.shadowSize = 5;
|
|
|
|
values.shadowX = 2;
|
|
|
|
values.shadowY = 2;
|
|
|
|
values.shadowColor = "rgba(0,0,0,0.1)";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
groups: {
|
|
|
|
object: {
|
|
|
|
shape: 'icon',
|
|
|
|
icon: {
|
|
|
|
face: 'FontAwesome',
|
|
|
|
size: 50
|
|
|
|
},
|
|
|
|
font: {
|
|
|
|
size: 18, // px
|
|
|
|
background: 'rgba(255, 255, 255, 0.7)'
|
|
|
|
},
|
|
|
|
},
|
|
|
|
obj_relation: {
|
|
|
|
size: 10,
|
|
|
|
color: {
|
|
|
|
border:'black'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
attribute: {
|
|
|
|
shape: 'box',
|
|
|
|
color: {
|
|
|
|
background:'orange',
|
|
|
|
border:'black'
|
|
|
|
},
|
|
|
|
size: 15
|
|
|
|
},
|
2018-03-26 12:18:56 +02:00
|
|
|
rootNodeObject: {
|
|
|
|
shape: 'icon',
|
|
|
|
icon: {
|
|
|
|
face: 'FontAwesome',
|
|
|
|
code: '\uf00a',
|
|
|
|
},
|
|
|
|
font: {
|
|
|
|
size: 18, // px
|
|
|
|
background: 'rgba(255, 255, 255, 0.7)'
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
rootNodeAttribute: {
|
|
|
|
shape: 'icon',
|
|
|
|
icon: {
|
|
|
|
face: 'FontAwesome',
|
|
|
|
code: '\uf1c0',
|
|
|
|
},
|
|
|
|
font: {
|
|
|
|
size: 18, // px
|
|
|
|
background: 'rgba(255, 255, 255, 0.7)'
|
|
|
|
},
|
|
|
|
},
|
|
|
|
clustered_object: {
|
|
|
|
shape: 'icon',
|
|
|
|
icon: {
|
|
|
|
face: 'FontAwesome',
|
|
|
|
code: '\uf009',
|
|
|
|
},
|
|
|
|
font: {
|
|
|
|
size: 18, // px
|
|
|
|
background: 'rgba(255, 255, 255, 0.7)'
|
|
|
|
},
|
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
},
|
|
|
|
locales: {
|
|
|
|
en: {
|
|
|
|
edit: 'Edit',
|
|
|
|
del: 'Delete selected',
|
|
|
|
back: 'Back',
|
|
|
|
addNode: 'Add Object or Attribute',
|
|
|
|
editNode: 'Edit selected item',
|
|
|
|
addDescription: 'Click in an empty space to place a new node.',
|
|
|
|
addEdge: 'Add Reference',
|
2018-03-22 16:53:53 +01:00
|
|
|
editEdge: 'Edit Reference',
|
2018-03-21 17:24:52 +01:00
|
|
|
edgeDescription: 'Click on an Object and drag the edge to another Object (or Attribute) to connect them.'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var typeaheadOption = {
|
|
|
|
source: function (query, process) {
|
|
|
|
if (typeaheadData === undefined) { // caching
|
|
|
|
typeaheadData = dataHandler.get_typeaheadData();
|
|
|
|
}
|
|
|
|
process(typeaheadData);
|
|
|
|
},
|
|
|
|
updater: function(value) {
|
|
|
|
var nodeID = dataHandler.mapping_value_to_nodeID.get(value);
|
2018-03-26 13:22:40 +02:00
|
|
|
// check if node in cluster
|
|
|
|
nested_length = eventGraph.network.findNode(nodeID).length;
|
|
|
|
if (nested_length > 1) { // Node is in cluster
|
|
|
|
// As vis.js cannot supply a way to uncluster a single node, we remove it and add it again
|
|
|
|
searched_node = eventGraph.nodes.get(nodeID);
|
|
|
|
// Remove old node and edges
|
|
|
|
eventGraph.nodes.remove(nodeID);
|
|
|
|
eventGraph.nodes.add(searched_node);
|
2018-03-26 13:31:03 +02:00
|
|
|
/* don't need to re-add the edge as it is the same */
|
|
|
|
eventGraph.focus_on_stabilized(nodeID);
|
|
|
|
} else {
|
|
|
|
// set focus to the network
|
|
|
|
eventGraph.network.focus(nodeID, {animation: true, scale: 1});
|
2018-03-26 13:22:40 +02:00
|
|
|
}
|
2018-03-21 17:24:52 +01:00
|
|
|
// select node and focus on it
|
|
|
|
eventGraph.network.selectNodes([nodeID]);
|
|
|
|
$("#network-typeahead").blur();
|
|
|
|
},
|
|
|
|
autoSelect: true
|
|
|
|
}
|
|
|
|
var max_displayed_char = 32;
|
|
|
|
var progressbar_length = 3; // divided by 100
|
|
|
|
var loadingText_fetching = 'Fetching data';
|
|
|
|
var loadingText_creating = 'Constructing network';
|
|
|
|
|
|
|
|
var shortcut_text = "<b>V:</b> Center camera"
|
|
|
|
+ "\n<b>X:</b> Expaned node"
|
|
|
|
+ "\n<b>C:</b> Collapse node"
|
|
|
|
+ "\n<b>SHIFT+E:</b> Edit node"
|
|
|
|
+ "\n<b>SHIFT+F:</b> Search for value"
|
|
|
|
+ "\n<b>SHIFT:</b> Hold to add a reference"
|
|
|
|
+ "\n<b>DEL:</b> Delete selected item";
|
2018-03-26 12:18:56 +02:00
|
|
|
|
|
|
|
function global_processProperties(clusterOptions, childNodes) {
|
|
|
|
var concerned_root_node;
|
|
|
|
var that = eventGraph;
|
|
|
|
that.cluster_index = that.cluster_index + 1;
|
|
|
|
var childrenCount = 0;
|
|
|
|
for (var i = 0; i < childNodes.length; i++) {
|
|
|
|
var childNodeID = childNodes[i].id
|
|
|
|
if ( childNodeID == "rootNode:object" || childNodeID == "rootNode:attribute") {
|
|
|
|
concerned_root_node = childNodeID;
|
|
|
|
}
|
|
|
|
childrenCount += childNodes[i].childrenCount || 1;
|
|
|
|
}
|
2018-03-26 12:23:43 +02:00
|
|
|
childrenCount--; // -1 because 2 nodes merged into 1
|
2018-03-26 12:18:56 +02:00
|
|
|
clusterOptions.childrenCount = childrenCount;
|
|
|
|
clusterOptions.font = {size: Math.sqrt(childrenCount)*0.5+30}
|
|
|
|
clusterOptions.id = 'cluster:' + that.cluster_index;
|
|
|
|
if (concerned_root_node !== undefined) {
|
|
|
|
clusterOptions.icon = { size: Math.sqrt(childrenCount)*5+100 };
|
|
|
|
if (concerned_root_node == "rootNode:object") {
|
|
|
|
clusterOptions.label = "Unreferenced Objects (" + childrenCount + ")";
|
|
|
|
clusterOptions.x = root_node_x_pos;
|
|
|
|
clusterOptions.group = 'rootNodeObject';
|
|
|
|
} else if (concerned_root_node == "rootNode:attribute") {
|
|
|
|
clusterOptions.label = "Unreferenced Attributes (" + childrenCount + ")";
|
|
|
|
clusterOptions.x = -root_node_x_pos;
|
|
|
|
clusterOptions.group = 'rootNodeAttribute';
|
|
|
|
}
|
|
|
|
clusterOptions.fixed = true;
|
|
|
|
}
|
|
|
|
clusterOptions.y = 0
|
|
|
|
that.clusters.push({id:'cluster:' + that.cluster_index, scale: that.cur_scale, group: clusterOptions.group});
|
|
|
|
return clusterOptions;
|
|
|
|
}
|