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 '| No area information reported by this MIB. |
';
+ }
+ foreach ($area_rows as $area) {
+ $area_row_class = in_array($area['isis_area_status'], ['active', 'on', 'up'], TRUE) ? 'up' : '';
+ echo '';
+ echo ' | ';
+ echo '' . escape_html($area['isis_area_address']) . ' | ';
+ echo '' . escape_html($area['isis_area_type']) . ' | ';
+ echo '' . isis_web_label($area['isis_area_status']) . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '' . $port_link . ' | ';
+ echo '' . escape_html($port['isis_circ_index']) . ' | ';
+ echo '' . isis_web_label($port['isis_circ_admin_state'], ['on', 'up', 'enable', 'enabled']) . ' | ';
+ echo '' . isis_web_label($port['isis_circ_oper_state'], ['active', 'inservice', 'up']) . ' | ';
+ echo '' . escape_html($port['isis_circ_type'] ?: $port['isis_circ_level_type']) . ' | ';
+ echo '' . escape_html($port['isis_circ_metric']) . ' | ';
+ echo '' . escape_html($port['isis_circ_hello_interval']) . (!safe_empty($port['isis_circ_hello_multiplier']) ? ' x ' . escape_html($port['isis_circ_hello_multiplier']) : '') . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '' . $port_link . ' | ';
+ echo '' . escape_html($adj['isis_adj_neigh_name'] ?: 'unknown') . ' | ';
+ echo '' . escape_html($adj['isis_adj_neigh_sysid']) . ' | ';
+ echo '' . isis_web_label($adj['isis_adj_state']) . (!safe_empty($adj['isis_adj_3way_state']) ? ' ' . isis_web_label($adj['isis_adj_3way_state']) : '') . ' | ';
+ echo '' . escape_html($adj['isis_adj_usage'] ?: $adj['isis_adj_neigh_type']) . ' | ';
+ echo '' . escape_html($adj['isis_adj_hold_remaining'] ?: $adj['isis_adj_hold_timer']) . ' | ';
+ echo '' . escape_html($adj['isis_adj_uptime']) . ' | ';
+ echo '' . escape_html(implode(', ', $restart)) . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '' . ($known_device ? generate_device_link($known_device, $node_name) : escape_html($node_name)) . ' | ';
+ echo '' . escape_html($node['isis_node_sysid']) . ' | ';
+ echo '' . escape_html($node['isis_node_source']) . ' | ';
+ echo '' . escape_html($node['isis_node_count']) . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '| ' . escape_html($isid['isid_id']) . ' | ';
+ echo '' . isis_ui_isid_name($isid['isid_name']) . ' | ';
+ echo '' . escape_html(isis_ui_isid_type($isid['isid_type'], $isid['isid_vlan'])) . ' | ';
+ echo '' . escape_html($isid['isid_vlan']) . ' | ';
+ echo '' . escape_html($isid['isid_vlan_name']) . ' | ';
+ echo '' . escape_html($isid['isid_origin']) . ' | ';
+ echo '' . isis_ui_wrap_csv($isid['isid_source'], 2) . ' | ';
+ echo '' . isis_ui_wrap_csv($isid['isid_ports'] ?: $isid['isid_ifindexes'], 8) . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '| ' . escape_html($row['isid_id']) . ' | ';
+ echo '' . escape_html($row['name']) . ' | ';
+ echo '' . escape_html(isid_ui_type($row['type'], array_keys($row['vlans']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($row['vlans']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($row['vlan_names']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($row['origins']))) . ' | ';
+ echo '' . implode(', ', $device_links) . ' | ';
+ echo '' . isid_ui_wrap_csv(implode(',', array_keys($row['ports'])), 6) . ' | ';
+ echo '
';
+}
+
+echo '
';
+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 '| ' . generate_device_link($device, NULL, ['tab' => 'routing', 'proto' => 'isis']) . ' | ';
+ echo '' . escape_html($mib) . ' | ';
+ echo '' . escape_html($display_sys_id) . ' | ';
+ echo '' . escape_html($instance['isis_sys_name']) . ' | ';
+ echo '' . isis_web_label($instance['isis_admin_state']) . ' | ';
+ echo '' . escape_html($instance['isis_level_type']) . ' | ';
+ echo '' . $area_count . ' | ';
+ echo '' . $port_count . ' | ';
+ echo '' . $adj_count . ' ' . $adj_up . ' up' . ($adj_down ? ' ' . $adj_down . ' down' : '') . ' | ';
+ echo '' . escape_html($instance['isis_lsp_count']) . ' | ';
+ echo '
';
+}
+
+echo '
';
+
+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 '| ' . ($node['known'] ? generate_device_link($node['known'], $node_name) : escape_html($node_name)) . ' | ';
+ echo '' . escape_html($node['sysid']) . ' | ';
+ echo '' . escape_html($node['seen']) . ' | ';
+ echo '' . implode(', ', $seen_from) . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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 '| ' . escape_html($isid['isid_id']) . ' | ';
+ echo '' . escape_html($isid['name']) . ' | ';
+ echo '' . escape_html($isid['type']) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($isid['vlans']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($isid['vlan_names']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($isid['origins']))) . ' | ';
+ echo '' . escape_html(implode(', ', array_keys($isid['sources']))) . ' | ';
+ echo '' . implode(', ', $seen_from) . ' | ';
+ echo '' . escape_html(implode(', ', array_slice(array_keys($isid['ports']), 0, 12))) . (count($isid['ports']) > 12 ? ' …' : '') . ' | ';
+ echo '
';
+ }
+ echo '
';
+ 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;
+