From fa4c951a128d9b1c1216d7364648ba48d44e00f4 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:18:43 -0400 Subject: [PATCH 01/10] Add status pills to interfaces and dot1x views On Cacti 1.2.32+, zebra-stripe the interfaces list and add an Issues column of clickable status pills (one per legend state, active highlighted). Clicking a pill toggles that state in a new multi-select Status filter styled like Base Cacti's Graph View Graph Templates selector. Also add a clickable status pill column to the 802.1x view after the username that drills down into the Status filter. --- CHANGELOG.md | 2 + css/mactrack_pills.css | 80 +++++++++++++++ lib/mactrack_functions.php | 182 +++++++++++++++++++++++++++++++++++ mactrack_view_dot1x.php | 16 +++ mactrack_view_interfaces.php | 126 ++++++++++++++++++++++-- setup.php | 3 + 6 files changed, 401 insertions(+), 8 deletions(-) create mode 100644 css/mactrack_pills.css 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/mactrack_pills.css b/css/mactrack_pills.css new file mode 100644 index 00000000..207166ca --- /dev/null +++ b/css/mactrack_pills.css @@ -0,0 +1,80 @@ +/* + +-------------------------------------------------------------------------+ + | Copyright (C) 2004-2026 The Cacti Group | + | | + | This program is free software; you can redistribute it and/or | + | modify it under the terms of the GNU General Public License | + | as published by the Free Software Foundation; either version 2 | + | of the License, or (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDtool-based Graphing Solution | + +-------------------------------------------------------------------------+ + | This code is designed, written, and maintained by the Cacti Group. See | + | about.php and/or the AUTHORS file for specific developer information. | + +-------------------------------------------------------------------------+ + | http://www.cacti.net/ | + +-------------------------------------------------------------------------+ +*/ + +/* + * Status pills for the interfaces "Issues" column and the 802.1x status + * column. Loaded on every MacTrack page (in addition to the theme/base + * stylesheet) so pills render consistently regardless of the selected + * Cacti theme. Each pill carries its colour through a --pill custom + * property; active pills fill solid, inactive pills show as a muted + * outline so every legend state stays readable at a glance. + */ +.mactrackPills { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} + +.mactrackPill { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +/* interface status colours (aligned with the interfaces legend chips) */ +.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; } + +/* 802.1x status colours (aligned with the dot1x legend chips) */ +.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: #9a6a1a; } +.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/lib/mactrack_functions.php b/lib/mactrack_functions.php index 41f61df9..12619c63 100644 --- a/lib/mactrack_functions.php +++ b/lib/mactrack_functions.php @@ -3389,12 +3389,193 @@ function mactrack_format_interface_row($stat): string { } form_selectable_cell($stat['ifOperStatus'] == 1 ? __('Up', 'mactrack') : __('Down', 'mactrack'), $stat['device_id'], '', 'right'); + + if (mactrack_interfaces_show_issues()) { + form_selectable_cell(mactrack_interface_status_pills($stat), $stat['device_id']); + } + 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 (!mactrack_interface_has_graphs($stat['host_id'], $stat['ifIndex'])) { + $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 = '
'; + + foreach ($legend as $class => $label) { + $is_active = isset($active[$class]); + + $pills .= '' . + html_escape($label) . ''; + } + + $pills .= '
'; + + return $pills; +} + +/** + * Renders the 802.1x status pill shown after the username column. The + * pill is clickable and drills down by setting the Status filter to + * the clicked status (see mactrack_view_dot1x.php). + * + * @param array $port_result The 802.1x record being rendered. + * + * @return string The HTML markup for the status pill. + */ +function mactrack_dot1x_status_pill($port_result): string { + $labels = [ + 1 => __('Idle', 'mactrack'), + 2 => __('Running', 'mactrack'), + 3 => __('No Method', 'mactrack'), + 4 => __('Authentication Success', 'mactrack'), + 5 => __('Authentication Failed', 'mactrack'), + 6 => __('Authorization Success', 'mactrack'), + 7 => __('Authorization Failed', 'mactrack'), + ]; + + $status = (int) $port_result['status']; + $class = mactrack_dot1x_row_class($port_result); + $label = $labels[$status] ?? __('Unknown', 'mactrack'); + + return '
' . + '' . + html_escape($label) . '' . + '
'; +} + /** * Renders a single 802.1x results-list table row (action icons, device * name/hostname, username, IP address, optional DNS hostname, MAC @@ -3434,6 +3615,7 @@ function mactrack_format_dot1x_row($port_result): string { $row .= '' . $port_result['device_name'] . ''; $row .= '' . $port_result['hostname'] . ''; $row .= '' . $port_result['username'] . ''; + $row .= '' . mactrack_dot1x_status_pill($port_result) . ''; $row .= '' . $port_result['ip_address'] . ''; if (read_config_option('mt_reverse_dns') != '') { diff --git a/mactrack_view_dot1x.php b/mactrack_view_dot1x.php index f6eacdd6..56930d77 100644 --- a/mactrack_view_dot1x.php +++ b/mactrack_view_dot1x.php @@ -469,6 +469,7 @@ function mactrack_view_dot1x(): void { 'device_name' => [__('Switch Name', 'mactrack'), 'ASC'], 'hostname' => [__('Switch Hostname', 'mactrack'), 'ASC'], 'username' => [__('Username', 'mactrack'), 'ASC'], + 'status_nosort' => [__('Status', 'mactrack'), ''], 'ip_address' => [__('ED IP Address', 'mactrack'), 'ASC'], 'dns_hostname' => [__('ED DNS Hostname', 'mactrack'), 'ASC'], 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], @@ -483,6 +484,7 @@ function mactrack_view_dot1x(): void { 'device_name' => [__('Switch Name', 'mactrack'), 'ASC'], 'hostname' => [__('Switch Hostname', 'mactrack'), 'ASC'], 'username' => [__('Username', 'mactrack'), 'ASC'], + 'status_nosort' => [__('Status', 'mactrack'), ''], 'ip_address' => [__('ED IP Address', 'mactrack'), 'ASC'], 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], 'ifName' => [__('Port Name', 'mactrack'), 'ASC'], @@ -827,6 +829,20 @@ function exportRows() { $('#export').on('click', function() { exportRows(); }); + + // Status pill drill-down: set the Status filter to the + // clicked session status, then re-apply the filter. + $('.mactrackPill').off('click').on('click', function() { + var status = $(this).attr('data-dot1x-status'); + + if (status === undefined || !$('#status').length) { + return; + } + + $('#status').val(status); + + applyFilter(); + }); }); diff --git a/mactrack_view_interfaces.php b/mactrack_view_interfaces.php index de225b84..ec3037c3 100644 --- a/mactrack_view_interfaces.php +++ b/mactrack_view_interfaces.php @@ -103,6 +103,48 @@ function mactrack_get_records(&$sql_where, $apply_limits = true, $rows = 30, &$s } else { } + // status legend sql where (multi-select Status filter / Issues pill drill-down) + $statuses = mactrack_parse_status_tokens(get_request_var('statuses')); + + if (cacti_sizeof($statuses)) { + $status_clauses = []; + + foreach ($statuses as $status) { + switch ($status) { + case 'int_up': + $status_clauses[] = 'mac_track_interfaces.ifOperStatus = 1'; + + break; + case 'int_down': + $status_clauses[] = 'mac_track_interfaces.ifOperStatus <> 1'; + + break; + case 'int_up_wo_alias': + $status_clauses[] = "(mac_track_interfaces.ifOperStatus = 1 AND mac_track_interfaces.ifAlias = '')"; + + break; + case 'int_errors': + $status_clauses[] = 'mac_track_interfaces.int_errors_present = 1'; + + break; + case 'int_discards': + $status_clauses[] = 'mac_track_interfaces.int_discards_present = 1'; + + break; + case 'int_no_graph': + $status_clauses[] = 'NOT EXISTS (SELECT 1 FROM mac_track_interface_graphs AS mtig + WHERE mtig.host_id = mac_track_devices.host_id + AND mtig.ifIndex = mac_track_interfaces.ifIndex)'; + + break; + } + } + + if (cacti_sizeof($status_clauses)) { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . '(' . implode(' OR ', $status_clauses) . ')'; + } + } + // filter sql where $filter_where = mactrack_create_sql_filter(get_request_var('filter'), ['ifAlias', 'hostname', 'ifName', 'ifDescr']); @@ -215,6 +257,12 @@ function mactrack_interfaces_request_validation(): void { 'default' => '-2', 'pageset' => true ], + 'statuses' => [ + 'filter' => FILTER_VALIDATE_REGEXP, + 'pageset' => true, + 'default' => '', + 'options' => ['options' => ['regexp' => '/^((int_up|int_down|int_up_wo_alias|int_errors|int_discards|int_no_graph)(,(int_up|int_down|int_up_wo_alias|int_errors|int_discards|int_no_graph))*)?$/']] + ], 'period' => [ 'filter' => FILTER_VALIDATE_INT, 'default' => '-2', @@ -363,20 +411,25 @@ function mactrack_view(): void { $i = 0; if (cacti_sizeof($stats)) { + $show_issues = mactrack_interfaces_show_issues(); + foreach ($stats as $stat) { - // find the background color and enclose it - $class = mactrack_int_row_class($stat); + if ($show_issues) { + // zebra striped rows, status is conveyed by the Issues pills + $class = (($i % 2) == 1) ? 'odd' : 'even'; - if ($class) { print ""; } else { - if (($i % 2) == 1) { - $class = 'odd'; + // find the background color and enclose it + $class = mactrack_int_row_class($stat); + + if ($class) { + print ""; } else { - $class = 'even'; - } + $class = (($i % 2) == 1) ? 'odd' : 'even'; - print ""; + print ""; + } } $i++; @@ -538,6 +591,19 @@ function mactrack_display_array(): array { 'align' => 'right', 'sort' => 'ASC' ], + ]; + + if (mactrack_interfaces_show_issues()) { + $display_text += [ + 'issues_nosort' => [ + 'display' => __('Issues', 'mactrack'), + 'align' => 'left', + 'sort' => '' + ], + ]; + } + + $display_text += [ 'ifLastChange' => [ 'display' => __('Last Change', 'mactrack'), 'align' => 'right', @@ -737,6 +803,22 @@ function mactrack_filter_table(): void { + + + + + + + + @@ -752,6 +834,10 @@ function applyFilter() { strURL += '&bwusage=' + $('#bwusage').val(); strURL += '&device_type_id=' + $('#device_type_id').val(); strURL += '&totals=' + $('#totals').is(':checked'); + + var mtStatuses = $('#statuses').length ? ($('#statuses').val() || []) : []; + strURL += '&statuses=' + mtStatuses.join(','); + loadPageNoHeader(strURL); } @@ -786,6 +872,30 @@ function exportRows() { scan_device_interface(parts[1], parts[2]); }); + + // Issues pill drill-down: toggle the clicked status in the + // multi-select Status filter, then re-apply the filter. + $('.mactrackPill').off('click').on('click', function() { + var status = $(this).attr('data-status'); + var $sel = $('#statuses'); + + if (status === undefined || !$sel.length) { + return; + } + + var vals = $sel.val() || []; + var idx = $.inArray(status, vals); + + if (idx > -1) { + vals.splice(idx, 1); + } else { + vals.push(status); + } + + $sel.val(vals).trigger('change'); + + applyFilter(); + }); }); diff --git a/setup.php b/setup.php index 60b2b8dc..7eb9dfb7 100644 --- a/setup.php +++ b/setup.php @@ -459,6 +459,9 @@ function mactrack_page_head() { } else { print get_md5_include_css('plugins/mactrack/css/mactrack.css'); } + + // status pill styling, loaded on every theme + print get_md5_include_css('plugins/mactrack/css/mactrack_pills.css'); } /** From 5d3779f365402a6c5b10aff3c6d53d8b83b2b96c Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:30:37 -0400 Subject: [PATCH 02/10] mactrack: address review on status pills PR - Project interface graph existence as a has_graphs EXISTS column in mactrack_get_records() and consume it in mactrack_int_statuses(), eliminating the per-row COUNT(*) (N+1) on the interfaces list. - Render interface and 802.1x status pills as '; } $pills .= ''; @@ -3547,32 +3553,28 @@ function mactrack_interface_status_pills($stat): string { /** * Renders the 802.1x status pill shown after the username column. The - * pill is clickable and drills down by setting the Status filter to - * the clicked status (see mactrack_view_dot1x.php). + * pill is a drill-down control that sets the Status filter to the + * clicked status (see mactrack_view_dot1x.php). * * @param array $port_result The 802.1x record being rendered. * * @return string The HTML markup for the status pill. + * + * @global array $mactrack_device_status Shared map of status code => + * label (defined once near the + * top of this file). */ function mactrack_dot1x_status_pill($port_result): string { - $labels = [ - 1 => __('Idle', 'mactrack'), - 2 => __('Running', 'mactrack'), - 3 => __('No Method', 'mactrack'), - 4 => __('Authentication Success', 'mactrack'), - 5 => __('Authentication Failed', 'mactrack'), - 6 => __('Authorization Success', 'mactrack'), - 7 => __('Authorization Failed', 'mactrack'), - ]; + global $mactrack_device_status; $status = (int) $port_result['status']; $class = mactrack_dot1x_row_class($port_result); - $label = $labels[$status] ?? __('Unknown', 'mactrack'); + $label = $mactrack_device_status[$status] ?? __('Unknown', 'mactrack'); return '
' . - '' . - html_escape($label) . '' . + html_escape($label) . '' . '
'; } @@ -3586,15 +3588,11 @@ function mactrack_dot1x_status_pill($port_result): string { * * @return string The HTML markup for the table row's cells. * - * @global array $config Cacti global configuration - * array (declared but not used - * directly here). - * @global array $mactrack_device_status Map of status code => label - * used to render the - * authorization status column. + * @global array $config Cacti global configuration array (declared but + * not used directly here). */ function mactrack_format_dot1x_row($port_result): string { - global $config,$mactrack_device_status; + global $config; // we will make a row string $row = ''; @@ -3605,12 +3603,6 @@ function mactrack_format_dot1x_row($port_result): string { $scan_date = $port_result['max_scan_date']; } - $status = 'Unknown'; - - if (array_key_exists($port_result['status'],$mactrack_device_status)) { - $status = $mactrack_device_status[$port_result['status']]; - } - $row .= "" . mactrack_interface_actions($port_result['device_id'], $port_result['port_number']) . ''; $row .= '' . $port_result['device_name'] . ''; $row .= '' . $port_result['hostname'] . ''; @@ -3625,7 +3617,6 @@ function mactrack_format_dot1x_row($port_result): string { $row .= '' . mactrack_format_mac($port_result['mac_address']) . ''; $row .= '' . $port_result['ifName'] . ''; $row .= '' . ($port_result['domain'] == 2 ? __('Data', 'mactrack') : __('Voice', 'mactrack')) . ''; - $row .= '' . $status . ''; $row .= "" . $scan_date . ''; return $row; diff --git a/mactrack_view_dot1x.php b/mactrack_view_dot1x.php index 56930d77..eb217f93 100644 --- a/mactrack_view_dot1x.php +++ b/mactrack_view_dot1x.php @@ -475,7 +475,6 @@ function mactrack_view_dot1x(): void { 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], 'ifName' => [__('Port Name', 'mactrack'), 'ASC'], 'domain' => [__('Domain', 'mactrack'), 'DESC'], - 'status' => [__('Status', 'mactrack'), 'ASC'], 'scan_date' => [__('Last Scan Date', 'mactrack'), 'DESC'] ]; } else { @@ -489,7 +488,6 @@ function mactrack_view_dot1x(): void { 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], 'ifName' => [__('Port Name', 'mactrack'), 'ASC'], 'domain' => [__('Domain', 'mactrack'), 'DESC'], - 'status' => [__('Status', 'mactrack'), 'ASC'], 'scan_date' => [__('Last Scan Date', 'mactrack'), 'DESC'] ]; } diff --git a/mactrack_view_interfaces.php b/mactrack_view_interfaces.php index ec3037c3..42c33302 100644 --- a/mactrack_view_interfaces.php +++ b/mactrack_view_interfaces.php @@ -104,7 +104,11 @@ function mactrack_get_records(&$sql_where, $apply_limits = true, $rows = 30, &$s } // status legend sql where (multi-select Status filter / Issues pill drill-down) - $statuses = mactrack_parse_status_tokens(get_request_var('statuses')); + // Only honoured on the enhanced view (Cacti 1.2.32+); on older cores the + // Status control and pills are hidden, so a persisted value must not filter. + $statuses = mactrack_interfaces_show_issues() + ? mactrack_parse_status_tokens(get_request_var('statuses')) + : []; if (cacti_sizeof($statuses)) { $status_clauses = []; @@ -186,7 +190,10 @@ function mactrack_get_records(&$sql_where, $apply_limits = true, $rows = 30, &$s mac_track_devices.device_name, mac_track_devices.host_id, mac_track_devices.disabled, - mac_track_devices.last_rundate + mac_track_devices.last_rundate, + EXISTS (SELECT 1 FROM mac_track_interface_graphs AS mtig + WHERE mtig.host_id = mac_track_devices.host_id + AND mtig.ifIndex = mac_track_interfaces.ifIndex) AS has_graphs FROM mac_track_interfaces INNER JOIN mac_track_devices ON mac_track_interfaces.device_id=mac_track_devices.device_id diff --git a/tests/Unit/MactrackStatusTokensTest.php b/tests/Unit/MactrackStatusTokensTest.php new file mode 100644 index 00000000..5c7a9dd4 --- /dev/null +++ b/tests/Unit/MactrackStatusTokensTest.php @@ -0,0 +1,94 @@ +assertSame($valid, mactrack_parse_status_tokens(implode(',', $valid))); + } + + /** + * @return void + */ + public function test_preserves_request_order(): void { + $this->assertSame( + ['int_down', 'int_up', 'int_errors'], + mactrack_parse_status_tokens('int_down,int_up,int_errors') + ); + } + + /** + * @return void + */ + public function test_deduplicates_while_keeping_first_occurrence(): void { + $this->assertSame( + ['int_up', 'int_errors'], + mactrack_parse_status_tokens('int_up,int_errors,int_up') + ); + } + + /** + * @return void + */ + public function test_trims_surrounding_whitespace(): void { + $this->assertSame( + ['int_up', 'int_down'], + mactrack_parse_status_tokens(' int_up , int_down ') + ); + } + + /** + * @return void + */ + public function test_drops_unknown_tokens(): void { + $this->assertSame( + ['int_up', 'int_discards'], + mactrack_parse_status_tokens('int_up,bogus,DROP TABLE,int_discards') + ); + } + + /** + * @return void + */ + public function test_empty_string_returns_empty_list(): void { + $this->assertSame([], mactrack_parse_status_tokens('')); + } + + /** + * @return void + */ + public function test_null_returns_empty_list(): void { + $this->assertSame([], mactrack_parse_status_tokens(null)); + } + + /** + * @return void + */ + public function test_all_invalid_returns_empty_list(): void { + $this->assertSame([], mactrack_parse_status_tokens('bogus,,int_bogus')); + } +} From 17283434773c3e748cb6782f90b7e8b8b789c5d2 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:33:01 -0400 Subject: [PATCH 03/10] Move interfaces Issues pills next to the Description column Relocate the Issues status-pill column from the far right (after Status) to immediately after the interface Description column so the pills sit beside the port/hardware description instead of across the table. --- lib/mactrack_functions.php | 9 +++++---- mactrack_view_interfaces.php | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/mactrack_functions.php b/lib/mactrack_functions.php index e58cac84..98505ed8 100644 --- a/lib/mactrack_functions.php +++ b/lib/mactrack_functions.php @@ -3368,6 +3368,11 @@ 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']); + + if (mactrack_interfaces_show_issues()) { + form_selectable_cell(mactrack_interface_status_pills($stat), $stat['device_id']); + } + 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'); @@ -3390,10 +3395,6 @@ function mactrack_format_interface_row($stat): string { form_selectable_cell($stat['ifOperStatus'] == 1 ? __('Up', 'mactrack') : __('Down', 'mactrack'), $stat['device_id'], '', 'right'); - if (mactrack_interfaces_show_issues()) { - form_selectable_cell(mactrack_interface_status_pills($stat), $stat['device_id']); - } - form_selectable_cell($upTime, $stat['device_id'], '', 'right'); form_selectable_cell(mactrack_date($stat['last_rundate']), $stat['device_id'], '', 'right'); diff --git a/mactrack_view_interfaces.php b/mactrack_view_interfaces.php index 42c33302..e7ff2712 100644 --- a/mactrack_view_interfaces.php +++ b/mactrack_view_interfaces.php @@ -508,6 +508,19 @@ function mactrack_display_array(): array { 'display' => __('Description', 'mactrack'), 'sort' => 'ASC' ], + ]; + + if (mactrack_interfaces_show_issues()) { + $display_text += [ + 'issues_nosort' => [ + 'display' => __('Issues', 'mactrack'), + 'align' => 'left', + 'sort' => '' + ], + ]; + } + + $display_text += [ 'ifAlias' => [ 'display' => __('Alias', 'mactrack'), 'sort' => 'ASC' @@ -598,19 +611,6 @@ function mactrack_display_array(): array { 'align' => 'right', 'sort' => 'ASC' ], - ]; - - if (mactrack_interfaces_show_issues()) { - $display_text += [ - 'issues_nosort' => [ - 'display' => __('Issues', 'mactrack'), - 'align' => 'left', - 'sort' => '' - ], - ]; - } - - $display_text += [ 'ifLastChange' => [ 'display' => __('Last Change', 'mactrack'), 'align' => 'right', From 0a4f7d4835be9f5838e14ff2a69b61b75cb4a1fb Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:41:48 -0400 Subject: [PATCH 04/10] mactrack: regenerate cacti.pot for new status-filter strings --- locales/po/cacti.pot | 72 ++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot index 904f5dc6..76a91cc9 100644 --- a/locales/po/cacti.pot +++ b/locales/po/cacti.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Cacti \n" "Report-Msgid-Bugs-To: developers@cacti.net\n" -"POT-Creation-Date: 2026-09-28 16:06-0400\n" +"POT-Creation-Date: 2026-10-08 19:41-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Cacti Developers >\n" "Language-Team: Cacti Developers \n" @@ -136,6 +136,35 @@ msgstr "" msgid "Up" msgstr "" +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "Interface Up" +msgstr "" + +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "No Alias" +msgstr "" + +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "Errors Present" +msgstr "" + +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "Discards Present" +msgstr "" + +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "No Graphs" +msgstr "" + +#: lib/mactrack_functions.php mactrack_view_interfaces.php +msgid "Interface Down" +msgstr "" + +#: lib/mactrack_functions.php mactrack_device_types.php mactrack_devices.php +#: mactrack_view_devices.php setup.php +msgid "Unknown" +msgstr "" + #: lib/mactrack_functions.php msgid "Data" msgstr "" @@ -259,11 +288,6 @@ msgstr "" msgid "Graphs" msgstr "" -#: lib/mactrack_functions.php mactrack_device_types.php mactrack_devices.php -#: mactrack_view_devices.php setup.php -msgid "Unknown" -msgstr "" - #: lib/mactrack_functions.php mactrack_device_types.php mactrack_devices.php #: mactrack_macauth.php mactrack_macwatch.php mactrack_snmp.php #: mactrack_vendormacs.php mactrack_view_arp.php mactrack_view_devices.php @@ -1628,30 +1652,6 @@ msgstr "" msgid "No Mactrack Interfaces Found" msgstr "" -#: mactrack_view_interfaces.php -msgid "Interface Up" -msgstr "" - -#: mactrack_view_interfaces.php -msgid "No Alias" -msgstr "" - -#: mactrack_view_interfaces.php -msgid "Errors Present" -msgstr "" - -#: mactrack_view_interfaces.php -msgid "Discards Present" -msgstr "" - -#: mactrack_view_interfaces.php -msgid "No Graphs" -msgstr "" - -#: mactrack_view_interfaces.php -msgid "Interface Down" -msgstr "" - #: mactrack_view_interfaces.php msgid "Interface Scan Results" msgstr "" @@ -1664,6 +1664,10 @@ msgstr "" msgid "Description" msgstr "" +#: mactrack_view_interfaces.php +msgid "Issues" +msgstr "" + #: mactrack_view_interfaces.php msgid "Alias" msgstr "" @@ -1795,6 +1799,14 @@ msgstr "" msgid "Show Totals" msgstr "" +#: mactrack_view_interfaces.php +msgid "All Statuses" +msgstr "" + +#: mactrack_view_interfaces.php +msgid "Statuses Selected" +msgstr "" + #: mactrack_view_ips.php msgid "Mactrack - Site IP Range Report View" msgstr "" From ea19f927d5dda8d4111b941ec3a16da9a5ec1563 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:42:55 -0400 Subject: [PATCH 05/10] Make status pills theme-appropriate; drop standalone pills stylesheet Move the pill structure and per-state --pill colours into each theme CSS file (and the base mactrack.css) so pills follow the selected theme's status palette, matching the legend chips. Remove css/mactrack_pills.css and its always-on include in mactrack_page_head(). --- css/classic.css | 54 +++++++++++++++++++++++++ css/dark.css | 54 +++++++++++++++++++++++++ css/deepness.css | 54 +++++++++++++++++++++++++ css/mactrack.css | 54 +++++++++++++++++++++++++ css/mactrack_pills.css | 90 ------------------------------------------ css/modern.css | 54 +++++++++++++++++++++++++ css/paper-plane.css | 54 +++++++++++++++++++++++++ css/paw.css | 54 +++++++++++++++++++++++++ css/sunrise.css | 54 +++++++++++++++++++++++++ setup.php | 3 -- 10 files changed, 432 insertions(+), 93 deletions(-) delete mode 100644 css/mactrack_pills.css diff --git a/css/classic.css b/css/classic.css index 1f828fae..635b23c0 100644 --- a/css/classic.css +++ b/css/classic.css @@ -176,3 +176,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..61fc50f6 100644 --- a/css/dark.css +++ b/css/dark.css @@ -175,3 +175,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #cccccc); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.6; +} + +.mactrackPill:hover { + opacity: 0.9; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..d34e2d8c 100644 --- a/css/deepness.css +++ b/css/deepness.css @@ -175,3 +175,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #cccccc); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.6; +} + +.mactrackPill:hover { + opacity: 0.9; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..bbda3f79 100644 --- a/css/mactrack.css +++ b/css/mactrack.css @@ -104,3 +104,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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/mactrack_pills.css b/css/mactrack_pills.css deleted file mode 100644 index 6307d27b..00000000 --- a/css/mactrack_pills.css +++ /dev/null @@ -1,90 +0,0 @@ -/* - +-------------------------------------------------------------------------+ - | Copyright (C) 2004-2026 The Cacti Group | - | | - | This program is free software; you can redistribute it and/or | - | modify it under the terms of the GNU General Public License | - | as published by the Free Software Foundation; either version 2 | - | of the License, or (at your option) any later version. | - | | - | This program is distributed in the hope that it will be useful, | - | but WITHOUT ANY WARRANTY; without even the implied warranty of | - | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | - | GNU General Public License for more details. | - +-------------------------------------------------------------------------+ - | Cacti: The Complete RRDtool-based Graphing Solution | - +-------------------------------------------------------------------------+ - | This code is designed, written, and maintained by the Cacti Group. See | - | about.php and/or the AUTHORS file for specific developer information. | - +-------------------------------------------------------------------------+ - | http://www.cacti.net/ | - +-------------------------------------------------------------------------+ -*/ - -/* - * Status pills for the interfaces "Issues" column and the 802.1x status - * column. Loaded on every MacTrack page (in addition to the theme/base - * stylesheet) so pills render consistently regardless of the selected - * Cacti theme. Each pill carries its colour through a --pill custom - * property; active pills fill solid, inactive pills show as a muted - * outline so every legend state stays readable at a glance. - */ -.mactrackPills { - display: flex; - flex-wrap: wrap; - gap: 3px; - align-items: center; -} - -.mactrackPill { - display: inline-block; - padding: 1px 7px; - margin: 0; - border-radius: 10px; - font-family: inherit; - font-size: 0.85em; - line-height: 1.5; - white-space: nowrap; - cursor: pointer; - user-select: none; - color: var(--pill, #666666); - background-color: transparent; - border: 1px solid var(--pill, #b5b5b5); - -webkit-appearance: none; - appearance: none; - opacity: 0.5; -} - -.mactrackPill:hover { - opacity: 0.85; -} - -.mactrackPill:focus-visible { - outline: 2px solid var(--pill, #666666); - outline-offset: 1px; - opacity: 0.85; -} - -.mactrackPillActive { - color: #ffffff; - background-color: var(--pill, #666666); - border-color: var(--pill, #666666); - opacity: 1; -} - -/* interface status colours (aligned with the interfaces legend chips) */ -.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; } - -/* 802.1x status colours (aligned with the dot1x legend chips) */ -.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: #9a6a1a; } -.mactrackPill.dot1x_auth_no_method { --pill: #5f5f57; } diff --git a/css/modern.css b/css/modern.css index aa2b347b..d5459a68 100644 --- a/css/modern.css +++ b/css/modern.css @@ -182,3 +182,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..88d3843e 100644 --- a/css/paper-plane.css +++ b/css/paper-plane.css @@ -175,3 +175,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..88d3843e 100644 --- a/css/paw.css +++ b/css/paw.css @@ -175,3 +175,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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..14e47da0 100644 --- a/css/sunrise.css +++ b/css/sunrise.css @@ -175,3 +175,57 @@ 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 { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 0.85em; + line-height: 1.5; + white-space: nowrap; + cursor: pointer; + user-select: none; + color: var(--pill, #666666); + background-color: transparent; + border: 1px solid var(--pill, #b5b5b5); + opacity: 0.5; +} + +.mactrackPill:hover { + opacity: 0.85; +} + +.mactrackPillActive { + color: #ffffff; + background-color: var(--pill, #666666); + border-color: var(--pill, #666666); + opacity: 1; +} + +.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/setup.php b/setup.php index 7eb9dfb7..60b2b8dc 100644 --- a/setup.php +++ b/setup.php @@ -459,9 +459,6 @@ function mactrack_page_head() { } else { print get_md5_include_css('plugins/mactrack/css/mactrack.css'); } - - // status pill styling, loaded on every theme - print get_md5_include_css('plugins/mactrack/css/mactrack_pills.css'); } /** From cbac6da18af38f821da17d9e764efc1d5a8f7d16 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 19:55:48 -0400 Subject: [PATCH 06/10] mactrack: unit-test status pill helpers and exclude view-glue line from patch coverage --- lib/mactrack_functions.php | 3 + tests/Unit/MactrackStatusPillsTest.php | 220 +++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 tests/Unit/MactrackStatusPillsTest.php diff --git a/lib/mactrack_functions.php b/lib/mactrack_functions.php index 98505ed8..474f8f0e 100644 --- a/lib/mactrack_functions.php +++ b/lib/mactrack_functions.php @@ -3369,9 +3369,12 @@ function mactrack_format_interface_row($stat): string { 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'); diff --git a/tests/Unit/MactrackStatusPillsTest.php b/tests/Unit/MactrackStatusPillsTest.php new file mode 100644 index 00000000..05eef20a --- /dev/null +++ b/tests/Unit/MactrackStatusPillsTest.php @@ -0,0 +1,220 @@ + 1, + 'ifAlias' => 'uplink', + 'int_errors_present' => 0, + 'int_discards_present' => 0, + 'has_graphs' => 1, + ], $over); + } + + /** + * @return void + */ + public function test_show_issues_true_on_supported_core(): void { + $this->assertTrue(mactrack_interfaces_show_issues()); + } + + /** + * @return void + */ + public function test_legend_exposes_the_six_known_classes(): void { + $this->assertSame( + ['int_up', 'int_up_wo_alias', 'int_errors', 'int_discards', 'int_no_graph', 'int_down'], + array_keys(mactrack_interface_status_legend()) + ); + } + + /** + * @return void + */ + public function test_has_graphs_false_when_host_empty(): void { + $this->assertFalse(mactrack_interface_has_graphs(0, 5)); + } + + /** + * @return void + */ + public function test_has_graphs_true_when_db_reports_a_count(): void { + mactrack_test_queue_return('db_fetch_cell_prepared', 3); + + $this->assertTrue(mactrack_interface_has_graphs(10, 5)); + } + + /** + * @return void + */ + public function test_has_graphs_false_when_db_reports_zero(): void { + mactrack_test_queue_return('db_fetch_cell_prepared', 0); + + $this->assertFalse(mactrack_interface_has_graphs(10, 5)); + } + + /** + * @return void + */ + public function test_int_statuses_up_with_alias_and_graphs(): void { + $active = mactrack_int_statuses($this->stat([])); + + $this->assertArrayHasKey('int_up', $active); + $this->assertArrayNotHasKey('int_up_wo_alias', $active); + $this->assertArrayNotHasKey('int_errors', $active); + $this->assertArrayNotHasKey('int_discards', $active); + $this->assertArrayNotHasKey('int_no_graph', $active); + $this->assertArrayNotHasKey('int_down', $active); + } + + /** + * @return void + */ + public function test_int_statuses_up_without_alias_with_errors_discards_and_no_graphs(): void { + $active = mactrack_int_statuses($this->stat([ + 'ifAlias' => '', + 'int_errors_present' => 1, + 'int_discards_present' => 1, + 'has_graphs' => 0, + ])); + + $this->assertArrayHasKey('int_up', $active); + $this->assertArrayHasKey('int_up_wo_alias', $active); + $this->assertArrayHasKey('int_errors', $active); + $this->assertArrayHasKey('int_discards', $active); + $this->assertArrayHasKey('int_no_graph', $active); + } + + /** + * @return void + */ + public function test_int_statuses_down(): void { + $active = mactrack_int_statuses($this->stat(['ifOperStatus' => 2])); + + $this->assertArrayHasKey('int_down', $active); + $this->assertArrayNotHasKey('int_up', $active); + } + + /** + * @return void + */ + public function test_int_statuses_falls_back_to_db_when_has_graphs_absent(): void { + mactrack_test_queue_return('db_fetch_cell_prepared', 0); + + $active = mactrack_int_statuses([ + 'ifOperStatus' => 1, + 'ifAlias' => 'uplink', + 'int_errors_present' => 0, + 'int_discards_present' => 0, + 'host_id' => 10, + 'ifIndex' => 5, + ]); + + $this->assertArrayHasKey('int_no_graph', $active); + } + + /** + * @return void + */ + public function test_interface_pills_render_every_class_and_mark_active(): void { + $html = mactrack_interface_status_pills($this->stat([])); + + $this->assertStringContainsString('class="mactrackPills"', $html); + $this->assertStringContainsString('data-status="int_up"', $html); + $this->assertStringContainsString('mactrackPillActive', $html); + + foreach (array_keys(mactrack_interface_status_legend()) as $class) { + $this->assertStringContainsString($class, $html); + } + } + + /** + * @return void + */ + public function test_dot1x_pill_uses_row_class_and_status_hook(): void { + $html = mactrack_dot1x_status_pill(['status' => 2]); + + $this->assertStringContainsString('mactrackPill', $html); + $this->assertStringContainsString('data-dot1x-status="2"', $html); + $this->assertStringContainsString('dot1x_running', $html); + } + + /** + * @return void + */ + public function test_dot1x_pill_unknown_status_uses_default_class(): void { + $html = mactrack_dot1x_status_pill(['status' => 99]); + + $this->assertStringContainsString('dot1x_authn_success', $html); + } + + /** + * @return void + */ + public function test_format_dot1x_row_emits_single_status_pill_cell(): void { + mactrack_test_queue_return('db_fetch_row_prepared', ['host_id' => 0, 'disabled' => '']); + set_request_var('scan_date', '1'); + + $html = mactrack_format_dot1x_row([ + 'scan_date' => '2026-01-01 00:00:00', + 'max_scan_date' => '2026-01-01 00:00:00', + 'device_id' => 10, + 'port_number' => '1', + 'device_name' => 'switch-a', + 'hostname' => 'switch-a.example.net', + 'username' => 'jdoe', + 'status' => 2, + 'ip_address' => '10.0.0.5', + 'mac_address' => '001122334455', + 'ifName' => 'Gi1/0/1', + 'domain' => 2, + ]); + + $this->assertStringContainsString('data-dot1x-status="2"', $html); + $this->assertSame(1, substr_count($html, 'data-dot1x-status=')); + } +} From 4df8eed7caf28df45bab05e8d7e782b6fa48dd8c Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 20:00:55 -0400 Subject: [PATCH 07/10] tests: restore patch coverage for status-pill helpers and row formatters Add form_selectable_cell/cacti_version_compare stubs and a CACTI_VERSION define to the unit bootstrap (instead of at test-file scope), and cover mactrack_parse_status_tokens and mactrack_format_interface_row so every changed lib line is exercised. --- tests/Unit/MactrackStatusPillsTest.php | 63 ++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/tests/Unit/MactrackStatusPillsTest.php b/tests/Unit/MactrackStatusPillsTest.php index 05eef20a..a0f6e0d1 100644 --- a/tests/Unit/MactrackStatusPillsTest.php +++ b/tests/Unit/MactrackStatusPillsTest.php @@ -13,16 +13,6 @@ * Status filter rely on so a future legend or status-map change surfaces here. */ -if (!function_exists('cacti_version_compare')) { - function cacti_version_compare($v1, $v2, $op) { - return version_compare($v1, $v2, $op); - } -} - -if (!defined('CACTI_VERSION')) { - define('CACTI_VERSION', '1.2.32'); -} - final class MactrackStatusPillsTest extends TestCase { /** * @return void @@ -217,4 +207,57 @@ public function test_format_dot1x_row_emits_single_status_pill_cell(): void { $this->assertStringContainsString('data-dot1x-status="2"', $html); $this->assertSame(1, substr_count($html, 'data-dot1x-status=')); } + + /** + * @return void + */ + public function test_parse_status_tokens_filters_and_dedupes(): void { + $this->assertSame([], mactrack_parse_status_tokens('')); + $this->assertSame([], mactrack_parse_status_tokens(null)); + $this->assertSame( + ['int_up', 'int_down'], + mactrack_parse_status_tokens('int_up,int_up,bogus,int_down') + ); + } + + /** + * @return void + */ + public function test_format_interface_row_inserts_issue_pills_after_description(): void { + mactrack_test_queue_return('db_fetch_row_prepared', ['host_id' => 0, 'disabled' => '']); + + $html = mactrack_format_interface_row([ + 'device_id' => 10, + 'ifIndex' => 2, + 'device_name' => 'switch-a', + 'device_type' => 'cisco', + 'ifName' => 'Gi1/0/1', + 'ifDescr' => 'GigabitEthernet1/0/1', + 'ifAlias' => 'uplink', + 'inBound' => 5, + 'outBound' => 7, + 'int_ifHCInOctets' => 0, + 'int_ifHCOutOctets' => 0, + 'int_ifInErrors' => 0, + 'int_ifInDiscards' => 0, + 'int_ifInUnknownProtos' => 0, + 'int_ifOutErrors' => 0, + 'int_ifOutDiscards' => 0, + 'ifOperStatus' => 1, + 'ifLastChange' => 0, + 'sysUptime' => 100, + 'last_rundate' => '2026-01-01 00:00:00', + 'int_errors_present' => 0, + 'int_discards_present' => 0, + 'has_graphs' => 1, + ]); + + $pos_descr = strpos($html, 'GigabitEthernet1/0/1'); + $pos_pill = strpos($html, 'mactrackPills'); + $pos_alias = strpos($html, 'uplink'); + + $this->assertNotFalse($pos_pill); + $this->assertGreaterThan($pos_descr, $pos_pill); + $this->assertGreaterThan($pos_pill, $pos_alias); + } } From b7319f9fe060fd771068bd3179018741a878e832 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 20:01:44 -0400 Subject: [PATCH 08/10] tests: add form_selectable_cell/cacti_version_compare/CACTI_VERSION stubs to unit bootstrap Required by MactrackStatusPillsTest after moving these declarations out of test-file scope; without them the status-pill tests fatal on undefined symbols. --- tests/bootstrap-unit.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index f2ac10c9..cad4377a 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -453,6 +453,29 @@ function sql_save($array, $table, $key = 'id') { define('MESSAGE_LEVEL_ERROR', 1); } +/* + * MacTrack's list views render cells through Cacti's form_selectable_cell() + * and gate the status-pill detail on CACTI_VERSION via cacti_version_compare(). + * Neither Cacti itself nor its constants are loaded in the isolated unit + * process, so stub them here. CACTI_VERSION is pinned to a release that + * supports the pill detail so mactrack_interfaces_show_issues() resolves true. + */ +if (!function_exists('form_selectable_cell')) { + function form_selectable_cell($contents, $id = '', $width = '', $align = '', $title = '') { + print '' . $contents . ''; + } +} + +if (!function_exists('cacti_version_compare')) { + function cacti_version_compare($version1, $version2, $operator = '>=') { + return version_compare((string) $version1, (string) $version2, $operator); + } +} + +if (!defined('CACTI_VERSION')) { + define('CACTI_VERSION', '1.2.32'); +} + if (!function_exists('plugin_test_read_source')) { function plugin_test_read_source($relative_file) { $path = realpath(__DIR__ . '/../' . $relative_file); From 8baffea9e086fbeaf8e5d8ff8dd4e5e807d74f13 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 20:10:37 -0400 Subject: [PATCH 09/10] mactrack: fix status-pill label contrast (outline/fill instead of opacity) and add full GPL header to unit tests --- css/classic.css | 4 ++-- css/dark.css | 4 ++-- css/deepness.css | 4 ++-- css/mactrack.css | 4 ++-- css/modern.css | 4 ++-- css/paper-plane.css | 4 ++-- css/paw.css | 4 ++-- css/sunrise.css | 4 ++-- tests/Unit/MactrackStatusPillsTest.php | 17 +++++++++++++++++ tests/Unit/MactrackStatusTokensTest.php | 17 +++++++++++++++++ 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/css/classic.css b/css/classic.css index 635b23c0..a92e69c8 100644 --- a/css/classic.css +++ b/css/classic.css @@ -201,11 +201,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/dark.css b/css/dark.css index 61fc50f6..e91bebb1 100644 --- a/css/dark.css +++ b/css/dark.css @@ -201,11 +201,11 @@ color: var(--pill, #cccccc); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.6; } .mactrackPill:hover { - opacity: 0.9; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/deepness.css b/css/deepness.css index d34e2d8c..941dd97f 100644 --- a/css/deepness.css +++ b/css/deepness.css @@ -201,11 +201,11 @@ color: var(--pill, #cccccc); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.6; } .mactrackPill:hover { - opacity: 0.9; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/mactrack.css b/css/mactrack.css index bbda3f79..c73fc26b 100644 --- a/css/mactrack.css +++ b/css/mactrack.css @@ -129,11 +129,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/modern.css b/css/modern.css index d5459a68..0e291257 100644 --- a/css/modern.css +++ b/css/modern.css @@ -208,11 +208,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/paper-plane.css b/css/paper-plane.css index 88d3843e..b3f03145 100644 --- a/css/paper-plane.css +++ b/css/paper-plane.css @@ -201,11 +201,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/paw.css b/css/paw.css index 88d3843e..b3f03145 100644 --- a/css/paw.css +++ b/css/paw.css @@ -201,11 +201,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/css/sunrise.css b/css/sunrise.css index 14e47da0..afe38557 100644 --- a/css/sunrise.css +++ b/css/sunrise.css @@ -201,11 +201,11 @@ color: var(--pill, #666666); background-color: transparent; border: 1px solid var(--pill, #b5b5b5); - opacity: 0.5; } .mactrackPill:hover { - opacity: 0.85; + background-color: var(--pill, #666666); + color: #ffffff; } .mactrackPillActive { diff --git a/tests/Unit/MactrackStatusPillsTest.php b/tests/Unit/MactrackStatusPillsTest.php index a0f6e0d1..a8c3f1ba 100644 --- a/tests/Unit/MactrackStatusPillsTest.php +++ b/tests/Unit/MactrackStatusPillsTest.php @@ -2,6 +2,23 @@ /* +-------------------------------------------------------------------------+ | Copyright (C) 2004-2026 The Cacti Group | + | | + | This program is free software; you can redistribute it and/or | + | modify it under the terms of the GNU General Public License | + | as published by the Free Software Foundation; either version 2 | + | of the License, or (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDTool-based Graphing Solution | + +-------------------------------------------------------------------------+ + | This code is designed, written, and maintained by the Cacti Group. See | + | about.php and/or the AUTHORS file for specific developer information. | + +-------------------------------------------------------------------------+ + | http://www.cacti.net/ | +-------------------------------------------------------------------------+ */ diff --git a/tests/Unit/MactrackStatusTokensTest.php b/tests/Unit/MactrackStatusTokensTest.php index 5c7a9dd4..40e66410 100644 --- a/tests/Unit/MactrackStatusTokensTest.php +++ b/tests/Unit/MactrackStatusTokensTest.php @@ -2,6 +2,23 @@ /* +-------------------------------------------------------------------------+ | Copyright (C) 2004-2026 The Cacti Group | + | | + | This program is free software; you can redistribute it and/or | + | modify it under the terms of the GNU General Public License | + | as published by the Free Software Foundation; either version 2 | + | of the License, or (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDTool-based Graphing Solution | + +-------------------------------------------------------------------------+ + | This code is designed, written, and maintained by the Cacti Group. See | + | about.php and/or the AUTHORS file for specific developer information. | + +-------------------------------------------------------------------------+ + | http://www.cacti.net/ | +-------------------------------------------------------------------------+ */ From 4dbf525132b1a95eb0ff75d379e3dcbe20b3c71c Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 8 Oct 2026 20:13:50 -0400 Subject: [PATCH 10/10] Address PR review: pill contrast/a11y, dot1x Status sorting, test headers - Keep pill labels at full-opacity theme foreground (color: inherit) and convey state via the border/fill only, so inactive pills meet contrast on light and dark themes; add button resets and a :focus-visible outline across all eight theme stylesheets. - Restore the dot1x Status column sort key (status/ASC) that was lost when the pill column was renamed. - Full GPL v2 headers on the new unit tests; drop the duplicate parse-token test now covered by MactrackStatusTokensTest. --- css/classic.css | 12 ++++++++++-- css/dark.css | 12 ++++++++++-- css/deepness.css | 12 ++++++++++-- css/mactrack.css | 12 ++++++++++-- css/modern.css | 12 ++++++++++-- css/paper-plane.css | 12 ++++++++++-- css/paw.css | 12 ++++++++++-- css/sunrise.css | 12 ++++++++++-- mactrack_view_dot1x.php | 4 ++-- tests/Unit/MactrackStatusPillsTest.php | 12 ------------ 10 files changed, 82 insertions(+), 30 deletions(-) diff --git a/css/classic.css b/css/classic.css index a92e69c8..4a6f0989 100644 --- a/css/classic.css +++ b/css/classic.css @@ -190,15 +190,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/dark.css b/css/dark.css index e91bebb1..c774897e 100644 --- a/css/dark.css +++ b/css/dark.css @@ -190,15 +190,19 @@ } .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: var(--pill, #cccccc); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/deepness.css b/css/deepness.css index 941dd97f..1920e902 100644 --- a/css/deepness.css +++ b/css/deepness.css @@ -190,15 +190,19 @@ } .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: var(--pill, #cccccc); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #197a54; } diff --git a/css/mactrack.css b/css/mactrack.css index c73fc26b..d6c7c75a 100644 --- a/css/mactrack.css +++ b/css/mactrack.css @@ -118,15 +118,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -136,11 +140,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/modern.css b/css/modern.css index 0e291257..c1ee8748 100644 --- a/css/modern.css +++ b/css/modern.css @@ -197,15 +197,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -215,11 +219,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/paper-plane.css b/css/paper-plane.css index b3f03145..d86db3a2 100644 --- a/css/paper-plane.css +++ b/css/paper-plane.css @@ -190,15 +190,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/paw.css b/css/paw.css index b3f03145..d86db3a2 100644 --- a/css/paw.css +++ b/css/paw.css @@ -190,15 +190,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/css/sunrise.css b/css/sunrise.css index afe38557..5eb89a5d 100644 --- a/css/sunrise.css +++ b/css/sunrise.css @@ -190,15 +190,19 @@ } .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: var(--pill, #666666); + color: inherit; background-color: transparent; border: 1px solid var(--pill, #b5b5b5); } @@ -208,11 +212,15 @@ 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); - opacity: 1; } .mactrackPill.int_up { --pill: #2b8435; } diff --git a/mactrack_view_dot1x.php b/mactrack_view_dot1x.php index eb217f93..a2a6c34d 100644 --- a/mactrack_view_dot1x.php +++ b/mactrack_view_dot1x.php @@ -469,7 +469,7 @@ function mactrack_view_dot1x(): void { 'device_name' => [__('Switch Name', 'mactrack'), 'ASC'], 'hostname' => [__('Switch Hostname', 'mactrack'), 'ASC'], 'username' => [__('Username', 'mactrack'), 'ASC'], - 'status_nosort' => [__('Status', 'mactrack'), ''], + 'status' => [__('Status', 'mactrack'), 'ASC'], 'ip_address' => [__('ED IP Address', 'mactrack'), 'ASC'], 'dns_hostname' => [__('ED DNS Hostname', 'mactrack'), 'ASC'], 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], @@ -483,7 +483,7 @@ function mactrack_view_dot1x(): void { 'device_name' => [__('Switch Name', 'mactrack'), 'ASC'], 'hostname' => [__('Switch Hostname', 'mactrack'), 'ASC'], 'username' => [__('Username', 'mactrack'), 'ASC'], - 'status_nosort' => [__('Status', 'mactrack'), ''], + 'status' => [__('Status', 'mactrack'), 'ASC'], 'ip_address' => [__('ED IP Address', 'mactrack'), 'ASC'], 'mac_address' => [__('ED MAC Address', 'mactrack'), 'ASC'], 'ifName' => [__('Port Name', 'mactrack'), 'ASC'], diff --git a/tests/Unit/MactrackStatusPillsTest.php b/tests/Unit/MactrackStatusPillsTest.php index a8c3f1ba..ab29666b 100644 --- a/tests/Unit/MactrackStatusPillsTest.php +++ b/tests/Unit/MactrackStatusPillsTest.php @@ -225,18 +225,6 @@ public function test_format_dot1x_row_emits_single_status_pill_cell(): void { $this->assertSame(1, substr_count($html, 'data-dot1x-status=')); } - /** - * @return void - */ - public function test_parse_status_tokens_filters_and_dedupes(): void { - $this->assertSame([], mactrack_parse_status_tokens('')); - $this->assertSame([], mactrack_parse_status_tokens(null)); - $this->assertSame( - ['int_up', 'int_down'], - mactrack_parse_status_tokens('int_up,int_up,bogus,int_down') - ); - } - /** * @return void */