From fe045962e7557772b15e73b5d9265f8bed48e1b8 Mon Sep 17 00:00:00 2001
From: fbsanchez <fborja.sanchez@artica.es>
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.'<br />';
         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;
 }