* @copyright 'map.inc.php' (C) 2023 Jens Brueckner * @copyright (C) Adam Armstrong * */ include_once($config['html_dir'] . '/includes/authenticate.inc.php'); // check if authenticated and user has global view ability if ($_SESSION['userlevel'] < 5) { display_error_http(401); } // register javascript to this file register_html_resource('js', 'vis-network.min.js'); register_html_title("Network Map"); // build navbar $navbar = [ 'brand' => "Network Map", 'class' => "navbar-narrow", 'style' => 'width: 100%;' ]; // 'Port Labels' navbar menu $navbar['options']['port_labels']['text'] = 'Port Labels'; $navbar['options']['port_labels']['id'] = 'toggle_labels'; $navbar['options']['port_labels']['icon'] = $config['icon']['cef']; // 'Devices' navbar submenu $navbar['options']['devices']['text'] = 'Devices'; $navbar['options']['devices']['class'] = 'dropdown-scrollable'; $navbar['options']['devices']['id'] = 'map-devices'; $navbar['options']['devices']['icon'] = $config['icon']['devices']; // 'Locations' navbar submenu if ($config['web_show_locations']) { // save all locations into a array $locations = get_locations(); // 'Locations' navbar menu $navbar['options']['locations']['text'] = 'Locations'; $navbar['options']['locations']['class'] = 'dropdown-scrollable'; $navbar['options']['locations']['icon'] = $config['icon']['location']; $navbar['options']['locations']['url'] = generate_url([ 'page' => 'map' ]); // when the user selected a location: put this location into the navbar if (isset($vars['location'])) { $navbar['options']['location']['text'] = '(' . var_decode($vars['location']['0']) . ')'; $navbar['options']['location']['class'] = 'active'; } // endif // all locations into the navbar foreach ($locations as $location) { // if location is empty, substitute by OBS_VAR_UNSET as empty location parameter would be ignored $location_name = ($location === '' ? OBS_VAR_UNSET : escape_html($location)); $location_url = var_encode($location_name); $navbar['options']['locations']['suboptions'][$location_name]['text'] = $location_name; $navbar['options']['locations']['suboptions'][$location_name]['icon'] = $config['icon']['location']; $navbar['options']['locations']['suboptions'][$location_name]['url'] = generate_url([ 'page' => 'map', 'location' => $location_url ]); } // endforeach } // endif // 'Groups' navbar submenu // save all groups into a array $groups = get_groups_by_type(); $navbar['options']['groups']['text'] = 'Groups'; $navbar['options']['groups']['class'] = 'dropdown-scrollable'; $navbar['options']['groups']['icon'] = $config['icon']['group']; $navbar['options']['groups']['url'] = generate_url([ 'page' => 'map' ]); // all groups into the navbar foreach ($groups['device'] as $group_id => $group) { // when the user selected a group: put this group into the navbar if ($vars['group_id'] == $group_id) { $navbar['options']['groups']['text'] .= ' (' . $group['group_name'] . ')'; $navbar['options']['groups']['class'] = 'active'; } // endif $navbar['options']['groups']['suboptions'][$group_id]['text'] = $group['group_name']; $navbar['options']['groups']['suboptions'][$group_id]['url'] = generate_url($vars, ['group_id' => $group_id, 'device_id' => NULL]); } // endforeach // define options for visjs $options = '{ configure: { enabled: false, showButton: false, }, nodes: { shapeProperties: { interpolation: false, }, fixed: { x: false, y: false }, "font": { "size": 20 }, }, edges: { smooth: { enabled: true, type: "dynamic", roundness: 0, }, font: { color: "#343434", strokeColor: "#ffffff", }, shadow: true }, layout: { improvedLayout: false }, physics: { enabled: false, maxVelocity: 146, solver: "forceAtlas2Based", timestep: 0.35, adaptiveTimestep: true, stabilization: { enabled: false, iterations: 600, updateInterval: 25, onlyDynamicEdges: false, fit: true, }, forceAtlas2Based: { gravitationalConstant: -30, centralGravity: 0.002, springLength: 225, springConstant: 0.025, avoidOverlap: 1, }, barnesHut: { gravitationalConstant: -2000, centralGravity: 0.3, springLength: 95, springConstant: 0.04, damping: 0.09, avoidOverlap: 1 }, repulsion: { centralGravity: 0.2, springLength: 200, springConstant: 0.05, nodeDistance: 100, damping: 0.09 }, hierarchicalRepulsion: { centralGravity: 0.0, springLength: 100, springConstant: 0.01, nodeDistance: 120, damping: 0.09 } }, interaction: { tooltipDelay: 3600000, hideEdgesOnDrag: false, hideEdgesOnZoom: false, selectConnectedEdges: true, navigationButtons: true, dragNodes: true, hover: true }, manipulation: { enabled: false, addNode: false, deleteNode: false, initiallyActive: false, } };'; // pre define some later used arrays $links = []; $ports = []; $devices = []; $devices_by_id = []; $link_seen = []; // define link colors for activity indication $link_load_0 = '#000000'; // black $link_load_1 = '#c0c0c0'; // dark grey $link_load_1_10 = '#8c00ff'; // electric indigo $link_load_10_25 = '#2020ff'; // blue $link_load_25_40 = '#00c0ff'; // deep sky blue $link_load_40_55 = '#00f000'; // lime $link_load_55_70 = '#f0f000'; // yellow $link_load_70_85 = '#ffc000'; // amber $link_load_85_95 = '#ff008c'; // deep pink $link_load_95_100 = '#a30101'; // dark red // get neighbours with port data from sql // note: only ports with operational status 'up', neighbour say's active and it's NOT a LAG (bc we want physical links) $ports_sql = ' SELECT `neighbours`.`active` as `active`, `neighbours`.`neighbour_id` as `neighbour_id`, `neighbours`.`device_id` as `local_device_id`, `neighbours`.`port_id` as `local_port_id`, `neighbours`.`remote_port_id` as `remote_port_id`, `neighbours`.`remote_hostname` as `remote_hostname`, `neighbours`.`remote_port` as `remote_port`, `d1`.`hostname` as `local_hostname`, `d1`.`type` as `type`, `p1`.`ifName` as `local_ifname`, `p1`.`ifSpeed` as `local_ifspeed`, `p1`.`ifOperStatus` as `local_ifstatus`, `p1`.`ifType` as `localiftype`, `p1`.`ifVlan` as `local_ifvlan`, `p1`.`ifInOctets_perc` as `local_ifInOctets_perc`, `p1`.`ifOutOctets_perc` as `local_ifOutOctets_perc`, `p2`.`device_id` as `remote_device_id`, `p2`.`ifName` as `remote_ifname`, `p2`.`ifSpeed` as `remote_ifspeed`, `p2`.`ifOperStatus` as `remote_ifstatus`, `p2`.`ifType` as `remote_iftype`, `p2`.`ifVlan` as `remote_ifvlan` FROM `neighbours` JOIN `devices` d1 ON `neighbours`.`device_id` = `d1`.`device_id` JOIN `ports` p1 ON `neighbours`.`port_id` = `p1`.`port_id` JOIN `ports` p2 ON `neighbours`.`remote_port_id` = `p2`.`port_id` WHERE (`d1`.`device_id` <> `neighbours`.`neighbour_id`) AND (`d1`.`type` IN (?, ?, ?)) AND (`active` = ?); '; $ports_sql_params = [ 'network', 'firewall', 'wireless', '1' ]; $ports = dbFetchRows($ports_sql, $ports_sql_params); // foreach all links foreach ($ports as $link) { // check if a device is choosen by the dropdown and save only these device links (depth = 1) if (isset($vars['device'])) { // check if the port is connected to the choosen device if (!($link['local_device_id'] === $vars['device']) || ($link['remote_device_id'] === $vars['device'])) { continue; } // endif } // endif // check if the link is a loop on the same device (e.g. internal mgmt links) if ($link['local_device_id'] == $link['remote_device_id']) { continue; } // endif // define link port side from $ports array $link_by_id_local = $link['local_port_id'] . ':' . $link['remote_port_id']; $link_by_id_remote = $link['remote_port_id'] . ':' . $link['local_port_id']; // check if the link is already in the links array for visjs or has another side if (!array_key_exists($link_by_id_local, $link_seen) && !array_key_exists($link_by_id_remote, $link_seen)) { // define link speed width $link_speed = $link['local_ifspeed'] / 1000000; if ($link_speed > 500000) { $width = 0.51; } else { $width = round(0.14 * ($link_speed ** 0.31)); } // endif // define summarized link load $link_load = ($link['local_ifInOctets_perc'] + $link['local_ifOutOctets_perc']) / 2; // define the traffic load color if ($link_load < 1) { $link_style = $link_load_1; } elseif ($link_load < 10) { $link_style = $link_load_1_10; } elseif ($link_load < 25) { $link_style = $link_load_10_25; } elseif ($link_load < 40) { $link_style = $link_load_25_40; } elseif ($link_load < 55) { $link_style = $link_load_40_55; } elseif ($link_load < 70) { $link_style = $link_load_55_70; } elseif ($link_load < 85) { $link_style = $link_load_70_85; } elseif ($link_load < 95) { $link_style = $link_load_85_95; } elseif ($link_load > 95) { $link_style = $link_load_95_100; } else { $link_style = $link_load_0; } // endif // define the link color if the link is on both sides down if ($link['local_ifstatus'] == 'down' && $link['remote_ifstatus'] == 'down') { $link_style = $link_style_unused; // FIXME. undefined } // add the link to the links array $links[] = array_merge_recursive([ 'from' => $link['local_device_id'], 'to' => $link['remote_device_id'], 'localPortId' => $link['local_port_id'], 'remotePortId' => $link['remote_port_id'], 'label' => $link['local_ifname'] . " <> " . $link['remote_ifname'], 'width' => $width, 'color' => $link_style, 'roundness' => rand(-10, 10) / 10 ]); } // endif $link_seen[$link_by_id_local] = 1; $link_seen[$link_by_id_remote] = 1; } // endforeach // get device data with groups from sql : only switches and firewalls $device_sql = ' SELECT `devices`.`device_id` AS `device_id`, `devices`.`hostname` AS `hostname`, `devices`.`type` AS `type`, `devices`.`disabled` AS `disabled`, `devices`.`location` AS `location`, `devices`.`status` AS `status` FROM `devices` WHERE `type` IN (?, ?, ?) ORDER BY `hostname` ASC; '; $device_params = [ 'network', 'firewall', 'wireless' ]; $devices = dbFetchRows($device_sql, $device_params); // performance check : if there are more than 250 devices, ask if the user want's to display a group if (safe_count($devices) >= 250) { //print_warning(' Too many devices! Show all? '); print_warning('Too many devices! The map could be slow..'); } // define node colors for status $node_style_normal = '#97C2FC'; $node_style_down = '#FB7E81'; $node_style_disabled = '#919191'; // foreach all devices foreach ($devices as $device) { // define local_device_id from $devices array $local_device_id = $device['device_id']; // define local_device_location from $devices array $local_device_location = $device['location']; // define local device_status from $devices array $device_status = $device['status']; // define local device_hostname as short_hostname from $devices array $device_hostname = device_name($device, TRUE); // when the user selected a location: check if the device is member in this location or don't put it into the array by continue the foreach if (isset($vars['location'])) { $location_choose = var_decode($vars['location']); if (!(in_array($local_device_location, $location_choose))) { continue; } // endif } // endif // check if the device got any link if (!(in_array($local_device_id, array_column($ports, 'local_device_id'))) && !(in_array($local_device_id, array_column($ports, 'remote_device_id')))) { // check if the user did NOT pressed the 'show all' button if (!isset($vars['showall'])) { // enable the 'show all' button and continue to not display the device without link $menu_showall_enable = TRUE; continue; } else { // disable the 'show all' button if the 'show all' button is pressed and do not skip : display all network devices on the map $menu_showall_enable = FALSE; } // endif } // endif // Check if the user selects a group if (OBSERVIUM_EDITION !== 'community' && isset($vars['group_id'])) { // get the group membership from the database $device_groups = get_entity_group_ids('device', $local_device_id); // If the device has no group or doesn't belong to the selected group, continue (skip outer loop) if (empty($device_groups) || !in_array($vars['group_id'], $device_groups)) { continue; } } // insert the device for filtering in the $navbar dropdown $navbar['options']['devices']['suboptions'][$local_device_id]['text'] = $device_hostname; $navbar['options']['devices']['suboptions'][$local_device_id]['link_opts'] = 'data-deviceid=' . $local_device_id; // define the node color depends on device status if ($device['disabled']) { $node_style = $node_style_disabled; } elseif ($device_status == '0') { $node_style = $node_style_down; } else { $node_style = $node_style_normal; } // endif // check if the device is already in the device_by_id array for visjs or add it to the nodes if (!array_key_exists($local_device_id, $devices_by_id)) { $devices_by_id[$local_device_id] = [ 'id' => $local_device_id, 'label' => $device_hostname, 'shape' => 'image', 'image' => 'img/router.png', 'url' => 'device/device=' . $local_device_id, 'color' => $node_style, 'size' => '32' ]; } // endif } // endforeach // json encode the links array $links = safe_json_encode($links); // json encode the devices_by_id array $devices_by_id = safe_json_encode(array_values($devices_by_id)); // export network map data $navbar['options_right']['legend'] = [ 'text' => 'Toggle Legend', 'id' => 'toggle_legend' ]; // export network map data $navbar['options_right']['export'] = [ 'text' => 'Export Data', 'url' => 'map/export' ]; // show all devices (default: only devices with links), but ONLY when there are hidden devices if ($menu_showall_enable) { $navbar['options_right']['hiddennodes'] = [ 'text' => 'Show all', 'url' => 'map/showall' ]; } // re-arrange button and enable node_cache usage (in JS / local browser storage) only when no location or group is choosen if (!isset($vars['location']) && !isset($vars['group_id']) && !isset($vars['showall'])) { $navbar['options_right']['re-arrange'] = [ 'text' => 'Re-Arrange', 'id' => 're-arrange' ]; $node_cache = TRUE; } // endit // 'reload' page button $navbar['options_right']['reset'] = ['text' => 'Reload Page', 'url' => generate_url([ 'page' => 'map' ])]; // for debugging purpose if (isset($vars['export'])) { // define the tempfile path and name $map_tmp_path = tempnam(sys_get_temp_dir(), 'map_debug_data-'); // open the temp file $map_tmp_file = fopen($map_tmp_path, 'w') or die('Export not possible.'); // combine all data for the export $map_debug_data = print_r($ports, TRUE) . PHP_EOL . print_r($devices, TRUE) . PHP_EOL . $links . PHP_EOL . $devices_by_id; // write the export data to the tempfile fwrite($map_tmp_file, $map_debug_data); // close the temp file data stream fclose($map_tmp_file); // set a new header on the page for the debug file header('Content-Description: File Transfer'); header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename=mapdebug.txt'); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($map_tmp_path)); // clear the previous output to get a clean file without html from observium ob_clean(); flush(); // download the file readfile($map_tmp_path); // remove the file unlink($map_tmp_path); // end the script here exit; } // endif // check if the final arrays are empty > display error if ((empty(safe_json_decode($devices_by_id))) || (empty(safe_json_decode($links)))) { print_error('No map to display, maybe you are not running autodiscovery or no devices are linked. Download the debug file here.'); if (isset($vars['group_id']) || isset($vars['location'])) { print_warning(' Reload Page '); } exit; } // endif ?>
Loading... 0%
Traffic Load:
  • 0%
  • 1%
  • 1-10%
  • 10-25%
  • 25-40%
  • 40-55%
  • 55-70%
  • 70-85%
  • 85-95%
  • 95-100%