From fe045962e7557772b15e73b5d9265f8bed48e1b8 Mon Sep 17 00:00:00 2001 From: fbsanchez Date: Fri, 15 Mar 2019 20:44:57 +0100 Subject: [PATCH] wip generate_dot Former-commit-id: f41d5a55ec9cd8f27124d6697e92c5fb3009a2d2 --- .../include/class/NetworkMap.class.php | 471 +++++++++++++++++- pandora_console/include/constants.php | 6 + .../include/functions_networkmap.php | 16 +- 3 files changed, 476 insertions(+), 17 deletions(-) diff --git a/pandora_console/include/class/NetworkMap.class.php b/pandora_console/include/class/NetworkMap.class.php index 8f153eb415..3e3110b285 100644 --- a/pandora_console/include/class/NetworkMap.class.php +++ b/pandora_console/include/class/NetworkMap.class.php @@ -31,6 +31,10 @@ global $config; require_once $config['homedir'].'/include/functions_networkmap.php'; enterprise_include_once('include/functions_networkmap.php'); +enterprise_include_once('include/functions_discovery.php'); + +// Avoid node overlapping. +define('GRAPHVIZ_RADIUS_CONVERSION_FACTOR', 20); /** * Manage networkmaps in Pandora FMS @@ -94,6 +98,13 @@ class NetworkMap */ public $nodes; + /** + * Useful to translate id_node to id_agent or id_module. + * + * @var array + */ + public $nodeMapping; + /** * Relationship map. * @@ -160,8 +171,8 @@ class NetworkMap 'x_offs' => 0, 'y_offs' => 0, 'z_dash' => 0.5, - 'node_sep' => 0.1, - 'rank_sep' => 0.1, + 'node_sep' => 3, + 'rank_sep' => 3, 'mindist' => 1, 'kval' => 0.1, ]; @@ -341,7 +352,7 @@ class NetworkMap if ($this->idTask) { $recon_task = db_get_row_filter( 'trecon_task', - ['id_rt' => $networkmap['source_data']] + ['id_rt' => $this->idTask] ); $this->network = $recon_task['subnet']; } @@ -356,6 +367,34 @@ class NetworkMap } + /** + * Retrieves node information using id_node as mapping instead element id. + * + * @param integer $id_node Target node. + * @param string $field Field to retrieve, if null, all are return. + * + * @return mixed Array (node data) or false if error. + */ + public function getNodeData(int $id_node, $field=null) + { + if (is_array($this->nodes) === false + || is_array($this->nodeMapping) === false + ) { + return false; + } + + if (is_array($this->nodes[$this->nodeMapping[$id_node]]) === true) { + if (isset($field) === false) { + return $this->nodes[$this->nodeMapping[$id_node]]; + } else { + return $this->nodes[$this->nodeMapping[$id_node]][$field]; + } + } else { + return false; + } + } + + /** * Return nodes of current map. * @@ -407,6 +446,112 @@ class NetworkMap } + /** + * Search for nodes in current map definition. + * + * @return array Nodes detected, internal variable also updated. + */ + public function calculateNodes() + { + // Calculate. + if (!$this->nodes) { + // Search. + if ($this->idTask) { + // Network map, based on discovery task. + $this->nodes = enterprise_hook( + 'get_discovery_agents', + [$this->idTask] + ); + } + + if ($this->network) { + // Network map, based on direct network. + $this->nodes = networkmap_get_new_nodes_from_ip_mask( + $this->network + ); + } else if ($this->mapOptions['map_filter']['empty_map']) { + // Empty map returns no data. + $this->nodes = []; + } else { + // Group map. + $this->nodes = agents_get_agents( + $filter, + [ + 'id_grupo', + 'nombre', + 'id_os', + 'id_parent', + 'id_agente', + 'normal_count', + 'warning_count', + 'critical_count', + 'unknown_count', + 'total_count', + 'notinit_count', + ], + 'AR', + [ + 'field' => 'id_parent', + 'order' => 'ASC', + ] + ); + } + } + + return $this->nodes; + } + + + /** + * Search for relations for a given node in current map definition. + * + * @param array $node Origin. + * @param array $id_node Id for source node. + * @param array $id_source Id for source data, agent or module. + * + * @return array Relations found for given node. + */ + public function calculateRelations( + $node, + $id_node, + $id_source + ) { + // Calculate. + if (is_array($node) === false) { + return false; + } + + $relations = []; + + if (isset($node['id_agente_modulo'])) { + // Module. + $relations = modules_get_relations( + [ + 'id_module' => $node['id_agente_modulo'], + ] + ); + } + + if (isset($node['id_agente'])) { + // Agent. + $relations = modules_get_relations( + [ + 'id_agent' => $node['id_agente'], + ] + ); + if ($relations === false) { + $relations = []; + } + + // Add also parent relationship. + $relations[$id_node] = $node['id_parent']; + } + + // Others. + return $relations; + } + + /** * Generates or loads nodes&relations array from data load * and stores it in $this->graph. @@ -474,6 +619,206 @@ class NetworkMap } + /** + * Generates a graph definition (header only) for dot graph. + * + * @return string Dot graph header. + */ + public function openDotFile() + { + global $config; + + $overlap = 'compress'; + + $map_filter = $this->mapOptions['map_filter']; + $nooverlap = $this->mapOptions['nooverlap']; + + if (isset($config['networkmap_max_width'])) { + $size_x = ($config['networkmap_max_width'] / 100); + $size_y = ($size_x * 0.8); + } else { + $size_x = 8; + $size_y = 5.4; + $size = ''; + } + + if ($zoom > 0) { + $size_x *= $zoom; + $size_y *= $zoom; + } + + $size = $size_x.','.$size_y; + + if ($size_canvas === null) { + $size = ($this->mapOptions['size_canvas']['x'] / 100); + $size .= ','.($this->mapOptions['size_canvas']['y'] / 100); + } + + // Graphviz custom values + if (isset($map_filter['node_sep'])) { + $node_sep = $map_filter['node_sep']; + } else { + $node_sep = 0.1; + } + + if (isset($map_filter['rank_sep'])) { + $rank_sep = $map_filter['rank_sep']; + } else { + if ($layout == 'radial') { + $rank_sep = 1.0; + } else { + $rank_sep = 0.5; + } + } + + if (isset($map_filter['mindist'])) { + $mindist = $map_filter['mindist']; + } else { + $mindist = 1.0; + } + + if (isset($map_filter['kval'])) { + $kval = $map_filter['kval']; + } else { + $kval = 0.1; + } + + // BEWARE: graphwiz DONT use single ('), you need double ("). + $head = 'graph networkmap { dpi=100; bgcolor="transparent"; labeljust=l; margin=0; pad="0.75,0.75";'; + if ($nooverlap != '') { + $head .= 'overlap=scale;'; + $head .= 'outputorder=first;'; + } + + if ($layout == 'flat' + || $layout == 'spring1' + || $layout == 'spring2' + ) { + if ($nooverlap != '') { + $head .= 'overlap="scalexy";'; + } + + if ($layout == 'flat') { + $head .= 'ranksep="'.$rank_sep.'";'; + } + + if ($layout == 'spring2') { + $head .= 'K="'.$kval.'";'; + } + } + + if ($layout == 'radial') { + $head .= 'ranksep="'.$rank_sep.'";'; + } + + if ($layout == 'circular') { + $head .= 'mindist="'.$mindist.'";'; + } + + $head .= 'ratio="fill";'; + $head .= 'root=0;'; + $head .= 'nodesep="'.$node_sep.'";'; + $head .= 'size="'.$size.'";'; + + $head .= "\n"; + + return $head; + } + + + /** + * Creates a node in dot format. + * Requirements: + * id_node + * id_source + * status => defines 'color' + * label + * image + * url + * + * @param array $data Node definition. + * + * @return string Dot node. + */ + public function createDotNode($data) + { + global $config; + global $hack_networkmap_mobile; + + $dot_str = ''; + + $color = COL_NORMAL; + $label = $data['label']; + $url = 'none'; + $parent = $data['parent']; + $font_size = $this->mapOptions['font_size']; + $radius = $this->mapOptions['map_filter']['node_radius']; + $radius /= GRAPHVIZ_RADIUS_CONVERSION_FACTOR; + + if (strlen($label) > 16) { + $label = ui_print_truncate_text($label, 16, false, true, false); + } + + // If radius is 0, set to 1 instead. + if ($radius <= 0) { + $radius = 1; + } + + // Simple node always. This kind of node is used only to + // retrieve X,Y positions from graphviz no for personalization. + $dot_str = $data['id_node'].' [ parent="'.$data['id_parent'].'"'; + $dot_str .= ', color="'.$color.'", fontsize='.$font_size; + $dot_str .= ', shape="doublecircle"'.$url_node_link; + $dot_str .= ', style="filled", fixedsize=true, width='.$radius.', height='.$radius; + $dot_str .= ', label="'.$label.'", tooltip="'.$ajax_prefix; + $dot_str .= 'ajax.php?page=operation/agentes/ver_agente'; + $dot_str .= '&get_agent_status_tooltip=1&id_agent='.$data['id_agente']; + $dot_str .= $meta_params.'"];'."\n"; + + return $dot_str; + } + + + /** + * Creates an edge in dot format. + * Requirements: + * from + * to + * + * @param array $data Edge content. + * + * @return string Dot code for given edge. + */ + public function createDotEdge($data) + { + if (is_array($data) === false) { + return ''; + } + + if (!isset($data['from']) || !isset($data['to'])) { + return ''; + } + + $edge = "\n".$data['from'].' -- '.$data['to']; + $edge .= '[len='.$this->mapOptions['map_filter']['rank_sep']; + $edge .= ', color="#BDBDBD", headclip=false, tailclip=false,'; + $edge .= ' edgeURL=""];'."\n"; + + return $edge; + } + + + /** + * Returns dot file end string. + * + * @return string Dot file end string. + */ + public function closeDotFile() + { + return '}'; + } + + /** * Generate a graphviz string structure to be used later. * @@ -481,9 +826,65 @@ class NetworkMap */ public function generateDotGraph() { + echo 'regenerar la net es: '.$this->network.'
'; if (!isset($this->dotGraph)) { // Generate dot file. - $this->dotGraph = networkmap_generate_dot( + $nodes = []; + $edges = []; + $graph = ''; + + // Search for nodes. + $nodes = $this->calculateNodes(); + + // Search for relations. + // Build dot structure. + // Open Graph. + $graph = $this->openDotFile(); + + // Create dot nodes. + $i = 0; + foreach ($nodes as $k => $node) { + $this->nodeMapping[$i] = $k; + $graph .= $this->createDotNode( + [ + 'id_node' => $i, + 'id_source' => $node['id_agente'], + 'status' => agents_get_status_from_counts($node), + 'label' => io_safe_output($node['alias']), + 'image' => null, + 'url' => 'none', + ] + ); + + // Keep reverse reference. + $this->nodes[$k]['id_node'] = $i; + + $edges[] = $this->calculateRelations($node, $i, $k); + + // Increase for next node. + $i++; + } + + hd($edges); + foreach ($edges as $rel) { + foreach ($rel as $from => $to) { + $graph .= $this->createDotEdge( + [ + 'from' => $from, + 'to' => $this->nodes[$to]['id_node'], + ] + ); + } + } + + // Create dot edges. + $graph .= $this->closeDotFile(); + + hd($graph); + $this->dotGraph = $graph; + + /* + $this->dotGraph = networkmap_generate_dot( get_product_name(), $this->idGroup, $this->mapOptions['simple'], @@ -508,7 +909,8 @@ class NetworkMap null, $this->mapOptions['old_mode'], $this->mapOptions['map_filter'] - ); + ); + */ } } @@ -554,10 +956,7 @@ class NetworkMap include_once 'include/functions_os.php'; - $map_filter = json_decode( - $this->map['filter'], - true - ); + $map_filter = $this->mapOptions['map_filter']; /* * Let graphviz place the nodes. @@ -692,6 +1091,55 @@ class NetworkMap $nodes_and_relations['nodes'][$index]['id'] = $node['id']; $nodes_and_relations['nodes'][$index]['id_map'] = $this->idMap; + // Retrieve properties. + $node['id_agent'] = $this->getNodeData( + $node['id'], + 'id_agente' + ); + $node['id_module'] = $this->getNodeData( + $node['id'], + 'id_agente_modulo' + ); + + // Specify node type and set values. + if (isset($node['id_agent']) === true) { + if (isset($node['id_module']) === false) { + // Agent. + $node['type'] = 'agent'; + $node['source_data'] = $node['id_agent']; + $node['text'] = $this->getNodeData( + $node['id'], + 'alias' + ); + + $node['image'] = ui_print_os_icon( + $this->getNodeData($node['id'], 'id_os'), + false, + true, + true, + true, + true, + true + ); + } else if (isset($node['id_module'])) { + // Module. + $node['type'] = 'module'; + $node['source_data'] = $node['id_module']; + $node['text'] = $this->getNodeData( + $node['id'], + 'nombre' + ); + + $node['image'] = ui_print_moduletype_icon( + $this->getNodeData($node['id'], 'id_tipo_modulo'), + true, + true, + false, + true + ); + } + } + $children_count = 0; foreach ($relation_nodes as $relation) { if (($relation['parent_type'] == 'agent') || ($relation['parent_type'] == '')) { @@ -705,6 +1153,7 @@ class NetworkMap } } + // Center map over node with most children. if (empty($node_center) || $node_center['counter'] < $children_count) { $node_center['x'] = (int) $node['coords'][0]; $node_center['y'] = (int) $node['coords'][1]; @@ -1509,7 +1958,9 @@ class NetworkMap } $networkmap = $this->map; - $networkmap['filter'] = json_decode($networkmap['filter'], true); + if (is_array($networkmap['filter']) === false) { + $networkmap['filter'] = json_decode($networkmap['filter'], true); + } $networkmap['filter']['l2_network_interfaces'] = 1; diff --git a/pandora_console/include/constants.php b/pandora_console/include/constants.php index a82d36c1d0..f54ca3d550 100644 --- a/pandora_console/include/constants.php +++ b/pandora_console/include/constants.php @@ -506,6 +506,12 @@ define('OPTION_COLOR_PICKER', 11); define('NODE_TYPE', 0); define('ARROW_TYPE', 1); +// Networkmap node types. +define('NODE_AGENT', 0); +define('NODE_MODULE', 1); +define('NODE_PANDORA', 2); +define('NODE_GENERIC', 3); + // SAML attributes constants. define('SAML_ROLE_AND_TAG', 'eduPersonEntitlement'); define('SAML_USER_DESC', 'commonName'); diff --git a/pandora_console/include/functions_networkmap.php b/pandora_console/include/functions_networkmap.php index fba71431bd..4f02ef3fdb 100644 --- a/pandora_console/include/functions_networkmap.php +++ b/pandora_console/include/functions_networkmap.php @@ -489,11 +489,11 @@ function networkmap_generate_dot( // Create void statistics array $stats = []; - - $count = 0; - $group_nodes = 10; - $graph .= networkmap_create_transparent_node($count); - foreach (array_keys($orphans) as $node) { + /* + $count = 0; + $group_nodes = 10; + $graph .= networkmap_create_transparent_node($count); + foreach (array_keys($orphans) as $node) { if ($group_nodes == 0) { $count++; $graph .= networkmap_create_transparent_node($count); @@ -507,7 +507,8 @@ function networkmap_generate_dot( ); $group_nodes--; - } + } + */ // Create nodes foreach ($nodes as $node_id => $node) { @@ -714,7 +715,7 @@ function networkmap_create_edge( // Option edgeURL allows node navigation. $edge = "\n".$head.' -- '.$tail; - $edge .= '[color="#BDBDBD", headclip=false, tailclip=false, edgeURL=""];'; + $edge .= '[len='.$ranksep.', color="#BDBDBD", headclip=false, tailclip=false, edgeURL=""];'; $edge .= "\n"; return $edge; @@ -2306,6 +2307,7 @@ function networkmap_loadfile( $relations_param[] = $row; } + hd($networkmap_nodes); return $networkmap_nodes; }