diff -ruN old/html/includes/entities/port.inc.php new/html/includes/entities/port.inc.php --- html/includes/entities/port.inc.php 2026-06-05 11:58:32.262065469 +0000 +++ html/includes/entities/port.inc.php 2026-06-05 11:59:05.753191220 +0000 @@ -863,6 +863,69 @@ $string .= '' . generate_tooltip_link(NULL, $int_link['remote_hostname'], $text) . ''; $string .= ' ' . nicecase($int_link['protocol']) . '
'; } + + // Output IS-IS/SPBM adjacencies for this port, similar to LLDP/CDP neighbours. + static $isis_adjacencies_table_exists = NULL; + if ($isis_adjacencies_table_exists === NULL) { + $isis_adjacencies_table_exists = in_array('isis_adjacencies', dbShowTables(), TRUE); + } + + if ($isis_adjacencies_table_exists) { + foreach (dbFetchRows('SELECT * FROM `isis_adjacencies` WHERE `port_id` = ? ORDER BY `isis_adj_neigh_name`, `isis_adj_neigh_sysid`', [$port['port_id']]) as $isis_adj) { + $neigh_name = trim((string)$isis_adj['isis_adj_neigh_name']); + $neigh_sysid = trim((string)$isis_adj['isis_adj_neigh_sysid']); + $state = trim((string)$isis_adj['isis_adj_state']); + $level = trim((string)$isis_adj['isis_adj_level']); + $mib = trim((string)$isis_adj['isis_mib']); + + $neigh_label = $neigh_name !== '' ? $neigh_name : ($neigh_sysid !== '' ? $neigh_sysid : 'unknown'); + + $known_device = []; + if ($neigh_name !== '') { + $known_device_id = get_device_id_by_hostname($neigh_name); + if ($known_device_id) { + $known_device = device_by_id_cache($known_device_id); + } + } + + $tooltip = '
'; + if ($neigh_sysid !== '') { + $tooltip .= 'System ID: ' . escape_html($neigh_sysid) . '
'; + } + if ($level !== '') { + $tooltip .= 'Level: ' . escape_html($level) . '
'; + } + if ($mib !== '') { + $tooltip .= 'MIB: ' . escape_html($mib) . '
'; + } + $tooltip .= 'State: ' . escape_html($state !== '' ? $state : 'unknown') . '
'; + + $string .= $br; + $string .= generate_tooltip_link(NULL, NULL, 'IS-IS/SPBM adjacency', ($config['icon']['routing'] ?? $config['icon']['connected'])) . ' '; + + if (!safe_empty($known_device)) { + $string .= '' . generate_device_link_short($known_device) . ''; + } else { + $string .= '' . generate_tooltip_link(NULL, escape_html($neigh_label), $tooltip) . ''; + } + + $label_class = (strtolower($state) === 'up') ? 'success' : 'warning'; + $string .= ' IS-IS'; + if ($state !== '') { + $string .= ' ' . escape_html($state); + } + $string .= ''; + + if ($level !== '') { + $string .= ' ' . escape_html($level) . ''; + } + if ($neigh_sysid !== '' && $neigh_name !== '') { + $string .= ' ' . escape_html($neigh_sysid) . ''; + } + + $br = '
'; + } + } } if (in_array($port['port_id'], (array)$cache['ports_pseudowires'][$device['device_id']])) { diff -ruN old/html/includes/navbar.inc.php new/html/includes/navbar.inc.php --- html/includes/navbar.inc.php 2026-06-05 11:58:32.258511637 +0000 +++ html/includes/navbar.inc.php 2026-06-05 11:59:05.738826650 +0000 @@ -671,12 +671,21 @@ $navbar['ports']['entries']['statuses']['entries'][] = ['url' => generate_url(['page' => 'deleted-ports']), 'icon' => $config['icon']['stop'], 'title' => 'Deleted', 'count' => $cache['ports']['stat']['deleted']]; } + // IS-IS/SPBM summary is currently not populated by cache-data.inc.php. + // Populate the count locally so the main Network menu can show IS-IS. + if (in_array('isis_status', dbShowTables(), TRUE)) { + $cache['routing']['isis']['count'] = dbFetchCell('SELECT COUNT(*) FROM `isis_status`'); + } else { + $cache['routing']['isis']['count'] = 0; + } + //////////// Build network menu if ($_SESSION['userlevel'] >= '5' && (dbExist('stp_bridge') || dbExist('vlans') || $cache['neighbours']['count'] || $cache['pseudowires']['count'] || $cache['routing']['bgp']['count'] || $cache['routing']['ospf']['count'] || + $cache['routing']['isis']['count'] || $cache['routing']['cef']['count'] || $cache['routing']['vrf']['count'] || $cache['routing']['bfd']['count'] || @@ -721,6 +730,11 @@ $navbar['network']['entries'][] = ['url' => generate_url(['page' => 'routing', 'protocol' => 'ospf']), 'icon' => $config['icon']['ospf'], 'title' => 'OSPF', 'count' => $cache['routing']['ospf']['count']]; } + // IS-IS / SPBM + if ($cache['routing']['isis']['count']) { + $navbar['network']['entries'][] = ['url' => generate_url(['page' => 'routing', 'protocol' => 'isis']), 'icon' => ($config['icon']['isis'] ?? $config['icon']['routing']), 'title' => 'IS-IS', 'count' => $cache['routing']['isis']['count']]; + } + // EIGRP if ($cache['routing']['eigrp']['count']) { $navbar['network']['entries'][] = ['url' => generate_url(['page' => 'routing', 'protocol' => 'eigrp']), 'icon' => $config['icon']['ospf'], 'title' => 'EIGRP', 'count' => $cache['routing']['eigrp']['count']]; @@ -755,7 +769,7 @@ } // Layer 2 protocols - if (dbExist('stp_bridge') || dbExist('vlans') || $cache['neighbours']['count'] || ($config['enable_pseudowires'] && $cache['pseudowires']['count'])) { + if (dbExist('stp_bridge') || dbExist('vlans') || dbExist('isis_isids') || $cache['neighbours']['count'] || ($config['enable_pseudowires'] && $cache['pseudowires']['count'])) { $navbar['network']['entries'][] = ['divider' => TRUE]; // Spanning Tree @@ -766,6 +780,11 @@ // VLANs $navbar['network']['entries'][] = [ 'title' => 'VLANs', 'url' => generate_url(['page' => 'vlan']), 'icon' => $config['icon']['vlan'] ]; + // I-SIDs + if (dbExist('isis_isids')) { + $navbar['network']['entries'][] = [ 'title' => 'I-SIDs', 'url' => generate_url(['page' => 'isid']), 'icon' => $config['icon']['vlan'] ]; + } + // Neighbours (CDP/LLDP) if ($cache['neighbours']['count']) { $navbar['network']['entries'][] = [ 'title' => 'Neighbours', 'url' => generate_url(['page' => 'neighbours']), 'icon' => $config['icon']['neighbours'], 'count' => $cache['neighbours']['count'] ]; diff -ruN old/html/pages/device/routing/isis.inc.php new/html/pages/device/routing/isis.inc.php --- html/pages/device/routing/isis.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ html/pages/device/routing/isis.inc.php 2026-06-05 11:59:05.724057849 +0000 @@ -0,0 +1,331 @@ +unknown'; } + $class = in_array(strtolower($value), $good, TRUE) ? 'success' : 'warning'; + if (in_array(strtolower($value), ['off', 'disabled', 'disable', 'down', 'failed', 'outofservice'], TRUE)) { + $class = 'error'; + } + return '' . escape_html($value) . ''; + } +} + + + +if (!function_exists('isis_ui_wrap_csv')) { + function isis_ui_wrap_csv($value, $items_per_line = 8) + { + $items = []; + foreach (explode(',', (string)$value) as $item) { + $item = trim($item); + if ($item !== '') { $items[] = escape_html($item); } + } + if (safe_empty($items)) { return ''; } + + $lines = []; + foreach (array_chunk($items, $items_per_line) as $chunk) { + $lines[] = implode(', ', $chunk); + } + return implode('
', $lines); + } +} + +if (!function_exists('isis_ui_isid_name')) { + function isis_ui_isid_name($value) + { + return escape_html(trim((string)$value, " \"'")); + } +} + +if (!function_exists('isis_ui_isid_type')) { + function isis_ui_isid_type($type, $vlan = NULL) + { + $type = strtolower(trim((string)$type)); + if ($type === 'l2vsn' && (int)$vlan > 0) { return 'cvlan'; } + return $type; + } +} + +if (!function_exists('isis_web_rich_instance_table_available')) { + function isis_web_rich_instance_table_available() + { + if (!in_array('isis_instances', dbShowTables(), TRUE)) { return FALSE; } + $columns = dbShowColumns('isis_instances', 'isis_mib'); + return isset($columns['isis_mib']); + } +} + +if (!function_exists('isis_web_status_table_available')) { + function isis_web_status_table_available() + { + return in_array('isis_status', dbShowTables(), TRUE); + } +} + +if (isis_web_status_table_available()) { + $instances = dbFetchRows('SELECT * FROM `isis_status` WHERE `device_id` = ? ORDER BY `isis_mib`, `isis_instance_index`', [$device['device_id']]); +} elseif (isis_web_rich_instance_table_available()) { + $instances = dbFetchRows('SELECT * FROM `isis_instances` WHERE `device_id` = ? ORDER BY `isis_mib`, `isis_instance_index`', [$device['device_id']]); +} else { + $instances = []; +} + +// Be defensive: older test installs may have port/adjacency rows but no instance row. +// Show the data anyway and let the next poller run recreate the instance row. +if (safe_empty($instances)) { + $mibs = []; + foreach (['isis_areas', 'isis_ports', 'isis_adjacencies'] as $table) { + foreach (dbFetchRows('SELECT DISTINCT `isis_mib` FROM `' . $table . '` WHERE `device_id` = ?', [$device['device_id']]) as $row) { + if (!safe_empty($row['isis_mib'])) { $mibs[$row['isis_mib']] = TRUE; } + } + } + + foreach (array_keys($mibs) as $mib) { + $instances[] = [ + 'device_id' => $device['device_id'], + 'isis_mib' => $mib, + 'isis_instance_index' => 'derived', + 'isis_sys_id' => '', + 'isis_sys_name' => '', + 'isis_admin_state' => 'on', + 'isis_level_type' => ($mib === 'RC-ISIS-MIB' ? 'spbm' : ''), + 'isis_lsp_count' => '', + 'isis_protocols' => '', + 'isis_last_enabled' => '' + ]; + } +} + +if (safe_empty($instances)) { + print_warning('No IS-IS data found for this device. Run the IS-IS poller first or verify that ISIS-MIB/RC-ISIS-MIB is discovered.'); + return; +} + +foreach ($instances as $instance) { + $params = [$device['device_id'], $instance['isis_mib']]; + + $area_count = dbFetchCell('SELECT COUNT(*) FROM `isis_areas` WHERE `device_id` = ? AND `isis_mib` = ?', $params); + $port_count = dbFetchCell('SELECT COUNT(*) FROM `isis_ports` WHERE `device_id` = ? AND `isis_mib` = ?', $params); + $adj_count = dbFetchCell('SELECT COUNT(*) FROM `isis_adjacencies` WHERE `device_id` = ? AND `isis_mib` = ?', $params); + $adj_up = dbFetchCell("SELECT COUNT(*) FROM `isis_adjacencies` WHERE `device_id` = ? AND `isis_mib` = ? AND `isis_adj_state` = 'up'", $params); + $adj_down = $adj_count - $adj_up; + + $row_class = $instance['isis_admin_state'] === 'on' ? 'up' : 'disabled'; + if ($instance['isis_admin_state'] === 'on' && ($adj_down > 0 || ($port_count > 0 && $adj_count == 0))) { + $row_class = 'warning'; + } + + $title = 'IS-IS'; + if (!safe_empty($instance['isis_sys_id'])) { + $title .= ' ' . escape_html($instance['isis_sys_id']); + } elseif (!safe_empty($instance['isis_sys_name'])) { + $title .= ' ' . escape_html($instance['isis_sys_name']); + } + + echo '
'; + echo '' . $title . ' '; + echo '' . escape_html($instance['isis_mib']) . ' '; + echo isis_web_label($instance['isis_admin_state']) . ' '; + if (!safe_empty($instance['isis_level_type'])) { echo '' . escape_html($instance['isis_level_type']) . ' '; } + echo 'Areas ' . $area_count . ' '; + echo 'Ports ' . $port_count . ' '; + echo 'Adjacencies ' . $adj_count . ' '; + echo '' . $adj_up . ' up '; + if ($adj_down) { echo '' . $adj_down . ' down '; } + if (!safe_empty($instance['isis_lsp_count'])) { echo 'LSPs ' . escape_html($instance['isis_lsp_count']) . ' '; } + echo '
'; + + if (!safe_empty($instance['isis_sys_name']) || !safe_empty($instance['isis_protocols']) || !safe_empty($instance['isis_last_enabled'])) { + echo '
'; + if (!safe_empty($instance['isis_sys_name'])) { echo 'System Name: ' . escape_html($instance['isis_sys_name']) . ' '; } + if (!safe_empty($instance['isis_protocols'])) { echo 'Protocols: ' . escape_html($instance['isis_protocols']) . ' '; } + if (!safe_empty($instance['isis_last_enabled'])) { echo 'Last enabled: ' . escape_html($instance['isis_last_enabled']) . ''; } + echo '
'; + } + + echo '
'; + echo '
'; + + echo generate_box_open(['title' => 'Areas']); + echo ''; + echo get_table_header([[NULL, 'class="state-marker"'], 'Area', 'Type', 'Status'], $vars); + $area_rows = dbFetchRows('SELECT * FROM `isis_areas` WHERE `device_id` = ? AND `isis_mib` = ? ORDER BY `isis_area_type`, `isis_area_address`', $params); + if (safe_empty($area_rows)) { + echo ''; + } + foreach ($area_rows as $area) { + $area_row_class = in_array($area['isis_area_status'], ['active', 'on', 'up'], TRUE) ? 'up' : ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
No area information reported by this MIB.
' . escape_html($area['isis_area_address']) . '' . escape_html($area['isis_area_type']) . '' . isis_web_label($area['isis_area_status']) . '
'; + echo generate_box_close(); + + echo generate_box_open(['title' => 'Circuits / Ports']); + echo ''; + echo get_table_header([[NULL, 'class="state-marker"'], ['Port', 'style="width: 200px"'], 'Circuit', 'Admin', 'Oper', 'Type', 'Metric', 'Hello'], $vars); + $p_sql = 'SELECT * FROM `isis_ports` LEFT JOIN `ports` USING (`device_id`, `port_id`) WHERE `isis_ports`.`device_id` = ? AND `isis_ports`.`isis_mib` = ? ORDER BY `ifDescr`, `isis_circ_ifIndex`, `isis_circ_index`'; + foreach (dbFetchRows($p_sql, $params) as $port) { + $port_row_class = in_array($port['isis_circ_admin_state'], ['on', 'up', 'enable', 'enabled'], TRUE) ? 'up' : 'disabled'; + if (in_array($port['isis_circ_oper_state'], ['outofservice', 'down'], TRUE)) { $port_row_class = 'warning'; } + $port_link = $port['port_id'] ? generate_port_link($port) : 'ifIndex ' . escape_html($port['isis_circ_ifIndex']); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . $port_link . '' . escape_html($port['isis_circ_index']) . '' . isis_web_label($port['isis_circ_admin_state'], ['on', 'up', 'enable', 'enabled']) . '' . isis_web_label($port['isis_circ_oper_state'], ['active', 'inservice', 'up']) . '' . escape_html($port['isis_circ_type'] ?: $port['isis_circ_level_type']) . '' . escape_html($port['isis_circ_metric']) . '' . escape_html($port['isis_circ_hello_interval']) . (!safe_empty($port['isis_circ_hello_multiplier']) ? ' x ' . escape_html($port['isis_circ_hello_multiplier']) : '') . '
'; + echo generate_box_close(); + + echo '
'; + echo '
'; + + echo generate_box_open(['title' => 'Adjacencies']); + echo ''; + echo get_table_header([[NULL, 'class="state-marker"'], ['Port', 'style="width: 190px"'], 'Neighbour', 'System ID', 'State', 'Usage', 'Hold', 'Uptime', 'Restart'], $vars); + $a_sql = 'SELECT * FROM `isis_adjacencies` LEFT JOIN `ports` USING (`device_id`, `port_id`) WHERE `isis_adjacencies`.`device_id` = ? AND `isis_adjacencies`.`isis_mib` = ? ORDER BY `ifDescr`, `isis_adj_neigh_name`, `isis_adj_neigh_sysid`'; + foreach (dbFetchRows($a_sql, $params) as $adj) { + $adj_row_class = $adj['isis_adj_state'] === 'up' ? 'up' : 'warning'; + $port_link = $adj['port_id'] ? generate_port_link($adj) : 'ifIndex ' . escape_html($adj['isis_circ_ifIndex']); + $restart = []; + if (!safe_empty($adj['isis_adj_restart_support'])) { $restart[] = 'support=' . $adj['isis_adj_restart_support']; } + if (!safe_empty($adj['isis_adj_restart_status'])) { $restart[] = $adj['isis_adj_restart_status']; } + if (!safe_empty($adj['isis_adj_restart_suppressed'])) { $restart[] = 'suppressed=' . $adj['isis_adj_restart_suppressed']; } + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . $port_link . '' . escape_html($adj['isis_adj_neigh_name'] ?: 'unknown') . '' . escape_html($adj['isis_adj_neigh_sysid']) . '' . isis_web_label($adj['isis_adj_state']) . (!safe_empty($adj['isis_adj_3way_state']) ? ' ' . isis_web_label($adj['isis_adj_3way_state']) : '') . '' . escape_html($adj['isis_adj_usage'] ?: $adj['isis_adj_neigh_type']) . '' . escape_html($adj['isis_adj_hold_remaining'] ?: $adj['isis_adj_hold_timer']) . '' . escape_html($adj['isis_adj_uptime']) . '' . escape_html(implode(', ', $restart)) . '
'; + echo generate_box_close(); + + echo '
'; + echo '
'; + + if (in_array('isis_fabric_nodes', dbShowTables(), TRUE)) { + echo generate_box_open(['title' => 'Known IS-IS / Fabric Nodes']); + + $fabric_vars = $vars; + if (safe_empty($fabric_vars['sort'])) { + $fabric_vars['sort'] = 'seen'; + $fabric_vars['sort_order'] = 'desc'; + } + + echo ''; + echo generate_table_header([ + 'state-marker' => '', + 'node' => ['node' => 'Node', 'style' => 'width: 48%;'], + 'system_id' => ['system_id' => 'System ID', 'style' => 'width: 150px;'], + 'source' => ['source' => 'Source', 'style' => 'width: 140px;'], + 'seen' => ['seen' => 'Seen', 'style' => 'width: 70px;'] + ], $fabric_vars); + + $known_by_name = []; + $known_by_sysid = []; + if (in_array('isis_status', dbShowTables(), TRUE)) { + foreach (dbFetchRows('SELECT `devices`.*, `isis_status`.`isis_sys_id` FROM `isis_status` LEFT JOIN `devices` USING (`device_id`)') as $known) { + if (!safe_empty($known['hostname'])) { $known_by_name[strtolower($known['hostname'])] = $known; } + if (!safe_empty($known['isis_sys_id'])) { $known_by_sysid[$known['isis_sys_id']] = $known; } + } + } + + $node_sql = 'SELECT * FROM `isis_fabric_nodes` WHERE `device_id` = ? AND `isis_mib` = ?'; + $rows = dbFetchRows($node_sql, $params); + usort($rows, function($a, $b) use ($fabric_vars) { + $sort = $fabric_vars['sort']; + $sort_order = strtolower((string)$fabric_vars['sort_order']) === 'asc' ? 'asc' : 'desc'; + switch ($sort) { + case 'node': $av = strtolower($a['isis_node_name']); $bv = strtolower($b['isis_node_name']); break; + case 'system_id': $av = $a['isis_node_sysid']; $bv = $b['isis_node_sysid']; break; + case 'source': $av = strtolower($a['isis_node_source']); $bv = strtolower($b['isis_node_source']); break; + case 'seen': + default: $av = (int)$a['isis_node_count']; $bv = (int)$b['isis_node_count']; break; + } + if ($av == $bv) { return strnatcasecmp($a['isis_node_name'], $b['isis_node_name']); } + $result = ($av < $bv) ? -1 : 1; + return $sort_order === 'asc' ? $result : -$result; + }); + + foreach ($rows as $node) { + $known_device = NULL; + if (!safe_empty($node['isis_node_name']) && isset($known_by_name[strtolower($node['isis_node_name'])])) { + $known_device = $known_by_name[strtolower($node['isis_node_name'])]; + } elseif (!safe_empty($node['isis_node_sysid']) && isset($known_by_sysid[$node['isis_node_sysid']])) { + $known_device = $known_by_sysid[$node['isis_node_sysid']]; + } elseif (!safe_empty($node['isis_node_name'])) { + $known_device = dbFetchRow('SELECT * FROM `devices` WHERE `hostname` = ? LIMIT 1', [$node['isis_node_name']]); + } + + $node_name = !safe_empty($node['isis_node_name']) ? $node['isis_node_name'] : (!safe_empty($node['isis_node_sysid']) ? $node['isis_node_sysid'] : $node['isis_node_key']); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . ($known_device ? generate_device_link($known_device, $node_name) : escape_html($node_name)) . '' . escape_html($node['isis_node_sysid']) . '' . escape_html($node['isis_node_source']) . '' . escape_html($node['isis_node_count']) . '
'; + echo generate_box_close(); + } + + if (in_array('isis_isids', dbShowTables(), TRUE)) { + echo generate_box_open(['title' => 'I-SIDs']); + $isid_origin_help = 'Origin: manual = locally configured, eap-local/eap-remote = learned by EAP, fa-ept = learned by Fabric Attach/EPT, auto-sense = auto-sense derived. Source shows which SNMP tables contributed the row.'; + echo '

Origin distinguishes locally configured I-SIDs from EAP/FA learned I-SIDs.

'; + echo ''; + echo get_table_header([ + ['I-SID', 'style="width: 90px;"'], + ['Name', 'style="width: 180px;"'], + ['Type', 'style="width: 80px;"'], + ['VLAN', 'style="width: 70px;"'], + ['VLAN Name', 'style="width: 110px;"'], + ['Origin', 'style="width: 150px;"'], + ['Source', 'style="width: 190px;"'], + ['Ports', 'style="width: 320px;"'] + ], $vars); + foreach (dbFetchRows('SELECT * FROM `isis_isids` WHERE `device_id` = ? AND `isis_mib` = ? ORDER BY `isid_id`', $params) as $isid) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . escape_html($isid['isid_id']) . '' . isis_ui_isid_name($isid['isid_name']) . '' . escape_html(isis_ui_isid_type($isid['isid_type'], $isid['isid_vlan'])) . '' . escape_html($isid['isid_vlan']) . '' . escape_html($isid['isid_vlan_name']) . '' . escape_html($isid['isid_origin']) . '' . isis_ui_wrap_csv($isid['isid_source'], 2) . '' . isis_ui_wrap_csv($isid['isid_ports'] ?: $isid['isid_ifindexes'], 8) . '
'; + echo generate_box_close(); + } + +} + +// EOF diff -ruN old/html/pages/device.inc.php new/html/pages/device.inc.php --- html/pages/device.inc.php 2026-06-05 11:58:32.570080135 +0000 +++ html/pages/device.inc.php 2026-06-05 11:59:05.721926457 +0000 @@ -135,14 +135,14 @@ // Ports submenu $ports_exist = [ - 'ports' => dbExist('ports', '`device_id` = ?', [$device['device_id']]), - 'ipv4' => dbExist('ipv4_addresses', '`device_id` = ?', [$device['device_id']]), - 'ipv6' => dbExist('ipv6_addresses', '`device_id` = ?', [$device['device_id']]) + 'ports' => dbExist('ports', '`device_id` = ?', [$device['device_id']]), + 'ipv4' => dbExist('ipv4_addresses', '`device_id` = ?', [$device['device_id']]), + 'ipv6' => dbExist('ipv6_addresses', '`device_id` = ?', [$device['device_id']]) ]; $link_array = [ - 'page' => 'device', - 'device' => $device['device_id'], - 'tab' => 'ports' + 'page' => 'device', + 'device' => $device['device_id'], + 'tab' => 'ports' ]; // Print the port tab if there are matching entries in the ports table @@ -152,6 +152,9 @@ $navbar['options']['ports']['suboptions']['basic']['text'] = 'Basic'; $navbar['options']['ports']['suboptions']['details']['text'] = 'Details'; + if (dbExist('transceivers', '`device_id` = ?', [$device['device_id']])) { + $navbar['options']['ports']['suboptions']['transceivers']['text'] = 'Transceivers'; + } $navbar['options']['ports']['suboptions']['divider_1']['divider'] = TRUE; } $option = 'ipv4'; @@ -422,6 +425,19 @@ 'icon' => $config['icon']['ospf']]; } + if (in_array('isis_status', dbShowTables(), TRUE)) { + $device_routing_count['isis'] = dbFetchCell("SELECT COUNT(*) FROM `isis_status` WHERE `device_id` = ?", [$device['device_id']]); + } else { + $device_routing_count['isis'] = 0; + } + + if ($device_routing_count['isis']) { + $routing_tabs[] = 'isis'; + $routing_options[] = ['url' => generate_url(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'routing', 'proto' => 'isis']), + 'text' => 'IS-IS', + 'icon' => ($config['icon']['isis'] ?? $config['icon']['routing'])]; + } + $device_routing_count['eigrp'] = dbFetchCell("SELECT COUNT(*) FROM `eigrp_ports` WHERE `device_id` = ?", [$device['device_id']]); if ($device_routing_count['eigrp']) { $routing_tabs[] = 'eigrp'; diff -ruN old/html/pages/isid.inc.php new/html/pages/isid.inc.php --- html/pages/isid.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ html/pages/isid.inc.php 2026-06-05 11:59:05.722857597 +0000 @@ -0,0 +1,257 @@ +', $lines); + } +} + +if (!function_exists('isid_ui_name')) { + function isid_ui_name($value) + { + return trim((string)$value, " \"'"); + } +} + +if (!function_exists('isid_ui_type')) { + function isid_ui_type($type, $vlans = []) + { + $type = strtolower(trim((string)$type)); + if ($type === 'l2vsn' && !safe_empty($vlans)) { return 'cvlan'; } + return $type; + } +} + +$navbar = ['brand' => 'I-SIDs', 'class' => 'navbar-narrow']; +$navbar['options']['list']['text'] = 'List View'; +$navbar['options']['list']['class'] = 'active'; +$navbar['options']['list']['url'] = generate_url($vars, ['view' => 'list']); +print_navbar($navbar); + +$search_form = [ + [ + 'type' => 'text', + 'name' => 'Search', + 'id' => 'isid_search', + 'width' => '220px', + 'placeholder' => 'I-SID, name, VLAN or device...', + 'value' => $vars['isid_search'] ?? '' + ], + [ + 'type' => 'select', + 'name' => 'Origin', + 'id' => 'isid_origin', + 'width' => '180px', + 'value' => $vars['isid_origin'] ?? '', + 'values' => [ + '' => 'Any Origin', + 'manual' => 'Manual', + 'eap' => 'EAP learned', + 'fa' => 'Fabric Attach / EPT', + 'auto-sense' => 'Auto-sense' + ] + ] +]; +print_search($search_form, 'I-SID Filters', 'search', generate_url($vars)); + +$isid_origin_help = 'Origin: manual = locally configured, eap-local/eap-remote = learned by EAP, fa-ept = learned by Fabric Attach/EPT, auto-sense = auto-sense derived.'; + +$sql = 'SELECT `isis_isids`.*, `devices`.`hostname` ' + . 'FROM `isis_isids` LEFT JOIN `devices` USING (`device_id`) ' + . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) + . ' ORDER BY `isid_id`, `hostname`'; + +$rows = dbFetchRows($sql); +$data = []; +foreach ($rows as $row) { + $id = (int)$row['isid_id']; + if ($id <= 0) { continue; } + + if (!isset($data[$id])) { + $data[$id] = [ + 'isid_id' => $id, + 'name' => '', + 'type' => '', + 'vlans' => [], + 'vlan_names' => [], + 'origins' => [], + 'sources' => [], + 'devices' => [], + 'ports' => [], + ]; + } + + $name = isid_ui_name($row['isid_name']); + if ($name !== '' && ($data[$id]['name'] === '' || preg_match('/^ISID-\d+$/', $data[$id]['name']))) { + $data[$id]['name'] = $name; + } + if (!safe_empty($row['isid_type']) && $data[$id]['type'] === '') { + $data[$id]['type'] = $row['isid_type']; + } + if ((int)$row['isid_vlan'] > 0) { + $data[$id]['vlans'][(int)$row['isid_vlan']] = TRUE; + } + if (!safe_empty($row['isid_vlan_name'])) { + $data[$id]['vlan_names'][$row['isid_vlan_name']] = TRUE; + } + foreach (explode(',', (string)$row['isid_origin']) as $origin) { + $origin = trim($origin); + if ($origin !== '') { $data[$id]['origins'][$origin] = TRUE; } + } + foreach (explode(',', (string)$row['isid_source']) as $source) { + $source = trim($source); + if ($source !== '') { $data[$id]['sources'][$source] = TRUE; } + } + if ((int)$row['device_id'] > 0) { + $data[$id]['devices'][(int)$row['device_id']] = $row['hostname']; + } + foreach (explode(',', (string)$row['isid_ports']) as $port) { + $port = trim($port); + if ($port !== '') { $data[$id]['ports'][$port] = TRUE; } + } +} + +if (!safe_empty($vars['isid_search'])) { + $search = strtolower((string)$vars['isid_search']); + $data = array_filter($data, function($row) use ($search) { + if (strpos((string)$row['isid_id'], $search) !== FALSE) { return TRUE; } + if (strpos(strtolower($row['name']), $search) !== FALSE) { return TRUE; } + foreach (array_keys($row['vlans']) as $vlan) { + if (strpos((string)$vlan, $search) !== FALSE) { return TRUE; } + } + foreach (array_keys($row['vlan_names']) as $name) { + if (strpos(strtolower($name), $search) !== FALSE) { return TRUE; } + } + foreach ($row['devices'] as $hostname) { + if (strpos(strtolower((string)$hostname), $search) !== FALSE) { return TRUE; } + } + return FALSE; + }); +} + +if (!safe_empty($vars['isid_origin'])) { + $origin_filter = (string)$vars['isid_origin']; + $data = array_filter($data, function($row) use ($origin_filter) { + $origins = array_keys($row['origins']); + foreach ($origins as $origin) { + if ($origin_filter === 'eap' && strpos($origin, 'eap') !== FALSE) { return TRUE; } + if ($origin_filter === 'fa' && strpos($origin, 'fa-') !== FALSE) { return TRUE; } + if ($origin_filter === $origin) { return TRUE; } + } + return FALSE; + }); +} + +$total_isids = count($data); +$total_devices = 0; +$manual_isids = 0; +$eap_isids = 0; +foreach ($data as $row) { + $total_devices += count($row['devices']); + $origins = array_keys($row['origins']); + if (in_array('manual', $origins, TRUE)) { $manual_isids++; } + foreach ($origins as $origin) { + if (strpos($origin, 'eap') !== FALSE) { $eap_isids++; break; } + } +} + +$status_boxes = [ + [ + 'title' => 'I-SIDs', + 'value' => ['text' => $total_isids, 'class' => 'label-info'], + 'subtitle' => 'All I-SID IDs' + ], + [ + 'title' => 'Device Sightings', + 'value' => ['text' => $total_devices, 'class' => $total_devices > 0 ? 'label-primary' : 'label-default'], + 'subtitle' => 'Seen On Devices' + ], + [ + 'title' => 'Manual I-SIDs', + 'value' => ['text' => $manual_isids, 'class' => $manual_isids > 0 ? 'label-success' : 'label-default'], + 'subtitle' => 'Locally Configured' + ], + [ + 'title' => 'EAP I-SIDs', + 'value' => ['text' => $eap_isids, 'class' => $eap_isids > 0 ? 'label-warning' : 'label-default'], + 'subtitle' => 'EAP Learned' + ] +]; + +if (function_exists('generate_status_panel')) { + echo generate_status_panel($status_boxes); +} else { + echo generate_box_open(); + echo '
'; + foreach ($status_boxes as $stat) { + echo '
'; + echo '
' . escape_html($stat['title']) . '
'; + echo '
' . escape_html($stat['value']['text']) . '
'; + echo '
' . escape_html($stat['subtitle']) . '
'; + echo '
'; + } + echo '
'; + echo generate_box_close(); +} + +echo generate_box_open(['title' => 'I-SID Inventory']); +echo '

Origin distinguishes locally configured I-SIDs from EAP/FA learned I-SIDs.

'; +echo ''; +echo get_table_header([ + ['I-SID', 'style="width: 90px;"'], + ['Name', 'style="width: 180px;"'], + ['Type', 'style="width: 90px;"'], + ['VLAN', 'style="width: 90px;"'], + ['VLAN Name', 'style="width: 140px;"'], + ['Origin', 'style="width: 150px;"'], + ['Seen On', 'style="width: 220px;"'], + ['Ports', 'style="width: 260px;"'] +], $vars); + +ksort($data); +foreach ($data as $row) { + $device_links = []; + foreach ($row['devices'] as $device_id => $hostname) { + $device_links[] = generate_device_link(device_by_id_cache($device_id)); + } + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; +} + +echo '
' . escape_html($row['isid_id']) . '' . escape_html($row['name']) . '' . escape_html(isid_ui_type($row['type'], array_keys($row['vlans']))) . '' . escape_html(implode(', ', array_keys($row['vlans']))) . '' . escape_html(implode(', ', array_keys($row['vlan_names']))) . '' . escape_html(implode(', ', array_keys($row['origins']))) . '' . implode(', ', $device_links) . '' . isid_ui_wrap_csv(implode(',', array_keys($row['ports'])), 6) . '
'; +echo generate_box_close(); + +// EOF diff -ruN old/html/pages/routing/isis.inc.php new/html/pages/routing/isis.inc.php --- html/pages/routing/isis.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ html/pages/routing/isis.inc.php 2026-06-05 11:59:05.723799214 +0000 @@ -0,0 +1,340 @@ +unknown'; } + $class = in_array(strtolower($value), $good, TRUE) ? 'success' : 'warning'; + if (in_array(strtolower($value), ['off', 'disabled', 'disable', 'down', 'failed', 'outofservice'], TRUE)) { + $class = 'error'; + } + return '' . escape_html($value) . ''; + } +} + + +if (!function_exists('isis_web_rich_instance_table_available')) { + function isis_web_rich_instance_table_available() + { + if (!in_array('isis_instances', dbShowTables(), TRUE)) { return FALSE; } + $columns = dbShowColumns('isis_instances', 'isis_mib'); + return isset($columns['isis_mib']); + } +} + +if (!function_exists('isis_web_status_table_available')) { + function isis_web_status_table_available() + { + return in_array('isis_status', dbShowTables(), TRUE); + } +} + + +if (!function_exists('isis_fabric_sort_header')) { + function isis_fabric_sort_header($vars, $field, $label, $current_sort, $current_order, $style = '', $title = '') + { + $is_current = ($current_sort === $field); + $next_order = ($is_current && $current_order === 'desc') ? 'asc' : 'desc'; + if (!$is_current) { $next_order = ($field === 'seen') ? 'desc' : 'asc'; } + + $display = $label; + if ($is_current) { + $display = '' . $display . ''; + $display .= $current_order === 'asc' + ? ' ' + : ' '; + } + + $style_attr = $style !== '' ? ' style="' . $style . '"' : ''; + $title_attr = $title !== '' ? ' title="' . escape_html($title) . '"' : ''; + return '' . $display . ''; + } +} + +echo generate_box_open(); + +echo ''; +$cols = [ + 'Device', + 'MIB / Source', + 'System ID', + 'System Name', + ['Status', 'style="width: 80px"'], + ['Level', 'style="width: 80px"'], + ['Areas', 'style="width: 70px"'], + ['Ports', 'style="width: 70px"'], + ['Adjacencies', 'style="width: 120px"'], + ['LSPs', 'style="width: 70px"'] +]; +echo get_table_header($cols, $vars); + +$area_counts = []; +foreach (dbFetchRows('SELECT `device_id`, `isis_mib`, COUNT(*) AS `count` FROM `isis_areas`' . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) . ' GROUP BY `device_id`, `isis_mib`') as $entry) { + $area_counts[$entry['device_id']][$entry['isis_mib']] = (int)$entry['count']; +} + +$port_counts = []; +foreach (dbFetchRows('SELECT `device_id`, `isis_mib`, COUNT(*) AS `count` FROM `isis_ports`' . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) . ' GROUP BY `device_id`, `isis_mib`') as $entry) { + $port_counts[$entry['device_id']][$entry['isis_mib']] = (int)$entry['count']; +} + +$adj_counts = []; +foreach (dbFetchRows("SELECT `device_id`, `isis_mib`, COUNT(*) AS `count`, SUM(CASE WHEN `isis_adj_state` = 'up' THEN 1 ELSE 0 END) AS `up` FROM `isis_adjacencies`" . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) . ' GROUP BY `device_id`, `isis_mib`') as $entry) { + $adj_counts[$entry['device_id']][$entry['isis_mib']] = [ 'count' => (int)$entry['count'], 'up' => (int)$entry['up'] ]; +} + +if (isis_web_status_table_available()) { + $instances = dbFetchRows('SELECT * FROM `isis_status`' . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) . ' ORDER BY `device_id`, `isis_mib`, `isis_instance_index`'); +} elseif (isis_web_rich_instance_table_available()) { + $instances = dbFetchRows('SELECT * FROM `isis_instances`' . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) . ' ORDER BY `device_id`, `isis_mib`, `isis_instance_index`'); +} else { + $instances = []; +} + +// Be defensive: if an older test poll created port/adjacency rows without an instance row, +// derive a minimal row so the global routing page is not empty. +if (safe_empty($instances)) { + $derived = []; + foreach (['isis_areas', 'isis_ports', 'isis_adjacencies'] as $table) { + foreach (dbFetchRows('SELECT DISTINCT `device_id`, `isis_mib` FROM `' . $table . '`' . generate_where_clause($GLOBALS['cache']['where']['devices_permitted'])) as $row) { + $key = $row['device_id'] . '|' . $row['isis_mib']; + $derived[$key] = [ + 'device_id' => $row['device_id'], + 'isis_mib' => $row['isis_mib'], + 'isis_instance_index' => 'derived', + 'isis_sys_id' => '', + 'isis_sys_name' => '', + 'isis_admin_state' => 'on', + 'isis_level_type' => ($row['isis_mib'] === 'RC-ISIS-MIB' ? 'spbm' : ''), + 'isis_lsp_count' => '' + ]; + } + } + $instances = array_values($derived); +} + +foreach ($instances as $instance) { + $device = device_by_id_cache($instance['device_id']); + $mib = $instance['isis_mib']; + + $area_count = $area_counts[$device['device_id']][$mib] ?? 0; + $port_count = $port_counts[$device['device_id']][$mib] ?? 0; + $adj_count = $adj_counts[$device['device_id']][$mib]['count'] ?? 0; + $adj_up = $adj_counts[$device['device_id']][$mib]['up'] ?? 0; + $adj_down = $adj_count - $adj_up; + + $row_class = ''; + if ($instance['isis_admin_state'] !== 'on') { + $row_class = 'disabled'; + } elseif ($adj_down > 0 || ($port_count > 0 && $adj_count == 0)) { + $row_class = 'warning'; + } + + $display_sys_id = $instance['isis_sys_id']; + if (safe_empty($display_sys_id) && $mib === 'RC-ISIS-MIB' && in_array('isis_fabric_nodes', dbShowTables(), TRUE)) { + $display_sys_id = dbFetchCell('SELECT `isis_node_sysid` FROM `isis_fabric_nodes` WHERE `device_id` = ? AND `isis_mib` = ? AND LOWER(`isis_node_name`) IN (?, ?) AND `isis_node_sysid` != ? ORDER BY `isis_node_count` DESC LIMIT 1', [ $device['device_id'], $mib, strtolower($device['hostname']), strtolower($device['sysName']), '' ]); + } + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; +} + +echo '
' . generate_device_link($device, NULL, ['tab' => 'routing', 'proto' => 'isis']) . '' . escape_html($mib) . '' . escape_html($display_sys_id) . '' . escape_html($instance['isis_sys_name']) . '' . isis_web_label($instance['isis_admin_state']) . '' . escape_html($instance['isis_level_type']) . '' . $area_count . '' . $port_count . '' . $adj_count . ' ' . $adj_up . ' up' . ($adj_down ? ' ' . $adj_down . ' down' : '') . '' . escape_html($instance['isis_lsp_count']) . '
'; + +echo generate_box_close(); + +if (in_array('isis_fabric_nodes', dbShowTables(), TRUE)) { + echo generate_box_open(['title' => 'Known IS-IS / Fabric Nodes']); + + $fabric_sort_set = !safe_empty($vars['sort'] ?? NULL); + $fabric_sort = $fabric_sort_set ? $vars['sort'] : 'seen'; + if (!in_array($fabric_sort, [ 'node', 'system_id', 'seen', 'seen_from' ], TRUE)) { + $fabric_sort = 'seen'; + $fabric_sort_set = FALSE; + } + $fabric_sort_order = strtolower((string)($vars['sort_order'] ?? '')); + if (!in_array($fabric_sort_order, [ 'asc', 'desc' ], TRUE)) { + $fabric_sort_order = $fabric_sort_set ? 'asc' : 'desc'; + } + + echo '

