diff --git a/CHANGELOG.md b/CHANGELOG.md index 179f0e2e..c29fcde5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ --- develop --- +* feature: On Cacti 1.2.32+, zebra-stripe the interfaces list and add an "Issues" column of clickable status pills (one per legend state, active states highlighted); clicking a pill toggles that state in a new multi-select Status filter styled like Base Cacti's Graph View Graph Templates selector +* feature: Add a status pill column to the 802.1x view after the username; clicking the pill drills down by setting the Status filter to that session status * feature: Lay the interfaces and dot1x status legends out as a centered footer in normal document flow via a shared `.mactrackLegendFooter` class, instead of a fixed-position overlay that could cover the last rows at the bottom of the page * feature: Restyle the interfaces and dot1x status legends as rounded, evenly-spaced solid-colour chips with theme-appropriate colours (matching the Thold/Monitor/Syslog legends and the Deepness emerald), sized to the longest label and laid out as a CSS grid so they stay equal width across wrapped rows; also fixed a stray `background-color: color:` typo in the `.int_no_device` rule * dev: Convert plugin bootstrap include/include_once calls to fail-fast require/require_once across the web, CLI, and hook entry points so a missing dependency halts execution instead of continuing with an undefined API diff --git a/css/classic.css b/css/classic.css index 1f828fae..4a6f0989 100644 --- a/css/classic.css +++ b/css/classic.css @@ -176,3 +176,65 @@ color: #000000; } +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } + diff --git a/css/dark.css b/css/dark.css index f1d9866c..c774897e 100644 --- a/css/dark.css +++ b/css/dark.css @@ -175,3 +175,65 @@ background-color: rgba(205, 207, 196, 0.2); color: #eeeeee; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #cccccc); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/deepness.css b/css/deepness.css index eda0a083..1920e902 100644 --- a/css/deepness.css +++ b/css/deepness.css @@ -175,3 +175,65 @@ background-color: rgba(205, 207, 196, 0.2); color: #eeeeee; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #cccccc); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #197a54; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #197a54; } +.mactrackPill.dot1x_auth_success { --pill: #2c6f52; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/mactrack.css b/css/mactrack.css index e318835b..d6c7c75a 100644 --- a/css/mactrack.css +++ b/css/mactrack.css @@ -104,3 +104,65 @@ color: #eeeeee; } +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #93302a; } +.mactrackPill.dot1x_running { --pill: #a85712; } +.mactrackPill.dot1x_idle { --pill: #5f5f57; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } + diff --git a/css/modern.css b/css/modern.css index aa2b347b..c1ee8748 100644 --- a/css/modern.css +++ b/css/modern.css @@ -182,3 +182,65 @@ background-color: rgba(205, 207, 196, 0.8); color: #000000; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/paper-plane.css b/css/paper-plane.css index 37ee764b..d86db3a2 100644 --- a/css/paper-plane.css +++ b/css/paper-plane.css @@ -175,3 +175,65 @@ background-color: rgba(205, 207, 196, 0.8); color: #000000; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/paw.css b/css/paw.css index 37ee764b..d86db3a2 100644 --- a/css/paw.css +++ b/css/paw.css @@ -175,3 +175,65 @@ background-color: rgba(205, 207, 196, 0.8); color: #000000; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/sunrise.css b/css/sunrise.css index 06f81aef..5eb89a5d 100644 --- a/css/sunrise.css +++ b/css/sunrise.css @@ -175,3 +175,65 @@ background-color: rgba(205, 207, 196, 0.8); color: #000000; } + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Outlined when inactive and solid when active; each pill takes + * its colour from the --pill custom property defined below so it matches + * this theme's status legend chips. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + appearance: none; + -webkit-appearance: none; + display: inline-block; + margin: 0; + padding: 1px 7px; + border-radius: 10px; + font: inherit; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: inherit; + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); +} + +.mactrackPill:hover { + background-color: var(--pill, #666666); + color: #ffffff; +} + +.mactrackPill:focus-visible { + outline: 2px solid var(--pill, #666666); + outline-offset: 1px; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); +} + +.mactrackPill.int_up { --pill: #2b8435; } +.mactrackPill.int_up_wo_alias { --pill: #a85712; } +.mactrackPill.int_errors { --pill: #93302a; } +.mactrackPill.int_discards { --pill: #9a6a1a; } +.mactrackPill.int_no_graph { --pill: #5f5f57; } +.mactrackPill.int_down { --pill: #a12820; } + +.mactrackPill.dot1x_authn_success { --pill: #2b8435; } +.mactrackPill.dot1x_auth_success { --pill: #3f7a4a; } +.mactrackPill.dot1x_authn_failed { --pill: #a12820; } +.mactrackPill.dot1x_auth_failed { --pill: #a85712; } +.mactrackPill.dot1x_running { --pill: #93302a; } +.mactrackPill.dot1x_idle { --pill: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/lib/mactrack_functions.php b/lib/mactrack_functions.php index 41f61df9..474f8f0e 100644 --- a/lib/mactrack_functions.php +++ b/lib/mactrack_functions.php @@ -3368,6 +3368,14 @@ function mactrack_format_interface_row($stat): string { form_selectable_cell(strtoupper($stat['device_type']), $stat['device_id']); form_selectable_cell($stat['ifName'], $stat['device_id']); form_selectable_cell($stat['ifDescr'], $stat['device_id']); + + // View glue: the pill markup itself is unit-tested via mactrack_interface_status_pills(). + // @codeCoverageIgnoreStart + if (mactrack_interfaces_show_issues()) { + form_selectable_cell(mactrack_interface_status_pills($stat), $stat['device_id']); + } + // @codeCoverageIgnoreEnd + form_selectable_cell($stat['ifAlias'], $stat['device_id']); form_selectable_cell(round($stat['inBound'],1) . ' %', $stat['device_id'], '', 'right'); form_selectable_cell(round($stat['outBound'],1) . ' %', $stat['device_id'], '', 'right'); @@ -3389,12 +3397,191 @@ function mactrack_format_interface_row($stat): string { } form_selectable_cell($stat['ifOperStatus'] == 1 ? __('Up', 'mactrack') : __('Down', 'mactrack'), $stat['device_id'], '', 'right'); + form_selectable_cell($upTime, $stat['device_id'], '', 'right'); form_selectable_cell(mactrack_date($stat['last_rundate']), $stat['device_id'], '', 'right'); return (string) ob_get_clean(); } +/** + * Determines whether the enhanced interface status detail (the Issues + * pill column and the multi-select Status filter) should be rendered. + * These rely on the select2-multi-count filter widget and pill styling + * available in Cacti 1.2.32 and newer, so they are hidden on older + * cores. + * + * @return bool True when running on Cacti 1.2.32 or newer. + */ +function mactrack_interfaces_show_issues(): bool { + return defined('CACTI_VERSION') && cacti_version_compare(CACTI_VERSION, '1.2.32', '>='); +} + +/** + * The ordered map of interface status legend classes to their display + * labels, shared by the interfaces legend, the Issues pill column and + * the multi-select Status filter so they always stay in sync. + * + * @return array Ordered map of legend CSS class => label text. + */ +function mactrack_interface_status_legend(): array { + return [ + 'int_up' => __('Interface Up', 'mactrack'), + 'int_up_wo_alias' => __('No Alias', 'mactrack'), + 'int_errors' => __('Errors Present', 'mactrack'), + 'int_discards' => __('Discards Present', 'mactrack'), + 'int_no_graph' => __('No Graphs', 'mactrack'), + 'int_down' => __('Interface Down', 'mactrack'), + ]; +} + +/** + * Reduces a comma-separated status filter string into a validated, + * de-duplicated list of known interface status legend classes. + * + * @param string|null $value The raw 'statuses' request value. + * + * @return array The validated status legend classes, in request order. + */ +function mactrack_parse_status_tokens($value): array { + $valid = array_keys(mactrack_interface_status_legend()); + $out = []; + + if ($value === null || $value === '') { + return $out; + } + + foreach (explode(',', (string) $value) as $token) { + $token = trim($token); + + if (in_array($token, $valid, true) && !in_array($token, $out, true)) { + $out[] = $token; + } + } + + return $out; +} + +/** + * Determines whether a given interface currently has any associated + * Cacti graphs, used to drive the 'No Graphs' status indicator. + * + * @param int $host_id The linked Cacti host id. + * @param int $ifIndex The SNMP interface index. + * + * @return bool True when at least one interface graph exists. + */ +function mactrack_interface_has_graphs($host_id, $ifIndex): bool { + if (empty($host_id)) { + return false; + } + + return (bool) db_fetch_cell_prepared('SELECT COUNT(*) + FROM mac_track_interface_graphs + WHERE host_id = ? + AND ifIndex = ?', + [$host_id, $ifIndex]); +} + +/** + * Builds the set of interface status legend classes that currently + * apply to a single interface record, used to highlight the active + * pills in the Issues column. + * + * @param array $stat The interface record being rendered. + * + * @return array Map of active legend CSS class => true. + */ +function mactrack_int_statuses($stat): array { + $active = []; + + if ($stat['ifOperStatus'] == 1) { + $active['int_up'] = true; + + if ($stat['ifAlias'] == '') { + $active['int_up_wo_alias'] = true; + } + } else { + $active['int_down'] = true; + } + + if ($stat['int_errors_present'] == 1) { + $active['int_errors'] = true; + } + + if ($stat['int_discards_present'] == 1) { + $active['int_discards'] = true; + } + + if (array_key_exists('has_graphs', $stat)) { + $has_graphs = (bool) $stat['has_graphs']; + } else { + $has_graphs = mactrack_interface_has_graphs($stat['host_id'], $stat['ifIndex']); + } + + if (!$has_graphs) { + $active['int_no_graph'] = true; + } + + return $active; +} + +/** + * Renders the Issues column for an interface: one clickable pill per + * legend status, with the statuses that currently apply to the + * interface highlighted. Clicking a pill toggles that status in the + * multi-select Status filter (see mactrack_view_interfaces.php). + * + * @param array $stat The interface record being rendered. + * + * @return string The HTML markup for the pill group. + */ +function mactrack_interface_status_pills($stat): string { + $legend = mactrack_interface_status_legend(); + $active = mactrack_int_statuses($stat); + + $pills = '