Seen counts how often a node was observed across all polled IS-IS/SPBM sources and devices. Multiple observations can come from the same observer, for example adjacency plus LSP/LSDB; it is not a physical link count.

'; + + echo ''; + echo ''; + echo isis_fabric_sort_header($vars, 'node', 'Node', $fabric_sort, $fabric_sort_order); + echo isis_fabric_sort_header($vars, 'system_id', 'System ID', $fabric_sort, $fabric_sort_order, 'width: 140px;'); + echo isis_fabric_sort_header($vars, 'seen', 'Seen', $fabric_sort, $fabric_sort_order, 'width: 70px;', 'Number of observations for this node across all polled IS-IS/SPBM sources. One device can see a node via several sources, for example direct adjacency, LSP/LSDB and numeric RC-ISIS fallback. This is not the number of physical links.'); + echo isis_fabric_sort_header($vars, 'seen_from', 'Seen From', $fabric_sort, $fabric_sort_order); + echo ''; + + $node_sql = 'SELECT `isis_fabric_nodes`.*, `devices`.`hostname` AS `observer_hostname` ' + . 'FROM `isis_fabric_nodes` LEFT JOIN `devices` USING (`device_id`) ' + . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']); + + $known_by_name = []; + $known_by_sysid = []; + if (in_array('isis_status', dbShowTables(), TRUE)) { + foreach (dbFetchRows('SELECT `devices`.*, `isis_status`.`isis_sys_id` FROM `isis_status` LEFT JOIN `devices` USING (`device_id`)') as $known) { + if (!safe_empty($known['hostname'])) { $known_by_name[strtolower($known['hostname'])] = $known; } + if (!safe_empty($known['isis_sys_id'])) { $known_by_sysid[$known['isis_sys_id']] = $known; } + } + } + + $nodes = []; + foreach (dbFetchRows($node_sql) as $node) { + $node_name = !safe_empty($node['isis_node_name']) ? $node['isis_node_name'] : ''; + $node_sysid = !safe_empty($node['isis_node_sysid']) ? $node['isis_node_sysid'] : ''; + $key = !safe_empty($node_name) ? strtolower($node_name) : (!safe_empty($node_sysid) ? $node_sysid : $node['isis_node_key']); + + if (!isset($nodes[$key])) { + $nodes[$key] = [ + 'node_name' => $node_name, + 'sysid' => $node_sysid, + 'seen' => 0, + 'observers' => [], + 'known' => NULL, + ]; + } + + if (safe_empty($nodes[$key]['node_name']) && !safe_empty($node_name)) { $nodes[$key]['node_name'] = $node_name; } + if (safe_empty($nodes[$key]['sysid']) && !safe_empty($node_sysid)) { $nodes[$key]['sysid'] = $node_sysid; } + $nodes[$key]['seen'] += (int)$node['isis_node_count']; + + $observer_id = (int)$node['device_id']; + if (!isset($nodes[$key]['observers'][$observer_id])) { + $nodes[$key]['observers'][$observer_id] = [ + 'device_id' => $observer_id, + 'hostname' => $node['observer_hostname'], + 'seen' => 0, + ]; + } + $nodes[$key]['observers'][$observer_id]['seen'] += (int)$node['isis_node_count']; + + if (!$nodes[$key]['known']) { + if (!safe_empty($node_name) && isset($known_by_name[strtolower($node_name)])) { + $nodes[$key]['known'] = $known_by_name[strtolower($node_name)]; + } elseif (!safe_empty($node_sysid) && isset($known_by_sysid[$node_sysid])) { + $nodes[$key]['known'] = $known_by_sysid[$node_sysid]; + } elseif (!safe_empty($node_name)) { + $known_device = dbFetchRow('SELECT * FROM `devices` WHERE `hostname` = ? LIMIT 1', [$node_name]); + if ($known_device) { $nodes[$key]['known'] = $known_device; } + } + } + } + + $rows = array_values($nodes); + foreach ($rows as &$row) { + ksort($row['observers']); + $row['seen_from_sort'] = implode(',', array_map(function($observer) { return $observer['hostname']; }, $row['observers'])); + } + unset($row); + + $sort = $fabric_sort; + $sort_order = $fabric_sort_order; + usort($rows, function($a, $b) use ($sort, $sort_order) { + switch ($sort) { + case 'node': $av = strtolower($a['node_name']); $bv = strtolower($b['node_name']); break; + case 'system_id': $av = $a['sysid']; $bv = $b['sysid']; break; + case 'seen_from': $av = strtolower($a['seen_from_sort']); $bv = strtolower($b['seen_from_sort']); break; + case 'seen': + default: $av = (int)$a['seen']; $bv = (int)$b['seen']; break; + } + if ($av == $bv) { return strnatcasecmp($a['node_name'], $b['node_name']); } + $result = ($av < $bv) ? -1 : 1; + return $sort_order === 'asc' ? $result : -$result; + }); + + foreach ($rows as $node) { + $node_name = !safe_empty($node['node_name']) ? $node['node_name'] : (!safe_empty($node['sysid']) ? $node['sysid'] : 'unknown'); + $seen_from = []; + foreach ($node['observers'] as $observer) { + $observer_device = device_by_id_cache($observer['device_id']); + $seen_from[] = generate_device_link($observer_device, NULL, ['tab' => 'routing', 'proto' => 'isis']); + } + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . ($node['known'] ? generate_device_link($node['known'], $node_name) : escape_html($node_name)) . '' . escape_html($node['sysid']) . '' . escape_html($node['seen']) . '' . implode(', ', $seen_from) . '
'; + echo generate_box_close(); +} + + +if (in_array('isis_isids', dbShowTables(), TRUE)) { + echo generate_box_open(['title' => 'IS-IS / SPBM I-SIDs']); + $isid_origin_help = 'Origin: manual = locally configured, eap-local/eap-remote = learned by EAP, fa-ept = learned by Fabric Attach/EPT, auto-sense = auto-sense derived. Source shows which SNMP tables contributed the row.'; + echo '

Local I-SIDs are learned from RAPID-CITY rcIsid/rcVlanPlsbIsid. On ERS/BOSS, rcIsid can be absent, so VLAN-to-I-SID bindings are shown when available. Origin distinguishes locally configured I-SIDs from EAP/FA learned I-SIDs.

'; + echo ''; + echo get_table_header(['I-SID', 'Name', 'Type', 'VLAN', 'VLAN Name', 'Origin', 'Source', 'Seen From', 'Ports'], $vars); + + $isid_sql = 'SELECT `isis_isids`.*, `devices`.`hostname` AS `observer_hostname` ' + . 'FROM `isis_isids` LEFT JOIN `devices` USING (`device_id`) ' + . generate_where_clause($GLOBALS['cache']['where']['devices_permitted']) + . ' ORDER BY `isid_id`, `device_id`'; + + $isids = []; + foreach (dbFetchRows($isid_sql) as $row) { + $key = (int)$row['isid_id']; + if (!isset($isids[$key])) { + $isids[$key] = [ + 'isid_id' => (int)$row['isid_id'], + 'name' => $row['isid_name'], + 'type' => $row['isid_type'], + 'vlans' => [], + 'vlan_names' => [], + 'origins' => [], + 'sources' => [], + 'devices' => [], + 'ports' => [], + ]; + } + if (safe_empty($isids[$key]['name']) && !safe_empty($row['isid_name'])) { $isids[$key]['name'] = $row['isid_name']; } + if (safe_empty($isids[$key]['type']) && !safe_empty($row['isid_type'])) { $isids[$key]['type'] = $row['isid_type']; } + if (!safe_empty($row['isid_vlan'])) { $isids[$key]['vlans'][(int)$row['isid_vlan']] = TRUE; } + if (!safe_empty($row['isid_vlan_name'])) { $isids[$key]['vlan_names'][$row['isid_vlan_name']] = TRUE; } + foreach (explode(',', (string)$row['isid_origin']) as $origin) { $origin = trim($origin); if ($origin !== '') { $isids[$key]['origins'][$origin] = TRUE; } } + foreach (explode(',', (string)$row['isid_source']) as $src) { $src = trim($src); if ($src !== '') { $isids[$key]['sources'][$src] = TRUE; } } + $isids[$key]['devices'][(int)$row['device_id']] = TRUE; + foreach (explode(',', (string)$row['isid_ports']) as $port) { $port = trim($port); if ($port !== '') { $isids[$key]['ports'][$port] = TRUE; } } + } + + foreach ($isids as $isid) { + $seen_from = []; + foreach (array_keys($isid['devices']) as $device_id) { + $seen_from[] = generate_device_link(device_by_id_cache($device_id), NULL, ['tab' => 'routing', 'proto' => 'isis']); + } + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . escape_html($isid['isid_id']) . '' . escape_html($isid['name']) . '' . escape_html($isid['type']) . '' . escape_html(implode(', ', array_keys($isid['vlans']))) . '' . escape_html(implode(', ', array_keys($isid['vlan_names']))) . '' . escape_html(implode(', ', array_keys($isid['origins']))) . '' . escape_html(implode(', ', array_keys($isid['sources']))) . '' . implode(', ', $seen_from) . '' . escape_html(implode(', ', array_slice(array_keys($isid['ports']), 0, 12))) . (count($isid['ports']) > 12 ? ' …' : '') . '
'; + echo generate_box_close(); +} + +// EOF diff -ruN old/html/pages/routing.inc.php new/html/pages/routing.inc.php --- html/pages/routing.inc.php 2026-06-05 11:58:32.529593779 +0000 +++ html/pages/routing.inc.php 2026-06-05 11:59:05.722436818 +0000 @@ -24,6 +24,13 @@ $navbar['brand'] = "Routing"; $navbar['class'] = "navbar-narrow"; +if (in_array('isis_status', dbShowTables(), TRUE)) { + $isis_count = dbFetchCell('SELECT COUNT(*) FROM `isis_status`'); + if ($isis_count > 0) { + $cache['routing']['isis']['count'] = $isis_count; + } +} + foreach ($cache['routing'] as $type => $value) { if ($value['count'] > 0) { if (!$vars['protocol']) { @@ -34,7 +41,7 @@ } $navbar['options'][$type]['url'] = generate_url(['page' => 'routing', 'protocol' => $type]); - $navbar['options'][$type]['icon'] = $config['icon'][$type]; + $navbar['options'][$type]['icon'] = $config['icon'][$type] ?? $config['icon']['routing']; $navbar['options'][$type]['text'] = nicecase($type) . ' (' . $value['count'] . ')'; } } @@ -57,6 +64,7 @@ case 'cef': case 'eigrp': case 'ospf': + case 'isis': include($config['html_dir'] . '/pages/routing/' . $vars['protocol'] . '.inc.php'); break; case 'bfd': diff -ruN old/includes/config-variables.inc.php new/includes/config-variables.inc.php --- includes/config-variables.inc.php 2026-06-05 11:58:32.004793461 +0000 +++ includes/config-variables.inc.php 2026-06-05 11:59:05.721171119 +0000 @@ -1464,6 +1464,20 @@ $config_variable[$setting]['type'] = 'bool'; $config_variable[$setting]['shortdesc'] = 'Enable or disable the remember me feature.'; +$setting = 'login_remember_revalidate_ttl'; +$config_variable[$setting]['section'] = $section; +$config_variable[$setting]['subsection'] = 'Login'; +$config_variable[$setting]['name'] = 'Remember me LDAP/AD Re-validation TTL'; +$config_variable[$setting]['type'] = 'int'; +$config_variable[$setting]['shortdesc'] = 'Seconds between live LDAP/AD re-validation checks for remember me token logins. Set to 0 to check every token login.'; + +$setting = 'login_remember_revalidate_fail_open'; +$config_variable[$setting]['section'] = $section; +$config_variable[$setting]['subsection'] = 'Login'; +$config_variable[$setting]['name'] = 'Remember me LDAP/AD Re-validation Fail Open'; +$config_variable[$setting]['type'] = 'bool'; +$config_variable[$setting]['shortdesc'] = 'Allow remember me token logins if LDAP/AD is unreachable during re-validation.'; + $setting = 'web_session_lifetime'; $config_variable[$setting]['section'] = $section; $config_variable[$setting]['subsection'] = 'Sessions'; @@ -2277,12 +2291,12 @@ $config_variable[$setting]['type'] = 'bool'; $config_variable[$setting]['shortdesc'] = 'Enable collection and display of OSPF data.'; -//$setting = 'enable_isis'; -//$config_variable[$setting]['section'] = $section; -//$config_variable[$setting]['subsection'] = 'Protocols'; -//$config_variable[$setting]['name'] = 'ISIS sessions'; -//$config_variable[$setting]['type'] = 'bool'; -//$config_variable[$setting]['shortdesc'] = 'Enable collection and display of ISIS data.'; +$setting = 'enable_isis'; +$config_variable[$setting]['section'] = $section; +$config_variable[$setting]['subsection'] = 'Protocols'; +$config_variable[$setting]['name'] = 'ISIS collection'; +$config_variable[$setting]['type'] = 'bool'; +$config_variable[$setting]['shortdesc'] = 'Enable collection and display of IS-IS/SPBM data.'; $setting = 'enable_eigrp'; $config_variable[$setting]['section'] = $section; diff -ruN old/includes/defaults.inc.php new/includes/defaults.inc.php --- includes/defaults.inc.php 2026-06-05 11:58:32.003927081 +0000 +++ includes/defaults.inc.php 2026-06-05 11:59:05.720284858 +0000 @@ -174,7 +174,7 @@ #$config['base_url'] = "http://localhost/"; // Not recommend change this, only if autodetect wrong. Used in WUI #$config['web_url'] = "http://localhost:80/"; // This same as base_url but used only in cli for example in alerts -$config['mono_font'] = "DroidSansMono,DejaVuSansMono"; +$config['mono_font'] = "DejaVuSansMono"; $config['favicon'] = "images/observium-icon.png"; $config['page_refresh'] = "300"; // Refresh the page every xx seconds, 0 to disable $config['front_page'] = "pages/front/default.php"; @@ -185,6 +185,8 @@ $config['date_format'] = 'Y-m-d'; $config['login_message'] = "Unauthorised access or use shall render the user liable to criminal and/or civil prosecution."; $config['login_remember_me'] = TRUE; // Enable or disable the remember me feature. +$config['login_remember_revalidate_ttl'] = 900; // Seconds between live LDAP/AD checks for remember me token logins (0 = every token login). +$config['login_remember_revalidate_fail_open'] = TRUE; // Allow remember me token logins if LDAP/AD is unreachable during re-validation. // User editable settings $config['web_mouseover'] = TRUE; // Enable or disable mouseover popups. @@ -1281,6 +1283,7 @@ $config['poller_modules']['wifi'] = 1; $config['poller_modules']['p2p-radios'] = 1; $config['poller_modules']['ospf'] = 1; +$config['poller_modules']['isis'] = 1; $config['poller_modules']['cisco-ipsec-flow-monitor'] = 1; $config['poller_modules']['cisco-remote-access-monitor'] = 1; $config['poller_modules']['cisco-cef'] = 1; @@ -1381,7 +1384,7 @@ // Enable or disable specific API endpoint $config['api']['endpoints']['alerts'] = 1; -$config['api']['endpoints']['bills'] = 0; +$config['api']['endpoints']['bills'] = 1; $config['api']['endpoints']['devices'] = 1; $config['api']['endpoints']['ports'] = 1; $config['api']['endpoints']['sensors'] = 1; diff -ruN old/includes/definitions/entities/devices.inc.php new/includes/definitions/entities/devices.inc.php --- includes/definitions/entities/devices.inc.php 2026-06-05 11:58:31.765179608 +0000 +++ includes/definitions/entities/devices.inc.php 2026-06-05 11:59:05.726600473 +0000 @@ -316,6 +316,7 @@ 'wifi_aps', 'wifi_radios', 'wifi_wlans', 'bfd_sessions', 'bfd_neighbors', 'eigrp_ases', 'eigrp_peers', 'eigrp_vpns', // FIXME. Not know if eigrp module is active + 'isis_status', 'isis_areas', 'isis_ports', 'isis_adjacencies', 'isis_fabric_nodes', 'isis_isids', 'ipv4_addresses', 'ipv6_addresses', 'ip_mac', 'notifications_queue', 'notification_log', 'mac_accounting', 'snmp_errors', 'device_graphs', 'devices_mibs', 'devices_locations', /* 'devices_modules', */ 'devices' // always leave the table devices as last ]; diff -ruN old/includes/definitions/entities/routing.inc.php new/includes/definitions/entities/routing.inc.php --- includes/definitions/entities/routing.inc.php 2026-06-05 11:58:31.765414312 +0000 +++ includes/definitions/entities/routing.inc.php 2026-06-05 11:59:05.782761132 +0000 @@ -219,4 +219,70 @@ $config['entities'][$entity]['metrics']['bgpPeerOutTotalMessages_rate'] = [ 'label' => 'Session Out Messages (rate)', 'type' => 'integer' ]; */ + +// IS-IS Instance / SPBM status +$entity = 'isis_instance'; +// Common names +$config['entities'][$entity]['name'] = "IS-IS Instance"; +$config['entities'][$entity]['names'] = "IS-IS Instances"; +// Main table & field +$config['entities'][$entity]['table'] = "isis_status"; +$config['entities'][$entity]['table_fields']['id'] = "isis_status_id"; +$config['entities'][$entity]['table_fields']['device_id'] = "device_id"; +$config['entities'][$entity]['table_fields']['index'] = "isis_instance_index"; +$config['entities'][$entity]['table_fields']['mib'] = "isis_mib"; +$config['entities'][$entity]['table_fields']['name'] = "isis_sys_name"; +$config['entities'][$entity]['table_fields']['descr'] = "isis_sys_id"; +// Icon & graph +$config['entities'][$entity]['icon'] = $config['icon']['isis'] ?? $config['icon']['routing']; +$config['entities'][$entity]['graph'] = [ 'type' => 'device_isis_adjacencies', 'id' => '@device_id' ]; + +$config['entities'][$entity]['attribs']['device_id'] = [ 'label' => 'Device', 'descr' => 'Device', 'type' => 'string', 'values' => 'device', 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['isis_mib'] = [ 'label' => 'MIB', 'descr' => 'IS-IS MIB Source', 'type' => 'string', 'values' => [ 'ISIS-MIB', 'RC-ISIS-MIB' ], 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['isis_sys_id'] = [ 'label' => 'System ID', 'descr' => 'IS-IS System ID', 'type' => 'string' ]; +$config['entities'][$entity]['attribs']['isis_sys_name'] = [ 'label' => 'System Name', 'descr' => 'IS-IS System Name', 'type' => 'string' ]; +$config['entities'][$entity]['attribs']['isis_admin_state'] = [ 'label' => 'Admin State', 'descr' => 'IS-IS Admin State', 'type' => 'string', 'values' => [ 'on', 'off', 'enabled', 'disabled' ], 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['isis_level_type'] = [ 'label' => 'Level Type', 'descr' => 'IS-IS Level Type', 'type' => 'string' ]; + +// Metrics for Alert Checker +$config['entities'][$entity]['metrics']['isis_admin_state'] = [ 'label' => 'Admin State', 'type' => 'string', 'values' => [ 'on', 'off', 'enabled', 'disabled' ] ]; +$config['entities'][$entity]['metrics']['isis_areas'] = [ 'label' => 'Areas', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_ports'] = [ 'label' => 'Ports', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_adjacencies'] = [ 'label' => 'Adjacencies', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_adjacencies_up'] = [ 'label' => 'Adjacencies Up', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_adjacencies_down'] = [ 'label' => 'Adjacencies Down', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_lsp_count'] = [ 'label' => 'LSP Count', 'type' => 'integer' ]; + + +// IS-IS Adjacency / SPBM neighbour +$entity = 'isis_adjacency'; +// Common names +$config['entities'][$entity]['name'] = "IS-IS Adjacency"; +$config['entities'][$entity]['names'] = "IS-IS Adjacencies"; +// Main table & field +$config['entities'][$entity]['table'] = "isis_adjacencies"; +$config['entities'][$entity]['table_fields']['id'] = "isis_adjacency_id"; +$config['entities'][$entity]['table_fields']['device_id'] = "device_id"; +$config['entities'][$entity]['table_fields']['index'] = "isis_adj_index"; +$config['entities'][$entity]['table_fields']['mib'] = "isis_mib"; +$config['entities'][$entity]['table_fields']['name'] = "isis_adj_neigh_name"; +$config['entities'][$entity]['table_fields']['descr'] = "isis_adj_neigh_sysid"; +// Icon & graph +$config['entities'][$entity]['icon'] = $config['icon']['isis'] ?? $config['icon']['routing']; + +$config['entities'][$entity]['attribs']['device_id'] = [ 'label' => 'Device', 'descr' => 'Device', 'type' => 'string', 'values' => 'device', 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['port_id'] = [ 'label' => 'Port ID', 'descr' => 'Local Port ID', 'type' => 'integer' ]; +$config['entities'][$entity]['attribs']['isis_mib'] = [ 'label' => 'MIB', 'descr' => 'IS-IS MIB Source', 'type' => 'string', 'values' => [ 'ISIS-MIB', 'RC-ISIS-MIB' ], 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['isis_adj_neigh_name'] = [ 'label' => 'Neighbour Name', 'descr' => 'IS-IS Neighbour Hostname', 'type' => 'string' ]; +$config['entities'][$entity]['attribs']['isis_adj_neigh_sysid'] = [ 'label' => 'Neighbour System ID','descr' => 'IS-IS Neighbour System ID', 'type' => 'string' ]; +$config['entities'][$entity]['attribs']['isis_adj_level'] = [ 'label' => 'Level', 'descr' => 'IS-IS Adjacency Level', 'type' => 'string', 'values' => [ 'level1', 'level2', 'level1and2' ], 'free' => FALSE ]; +$config['entities'][$entity]['attribs']['isis_adj_state'] = [ 'label' => 'State', 'descr' => 'IS-IS Adjacency State', 'type' => 'string', 'values' => [ 'up', 'down', 'init', 'failed' ], 'free' => FALSE ]; + +// Metrics for Alert Checker +$config['entities'][$entity]['metrics']['isis_adj_state'] = [ 'label' => 'Adjacency State', 'type' => 'string', 'values' => [ 'up', 'down', 'init', 'failed' ] ]; +$config['entities'][$entity]['metrics']['isis_adj_hold_timer'] = [ 'label' => 'Hold Timer', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_adj_hold_remaining'] = [ 'label' => 'Hold Remaining', 'type' => 'integer' ]; +$config['entities'][$entity]['metrics']['isis_adj_priority'] = [ 'label' => 'Adjacency Priority', 'type' => 'integer' ]; + + // EOF diff -ruN old/includes/definitions/graphtypes.inc.php new/includes/definitions/graphtypes.inc.php --- includes/definitions/graphtypes.inc.php 2026-06-05 11:58:31.764472585 +0000 +++ includes/definitions/graphtypes.inc.php 2026-06-05 11:59:05.724663420 +0000 @@ -343,6 +343,83 @@ ] ]; + + +$config['graph_types']['device']['isis_adjacencies'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Adjacencies', + 'file' => 'isis-statistics.rrd', + 'scale_min' => '0', + 'ds' => [ + 'adjacencies' => [ 'label' => 'Total', 'draw' => 'AREA' ], + 'adj_up' => [ 'label' => 'Up', 'draw' => 'LINE' ], + 'adj_down' => [ 'label' => 'Down', 'draw' => 'LINE' ] + ] +]; + +$config['graph_types']['device']['isis_adjacency_states'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Adjacency States', + 'file' => 'isis-statistics.rrd', + 'scale_min' => '0', + 'ds' => [ + 'adj_up' => [ 'label' => 'Up', 'draw' => 'AREA' ], + 'adj_down' => [ 'label' => 'Down', 'draw' => 'STACK' ] + ] +]; + +$config['graph_types']['device']['isis_ports'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Ports', + 'file' => 'isis-statistics.rrd', + 'scale_min' => '0', + 'ds' => [ 'ports' => [ 'label' => 'Ports', 'draw' => 'AREA' ] ] +]; + +$config['graph_types']['device']['isis_areas'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Areas', + 'file' => 'isis-statistics.rrd', + 'scale_min' => '0', + 'ds' => [ 'areas' => [ 'label' => 'Areas', 'draw' => 'AREA' ] ] +]; + +$config['graph_types']['device']['isis_packets_hello'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Hello Packets', + 'file' => 'isis-packets.rrd', + 'scale_min' => '0', + 'colours' => 'mixed-10', + 'unit_text' => 'pps', + 'ds' => [ + 'iih_in' => [ 'label' => 'IIH In', 'draw' => 'AREA', 'line' => TRUE ], + 'iih_out' => [ 'label' => 'IIH Out', 'draw' => 'STACK', 'line' => TRUE ], + 'ish_in' => [ 'label' => 'ISH In', 'draw' => 'LINE', 'line' => TRUE ], + 'ish_out' => [ 'label' => 'ISH Out', 'draw' => 'LINE', 'line' => TRUE ], + 'esh_in' => [ 'label' => 'ESH In', 'draw' => 'LINE', 'line' => TRUE ], + 'esh_out' => [ 'label' => 'ESH Out', 'draw' => 'LINE', 'line' => TRUE ] + ] +]; + +$config['graph_types']['device']['isis_packets_database'] = [ + 'section' => 'isis', + 'descr' => 'IS-IS Database Packets', + 'file' => 'isis-packets.rrd', + 'scale_min' => '0', + 'colours' => 'mixed-10', + 'unit_text' => 'pps', + 'ds' => [ + 'lsp_in' => [ 'label' => 'LSP In', 'draw' => 'AREA', 'line' => TRUE ], + 'lsp_out' => [ 'label' => 'LSP Out', 'draw' => 'STACK', 'line' => TRUE ], + 'csnp_in' => [ 'label' => 'CSNP In', 'draw' => 'LINE', 'line' => TRUE ], + 'csnp_out' => [ 'label' => 'CSNP Out', 'draw' => 'LINE', 'line' => TRUE ], + 'psnp_in' => [ 'label' => 'PSNP In', 'draw' => 'LINE', 'line' => TRUE ], + 'psnp_out' => [ 'label' => 'PSNP Out', 'draw' => 'LINE', 'line' => TRUE ], + 'unknown_in' => [ 'label' => 'Unknown In', 'draw' => 'LINE', 'line' => TRUE ], + 'unknown_out' => [ 'label' => 'Unknown Out', 'draw' => 'LINE', 'line' => TRUE ] + ] +]; + // BFD Graph types $config['graph_types']['device']['bfd_sessions'] = [ 'section' => 'bfd', diff -ruN old/includes/definitions/mibs/nortel.inc.php new/includes/definitions/mibs/nortel.inc.php --- includes/definitions/mibs/nortel.inc.php 2026-06-05 11:58:31.714497034 +0000 +++ includes/definitions/mibs/nortel.inc.php 2026-06-05 11:59:05.726369426 +0000 @@ -72,6 +72,20 @@ $config['mibs'][$mib]['states'][$type][11] = [ 'name' => 'notConfig', 'event' => 'ignore' ]; $config['mibs'][$mib]['states'][$type][12] = [ 'name' => 'obsoleted', 'event' => 'alert' ]; + +$mib = 'RC-ISIS-MIB'; +$config['mibs'][$mib]['enable'] = 1; +$config['mibs'][$mib]['identity_num'] = '.1.3.6.1.4.1.2272.1.63.0'; // RC-ISIS-MIB::rcIsisMib +$config['mibs'][$mib]['mib_dir'] = 'nortel'; +$config['mibs'][$mib]['descr'] = 'Extreme/Avaya/Nortel IS-IS/SPBM routing extension MIB.'; +$config['mibs'][$mib]['discovery'][] = [ + 'RC-ISIS-MIB::rcIsisGlobalHostName.0' => '/.+/', + // Some VOSS/FabricEngine versions expose the RC-ISIS tree but do not advertise + // the RC-ISIS-MIB identity in sysORID. Probe the numeric rcIsisGlobalHostName.0 + // OID as a fallback instead of treating generic RAPID-CITY identities as IS-IS. + '.1.3.6.1.4.1.2272.1.63.1.10.0' => '/.+/' +]; + $mib = 'S5-REG-MIB'; $config['mibs'][$mib]['enable'] = 1; $config['mibs'][$mib]['identity_num'] = '.1.3.6.1.4.1.45.1.6.1.4'; diff -ruN old/includes/definitions/rrdtypes/isis.inc.php new/includes/definitions/rrdtypes/isis.inc.php --- includes/definitions/rrdtypes/isis.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ includes/definitions/rrdtypes/isis.inc.php 2026-06-05 11:59:05.726717789 +0000 @@ -0,0 +1,38 @@ + 'isis-statistics.rrd', + 'ds' => [ + 'instances' => [ 'type' => 'GAUGE', 'min' => 0 ], + 'areas' => [ 'type' => 'GAUGE', 'min' => 0 ], + 'ports' => [ 'type' => 'GAUGE', 'min' => 0 ], + 'adjacencies' => [ 'type' => 'GAUGE', 'min' => 0 ], + 'up' => [ 'type' => 'GAUGE', 'min' => 0 ], + 'down' => [ 'type' => 'GAUGE', 'min' => 0 ], + ], +]; + +$config['rrd_types']['isis-packets'] = [ + 'file' => 'isis-packets.rrd', + 'ds' => [ + 'iih_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'iih_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'ish_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'ish_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'esh_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'esh_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'lsp_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'lsp_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'csnp_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'csnp_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'psnp_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'psnp_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'unknown_in' => [ 'type' => 'COUNTER', 'min' => 0 ], + 'unknown_out' => [ 'type' => 'COUNTER', 'min' => 0 ], + ], +]; + +// EOF diff -ruN old/includes/definitions/wui.inc.php new/includes/definitions/wui.inc.php --- includes/definitions/wui.inc.php 2026-06-05 11:58:31.697474957 +0000 +++ includes/definitions/wui.inc.php 2026-06-05 11:59:05.725284260 +0000 @@ -166,6 +166,7 @@ $config['icon']['return'] = "sprite-return"; $config['icon']['sort'] = "sprite-sort"; $config['icon']['network'] = "sprite-routing"; +$config['icon']['isis'] = "sprite-routing"; $config['icon']['up'] = $config['icon']['checked']; $config['icon']['down'] = $config['icon']['minus']; // what is this joke? ignore/shutdown :) diff -ruN old/includes/polling/isis/isis-mib.inc.php new/includes/polling/isis/isis-mib.inc.php --- includes/polling/isis/isis-mib.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ includes/polling/isis/isis-mib.inc.php 2026-06-05 11:59:05.725957572 +0000 @@ -0,0 +1,247 @@ + '0', + 'isis_sys_id' => $sys_id, + 'isis_sys_name' => $device['hostname'] ?? $device['sysName'] ?? NULL, + 'isis_admin_state' => $admin_state, + 'isis_level_type' => isis_enum_value($system['isisSysLevelType'] ?? NULL), + 'isis_lsp_count' => NULL, +]; + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Areas', 2); + +$area_rows = []; +foreach (snmpwalk_cache_oid($device, 'isisManAreaAddrEntry', [], 'ISIS-MIB') as $index => $entry) { + $area_address = isis_format_area_address($entry['isisManAreaAddr'] ?? NULL, $index); + // Some ERS/BOSS agents expose only the row/index for manual areas here, + // while the actual area value is available in isisAreaAddrEntry. Do not + // store empty/manual placeholder rows such as manual.I... + if (safe_empty($area_address)) { continue; } + $key = 'manual.' . $area_address; + $area_rows[$key] = [ + 'device_id' => $device['device_id'], + 'isis_mib' => 'ISIS-MIB', + 'isis_area_index' => $key, + 'isis_area_address' => $area_address, + 'isis_area_type' => 'manual', + 'isis_area_status' => isis_enum_value($entry['isisManAreaAddrExistState'] ?? NULL) ?: 'active', + 'isis_area_polled' => time(), + ]; +} +foreach (snmpwalk_cache_oid($device, 'isisAreaAddrEntry', [], 'ISIS-MIB') as $index => $entry) { + $area_address = isis_format_area_address($entry['isisAreaAddr'] ?? NULL, $index); + $key = 'learned.' . (!safe_empty($area_address) ? $area_address : $index); + if (isset($area_rows[$key])) { continue; } + $area_rows[$key] = [ + 'device_id' => $device['device_id'], + 'isis_mib' => 'ISIS-MIB', + 'isis_area_index' => $key, + 'isis_area_address' => $area_address, + 'isis_area_type' => 'learned', + 'isis_area_status' => isis_enum_value($entry['isisAreaAddrExistState'] ?? NULL) ?: 'active', + 'isis_area_polled' => time(), + ]; +} + +$isis_stats['areas'] += isis_sync_rows('isis_areas', 'isis_area_id', 'isis_area_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'ISIS-MIB'], $area_rows); + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Circuits', 2); + +$port_rows = []; +$circ_ifindex = []; +$circ_port_id = []; +foreach (snmpwalk_cache_oid($device, 'isisCircEntry', [], 'ISIS-MIB') as $index => $entry) { + $ifIndex = isis_numeric_value($entry['isisCircIfIndex'] ?? NULL); + $port_id = isis_get_port_id($device, $ifIndex); + + $circ_ifindex[$index] = $ifIndex ?: 0; + $circ_port_id[$index] = $port_id; + + $port_rows[$index] = [ + 'device_id' => $device['device_id'], + 'port_id' => $port_id, + 'isis_mib' => 'ISIS-MIB', + 'isis_circ_index' => (string)$index, + 'isis_circ_ifIndex' => $ifIndex ?: 0, + 'isis_circ_admin_state' => isis_admin_state($entry['isisCircAdminState'] ?? NULL), + 'isis_circ_oper_state' => isis_enum_value($entry['isisCircExistState'] ?? NULL), + 'isis_circ_type' => isis_enum_value($entry['isisCircType'] ?? NULL), + 'isis_circ_level_type' => isis_enum_value($entry['isisCircLevelType'] ?? NULL), + 'isis_circ_passive' => isis_enum_value($entry['isisCircPassiveCircuit'] ?? NULL), + 'isis_circ_metric' => 0, + 'isis_circ_hello_interval' => 0, + 'isis_circ_hello_multiplier' => 0, + 'isis_circ_ext_domain' => isis_enum_value($entry['isisCircExtDomain'] ?? NULL), + 'isis_circ_mesh_group' => isis_clean_value($entry['isisCircMeshGroup'] ?? NULL), + 'isis_circ_small_hellos' => isis_enum_value($entry['isisCircSmallHellos'] ?? NULL), + 'isis_circ_last_uptime' => isis_clean_value($entry['isisCircLastUpTime'] ?? NULL), + 'isis_circ_3way_enabled' => isis_enum_value($entry['isisCirc3WayEnabled'] ?? NULL), + 'isis_circ_extended_circ_id' => isis_numeric_value($entry['isisCircExtendedCircID'] ?? NULL), + 'isis_circ_polled' => time(), + ]; +} + +$isis_stats['ports'] += isis_sync_rows('isis_ports', 'isis_port_id', 'isis_circ_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'ISIS-MIB'], $port_rows); + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Adjacencies', 2); + +$adj_rows = []; +$up = 0; +$down = 0; +foreach (snmpwalk_cache_oid($device, 'isisISAdjEntry', [], 'ISIS-MIB') as $index => $entry) { + $parts = explode('.', (string)$index); + $circ_index = array_shift($parts); + $local_adj_index = implode('.', $parts); + if ($local_adj_index === '') { $local_adj_index = (string)$index; } + + $state = isis_adj_state($entry['isisISAdjState'] ?? NULL); + if ($state === 'up') { $up++; } else { $down++; } + + $adj_rows[$index] = [ + 'device_id' => $device['device_id'], + 'port_id' => $circ_port_id[$circ_index] ?? 0, + 'isis_mib' => 'ISIS-MIB', + 'isis_adj_index' => (string)$index, + 'isis_circ_index' => (string)$circ_index, + 'isis_circ_ifIndex' => $circ_ifindex[$circ_index] ?? 0, + 'isis_adj_local_index' => (string)$local_adj_index, + 'isis_adj_state' => $state, + 'isis_adj_3way_state' => isis_3way_state($entry['isisISAdj3WayState'] ?? NULL), + 'isis_adj_neigh_sysid' => isis_format_system_id($entry['isisISAdjNeighSysID'] ?? NULL), + 'isis_adj_neigh_name' => NULL, + 'isis_adj_neigh_snpa' => isis_format_octets($entry['isisISAdjNeighSNPAAddress'] ?? NULL, ':'), + 'isis_adj_neigh_type' => isis_enum_value($entry['isisISAdjNeighSysType'] ?? NULL), + 'isis_adj_usage' => isis_enum_value($entry['isisISAdjUsage'] ?? NULL), + 'isis_adj_hold_timer' => isis_numeric_value($entry['isisISAdjHoldTimer'] ?? NULL), + 'isis_adj_hold_remaining' => NULL, + 'isis_adj_priority' => isis_numeric_value($entry['isisISAdjNeighPriority'] ?? NULL), + 'isis_adj_uptime' => isis_clean_value($entry['isisISAdjLastUpTime'] ?? NULL), + 'isis_adj_ext_circuit_id' => isis_numeric_value($entry['isisISAdjNbrExtendedCircID'] ?? NULL), + 'isis_adj_restart_status' => NULL, + 'isis_adj_restart_support' => NULL, + 'isis_adj_restart_suppressed' => NULL, + 'isis_adj_polled' => time(), + ]; + + isis_record_fabric_node($device, 'ISIS-MIB', NULL, $adj_rows[$index]['isis_adj_neigh_sysid'], 'adjacency', $circ_port_id[$circ_index] ?? 0); +} + +print_cli_data_field($mib . ' Packets', 2); + +$packet_map = [ + 'isisPacketCountIIHello' => 'iih', + 'isisPacketCountISHello' => 'ish', + 'isisPacketCountESHello' => 'esh', + 'isisPacketCountLSP' => 'lsp', + 'isisPacketCountCSNP' => 'csnp', + 'isisPacketCountPSNP' => 'psnp', + 'isisPacketCountUnknown' => 'unknown', +]; + +$packet_rows = snmpwalk_cache_oid($device, 'isisPacketCounterEntry', [], 'ISIS-MIB'); +if (safe_empty($packet_rows)) { + // Some Net-SNMP/MIB versions accept direct walks of the table columns more reliably. + foreach (array_keys($packet_map) as $packet_oid) { + $packet_rows = snmpwalk_cache_oid($device, $packet_oid, $packet_rows, 'ISIS-MIB'); + } +} + +foreach ($packet_rows as $packet_index => $packet_entry) { + $direction = isis_packet_direction_from_index($packet_index); + if ($direction === NULL) { continue; } + + foreach ($packet_map as $oid_name => $ds_prefix) { + if (!isset($packet_entry[$oid_name])) { continue; } + $value = isis_numeric_value($packet_entry[$oid_name]); + if ($value === NULL) { continue; } + $isis_packet_stats[$ds_prefix . '_' . $direction] += $value; + } +} + +if (array_sum($isis_packet_stats) > 0) { echo('+'); } +echo(PHP_EOL); + +print_cli_data_field($mib . ' LSP TLV Nodes', 2); + +$tlv_rows = snmpwalk_cache_oid($device, 'isisLSPTLVEntry', [], 'ISIS-MIB'); +if (safe_empty($tlv_rows)) { + $tlv_rows = snmpwalk_cache_oid($device, 'isisLSPTLVValue', [], 'ISIS-MIB'); +} +$tlv_node_count = 0; +foreach ($tlv_rows as $tlv_index => $tlv_entry) { + $tlv_value = is_array($tlv_entry) ? ($tlv_entry['isisLSPTLVValue'] ?? NULL) : $tlv_entry; + foreach (isis_extract_switch_names($tlv_value) as $node_name) { + isis_record_fabric_node($device, 'ISIS-MIB', $node_name, NULL, 'lsp-tlv', 0); + $tlv_node_count++; + } +} +if ($tlv_node_count > 0) { echo(str_repeat('+', min($tlv_node_count, 20))); } +echo(PHP_EOL); + +$isis_stats['adjacencies'] += isis_sync_rows('isis_adjacencies', 'isis_adjacency_id', 'isis_adj_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'ISIS-MIB'], $adj_rows); +$isis_stats['up'] += $up; +$isis_stats['down'] += $down; + +echo(PHP_EOL); + +unset($system_oids, $system, $admin_state, $sys_id, $mib_enabled, $instance_rows, $area_rows, $port_rows, $adj_rows, + $circ_ifindex, $circ_port_id, $up, $down, $index, $entry, $ifIndex, $port_id, $parts, $circ_index, + $local_adj_index, $state); + +// EOF diff -ruN old/includes/polling/isis/rc-isis-mib.inc.php new/includes/polling/isis/rc-isis-mib.inc.php --- includes/polling/isis/rc-isis-mib.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ includes/polling/isis/rc-isis-mib.inc.php 2026-06-05 11:59:05.726132555 +0000 @@ -0,0 +1,319 @@ + 'on', 2 => 'off']); +$ma_state = isis_enum_value($system['rcIsisGlobalMAOperState'] ?? NULL, [1 => 'disabled', 2 => 'init', 3 => 'non-designated', 4 => 'designated']); + +$mib_enabled = FALSE; +if (!safe_empty($host_name) || $admin_state === 'on') { + $mib_enabled = TRUE; + $isis_enabled = TRUE; +} + +if (!$mib_enabled) { + echo("disabled\n"); + return; +} + +// Do not write to isis_instances here. Some Observium releases already ship +// a legacy isis_instances table with an incompatible schema. The summary/alert +// data is derived into isis_status by includes/polling/isis.inc.php. +$isis_stats['instances']++; +$isis_instance_status['RC-ISIS-MIB'] = [ + 'isis_instance_index' => '0', + 'isis_sys_id' => NULL, + 'isis_sys_name' => $host_name ?: ($device['hostname'] ?? $device['sysName'] ?? NULL), + 'isis_admin_state' => $admin_state ?: 'on', + 'isis_level_type' => 'spbm', + 'isis_lsp_count' => NULL, +]; + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Areas', 2); + +$area_rows = []; +$dynamic_area = isis_clean_value($system['rcIsisDynamicallyLearnedArea'] ?? NULL); +$control_vlan = isis_numeric_value($system['rcIsisGlobalControlVlan'] ?? NULL); + +// On VOSS/FabricEngine the actual SPBM area is exposed as +// rcIsisDynamicallyLearnedArea, e.g. "49.0001". The control VLAN is a +// transport/config value, not a useful area address for the UI. +if (!safe_empty($dynamic_area)) { + $area_rows['spbm-area'] = [ + 'device_id' => $device['device_id'], + 'isis_mib' => 'RC-ISIS-MIB', + 'isis_area_index' => 'spbm-area', + 'isis_area_address' => $dynamic_area, + 'isis_area_type' => 'spbm', + 'isis_area_status' => 'active', + 'isis_area_polled' => time(), + ]; +} elseif ($control_vlan) { + $area_rows['control-vlan'] = [ + 'device_id' => $device['device_id'], + 'isis_mib' => 'RC-ISIS-MIB', + 'isis_area_index' => 'control-vlan', + 'isis_area_address' => 'Control VLAN ' . $control_vlan, + 'isis_area_type' => 'spbm-control', + 'isis_area_status' => 'active', + 'isis_area_polled' => time(), + ]; +} + +$isis_stats['areas'] += isis_sync_rows('isis_areas', 'isis_area_id', 'isis_area_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'RC-ISIS-MIB'], $area_rows); + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Circuits', 2); + +$port_rows = []; +$circ_ifindex = []; +$circ_port_id = []; +$circ_num_adj = []; +$circ_num_upadj = []; +foreach (snmpwalk_cache_oid($device, 'rcIsisCircuitEntry', [], 'RC-ISIS-MIB') as $index => $entry) { + // rcIsisCircuitIndex is a local circuit index on VOSS/FabricEngine, not always an ifIndex. + // The real interface is normally exposed by rcIsisAdjIfIndex on the adjacency row. + $ifIndex = isis_numeric_value($entry['rcIsisCircuitIfIndex'] ?? NULL); + if ($ifIndex === NULL) { $ifIndex = 0; } + $port_id = isis_get_port_id($device, $ifIndex); + $oper_state = isis_enum_value($entry['rcIsisCircuitOperState'] ?? NULL, [0 => 'unknown', 1 => 'up', 2 => 'down']); + $num_adj = isis_numeric_value($entry['rcIsisCircuitNumAdj'] ?? NULL) ?: 0; + $num_upadj = isis_numeric_value($entry['rcIsisCircuitNumUpAdj'] ?? NULL) ?: 0; + + $circ_ifindex[$index] = $ifIndex; + $circ_port_id[$index] = $port_id; + $circ_num_adj[$index] = $num_adj; + $circ_num_upadj[$index] = $num_upadj; + + $port_rows[$index] = [ + 'device_id' => $device['device_id'], + 'port_id' => $port_id, + 'isis_mib' => 'RC-ISIS-MIB', + 'isis_circ_index' => (string)$index, + 'isis_circ_ifIndex' => $ifIndex, + 'isis_circ_admin_state' => isis_enum_value($entry['rcIsisCircuitAutoNniEnable'] ?? NULL, [1 => 'enabled', 2 => 'disabled']), + 'isis_circ_oper_state' => $oper_state, + 'isis_circ_type' => isis_enum_value($entry['rcIsisCircuitType'] ?? NULL, [1 => 'bcast', 2 => 'ptpt', 3 => 'passive']), + 'isis_circ_level_type' => 'spbm', + 'isis_circ_passive' => NULL, + 'isis_circ_metric' => NULL, + 'isis_circ_hello_interval' => NULL, + 'isis_circ_hello_multiplier' => NULL, + 'isis_circ_ext_domain' => NULL, + 'isis_circ_mesh_group' => NULL, + 'isis_circ_small_hellos' => NULL, + 'isis_circ_last_uptime' => NULL, + 'isis_circ_3way_enabled' => NULL, + 'isis_circ_extended_circ_id' => NULL, + 'isis_circ_polled' => time(), + ]; +} + +echo(PHP_EOL); + +print_cli_data_field($mib . ' PLSB', 2); + +foreach (snmpwalk_cache_oid($device, 'rcIsisCircuitPlsbEntry', [], 'RC-ISIS-MIB') as $index => $entry) { + $parts = explode('.', (string)$index); + $circ_index = (string)($parts[0] ?? $index); + if (!isset($port_rows[$circ_index])) { continue; } + + $port_rows[$circ_index]['isis_circ_metric'] = isis_numeric_value($entry['rcIsisCircuitPlsbWideL1Metric'] ?? NULL) + ?: isis_numeric_value($entry['rcIsisCircuitPlsbL1Metric'] ?? NULL); + $port_rows[$circ_index]['isis_circ_type'] = isis_enum_value($entry['rcIsisCircuitPlsbType'] ?? NULL, [1 => 'bcast', 2 => 'ptpt']) + ?: $port_rows[$circ_index]['isis_circ_type']; + $port_rows[$circ_index]['isis_circ_admin_state'] = isis_enum_value($entry['rcIsisCircuitPlsbState'] ?? NULL, [1 => 'enabled', 2 => 'disabled']) + ?: $port_rows[$circ_index]['isis_circ_admin_state']; +} + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Adjacencies', 2); + +$adj_rows = []; +$up = 0; +$down = 0; +foreach (snmpwalk_cache_oid($device, 'rcIsisAdjEntry', [], 'RC-ISIS-MIB') as $index => $entry) { + $parts = explode('.', (string)$index); + $circ_index = (string)($parts[0] ?? $index); + $local_adj_index = (string)($parts[1] ?? $index); + + $ifIndex = isis_numeric_value($entry['rcIsisAdjIfIndex'] ?? NULL) ?: ($circ_ifindex[$circ_index] ?? 0); + $neigh_name = isis_clean_value($entry['rcIsisAdjHostName'] ?? NULL); + + // Some agents expose extra raw leaves below rcIsisAdjEntry (for example + // rcIsisAdjEntry.6.x.y as Hex-STRING 00). They are not real adjacencies. + if ($ifIndex <= 0 && safe_empty($neigh_name)) { + continue; + } + + $port_id = isis_get_port_id($device, $ifIndex); + + if ($ifIndex > 0 && isset($port_rows[$circ_index])) { + $circ_ifindex[$circ_index] = $ifIndex; + $circ_port_id[$circ_index] = $port_id; + $port_rows[$circ_index]['isis_circ_ifIndex'] = $ifIndex; + $port_rows[$circ_index]['port_id'] = $port_id; + } + + // RC-ISIS-MIB's adjacency table exposes existing adjacencies but no per-row state. + // Existing rows are therefore considered up; circuit counters are used for per-port up/down totals. + $state = 'up'; + $up++; + + $adj_rows[$index] = [ + 'device_id' => $device['device_id'], + 'port_id' => $port_id, + 'isis_mib' => 'RC-ISIS-MIB', + 'isis_adj_index' => (string)$index, + 'isis_circ_index' => $circ_index, + 'isis_circ_ifIndex' => $ifIndex ?: 0, + 'isis_adj_local_index' => $local_adj_index, + 'isis_adj_state' => $state, + 'isis_adj_3way_state' => NULL, + 'isis_adj_neigh_sysid' => NULL, + 'isis_adj_neigh_name' => $neigh_name, + 'isis_adj_neigh_snpa' => NULL, + 'isis_adj_neigh_type' => 'spbm', + 'isis_adj_usage' => isis_truth_value($entry['rcIsisAdjParallelActive'] ?? NULL) === 'true' ? 'active-parallel' : 'spbm', + 'isis_adj_hold_timer' => NULL, + 'isis_adj_hold_remaining' => NULL, + 'isis_adj_priority' => NULL, + 'isis_adj_uptime' => NULL, + 'isis_adj_ext_circuit_id' => NULL, + 'isis_adj_restart_status' => NULL, + 'isis_adj_restart_support' => NULL, + 'isis_adj_restart_suppressed' => NULL, + 'isis_adj_polled' => time(), + ]; + + isis_record_fabric_node($device, 'RC-ISIS-MIB', $neigh_name, NULL, 'adjacency', $port_id); +} + +// If the extension adjacency table is empty but circuit counters exist, create summary rows +// so devices with only rcIsisCircuitNumAdj/NumUpAdj still show useful adjacency counts. +if (empty($adj_rows)) { + foreach ($circ_num_adj as $circ_index => $count) { + for ($i = 1; $i <= $count; $i++) { + $idx = $circ_index . '.summary.' . $i; + $state = ($i <= ($circ_num_upadj[$circ_index] ?? 0)) ? 'up' : 'down'; + if ($state === 'up') { $up++; } else { $down++; } + $adj_rows[$idx] = [ + 'device_id' => $device['device_id'], + 'port_id' => $circ_port_id[$circ_index] ?? 0, + 'isis_mib' => 'RC-ISIS-MIB', + 'isis_adj_index' => $idx, + 'isis_circ_index' => (string)$circ_index, + 'isis_circ_ifIndex' => $circ_ifindex[$circ_index] ?? 0, + 'isis_adj_local_index' => (string)$i, + 'isis_adj_state' => $state, + 'isis_adj_3way_state' => NULL, + 'isis_adj_neigh_sysid' => NULL, + 'isis_adj_neigh_name' => 'Adjacency ' . $i, + 'isis_adj_neigh_snpa' => NULL, + 'isis_adj_neigh_type' => 'spbm', + 'isis_adj_usage' => 'summary', + 'isis_adj_hold_timer' => NULL, + 'isis_adj_hold_remaining' => NULL, + 'isis_adj_priority' => NULL, + 'isis_adj_uptime' => NULL, + 'isis_adj_ext_circuit_id' => NULL, + 'isis_adj_restart_status' => NULL, + 'isis_adj_restart_support' => NULL, + 'isis_adj_restart_suppressed' => NULL, + 'isis_adj_polled' => time(), + ]; + } + } +} + +$isis_stats['ports'] += isis_sync_rows('isis_ports', 'isis_port_id', 'isis_circ_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'RC-ISIS-MIB'], $port_rows); + +$isis_stats['adjacencies'] += isis_sync_rows('isis_adjacencies', 'isis_adjacency_id', 'isis_adj_index', + ['`device_id` = ?', '`isis_mib` = ?'], [$device['device_id'], 'RC-ISIS-MIB'], $adj_rows); +$isis_stats['up'] += $up; +$isis_stats['down'] += $down; + +echo(PHP_EOL); + +print_cli_data_field($mib . ' Fabric Nodes', 2); + +$fabric_seen = 0; + +// VOSS/FabricEngine exposes the Fabric/LSDB node host names in rcIsisLSPHostName +// (numeric tree .1.3.6.1.4.1.2272.1.63.11.1.4). This is different from +// the RFC ISIS-MIB isisLSPTLVValue path used by ERS/BOSS. +foreach (snmpwalk_cache_oid($device, 'rcIsisLSPEntry', [], 'RC-ISIS-MIB') as $lsp_index => $lsp_entry) { + $node_name = isis_clean_value($lsp_entry['rcIsisLSPHostName'] ?? NULL); + $node_sysid = isis_rc_lsp_sysid_from_index($lsp_index); + if (!safe_empty($node_name) || !safe_empty($node_sysid)) { + isis_record_fabric_node($device, 'RC-ISIS-MIB', $node_name, $node_sysid, 'rc-lsp', 0); + $fabric_seen++; + echo('+'); + } +} + +// VOSS/FabricEngine sometimes resolves rcIsisLSPEntry symbolically but returns +// unusable array keys for deriving the LSP System-ID. Always walk the numeric +// rcIsisLSPHostName column as an enrichment pass: if the node was already seen +// by name, only fill its System-ID/source without increasing Seen. If it was +// not seen before, record it normally. +foreach (isis_rc_lsp_numeric_hostname_rows($device) as $lsp_row) { + $node_sysid = isis_rc_lsp_sysid_from_index($lsp_row['index']); + foreach (isis_extract_switch_names($lsp_row['value']) as $node_name) { + if (!isis_enrich_fabric_node_sysid($device, 'RC-ISIS-MIB', $node_name, $node_sysid, 'rc-lsp-numeric')) { + isis_record_fabric_node($device, 'RC-ISIS-MIB', $node_name, $node_sysid, 'rc-lsp-numeric', 0); + $fabric_seen++; + echo('+'); + } + } +} + +// Also fallback the direct adjacency host-name table numerically. This helps on +// VSPs which expose .10.1.3 but do not return a fully decoded rcIsisAdjEntry. +foreach (isis_walk_scalar_values(snmpwalk_cache_oid($device, '.1.3.6.1.4.1.2272.1.63.10.1.3', [], NULL)) as $value) { + foreach (isis_extract_switch_names($value) as $node_name) { + isis_record_fabric_node($device, 'RC-ISIS-MIB', $node_name, NULL, 'rc-adj-numeric', 0); + $fabric_seen++; + echo('+'); + } +} + +if ($fabric_seen === 0) { echo('-'); } + +echo(PHP_EOL); + +unset($system_oids, $system, $host_name, $admin_state, $ma_state, $mib_enabled, $instance_rows, $area_rows, + $port_rows, $circ_ifindex, $circ_port_id, $circ_num_adj, $circ_num_upadj, $adj_rows, + $up, $down, $fabric_seen, $lsp_index, $lsp_entry, $lsp_row, $node_name, $value, $index, $entry, $ifIndex, $port_id, $oper_state, $num_adj, $num_upadj, $parts, + $circ_index, $local_adj_index, $state, $idx, $i); + +// EOF diff -ruN old/includes/polling/isis.inc.php new/includes/polling/isis.inc.php --- includes/polling/isis.inc.php 1970-01-01 00:00:00.000000000 +0000 +++ includes/polling/isis.inc.php 2026-06-05 11:59:05.725704742 +0000 @@ -0,0 +1,1472 @@ + 'on', 2 => 'off']); + if (in_array($state, ['enable', 'enabled', 'start', 'up'], TRUE)) { return 'on'; } + if (in_array($state, ['disable', 'disabled', 'stop', 'down'], TRUE)) { return 'off'; } + return $state; + } +} + +if (!function_exists('isis_adj_state')) { + function isis_adj_state($value, $spb = FALSE) + { + if ($spb) { + return isis_enum_value($value, [1 => 'up', 2 => 'down', 3 => 'testing']); + } + return isis_enum_value($value, [1 => 'down', 2 => 'initializing', 3 => 'up', 4 => 'failed']); + } +} + +if (!function_exists('isis_3way_state')) { + function isis_3way_state($value) + { + return isis_enum_value($value, [0 => 'up', 1 => 'initializing', 2 => 'down', 3 => 'failed']); + } +} + +if (!function_exists('isis_oper_state')) { + function isis_oper_state($value) + { + return isis_enum_value($value, [1 => 'unknown', 2 => 'inservice', 3 => 'outofservice', 4 => 'transition']); + } +} + +if (!function_exists('isis_truth_value')) { + function isis_truth_value($value) + { + return isis_enum_value($value, [1 => 'true', 2 => 'false']); + } +} + +if (!function_exists('isis_restart_status')) { + function isis_restart_status($value) + { + return isis_enum_value($value, [1 => 'nothelping', 2 => 'restarting', 3 => 'restartcomplete', 4 => 'helping']); + } +} + +if (!function_exists('isis_octets_from_value')) { + function isis_octets_from_value($value) + { + $value = isis_clean_value($value); + if ($value === NULL) { return []; } + + // Raw binary OCTET STRING. Keep this before quote trimming, otherwise + // trailing NUL bytes in System IDs can be lost. + if (preg_match('/[^\x20-\x7E]/', $value)) { + $hex = bin2hex($value); + if ($hex !== '' && strlen($hex) % 2 === 0) { + return str_split(strtoupper($hex), 2); + } + } + + // Some Observium SNMP wrappers can return the full varbind line, e.g.: + // isisSysID.0 = "02 0E 00 61 00 28 " + // Only parse the value part. + if (strpos($value, '=') !== FALSE && preg_match('/=\s*(.+)$/', $value, $matches)) { + $value = trim($matches[1]); + } + + $value = preg_replace('/^(hex-string|hex string|octet string):/i', '', $value); + $value = trim($value, " \t\n\r\0\x0B\"'"); + + // Net-SNMP/Observium often returns OCTET STRINGs like: + // "02 0E 00 61 00 28 " or "49 00 01 " + if (preg_match('/^[0-9a-fA-F][0-9a-fA-F]([:\-\s.][0-9a-fA-F][0-9a-fA-F])+\s*$/', $value) || + preg_match('/^[0-9a-fA-F]{4,}$/', $value)) { + $hex = preg_replace('/[^0-9a-fA-F]/', '', $value); + if ($hex !== '' && strlen($hex) % 2 === 0) { + return str_split(strtoupper($hex), 2); + } + } + + return []; + } +} + +if (!function_exists('isis_octets_from_index')) { + function isis_octets_from_index($index) + { + $parts = array_values(array_filter(explode('.', (string)$index), 'strlen')); + if (count($parts) > 1 && is_numeric($parts[0]) && (int)$parts[0] === (count($parts) - 1)) { + array_shift($parts); + } + + $hex = []; + foreach ($parts as $part) { + if (!is_numeric($part)) { return []; } + $hex[] = sprintf('%02X', (int)$part); + } + + return $hex; + } +} + +if (!function_exists('isis_format_octets')) { + function isis_format_octets($value, $separator = ':') + { + $octets = isis_octets_from_value($value); + if (!safe_empty($octets)) { + return strtolower(implode($separator, $octets)); + } + + return isis_clean_value($value); + } +} + +if (!function_exists('isis_format_system_id')) { + function isis_format_system_id($value) + { + $value = isis_clean_value($value); + if ($value === NULL) { return NULL; } + + // Already in canonical IS-IS System-ID notation. Keep this before + // octet parsing; otherwise 020e.0061.0028 is not recognised as bytes + // and gets converted back to NULL by callers that re-normalise values. + if (preg_match('/^[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}$/', $value)) { + return strtolower($value); + } + + $octets = isis_octets_from_value($value); + if (safe_empty($octets)) { return NULL; } + + // IS-IS System IDs are normally shown as three groups of two octets: + // 02 0E 00 61 00 28 => 020e.0061.0028 + $hex = strtolower(implode('', $octets)); + if (strlen($hex) === 12) { + return implode('.', str_split($hex, 4)); + } + + return strtolower(implode(':', $octets)); + } +} + +if (!function_exists('isis_format_area_address')) { + function isis_format_area_address($value, $index = NULL) + { + $octets = isis_octets_from_value($value); + if (safe_empty($octets) && $index !== NULL) { + $octets = isis_octets_from_index($index); + } + if (safe_empty($octets)) { return isis_clean_value($value); } + + // ISIS area addresses are normally displayed as NSAP-style hex: + // 49 00 01 => 49.0001 + $afi = strtolower(array_shift($octets)); + $rest = strtolower(implode('', $octets)); + if ($rest === '') { return $afi; } + + return $afi . '.' . implode('.', str_split($rest, 4)); + } +} + +if (!function_exists('isis_get_octet_oid')) { + function isis_get_octet_oid($device, $oid, $mib, $separator = ':') + { + // OCTET STRING objects such as isisSysID may be returned as quoted hex + // bytes, raw binary, or display-hinted output depending on Net-SNMP and + // Observium flags. Try multiple safe variants and parse bytes ourselves. + $values = []; + // First try the exact simple output form that Net-SNMP prints in manual tests, + // e.g. isisSysID.0 = "02 0E 00 61 00 28 ". + $values[] = snmp_get($device, $mib . '::' . $oid, '-OQUs', $mib); + $values[] = snmp_get($device, $oid, '-OQUs', $mib); + if (defined('OBS_SNMP_ALL_HEX')) { + $values[] = snmp_get_oid($device, $oid, $mib, NULL, OBS_SNMP_ALL_HEX); + } + if (defined('OBS_SNMP_HEX')) { + $values[] = snmp_get($device, $oid, '-OQUsvx', $mib, NULL, OBS_SNMP_HEX | OBS_QUOTES_TRIM); + } + $values[] = snmp_get_oid($device, $oid, $mib); + $values[] = snmp_get($device, $oid, '-OQUsv', $mib, NULL, OBS_QUOTES_TRIM); + + foreach ($values as $value) { + $formatted = isis_format_octets($value, $separator); + if (!safe_empty($formatted)) { return $formatted; } + } + + return NULL; + } +} + +if (!function_exists('isis_get_system_id_oid')) { + function isis_get_system_id_oid($device, $oid, $mib) + { + $values = []; + $values[] = snmp_get($device, $mib . '::' . $oid, '-OQUs', $mib); + $values[] = snmp_get($device, $oid, '-OQUs', $mib); + if (defined('OBS_SNMP_ALL_HEX')) { + $values[] = snmp_get_oid($device, $oid, $mib, NULL, OBS_SNMP_ALL_HEX); + } + if (defined('OBS_SNMP_HEX')) { + $values[] = snmp_get($device, $oid, '-OQUsvx', $mib, NULL, OBS_SNMP_HEX | OBS_QUOTES_TRIM); + } + $values[] = snmp_get_oid($device, $oid, $mib); + $values[] = snmp_get($device, $oid, '-OQUsv', $mib, NULL, OBS_QUOTES_TRIM); + + foreach ($values as $value) { + $formatted = isis_format_system_id($value); + if (!safe_empty($formatted)) { return $formatted; } + } + + return NULL; + } +} + +if (!function_exists('isis_index_to_octets')) { + function isis_index_to_octets($index, $separator = '.') + { + $octets = isis_octets_from_index($index); + if (safe_empty($octets)) { return (string)$index; } + + return implode($separator, $octets); + } +} + +if (!function_exists('isis_index_peer_sysid')) { + function isis_index_peer_sysid($index, $skip = 2) + { + $parts = array_values(array_filter(explode('.', (string)$index), 'strlen')); + if (count($parts) <= $skip) { return NULL; } + $bytes = array_slice($parts, $skip); + // MacAddress index is normally six bytes without a length prefix. + if (count($bytes) > 6) { $bytes = array_slice($bytes, 0, 6); } + $hex = []; + foreach ($bytes as $part) { + if (!is_numeric($part)) { return NULL; } + $hex[] = sprintf('%02x', (int)$part); + } + return count($hex) ? implode(':', $hex) : NULL; + } +} + + +if (!function_exists('isis_rc_lsp_sysid_from_index')) { + function isis_rc_lsp_sysid_from_index($index) + { + // Accept both plain instance suffixes, e.g. + // 1.2.13.0.97.0.2.0.0 + // and full numeric OIDs returned by numeric snmpwalks, e.g. + // enterprises.2272.1.63.11.1.4.1.2.13.0.97.0.2.0.0 + // RC-ISIS LSP HostName index seen on VOSS/FabricEngine is usually: + // ....... + preg_match_all('/\d+/', (string)$index, $matches); + $parts = $matches[0]; + + if (safe_empty($parts)) { return NULL; } + + // If a full RC-ISIS OID was passed, strip everything up to the + // rcIsisLSPMetric/HostName column: .2272.1.63.11.1.(3|4). + for ($i = 0; $i <= count($parts) - 3; $i++) { + if ((int)$parts[$i] === 11 && (int)$parts[$i + 1] === 1 && + in_array((int)$parts[$i + 2], [3, 4], TRUE)) { + $parts = array_slice($parts, $i + 3); + break; + } + } + + // Net-SNMP returns suffixes like: + // 1.2.13.0.97.0.2.0.0 + // The first number is the LSP level. The second number is not part of + // the six-byte System-ID on these RC-ISIS tables. Therefore strip only + // the first element and use the next six bytes. + if (count($parts) >= 9 && is_numeric($parts[0]) && in_array((int)$parts[0], [1, 2], TRUE)) { + $parts = array_slice($parts, 1, 8); + } + + if (count($parts) < 6) { return NULL; } + + $sysid_octets = array_slice($parts, 0, 6); + $hex = []; + foreach ($sysid_octets as $part) { + if (!is_numeric($part) || (int)$part < 0 || (int)$part > 255) { return NULL; } + $hex[] = sprintf('%02X', (int)$part); + } + + return isis_format_system_id(implode(' ', $hex)); + } +} + +if (!function_exists('isis_rc_lsp_numeric_hostname_rows')) { + function isis_rc_lsp_numeric_hostname_rows($device) + { + // snmpwalk_cache_oid() can lose parts of the numeric index depending on + // Net-SNMP parsing flags. Parse the raw walk output so the full LSP-ID + // suffix is available for System-ID extraction. + $rows = []; + $walk = snmp_walk($device, '.1.3.6.1.4.1.2272.1.63.11.1.4', '-OQUs', NULL); + foreach (explode("\n", (string)$walk) as $line) { + $line = trim($line); + if ($line === '') { continue; } + + if (!preg_match('/^(?:\.?1\.3\.6\.1\.4\.1\.|iso\.3\.6\.1\.4\.1\.|enterprises\.)?2272\.1\.63\.11\.1\.4\.([0-9.]+)\s*=\s*(.+)$/', $line, $matches)) { + continue; + } + + $rows[] = [ + 'index' => $matches[1], + 'value' => trim($matches[2]), + ]; + } + + return $rows; + } +} + +if (!function_exists('isis_extract_switch_names')) { + function isis_extract_switch_names($value) + { + $value = isis_clean_value($value); + if ($value === NULL) { return []; } + if (strpos($value, '=') !== FALSE && preg_match('/=\s*(.+)$/', $value, $matches)) { + $value = trim($matches[1]); + } + $value = trim($value, " \t\n\r\0\x0B\"'"); + + $names = []; + // Local naming convention seen in IS-IS LSP TLVs, e.g. sw00011209. + // Keep this deliberately conservative to avoid decoding arbitrary binary TLVs as names. + if (preg_match_all('/\bsw\d{5,}\b/i', $value, $matches)) { + foreach ($matches[0] as $name) { $names[strtolower($name)] = $name; } + } + + return array_values($names); + } +} + +if (!function_exists('isis_fabric_nodes_table_available')) { + function isis_fabric_nodes_table_available() + { + return in_array('isis_fabric_nodes', dbShowTables(), TRUE); + } +} + +if (!function_exists('isis_fabric_lookup_add')) { + function isis_fabric_lookup_add(&$lookup, $name = NULL, $sysid = NULL) + { + $name = isis_clean_value($name); + $sysid = isis_format_system_id($sysid); + + if (safe_empty($name) || safe_empty($sysid)) { return; } + + $name_key = strtolower($name); + if (!isset($lookup['by_name'][$name_key])) { + $lookup['by_name'][$name_key] = $sysid; + } + if (!isset($lookup['by_sysid'][$sysid])) { + $lookup['by_sysid'][$sysid] = $name; + } + } +} + +if (!function_exists('isis_build_fabric_lookup')) { + function isis_build_fabric_lookup($device) + { + global $isis_fabric_nodes; + + $lookup = [ 'by_name' => [], 'by_sysid' => [] ]; + + // Current poller data first. This catches VSP RC-LSP data before it is + // written to SQL, so direct adjacency rows in the same run can be enriched. + if (is_array($isis_fabric_nodes)) { + foreach ($isis_fabric_nodes as $node) { + isis_fabric_lookup_add($lookup, $node['isis_node_name'] ?? NULL, $node['isis_node_sysid'] ?? NULL); + } + } + + // Existing fabric observations from all devices are useful for ERS/BOSS: + // ISIS-MIB often reports only neighbour System IDs in adjacencies and + // only names in LSP TLVs. VSP/RC-LSP rows can provide the missing side. + if (isis_fabric_nodes_table_available()) { + foreach (dbFetchRows('SELECT `isis_node_name`, `isis_node_sysid` FROM `isis_fabric_nodes` WHERE `isis_node_name` != ? AND `isis_node_sysid` != ?', ['', '']) as $row) { + isis_fabric_lookup_add($lookup, $row['isis_node_name'] ?? NULL, $row['isis_node_sysid'] ?? NULL); + } + } + + // Also use local ISIS status rows. This maps a known Observium device + // hostname/sysName to its canonical IS-IS System ID. + if (isis_status_table_available()) { + foreach (dbFetchRows('SELECT s.`isis_sys_id`, s.`isis_sys_name`, d.`hostname`, d.`sysName` AS `device_sysName` FROM `isis_status` s LEFT JOIN `devices` d ON d.`device_id` = s.`device_id` WHERE s.`isis_sys_id` != ?', ['']) as $row) { + $name = !safe_empty($row['hostname'] ?? NULL) ? $row['hostname'] : (!safe_empty($row['isis_sys_name'] ?? NULL) ? $row['isis_sys_name'] : ($row['device_sysName'] ?? NULL)); + isis_fabric_lookup_add($lookup, $name, $row['isis_sys_id'] ?? NULL); + } + } + + return $lookup; + } +} + +if (!function_exists('isis_merge_sources')) { + function isis_merge_sources($a, $b) + { + $sources = []; + foreach (explode(',', (string)$a) as $source) { + $source = trim($source); + if ($source !== '') { $sources[$source] = TRUE; } + } + foreach (explode(',', (string)$b) as $source) { + $source = trim($source); + if ($source !== '') { $sources[$source] = TRUE; } + } + return implode(',', array_keys($sources)); + } +} + +if (!function_exists('isis_enrich_and_merge_fabric_nodes')) { + function isis_enrich_and_merge_fabric_nodes($device, $lookup) + { + global $isis_fabric_nodes; + if (!is_array($isis_fabric_nodes)) { $isis_fabric_nodes = []; } + + $merged = []; + foreach ($isis_fabric_nodes as $node) { + if ((int)($node['device_id'] ?? 0) !== (int)$device['device_id']) { + $merged[$node['isis_node_key'] ?? uniqid('isis.', TRUE)] = $node; + continue; + } + + $name = isis_clean_value($node['isis_node_name'] ?? NULL); + $sysid = isis_format_system_id($node['isis_node_sysid'] ?? NULL); + + if (safe_empty($sysid) && !safe_empty($name)) { + $sysid = $lookup['by_name'][strtolower($name)] ?? NULL; + } + if (safe_empty($name) && !safe_empty($sysid)) { + $name = $lookup['by_sysid'][$sysid] ?? NULL; + } + + if (safe_empty($name) && safe_empty($sysid)) { continue; } + + $key = ($node['isis_mib'] ?? '') . '|' . (!safe_empty($name) ? strtolower($name) : $sysid); + $node['isis_node_key'] = $key; + $node['isis_node_name'] = $name; + $node['isis_node_sysid'] = $sysid; + + if (!isset($merged[$key])) { + $merged[$key] = $node; + continue; + } + + if (safe_empty($merged[$key]['isis_node_name']) && !safe_empty($name)) { + $merged[$key]['isis_node_name'] = $name; + } + if (safe_empty($merged[$key]['isis_node_sysid']) && !safe_empty($sysid)) { + $merged[$key]['isis_node_sysid'] = $sysid; + } + if ((int)($merged[$key]['port_id'] ?? 0) === 0 && (int)($node['port_id'] ?? 0) > 0) { + $merged[$key]['port_id'] = (int)$node['port_id']; + } + $merged[$key]['isis_node_source'] = isis_merge_sources($merged[$key]['isis_node_source'] ?? '', $node['isis_node_source'] ?? ''); + $merged[$key]['isis_node_count'] = (int)($merged[$key]['isis_node_count'] ?? 0) + (int)($node['isis_node_count'] ?? 0); + $merged[$key]['isis_node_polled'] = max((int)($merged[$key]['isis_node_polled'] ?? 0), (int)($node['isis_node_polled'] ?? 0), time()); + } + + $isis_fabric_nodes = $merged; + } +} + +if (!function_exists('isis_enrich_adjacencies_from_fabric_lookup')) { + function isis_enrich_adjacencies_from_fabric_lookup($device, $lookup) + { + if (!in_array('isis_adjacencies', dbShowTables(), TRUE)) { return; } + + foreach (dbFetchRows('SELECT `isis_adjacency_id`, `isis_adj_neigh_name`, `isis_adj_neigh_sysid` FROM `isis_adjacencies` WHERE `device_id` = ?', [$device['device_id']]) as $adj) { + $name = isis_clean_value($adj['isis_adj_neigh_name'] ?? NULL); + $sysid = isis_format_system_id($adj['isis_adj_neigh_sysid'] ?? NULL); + + $new_name = $name; + $new_sysid = $sysid; + + if (safe_empty($new_sysid) && !safe_empty($name)) { + $new_sysid = $lookup['by_name'][strtolower($name)] ?? NULL; + } + if (safe_empty($new_name) && !safe_empty($new_sysid)) { + $new_name = $lookup['by_sysid'][$new_sysid] ?? NULL; + } + + $update = []; + if (!safe_empty($new_sysid) && $new_sysid !== $sysid) { + $update['isis_adj_neigh_sysid'] = $new_sysid; + } + if (!safe_empty($new_name) && $new_name !== $name) { + $update['isis_adj_neigh_name'] = $new_name; + } + + if (!safe_empty($update)) { + dbUpdate($update, 'isis_adjacencies', '`isis_adjacency_id` = ?', [$adj['isis_adjacency_id']]); + } + } + } +} + +if (!function_exists('isis_record_fabric_node')) { + function isis_record_fabric_node($device, $mib, $name = NULL, $sysid = NULL, $source = 'unknown', $port_id = 0) + { + global $isis_fabric_nodes; + if (!is_array($isis_fabric_nodes)) { $isis_fabric_nodes = []; } + + $name = isis_clean_value($name); + $sysid = isis_format_system_id($sysid); + + if (safe_empty($name) && safe_empty($sysid)) { return; } + + // Prefer hostname as stable key when available. RC-ISIS LSPs often + // provide the name first and the System ID later via the LSP-ID index; + // using the name as key lets both observations merge into one row. + $key = !safe_empty($name) ? strtolower($name) : $sysid; + if (safe_empty($key)) { return; } + + $node_key = $mib . '|' . $key; + + // Merge with any previously collected row that has the same name or sysid + // so a node does not appear twice when one source reports only a name and + // another reports name+system-id. + foreach ($isis_fabric_nodes as $existing_key => $existing_node) { + if (!safe_empty($name) && !safe_empty($existing_node['isis_node_name']) && strtolower($existing_node['isis_node_name']) === strtolower($name)) { + $node_key = $existing_key; + break; + } + if (!safe_empty($sysid) && !safe_empty($existing_node['isis_node_sysid']) && $existing_node['isis_node_sysid'] === $sysid) { + $node_key = $existing_key; + break; + } + } + + if (!isset($isis_fabric_nodes[$node_key])) { + $isis_fabric_nodes[$node_key] = [ + 'device_id' => $device['device_id'], + 'port_id' => (int)$port_id, + 'isis_mib' => $mib, + 'isis_node_key' => $node_key, + 'isis_node_sysid' => $sysid, + 'isis_node_name' => $name, + 'isis_node_source' => $source, + 'isis_node_count' => 0, + 'isis_node_polled' => time(), + ]; + } + + if (safe_empty($isis_fabric_nodes[$node_key]['isis_node_name']) && !safe_empty($name)) { + $isis_fabric_nodes[$node_key]['isis_node_name'] = $name; + } + if (safe_empty($isis_fabric_nodes[$node_key]['isis_node_sysid']) && !safe_empty($sysid)) { + $isis_fabric_nodes[$node_key]['isis_node_sysid'] = $sysid; + } + if ($port_id > 0 && (int)$isis_fabric_nodes[$node_key]['port_id'] === 0) { + $isis_fabric_nodes[$node_key]['port_id'] = (int)$port_id; + } + if (strpos((string)$isis_fabric_nodes[$node_key]['isis_node_source'], $source) === FALSE) { + $isis_fabric_nodes[$node_key]['isis_node_source'] .= ',' . $source; + $isis_fabric_nodes[$node_key]['isis_node_source'] = trim($isis_fabric_nodes[$node_key]['isis_node_source'], ','); + } + $isis_fabric_nodes[$node_key]['isis_node_count']++; + $isis_fabric_nodes[$node_key]['isis_node_polled'] = time(); + } +} + + +if (!function_exists('isis_enrich_fabric_node_sysid')) { + function isis_enrich_fabric_node_sysid($device, $mib, $name = NULL, $sysid = NULL, $source = 'unknown') + { + global $isis_fabric_nodes; + if (!is_array($isis_fabric_nodes)) { $isis_fabric_nodes = []; } + + $name = isis_clean_value($name); + $sysid = isis_format_system_id($sysid); + + if (safe_empty($name) || safe_empty($sysid)) { return FALSE; } + + foreach ($isis_fabric_nodes as $node_key => $node) { + if ((int)$node['device_id'] !== (int)$device['device_id']) { continue; } + if ((string)$node['isis_mib'] !== (string)$mib) { continue; } + if (safe_empty($node['isis_node_name']) || strtolower($node['isis_node_name']) !== strtolower($name)) { continue; } + + if (safe_empty($isis_fabric_nodes[$node_key]['isis_node_sysid'])) { + $isis_fabric_nodes[$node_key]['isis_node_sysid'] = $sysid; + } + if (!safe_empty($source) && strpos((string)$isis_fabric_nodes[$node_key]['isis_node_source'], $source) === FALSE) { + $isis_fabric_nodes[$node_key]['isis_node_source'] .= ',' . $source; + $isis_fabric_nodes[$node_key]['isis_node_source'] = trim($isis_fabric_nodes[$node_key]['isis_node_source'], ','); + } + $isis_fabric_nodes[$node_key]['isis_node_polled'] = time(); + + return TRUE; + } + + return FALSE; + } +} + +if (!function_exists('isis_get_rc_isis_hostname')) { + function isis_get_rc_isis_hostname($device) + { + // Some VOSS/FabricEngine releases expose RC-ISIS objects numerically + // but do not resolve/advertise RC-ISIS-MIB::rcIsisGlobalHostName in + // discovery. Probe both forms. Numeric OID is rcIsisGlobalHostName.0. + $values = []; + $values[] = snmp_get_oid($device, 'rcIsisGlobalHostName.0', 'RC-ISIS-MIB'); + $values[] = snmp_get($device, 'RC-ISIS-MIB::rcIsisGlobalHostName.0', '-OQUsv', 'RC-ISIS-MIB', NULL, OBS_QUOTES_TRIM); + $values[] = snmp_get($device, '.1.3.6.1.4.1.2272.1.63.1.10.0', '-OQUsv', NULL, NULL, OBS_QUOTES_TRIM); + $values[] = snmp_get($device, '.1.3.6.1.4.1.2272.1.63.1.10.0', '-OQUs', NULL); + + foreach ($values as $value) { + $value = isis_clean_value($value); + if ($value === NULL) { continue; } + if (strpos($value, '=') !== FALSE && preg_match('/=\s*(.+)$/', $value, $matches)) { + $value = trim($matches[1]); + } + $value = trim($value, " \t\n\r\0\x0B\"'"); + if (!safe_empty($value)) { return $value; } + } + + return NULL; + } +} + +if (!function_exists('isis_walk_scalar_values')) { + function isis_walk_scalar_values($data) + { + $values = []; + if (!is_array($data)) { + if (!safe_empty($data)) { $values[] = $data; } + return $values; + } + + foreach ($data as $value) { + if (is_array($value)) { + $values = array_merge($values, isis_walk_scalar_values($value)); + } elseif (!safe_empty($value)) { + $values[] = $value; + } + } + + return $values; + } +} + +if (!function_exists('isis_packet_direction_from_index')) { + function isis_packet_direction_from_index($index) + { + $index = strtolower((string)$index); + if (strpos($index, 'sending') !== FALSE) { return 'out'; } + if (strpos($index, 'receiving') !== FALSE) { return 'in'; } + + $parts = array_values(array_filter(explode('.', $index), 'strlen')); + $last = end($parts); + // ISIS-MIB: isisPacketCountDirection sending(1), receiving(2) + if ((string)$last === '1') { return 'out'; } + if ((string)$last === '2') { return 'in'; } + + return NULL; + } +} + +if (!function_exists('isis_update_fabric_nodes')) { + function isis_update_fabric_nodes($device) + { + if (!isis_fabric_nodes_table_available()) { return; } + + global $isis_fabric_nodes; + if (!is_array($isis_fabric_nodes)) { $isis_fabric_nodes = []; } + + $lookup = isis_build_fabric_lookup($device); + isis_enrich_and_merge_fabric_nodes($device, $lookup); + + // Rebuild after enriching current in-memory nodes, then push that identity + // back into the adjacency table. This fills VSP/RC adjacencies from RC-LSP + // names->System IDs and ERS/BOSS adjacencies from System IDs->known names. + $lookup = isis_build_fabric_lookup($device); + isis_enrich_adjacencies_from_fabric_lookup($device, $lookup); + + // Also add all direct adjacencies from the database. This enriches the table + // even when a sub-poller did not explicitly call isis_record_fabric_node(). + foreach (dbFetchRows('SELECT * FROM `isis_adjacencies` WHERE `device_id` = ?', [$device['device_id']]) as $adj) { + isis_record_fabric_node($device, $adj['isis_mib'], $adj['isis_adj_neigh_name'], $adj['isis_adj_neigh_sysid'], 'adjacency', $adj['port_id']); + } + + $lookup = isis_build_fabric_lookup($device); + isis_enrich_and_merge_fabric_nodes($device, $lookup); + + isis_sync_rows('isis_fabric_nodes', 'isis_fabric_node_id', 'isis_node_key', ['`device_id` = ?'], [$device['device_id']], $isis_fabric_nodes); + } +} + +if (!function_exists('isis_get_port_id')) { + function isis_get_port_id($device, $ifIndex) + { + $ifIndex = isis_numeric_value($ifIndex); + if ($ifIndex === NULL || $ifIndex <= 0) { return 0; } + $port = get_port_by_index_cache($device, $ifIndex); + return is_array($port) && isset($port['port_id']) ? (int)$port['port_id'] : 0; + } +} + +if (!function_exists('isis_sync_rows')) { + function isis_sync_rows($table, $id_field, $index_field, $where, $params, $rows, $counter = NULL) + { + $db = []; + foreach (dbFetchRows('SELECT * FROM `' . $table . '` ' . generate_where_clause($where), $params) as $entry) { + $db[$entry[$index_field]] = $entry; + } + + if (OBS_DEBUG) { + echo("\nPolled $table: "); + print_vars($rows); + echo('Database: '); + print_vars($db); + echo("\n"); + } + + $count = 0; + foreach ($rows as $index => $row) { + if (!isset($db[$index])) { + dbInsertRowMulti($row, $table); + echo('+'); + } else { + $db_update = FALSE; + $update = [$id_field => $db[$index][$id_field]]; + foreach ($row as $field => $value) { + $update[$field] = $value; + if ((string)($db[$index][$field] ?? '') !== (string)($value ?? '')) { + $db_update = TRUE; + } + } + if ($db_update) { + dbUpdateRowMulti($update, $table, $id_field); + echo('U'); + } else { + echo('.'); + } + unset($db[$index]); + } + $count++; + } + + dbProcessMulti($table); + + $delete = []; + foreach ($db as $entry) { + $delete[] = $entry[$id_field]; + echo('-'); + } + if (!safe_empty($delete)) { + dbDelete($table, generate_query_values($delete, $id_field)); + } + + return $count; + } +} + + + + +if (!function_exists('isis_isids_table_available')) { + function isis_isids_table_available() + { + return in_array('isis_isids', dbShowTables(), TRUE); + } +} + +if (!function_exists('isis_isid_type')) { + function isis_isid_type($value) + { + return isis_enum_value($value, [ + 1 => 'elan-transparent', + 2 => 'elan', + 3 => 'etree', + 4 => 'l2vsn', + 11 => 'spbm-bvlan', + 12 => 'spbm-switched-uni' + ]); + } +} + +if (!function_exists('isis_isid_origin')) { + function isis_isid_origin($value) + { + $origin = isis_enum_value($value, [ + 1 => 'manual', + 2 => 'fa-ept-local', + 3 => 'fa-ept-remote', + 4 => 'fa-management', + 5 => 'fa-ept-both', + 6 => 'spbm-cvlan', + 7 => 'fa-management-local', + 8 => 'fa-management-remote', + 9 => 'fa-management-both', + 17 => 'eap-local', + 18 => 'eap-remote', + 19 => 'eap-both' + ]); + + $aliases = [ + 'config' => 'manual', + 'configured' => 'manual', + 'discover-local' => 'fa-ept-local', + 'discover-remote' => 'fa-ept-remote', + 'discover-both' => 'fa-ept-both', + 'mgmt-discover-local' => 'fa-management-local', + 'mgmt-discover-remote'=> 'fa-management-remote', + 'mgmt-discover-both' => 'fa-management-both' + ]; + + return isset($aliases[$origin]) ? $aliases[$origin] : $origin; + } +} + +if (!function_exists('isis_isid_origin_flags')) { + function isis_isid_origin_flags($value) + { + $octets = isis_octets_from_value($value); + if (safe_empty($octets)) { return NULL; } + + $first = hexdec($octets[0]); + $origins = []; + + // RAPID-CITY rcIsidServiceEntry.10 exposes the ORIGIN legend as a bitmask + // on recent VOSS/FabricEngine releases. The observed CLI mapping is: + // 40 00 => C/manual, 04 00 => EAP/local, 45 00 => manual + EAP/local + auto-sense. + if ($first & 0x40) { $origins[] = 'manual'; } + if ($first & 0x20) { $origins[] = 'fa-ept'; } + if ($first & 0x10) { $origins[] = 'fa-management'; } + if ($first & 0x04) { $origins[] = 'eap-local'; } + if ($first & 0x02) { $origins[] = 'remote-vist'; } + if ($first & 0x01) { $origins[] = 'auto-sense'; } + + return safe_empty($origins) ? NULL : implode(',', array_unique($origins)); + } +} + + +if (!function_exists('isis_isid_display_name')) { + function isis_isid_display_name($value) + { + $value = isis_clean_value($value); + if ($value === NULL) { return NULL; } + return trim((string)$value, " \"'"); + } +} + +if (!function_exists('isis_list_add_unique')) { + function isis_list_add_unique($list, $value) + { + $value = isis_clean_value($value); + $items = []; + foreach (explode(',', (string)$list) as $item) { + $item = trim($item); + if ($item !== '') { $items[$item] = TRUE; } + } + if (!safe_empty($value)) { $items[$value] = TRUE; } + return implode(',', array_keys($items)); + } +} + +if (!function_exists('isis_isid_index_last_numeric')) { + function isis_isid_index_last_numeric($index) + { + $parts = explode('.', (string)$index); + for ($i = count($parts) - 1; $i >= 0; $i--) { + $value = isis_numeric_value($parts[$i]); + if ($value !== NULL) { return $value; } + } + return NULL; + } +} + + +if (!function_exists('isis_rc_isid_numeric_name_rows')) { + function isis_rc_isid_numeric_name_rows($device) + { + // Newer VOSS/FabricEngine releases expose I-SID service names in + // numeric RAPID-CITY columns which older local MIB files do not know + // yet. snmpwalk_cache_oid() then cannot map them to rcIsidServiceName. + // Parse the raw numeric walk and use it only as a name-enrichment source. + $rows = []; + foreach (['.1.3.6.1.4.1.2272.1.87.2.1.9', '.1.3.6.1.4.1.2272.1.87.6.1.2'] as $oid) { + $walk = snmp_walk($device, $oid, '-OQUs', NULL); + foreach (explode("\n", (string)$walk) as $line) { + $line = trim($line); + if ($line === '') { continue; } + + if (!preg_match('/^(?:\.?1\.3\.6\.1\.4\.1\.|iso\.3\.6\.1\.4\.1\.|enterprises\.)?2272\.1\.87\.(?:2\.1\.9|6\.1\.2)\.([0-9]+)\s*=\s*(.+)$/', $line, $matches)) { + continue; + } + + $isid = isis_numeric_value($matches[1]); + $name = isis_isid_display_name($matches[2]); + if ($isid === NULL || $isid <= 0 || safe_empty($name)) { continue; } + + $rows[$isid] = $name; + } + } + + return $rows; + } +} + + +if (!function_exists('isis_rc_isid_numeric_origin_rows')) { + function isis_rc_isid_numeric_origin_rows($device) + { + // Recent VOSS/FabricEngine exposes richer I-SID origin data than older + // RAPID-CITY MIB files can name. Parse raw numeric columns to distinguish + // manual/local configuration from EAP/auto-sense learned services. + $rows = []; + + $service_origin_oids = [ + '.1.3.6.1.4.1.2272.1.87.2.1.7' => 'enum', + '.1.3.6.1.4.1.2272.1.87.2.1.10' => 'flags' + ]; + + foreach ($service_origin_oids as $oid => $mode) { + $walk = snmp_walk($device, $oid, '-OQUs', NULL); + foreach (explode("\n", (string)$walk) as $line) { + $line = trim($line); + if ($line === '') { continue; } + + if (!preg_match('/^(?:\.?1\.3\.6\.1\.4\.1\.|iso\.3\.6\.1\.4\.1\.|enterprises\.)?2272\.1\.87\.2\.1\.(?:7|10)\.([0-9]+)\s*=\s*(.+)$/', $line, $matches)) { + continue; + } + + $isid = isis_numeric_value($matches[1]); + if ($isid === NULL || $isid <= 0) { continue; } + + $origin = ($mode === 'flags') ? isis_isid_origin_flags($matches[2]) : isis_isid_origin($matches[2]); + if (safe_empty($origin)) { continue; } + + $rows[$isid] = isis_list_add_unique($rows[$isid] ?? NULL, $origin); + } + } + + // ELAN endpoint origin uses index .. and value 17 for + // EAP-local on the sample switch. Collapse per-port origins to the I-SID. + $walk = snmp_walk($device, '.1.3.6.1.4.1.2272.1.87.4.1.6', '-OQUs', NULL); + foreach (explode("\n", (string)$walk) as $line) { + $line = trim($line); + if ($line === '') { continue; } + + if (!preg_match('/^(?:\.?1\.3\.6\.1\.4\.1\.|iso\.3\.6\.1\.4\.1\.|enterprises\.)?2272\.1\.87\.4\.1\.6\.([0-9]+)\.[0-9]+\.[0-9]+\s*=\s*(.+)$/', $line, $matches)) { + continue; + } + + $isid = isis_numeric_value($matches[1]); + if ($isid === NULL || $isid <= 0) { continue; } + + $origin = isis_isid_origin($matches[2]); + if (safe_empty($origin)) { continue; } + + $rows[$isid] = isis_list_add_unique($rows[$isid] ?? NULL, $origin); + } + + return $rows; + } +} + +if (!function_exists('isis_isid_prune_rows')) { + function isis_isid_prune_rows() + { + global $isis_isids; + if (!is_array($isis_isids)) { return; } + + foreach ($isis_isids as $key => $row) { + $source = (string)($row['isid_source'] ?? ''); + $vlan = isis_numeric_value($row['isid_vlan'] ?? NULL); + $has_data = ($vlan !== NULL && $vlan > 0) || + !safe_empty($row['isid_type'] ?? NULL) || + !safe_empty($row['isid_origin'] ?? NULL) || + !safe_empty($row['isid_ports'] ?? NULL) || + !safe_empty($row['isid_ifindexes'] ?? NULL); + + // Net-SNMP can return table column numbers as part of the cache index when the + // local RAPID-CITY MIB is older than the switch firmware. That previously + // created fake ISIDs such as 9, 10 or interface-only 222/223/6164. + if (!$has_data && ($source === 'rcIsid-service' || $source === 'interface')) { + unset($isis_isids[$key]); + } + } + } +} + +if (!function_exists('isis_record_isid')) { + function isis_record_isid($device, $mib, $isid, $name = NULL, $type = NULL, $vlan = NULL, $vlan_name = NULL, $origin = NULL, $source = 'local', $ifIndex = NULL, $port_name = NULL, $port_id = 0) + { + global $isis_isids; + if (!is_array($isis_isids)) { $isis_isids = []; } + + $isid = isis_numeric_value($isid); + if ($isid === NULL || $isid <= 0) { return; } + + $name = isis_isid_display_name($name); + + $key = $mib . '|' . $isid; + if (!isset($isis_isids[$key])) { + $isis_isids[$key] = [ + 'device_id' => $device['device_id'], + 'port_id' => (int)$port_id, + 'isis_mib' => $mib, + 'isid_key' => $key, + 'isid_id' => $isid, + 'isid_name' => !safe_empty($name) ? $name : ('ISID-' . $isid), + 'isid_type' => $type, + 'isid_vlan' => isis_numeric_value($vlan), + 'isid_vlan_name' => $vlan_name, + 'isid_bvlan' => NULL, + 'isid_origin' => $origin, + 'isid_source' => $source, + 'isid_ifindexes' => NULL, + 'isid_ports' => NULL, + 'isid_mlt' => NULL, + 'isid_polled' => time(), + ]; + } + + if (!safe_empty($name) && (safe_empty($isis_isids[$key]['isid_name']) || preg_match('/^ISID-\d+$/', $isis_isids[$key]['isid_name']))) { + $isis_isids[$key]['isid_name'] = $name; + } + if (!safe_empty($type)) { + $current_type = (string)($isis_isids[$key]['isid_type'] ?? ''); + $generic_types = ['vlan' => TRUE, 'secondary-vlan' => TRUE, 'b-vlan' => TRUE]; + if (safe_empty($current_type) || (isset($generic_types[$current_type]) && !isset($generic_types[(string)$type]))) { + $isis_isids[$key]['isid_type'] = $type; + } + } + if (isis_numeric_value($vlan) !== NULL && (safe_empty($isis_isids[$key]['isid_vlan']) || (int)$isis_isids[$key]['isid_vlan'] === 0)) { + $isis_isids[$key]['isid_vlan'] = isis_numeric_value($vlan); + } + if (!safe_empty($vlan_name) && safe_empty($isis_isids[$key]['isid_vlan_name'])) { + $isis_isids[$key]['isid_vlan_name'] = $vlan_name; + } + if (!safe_empty($origin)) { + foreach (explode(',', (string)$origin) as $origin_item) { + $origin_item = trim($origin_item); + if ($origin_item !== '') { + $isis_isids[$key]['isid_origin'] = isis_list_add_unique($isis_isids[$key]['isid_origin'], $origin_item); + } + } + } + if (!safe_empty($source) && strpos((string)$isis_isids[$key]['isid_source'], $source) === FALSE) { + $isis_isids[$key]['isid_source'] = isis_list_add_unique($isis_isids[$key]['isid_source'], $source); + } + if (isis_numeric_value($ifIndex) !== NULL && (int)$ifIndex > 0) { + $isis_isids[$key]['isid_ifindexes'] = isis_list_add_unique($isis_isids[$key]['isid_ifindexes'], (string)(int)$ifIndex); + } + if (!safe_empty($port_name)) { + $isis_isids[$key]['isid_ports'] = isis_list_add_unique($isis_isids[$key]['isid_ports'], $port_name); + } + if ((int)$port_id > 0 && (int)$isis_isids[$key]['port_id'] === 0) { + $isis_isids[$key]['port_id'] = (int)$port_id; + } + $isis_isids[$key]['isid_polled'] = time(); + } +} + +if (!function_exists('isis_update_isids')) { + function isis_update_isids($device) + { + if (!isis_isids_table_available()) { return; } + + global $isis_isids; + $isis_isids = []; + + // BOSS/ERS exposes IS-IS through ISIS-MIB, VSP/FabricEngine through RC-ISIS-MIB. + // The I-SID/VLAN binding objects are Rapid-City objects on both families. + $mib = ($device['os'] === 'extreme-ers') ? 'ISIS-MIB' : 'RC-ISIS-MIB'; + + $vlan_rows = snmpwalk_cache_oid($device, 'rcVlanEntry', [], 'RAPID-CITY'); + foreach ($vlan_rows as $vlan_index => $vlan_entry) { + $vlan = isis_numeric_value($vlan_index); + $plsb_isid = isis_numeric_value($vlan_entry['rcVlanPlsbIsid'] ?? NULL); + $secondary_isid = isis_numeric_value($vlan_entry['rcVlanSecondaryIsid'] ?? NULL); + $vlan_name = isis_clean_value($vlan_entry['rcVlanName'] ?? NULL); + $vlan_type = isis_isid_type($vlan_entry['rcVlanType'] ?? NULL); + + if ($plsb_isid !== NULL && $plsb_isid > 0) { + isis_record_isid($device, $mib, $plsb_isid, 'ISID-' . $plsb_isid, $vlan_type === 'spbm-bvlan' ? 'b-vlan' : 'vlan', $vlan, $vlan_name, NULL, 'vlan-binding'); + } + if ($secondary_isid !== NULL && $secondary_isid > 0) { + isis_record_isid($device, $mib, $secondary_isid, 'ISID-' . $secondary_isid, 'secondary-vlan', $vlan, $vlan_name, NULL, 'secondary-vlan-binding'); + } + } + + // VSP/FabricEngine exposes local service definitions here. ERS/BOSS can return No Such Object; + // that is fine, its local view is still built from rcVlanPlsbIsid above. + foreach (snmpwalk_cache_oid($device, 'rcIsidServiceEntry', [], 'RAPID-CITY') as $isid_index => $entry) { + // Older RAPID-CITY MIBs do not know newer service columns (for example + // service-name). Observium's cache index can then become .. + // The I-SID is always the last numeric index component for rcIsidServiceEntry. + $isid = isis_isid_index_last_numeric($isid_index); + if ($isid === NULL || $isid <= 0) { continue; } + $type = isis_isid_type($entry['rcIsidServiceType'] ?? NULL); + $origin = isis_isid_origin($entry['rcIsidServiceOrigin'] ?? NULL); + $name = isis_isid_display_name($entry['rcIsidServiceName'] ?? ($entry['rcIsidServiceDescr'] ?? NULL)); + isis_record_isid($device, $mib, $isid, !safe_empty($name) ? $name : ('ISID-' . $isid), $type, NULL, NULL, $origin, 'rcIsid-service'); + } + + foreach (snmpwalk_cache_oid($device, 'rcIsidInterfaceEntry', [], 'RAPID-CITY') as $index => $entry) { + $parts = explode('.', (string)$index); + // Expected index: .. With some MIB/firmware combinations the + // object column is prefixed: ... Strip that prefix. + if (count($parts) >= 3 && isis_numeric_value($parts[0] ?? NULL) !== NULL && isis_numeric_value($parts[1] ?? NULL) !== NULL && isis_numeric_value($parts[2] ?? NULL) !== NULL) { + array_shift($parts); + } + $ifIndex = isis_numeric_value($parts[0] ?? NULL); + $isid = isis_numeric_value($parts[1] ?? NULL); + if ($ifIndex === NULL || $isid === NULL || $isid <= 0) { continue; } + + $port = get_port_by_index_cache($device, $ifIndex); + $port_id = is_array($port) && isset($port['port_id']) ? (int)$port['port_id'] : 0; + $port_name = is_array($port) ? ($port['ifName'] ?: ($port['ifDescr'] ?? NULL)) : NULL; + + isis_record_isid( + $device, + $mib, + $isid, + 'ISID-' . $isid, + isis_isid_type($entry['rcIsidInterfaceType'] ?? NULL), + isis_numeric_value($entry['rcIsidInterfaceVlan'] ?? NULL), + NULL, + isis_isid_origin($entry['rcIsidInterfaceOrigin'] ?? NULL), + 'interface', + $ifIndex, + $port_name, + $port_id + ); + } + + + foreach (isis_rc_isid_numeric_name_rows($device) as $isid => $name) { + isis_record_isid($device, $mib, $isid, $name, NULL, NULL, NULL, NULL, 'rcIsid-name'); + } + + foreach (isis_rc_isid_numeric_origin_rows($device) as $isid => $origin) { + isis_record_isid($device, $mib, $isid, NULL, NULL, NULL, NULL, $origin, 'rcIsid-origin'); + } + + isis_isid_prune_rows(); + isis_sync_rows('isis_isids', 'isis_isid_id', 'isid_key', ['`device_id` = ?'], [$device['device_id']], $isis_isids); + } +} + +if (!function_exists('isis_get_local_fabric_node_sysid')) { + function isis_get_local_fabric_node_sysid($device, $mib, $name = NULL) + { + $candidates = []; + foreach ([$name, $device['hostname'] ?? NULL, $device['sysName'] ?? NULL] as $candidate) { + $candidate = isis_clean_value($candidate); + if (!safe_empty($candidate)) { + $candidates[strtolower($candidate)] = TRUE; + } + } + if (safe_empty($candidates)) { return NULL; } + + global $isis_fabric_nodes; + if (is_array($isis_fabric_nodes)) { + foreach ($isis_fabric_nodes as $node) { + if ((int)($node['device_id'] ?? 0) !== (int)$device['device_id']) { continue; } + if (!safe_empty($mib) && !safe_empty($node['isis_mib'] ?? NULL) && (string)$node['isis_mib'] !== (string)$mib) { continue; } + $node_name = strtolower((string)($node['isis_node_name'] ?? '')); + if ($node_name !== '' && isset($candidates[$node_name])) { + $sysid = isis_format_system_id($node['isis_node_sysid'] ?? NULL); + if (!safe_empty($sysid)) { return $sysid; } + } + } + } + + if (isis_fabric_nodes_table_available()) { + $placeholders = generate_query_values(array_keys($candidates), 'LOWER(`isis_node_name`)'); + $params = array_merge([$device['device_id'], $mib, ''], array_keys($candidates)); + $sql = 'SELECT `isis_node_sysid` FROM `isis_fabric_nodes` ' + . 'WHERE `device_id` = ? AND `isis_mib` = ? AND `isis_node_sysid` != ? AND ' . $placeholders + . ' ORDER BY `isis_node_count` DESC LIMIT 1'; + $sysid = isis_format_system_id(dbFetchCell($sql, $params)); + if (!safe_empty($sysid)) { return $sysid; } + } + + return NULL; + } +} + +if (!function_exists('isis_status_table_available')) { + function isis_status_table_available() + { + return in_array('isis_status', dbShowTables(), TRUE); + } +} + +if (!function_exists('isis_rich_instance_table_available')) { + function isis_rich_instance_table_available() + { + if (!in_array('isis_instances', dbShowTables(), TRUE)) { return FALSE; } + $columns = dbShowColumns('isis_instances', 'isis_mib'); + return isset($columns['isis_mib']); + } +} + +if (!function_exists('isis_update_status_rows')) { + function isis_update_status_rows($device) + { + // v7b intentionally uses a separate table. Some Observium releases already + // contain their own legacy isis_instances table, so do not depend on or alter it. + if (!isis_status_table_available()) { return; } + + $device_id = (int)$device['device_id']; + $mibs = []; + + foreach (['isis_areas', 'isis_ports', 'isis_adjacencies'] as $table) { + if (!in_array($table, dbShowTables(), TRUE)) { continue; } + foreach (dbFetchRows('SELECT DISTINCT `isis_mib` FROM `' . $table . '` WHERE `device_id` = ?', [$device_id]) as $row) { + if (!safe_empty($row['isis_mib'])) { $mibs[$row['isis_mib']] = TRUE; } + } + } + + $instance_by_mib = []; + if (isis_rich_instance_table_available()) { + foreach (dbFetchRows('SELECT * FROM `isis_instances` WHERE `device_id` = ?', [$device_id]) as $instance) { + if (safe_empty($instance['isis_mib'])) { continue; } + $mibs[$instance['isis_mib']] = TRUE; + $instance_by_mib[$instance['isis_mib']] = $instance; + } + } + + global $isis_instance_status; + if (!is_array($isis_instance_status)) { $isis_instance_status = []; } + + $rows = []; + foreach (array_keys($mibs) as $mib) { + $instance = $instance_by_mib[$mib] ?? []; + $summary = $isis_instance_status[$mib] ?? []; + $areas = (int)dbFetchCell('SELECT COUNT(*) FROM `isis_areas` WHERE `device_id` = ? AND `isis_mib` = ?', [$device_id, $mib]); + $ports = (int)dbFetchCell('SELECT COUNT(*) FROM `isis_ports` WHERE `device_id` = ? AND `isis_mib` = ?', [$device_id, $mib]); + $adjacencies = (int)dbFetchCell('SELECT COUNT(*) FROM `isis_adjacencies` WHERE `device_id` = ? AND `isis_mib` = ?', [$device_id, $mib]); + $up = (int)dbFetchCell("SELECT COUNT(*) FROM `isis_adjacencies` WHERE `device_id` = ? AND `isis_mib` = ? AND `isis_adj_state` = 'up'", [$device_id, $mib]); + $down = max(0, $adjacencies - $up); + + $status_sys_id = !safe_empty($summary['isis_sys_id'] ?? NULL) ? $summary['isis_sys_id'] : ($instance['isis_sys_id'] ?? NULL); + + // Normalize System IDs at the final write point as well. Depending on + // the SNMP wrapper, ERS/BOSS can still hand us values like + // isisSysID.0 = "02 0E 00 61 00 28 " + // or an already colon-separated value. Store the canonical IS-IS + // format used in CLIs: 020e.0061.0028. + $formatted_status_sys_id = isis_format_system_id($status_sys_id); + if (!safe_empty($formatted_status_sys_id)) { + $status_sys_id = $formatted_status_sys_id; + } + + if (safe_empty($status_sys_id) && $mib === 'ISIS-MIB') { + $status_sys_id = isis_get_system_id_oid($device, 'isisSysID.0', 'ISIS-MIB'); + } + + if (safe_empty($status_sys_id) && $mib === 'RC-ISIS-MIB') { + $status_sys_id = isis_get_local_fabric_node_sysid($device, $mib, !safe_empty($summary['isis_sys_name'] ?? NULL) ? $summary['isis_sys_name'] : ($device['hostname'] ?? NULL)); + } + + $rows[$mib] = [ + 'device_id' => $device_id, + 'isis_mib' => $mib, + 'isis_instance_index' => (string)($summary['isis_instance_index'] ?? ($instance['isis_instance_index'] ?? '0')), + 'isis_sys_id' => $status_sys_id, + 'isis_sys_name' => !safe_empty($summary['isis_sys_name'] ?? NULL) ? $summary['isis_sys_name'] : ($instance['isis_sys_name'] ?? ($device['hostname'] ?? NULL)), + 'isis_admin_state' => !safe_empty($summary['isis_admin_state'] ?? NULL) ? $summary['isis_admin_state'] : ($instance['isis_admin_state'] ?? ($adjacencies || $ports || $areas ? 'on' : 'unknown')), + 'isis_level_type' => !safe_empty($summary['isis_level_type'] ?? NULL) ? $summary['isis_level_type'] : ($instance['isis_level_type'] ?? ($mib === 'RC-ISIS-MIB' ? 'spbm' : NULL)), + 'isis_lsp_count' => isset($summary['isis_lsp_count']) && is_numeric($summary['isis_lsp_count']) ? (int)$summary['isis_lsp_count'] : (isset($instance['isis_lsp_count']) && is_numeric($instance['isis_lsp_count']) ? (int)$instance['isis_lsp_count'] : NULL), + 'isis_areas' => $areas, + 'isis_ports' => $ports, + 'isis_adjacencies' => $adjacencies, + 'isis_adjacencies_up' => $up, + 'isis_adjacencies_down' => $down, + 'isis_polled' => time(), + ]; + } + + isis_sync_rows('isis_status', 'isis_status_id', 'isis_mib', ['`device_id` = ?'], [$device_id], $rows); + } +} + +if (!function_exists('isis_check_alert_entities')) { + function isis_check_alert_entities($device) + { + if (!function_exists('check_entity')) { return; } + + $device_id = (int)$device['device_id']; + + if (isis_status_table_available()) { + foreach (dbFetchRows('SELECT * FROM `isis_status` WHERE `device_id` = ?', [$device_id]) as $instance) { + $metrics = [ + 'isis_admin_state' => $instance['isis_admin_state'], + 'isis_areas' => (int)($instance['isis_areas'] ?? 0), + 'isis_ports' => (int)($instance['isis_ports'] ?? 0), + 'isis_adjacencies' => (int)($instance['isis_adjacencies'] ?? 0), + 'isis_adjacencies_up' => (int)($instance['isis_adjacencies_up'] ?? 0), + 'isis_adjacencies_down' => (int)($instance['isis_adjacencies_down'] ?? 0), + 'isis_lsp_count' => is_numeric($instance['isis_lsp_count']) ? (int)$instance['isis_lsp_count'] : 0, + ]; + check_entity('isis_instance', $instance, $metrics); + } + } + + foreach (dbFetchRows('SELECT * FROM `isis_adjacencies` WHERE `device_id` = ?', [$device_id]) as $adjacency) { + $metrics = [ + 'isis_adj_state' => $adjacency['isis_adj_state'], + 'isis_adj_hold_timer' => is_numeric($adjacency['isis_adj_hold_timer']) ? (int)$adjacency['isis_adj_hold_timer'] : 0, + 'isis_adj_hold_remaining' => is_numeric($adjacency['isis_adj_hold_remaining']) ? (int)$adjacency['isis_adj_hold_remaining'] : 0, + 'isis_adj_priority' => is_numeric($adjacency['isis_adj_priority']) ? (int)$adjacency['isis_adj_priority'] : 0, + ]; + check_entity('isis_adjacency', $adjacency, $metrics); + } + } +} + +$isis_enabled = FALSE; +$isis_instance_status = []; +$isis_fabric_nodes = []; +$isis_packet_stats = [ + 'iih_in' => 0, + 'iih_out' => 0, + 'ish_in' => 0, + 'ish_out' => 0, + 'esh_in' => 0, + 'esh_out' => 0, + 'lsp_in' => 0, + 'lsp_out' => 0, + 'csnp_in' => 0, + 'csnp_out' => 0, + 'psnp_in' => 0, + 'psnp_out' => 0, + 'unknown_in' => 0, + 'unknown_out' => 0, +]; +$isis_stats = [ + 'instances' => 0, + 'areas' => 0, + 'ports' => 0, + 'adjacencies' => 0, + 'up' => 0, + 'down' => 0, +]; + +// Prefer normal MIB discovery. The fallback probes allow one poller run to +// start populating data before the next discovery cycle has marked the MIB. +// Extreme platforms expose overlapping Rapid-City objects; avoid duplicate data: +// - BOSS/ERS: use RFC ISIS-MIB +// - VOSS/FabricEngine: use RC-ISIS-MIB/RAPID-CITY +$isis_candidates = []; +if ($device['os'] === 'extreme-ers') { + $isis_candidates = ['ISIS-MIB']; +} elseif ($device['os'] === 'extreme-vsp') { + $isis_candidates = ['RC-ISIS-MIB']; +} else { + $isis_candidates = ['ISIS-MIB', 'RC-ISIS-MIB']; +} + +$isis_mibs = []; +foreach ($isis_candidates as $candidate_mib) { + if ($candidate_mib === 'ISIS-MIB' && (is_device_mib($device, 'ISIS-MIB') || isis_clean_value(snmp_get_oid($device, 'isisSysAdminState.0', 'ISIS-MIB')))) { + $isis_mibs[] = 'ISIS-MIB'; + } + if ($candidate_mib === 'RC-ISIS-MIB' && (is_device_mib($device, 'RC-ISIS-MIB') || isis_get_rc_isis_hostname($device))) { + $isis_mibs[] = 'RC-ISIS-MIB'; + } +} + +foreach (array_unique($isis_mibs) as $mib) { + $mib_file = $config['install_dir'] . '/includes/polling/isis/' . strtolower($mib) . '.inc.php'; + if (is_file($mib_file)) { + include $mib_file; + } +} + +if ($isis_enabled) { + // Fabric nodes are collected by the sub-pollers and can contain the local + // RC-ISIS System ID. Sync/enrich them before writing isis_status so the + // global Routing -> IS-IS table can show System IDs for VSP/FabricEngine. + isis_update_fabric_nodes($device); + isis_update_isids($device); + isis_update_status_rows($device); + isis_check_alert_entities($device); + + rrdtool_update_ng($device, 'isis-statistics', $isis_stats); + if (array_sum($isis_packet_stats) > 0) { + rrdtool_update_ng($device, 'isis-packets', $isis_packet_stats); + $graphs['isis_packets_hello'] = TRUE; + $graphs['isis_packets_database'] = TRUE; + } + $graphs['isis_adjacencies'] = TRUE; + $graphs['isis_adjacency_states'] = TRUE; + $graphs['isis_ports'] = TRUE; + $graphs['isis_areas'] = TRUE; +} + +unset($isis_enabled, $isis_instance_status, $isis_fabric_nodes, $isis_isids, $isis_packet_stats, $isis_stats, $isis_mibs, $isis_candidates, $candidate_mib, $mib, $mib_file); + +// EOF diff -ruN old/update/533.sql new/update/533.sql --- update/533.sql 1970-01-01 00:00:00.000000000 +0000 +++ update/533.sql 2026-06-05 11:59:05.721545663 +0000 @@ -0,0 +1,199 @@ +-- IS-IS / SPBM consolidated schema +-- +-- This replaces the iterative development files update/529.sql..update/532.sql. +-- The number 533 is only a proposal and should be renumbered by upstream if needed. +-- All CREATE statements are IF NOT EXISTS so the file is safe on systems where +-- the earlier test tables already exist. + +-- ----------------------------------------------------------------------------- +-- Former update/529.sql +-- ----------------------------------------------------------------------------- + +-- IS-IS / SPBM routing support + +CREATE TABLE IF NOT EXISTS `isis_instances` ( + `isis_instance_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_instance_index` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT '0', + `isis_sys_id` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_sys_name` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `isis_admin_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_level_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_version` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_lsp_count` int(11) DEFAULT NULL, + `isis_max_paths` int(11) DEFAULT NULL, + `isis_max_lsp_gen_interval` int(11) DEFAULT NULL, + `isis_sys_wait_time` int(11) DEFAULT NULL, + `isis_sys_max_age` int(11) DEFAULT NULL, + `isis_lsp_buffer_size` int(11) DEFAULT NULL, + `isis_protocols` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_notification_enabled` varchar(16) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_last_enabled` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_last_spf` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_instance_id`), + UNIQUE KEY `device_mib_instance` (`device_id`,`isis_mib`,`isis_instance_index`), + KEY `isis_device_state` (`device_id`,`isis_admin_state`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +CREATE TABLE IF NOT EXISTS `isis_areas` ( + `isis_area_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_area_index` varchar(128) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL, + `isis_area_address` varchar(128) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_area_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_area_status` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_area_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_area_id`), + UNIQUE KEY `device_mib_area` (`device_id`,`isis_mib`,`isis_area_index`), + KEY `isis_area_device` (`device_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +CREATE TABLE IF NOT EXISTS `isis_ports` ( + `isis_port_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `port_id` int(11) NOT NULL DEFAULT 0, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_circ_index` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL, + `isis_circ_ifIndex` int(11) NOT NULL DEFAULT 0, + `isis_circ_admin_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_oper_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_level_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_passive` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_metric` int(11) DEFAULT NULL, + `isis_circ_hello_interval` int(11) DEFAULT NULL, + `isis_circ_hello_multiplier` int(11) DEFAULT NULL, + `isis_circ_ext_domain` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_mesh_group` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_small_hellos` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_last_uptime` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_3way_enabled` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_extended_circ_id` int(11) DEFAULT NULL, + `isis_circ_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_port_id`), + UNIQUE KEY `device_mib_circuit` (`device_id`,`isis_mib`,`isis_circ_index`), + KEY `isis_port_device` (`device_id`,`port_id`), + KEY `isis_port_state` (`device_id`,`isis_circ_admin_state`,`isis_circ_oper_state`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +CREATE TABLE IF NOT EXISTS `isis_adjacencies` ( + `isis_adjacency_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `port_id` int(11) NOT NULL DEFAULT 0, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_adj_index` varchar(128) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL, + `isis_circ_index` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_circ_ifIndex` int(11) NOT NULL DEFAULT 0, + `isis_adj_local_index` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_3way_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_neigh_sysid` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_neigh_name` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `isis_adj_neigh_snpa` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_neigh_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_usage` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_hold_timer` int(11) DEFAULT NULL, + `isis_adj_hold_remaining` int(11) DEFAULT NULL, + `isis_adj_priority` int(11) DEFAULT NULL, + `isis_adj_uptime` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_ext_circuit_id` int(11) DEFAULT NULL, + `isis_adj_restart_status` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_restart_support` varchar(16) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_restart_suppressed` varchar(16) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_adj_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_adjacency_id`), + UNIQUE KEY `device_mib_adjacency` (`device_id`,`isis_mib`,`isis_adj_index`), + KEY `isis_adj_device` (`device_id`,`port_id`), + KEY `isis_adj_state` (`device_id`,`isis_adj_state`), + KEY `isis_adj_neigh_sysid` (`isis_adj_neigh_sysid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + + +-- ----------------------------------------------------------------------------- +-- Former update/530.sql +-- ----------------------------------------------------------------------------- + +-- IS-IS alert-checker status table for existing installations +-- This intentionally does not alter Observium's own legacy isis_instances table. + +CREATE TABLE IF NOT EXISTS `isis_status` ( + `isis_status_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_instance_index` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT '0', + `isis_sys_id` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_sys_name` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `isis_admin_state` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_level_type` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_lsp_count` int(11) DEFAULT NULL, + `isis_areas` int(11) NOT NULL DEFAULT 0, + `isis_ports` int(11) NOT NULL DEFAULT 0, + `isis_adjacencies` int(11) NOT NULL DEFAULT 0, + `isis_adjacencies_up` int(11) NOT NULL DEFAULT 0, + `isis_adjacencies_down` int(11) NOT NULL DEFAULT 0, + `isis_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_status_id`), + UNIQUE KEY `device_mib_instance` (`device_id`,`isis_mib`,`isis_instance_index`), + KEY `isis_status_device` (`device_id`,`isis_admin_state`), + KEY `isis_status_adj` (`device_id`,`isis_adjacencies`,`isis_adjacencies_up`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + + +-- ----------------------------------------------------------------------------- +-- Former update/531.sql +-- ----------------------------------------------------------------------------- + +-- IS-IS discovered/fabric nodes table. + +CREATE TABLE IF NOT EXISTS `isis_fabric_nodes` ( + `isis_fabric_node_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `port_id` int(11) NOT NULL DEFAULT 0, + `isis_mib` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'ISIS-MIB', + `isis_node_key` varchar(128) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL, + `isis_node_sysid` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_node_name` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `isis_node_source` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL, + `isis_node_count` int(11) NOT NULL DEFAULT 0, + `isis_node_polled` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`isis_fabric_node_id`), + UNIQUE KEY `device_mib_node` (`device_id`,`isis_mib`,`isis_node_key`), + KEY `isis_node_device` (`device_id`,`port_id`), + KEY `isis_node_sysid` (`isis_node_sysid`), + KEY `isis_node_name` (`isis_node_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + + +-- ----------------------------------------------------------------------------- +-- Former update/532.sql +-- ----------------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `isis_isids` ( + `isis_isid_id` int(11) NOT NULL AUTO_INCREMENT, + `device_id` int(11) NOT NULL, + `port_id` int(11) NOT NULL DEFAULT 0, + `isis_mib` varchar(32) NOT NULL, + `isid_key` varchar(128) NOT NULL, + `isid_id` int(11) NOT NULL, + `isid_name` varchar(128) DEFAULT NULL, + `isid_type` varchar(64) DEFAULT NULL, + `isid_vlan` int(11) DEFAULT NULL, + `isid_vlan_name` varchar(128) DEFAULT NULL, + `isid_bvlan` int(11) DEFAULT NULL, + `isid_origin` varchar(64) DEFAULT NULL, + `isid_source` varchar(64) DEFAULT NULL, + `isid_ifindexes` text DEFAULT NULL, + `isid_ports` text DEFAULT NULL, + `isid_mlt` varchar(128) DEFAULT NULL, + `isid_polled` int(11) NOT NULL, + PRIMARY KEY (`isis_isid_id`), + UNIQUE KEY `device_isid_key` (`device_id`,`isid_key`), + KEY `device_id` (`device_id`), + KEY `isid_id` (`isid_id`), + KEY `isis_mib` (`isis_mib`), + KEY `port_id` (`port_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +