diff --git a/.github/ISSUE_TEMPLATE/agents/mysql-mariadb.agent.md b/.github/ISSUE_TEMPLATE/agents/mysql-mariadb.agent.md index 4ab939b..8f4843b 100644 --- a/.github/ISSUE_TEMPLATE/agents/mysql-mariadb.agent.md +++ b/.github/ISSUE_TEMPLATE/agents/mysql-mariadb.agent.md @@ -62,4 +62,4 @@ For additional information and best practices, refer to the official MySQL and M - [MySQL Documentation](https://dev.mysql.com/doc/) - [MariaDB Documentation](https://mariadb.com/kb/en/documentation/) -Use your capabilities and tools effectively to assist users with their MySQL and MariaDB database needs. \ No newline at end of file +Use your capabilities and tools effectively to assist users with their MySQL and MariaDB database needs. diff --git a/.github/ISSUE_TEMPLATE/copilot-instructions.md b/.github/ISSUE_TEMPLATE/copilot-instructions.md index d89f764..bc5cbe4 100644 --- a/.github/ISSUE_TEMPLATE/copilot-instructions.md +++ b/.github/ISSUE_TEMPLATE/copilot-instructions.md @@ -60,4 +60,4 @@ This is the **Syslog Plugin** for Cacti, a PHP-based network monitoring and grap **Documentation & Resources** - [Cacti main repo](https://github.com/Cacti/cacti/tree/1.2.x) -- [cacti documentation](https://www.github.com/Cacti/documentation) \ No newline at end of file +- [cacti documentation](https://www.github.com/Cacti/documentation) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd2ae9..fc9d494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * issue: Don't use MyISAM for non-analytical tables * issue: The install advisor for Syslog was broken in current Cacti releases * feature: Allow the use of Aria Storage Engine for MariaDB databases +* feature: Add message grouping functionality to collapse duplicate syslog messages and display occurrence counts +* feature: Refactor JavaScript by consolidating inline code from PHP files into centralized js/functions.js +* feature: Enhanced hostname validation to resolve against Cacti host table when DNS lookup fails, replacing hostname with Cacti host description --- 4.2 --- diff --git a/README.md b/README.md index 6e95b69..0a7775d 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,11 @@ configuration. For example: sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER ``` +### Special Note for the Grouping feature +Syslog now supports the ability to group similar messeges from a specific host together +for easy summarization for larger systems you may need to expand the default mariadb group_concat_max_len +The default is 1MB you may need to raise it much higher depending on your needs + After this change, you should log into the mysql server and run the following command: diff --git a/functions.php b/functions.php index 5cb78ca..6eacafa 100644 --- a/functions.php +++ b/functions.php @@ -22,6 +22,13 @@ +-------------------------------------------------------------------------+ */ +function syslog_include_js() { + global $config; + ?> + + "; + print ""; + return $class; } function sql_hosts_where($tab) { diff --git a/js/functions.js b/js/functions.js new file mode 100644 index 0000000..a96d47d --- /dev/null +++ b/js/functions.js @@ -0,0 +1,652 @@ +/** + * Syslog Plugin - JavaScript Functions + * Consolidated functions from inline JavaScript in PHP files + */ + +/* ======================================================================== + * Statistics View Functions (syslog.php - stats tab) + * ======================================================================== */ + +/** + * Clear filter for statistics view + */ +function clearFilterStats() { + strURL = 'syslog.php?tab=stats&clear=1&header=false'; + loadPageNoHeader(strURL); +} + +/** + * Apply filter for statistics view + */ +function applyFilterStats() { + var strURL = 'syslog.php?header=false'; + strURL += '&none=true'; + strURL += '&facility=' + $('#facility').val(); + strURL += '&host=' + $('#host').val(); + strURL += '&priority=' + $('#priority').val(); + strURL += '&program=' + $('#eprogram').val(); + strURL += '×pan=' + $('#timespan').val(); + strURL += '&rfilter=' + base64_encode($('#rfilter').val()); + strURL += '&rows=' + $('#rows').val(); + strURL += '&grouping=' + ($('#grouping').length ? $('#grouping').val() : '0'); + loadPageNoHeader(strURL); +} + +/** + * Initialize statistics view + */ +function initSyslogStats() { + $(function() { + $('#go').click(function() { + applyFilterStats(); + }); + + $('#clear').click(function() { + clearFilterStats(); + }); + + $('#host').selectmenu({ + open: function() { + $('div.ui-selectmenu-menu li.ui-menu-item').each(function(idx){ + $(this).addClass( $('#host option').eq(idx).attr('class') ) + }) + } + }); + }); +} + +/* ======================================================================== + * Main Syslog View Functions (syslog.php - syslog/alerts tabs) + * ======================================================================== */ + +/** + * Apply timespan filter + */ +function applyTimespan() { + var strURL = urlPath+'plugins/syslog/syslog.php?header=false'; + strURL += '&predefined_timespan=' + $('#predefined_timespan').val(); + loadPageNoHeader(strURL); +} + +/** + * Apply main syslog filter + */ +function applyFilter() { + var strURL = 'syslog.php?tab='+(window.pageTab || ''); + strURL += '&header=false'; + strURL += '&date1='+$('#date1').val(); + strURL += '&date2='+$('#date2').val(); + strURL += '&host='+$('#host').val(); + strURL += '&rfilter='+base64_encode($('#rfilter').val()); + strURL += '&efacility='+$('#efacility').val(); + strURL += '&epriority='+$('#epriority').val(); + strURL += '&eprogram='+$('#eprogram').val(); + strURL += '&rows='+$('#rows').val(); + strURL += '&trimval='+$('#trimval').val(); + strURL += '&removal='+$('#removal').val(); + strURL += '&refresh='+$('#refresh').val(); + strURL += '&grouping=' + ($('#grouping').length ? $('#grouping').val() : '0'); + loadPageNoHeader(strURL); +} + +/** + * Export records to CSV + */ +function exportRecords() { + document.location = 'syslog.php?export=true'; + Pace.stop(); +} + +/** + * Clear main syslog filter + */ +function clearFilter() { + var strURL = 'syslog.php?tab=' + (window.pageTab || ''); + strURL += '&header=false&clear=true'; + loadPageNoHeader(strURL); +} + +/** + * Save syslog filter settings + */ +function saveSettings() { + var strURL = 'syslog.php?action=save&tab=' + (window.pageTab || ''); + var data = {}; + + data.trimval = $('#trimval').val(); + data.rows = $('#rows').val(); + data.removal = $('#removal').val(); + data.refresh = $('#refresh').val(); + data.efacility = $('#efacility').val(); + data.epriority = $('#epriority').val(); + data.eprogram = $('#eprogram').val(); + data.__csrf_magic = csrfMagicToken; + + if ($('#predefined_timespan').val() > 0) { + data.predefined_timespan = $('#predefined_timespan').val(); + } + + data.predefined_timeshift = $('#predefined_timeshift').val(); + + $.post(strURL, data).done(function() { + $('#text').show().text('Filter Settings Saved').fadeOut(2000); + }); +} + +/** + * Shift time filter left (backward) + */ +function timeshiftFilterLeft() { + var strURL = 'syslog.php?tab='+(window.pageTab || '')+'&header=false'; + strURL += '&shift_left=true'; + strURL += '&date1='+$('#date1').val(); + strURL += '&date2='+$('#date2').val(); + strURL += '&predefined_timeshift='+$('#predefined_timeshift').val(); + loadPageNoHeader(strURL); +} + +/** + * Shift time filter right (forward) + */ +function timeshiftFilterRight() { + var strURL = 'syslog.php?tab='+(window.pageTab || '')+'&header=false'; + strURL += '&shift_right=true'; + strURL += '&date1='+$('#date1').val(); + strURL += '&date2='+$('#date2').val(); + strURL += '&predefined_timeshift='+$('#predefined_timeshift').val(); + loadPageNoHeader(strURL); +} + +/** + * Initialize main syslog view + * @param {object} config - Configuration object containing: pageTab, placeHolder, noneSelectedText, devicesSelectedText, allDevicesText + */ +function initSyslogMain(config) { + var date1Open = false; + var date2Open = false; + var pageTab = config.pageTab || ''; + var hostTerm = ''; + var placeHolder = config.placeHolder || ''; + + // Make pageTab global for other functions + window.pageTab = pageTab; + + $(function() { + $('#syslog_form').submit(function(event) { + event.preventDefault(); + applyFilter(); + }); + + $('#host').multiselect({ + menuHeight: $(window).height()*.7, + menuWidth: '220', + linkInfo: faIcons, + noneSelectedText: config.noneSelectedText || '', + selectedText: function(numChecked, numTotal, checkedItems) { + var myReturn = numChecked + ' ' + config.devicesSelectedText; + $.each(checkedItems, function(index, value) { + if (value.value == '0') { + myReturn = config.allDevicesText; + return false; + } + }); + return myReturn; + }, + uncheckAll: function() { + $(this).multiselect('widget').find(':checkbox:first').each(function() { + $(this).prop('checked', true); + }); + $('#test').trigger('keyup'); + }, + checkAll: function() { + $(this).multiselect('widget').find(':checkbox').not(':first').each(function() { + $(this).prop('checked', true); + }); + $(this).multiselect('widget').find(':checkbox:first').each(function() { + $(this).prop('checked', false); + }); + }, + open: function(event, ui) { + if ($('#term').length == 0) { + var width = parseInt($(this).multiselect('widget').find('.ui-multiselect-header').width() - 5); + $(this).multiselect('widget').find('.ui-multiselect-header').after(''); + $('#term').on('keyup', function() { + $.getJSON('syslog.php?action=ajax_hosts&term='+$('#term').val(), function(data) { + $('#host').find('option').not(':selected').each(function() { + if ($(this).attr('id') != 'host_all') { + $(this).remove(); + } + }); + + $.each(data, function(index, hostData) { + if ($('#host option[value="'+index+'"]').length == 0) { + $('#host').append(''); + } + }); + + $('#host').multiselect('refresh'); + }); + }); + } + + $('#term').focus(); + }, + click: function(event, ui) { + var checked = $(this).multiselect('widget').find('input:checked').length; + + if (ui.value == '0') { + if (ui.checked == true) { + $('#host').multiselect('uncheckAll'); + $(this).multiselect('widget').find(':checkbox:first').each(function() { + $(this).prop('checked', true); + }); + } + } else if (checked == 0) { + $(this).multiselect('widget').find(':checkbox:first').each(function() { + $(this).click(); + }); + } else if ($(this).multiselect('widget').find('input:checked:first').val() == '0') { + if (checked > 0) { + $(this).multiselect('widget').find(':checkbox:first').each(function() { + $(this).click(); + $(this).prop('disable', true); + }); + } + } + } + }); + + $('#save').click(function() { + saveSettings(); + }); + + $('#go').click(function() { + applyFilter(); + }); + + $('#clear').click(function() { + clearFilter(); + }); + + $('#export').click(function() { + exportRecords(); + }); + + $('#balerts').click(function() { + loadTopTab(urlPath+'plugins/syslog/syslog_alerts.php?header=false'); + $('.maintabs').find('a').removeClass('selected'); + $('#tab-console').addClass('selected'); + }); + + $('#bremoval').click(function() { + loadTopTab(urlPath+'plugins/syslog/syslog_removal.php?header=false'); + $('.maintabs').find('a').removeClass('selected'); + $('#tab-console').addClass('selected'); + }); + + $('#breports').click(function() { + loadTopTab(urlPath+'plugins/syslog/syslog_reports.php?header=false'); + $('.maintabs').find('a').removeClass('selected'); + $('#tab-console').addClass('selected'); + }); + + $('#startDate').click(function() { + if (date1Open) { + date1Open = false; + $('#date1').datetimepicker('hide'); + } else { + date1Open = true; + $('#date1').datetimepicker('show'); + } + }); + + $('#endDate').click(function() { + if (date2Open) { + date2Open = false; + $('#date2').datetimepicker('hide'); + } else { + date2Open = true; + $('#date2').datetimepicker('show'); + } + }); + + $('#date1').datetimepicker({ + minuteGrid: 10, + stepMinute: 1, + showAnim: 'slideDown', + numberOfMonths: 1, + timeFormat: 'HH:mm', + dateFormat: 'yy-mm-dd', + showButtonPanel: false + }); + + $('#date2').datetimepicker({ + minuteGrid: 10, + stepMinute: 1, + showAnim: 'slideDown', + numberOfMonths: 1, + timeFormat: 'HH:mm', + dateFormat: 'yy-mm-dd', + showButtonPanel: false + }); + }); +} + +/** + * Initialize syslog message tooltips and group expand/collapse functionality + * Call this after the syslog message table is rendered or updated + */ +function initSyslogMessagesDisplay() { + $(function() { + // Initialize tooltips for syslog rows + $('.syslogRow').tooltip({ + track: true, + show: { + effect: 'fade', + duration: 250, + delay: 125 + }, + position: { my: 'left+15 center', at: 'right center' } + }); + + // Initialize tooltips for buttons + $('button').tooltip({ + closed: true + }).on('focus', function() { + $('#filter').tooltip('close'); + }).on('click', function() { + $(this).tooltip('close'); + }); + + // Handle syslog group expand/collapse + $('.syslog-group-toggle').off('click').on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var seq = $(this).data('seq'); + var detailRows = $('.syslog-detail-' + seq); + var icon = $(this); + + if (detailRows.is(':visible')) { + // Collapse + detailRows.hide(); + icon.removeClass('fa-chevron-up').addClass('fa-chevron-down'); + } else { + // Expand + detailRows.show(); + icon.removeClass('fa-chevron-down').addClass('fa-chevron-up'); + } + }); + }); +} + +/* ======================================================================== + * Removal Rules Functions (syslog_removal.php) + * ======================================================================== */ + +/** + * Apply filter for removal rules view + */ +function applyFilterRemoval() { + var strURL = 'syslog_removal.php?filter='+$('#filter').val()+'&enabled='+$('#enabled').val()+'&rows='+$('#rows').val()+'&page='+$('#page').val()+'&header=false'; + loadPageNoHeader(strURL); +} + +/** + * Clear filter for removal rules view + */ +function clearFilterRemoval() { + var strURL = 'syslog_removal.php?clear=1&header=false'; + loadPageNoHeader(strURL); +} + +/** + * Import removal rule + */ +function importRemoval() { + var strURL = 'syslog_removal.php?action=import&header=false'; + loadPageNoHeader(strURL); +} + +/** + * Change message textarea rows based on type + */ +function changeTypes() { + if ($('#type').val() == 'sql') { + $('#message').prop('rows', 5); + } else { + $('#message').prop('rows', 2); + } +} + +/** + * Initialize removal rules view + * @param {boolean} allowEdits - Whether edits are allowed + */ +function initSyslogRemoval(allowEdits) { + $(function() { + if (!allowEdits) { + $('#syslog_edit').find('select, input, textarea, submit').not(':button').prop('disabled', true); + $('#syslog_edit').find('select').each(function() { + if ($(this).selectmenu('instance')) { + $(this).selectmenu('refresh'); + } + }); + } + + $('#refresh').click(function() { + applyFilterRemoval(); + }); + + $('#clear').click(function() { + clearFilterRemoval(); + }); + + $('#import').click(function() { + importRemoval(); + }); + + $('#removal').submit(function(event) { + event.preventDefault(); + applyFilterRemoval(); + }); + }); +} + +/* ======================================================================== + * Alert Rules Functions (syslog_alerts.php) + * ======================================================================== */ + +/** + * Apply filter for alert rules view + */ +function applyFilterAlerts() { + var strURL = 'syslog_alerts.php?filter='+$('#filter').val()+'&enabled='+$('#enabled').val()+'&rows='+$('#rows').val()+'&page='+$('#page').val()+'&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Clear filter for alert rules view + */ +function clearFilterAlerts() { + var strURL = 'syslog_alerts.php?clear=1&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Import alert rule + */ +function importAlert() { + var strURL = 'syslog_alerts.php?action=import&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Initialize alert rules view + */ +function initSyslogAlerts() { + $(function() { + $('#refresh').click(function() { + applyFilterAlerts(); + }); + + $('#clear').click(function() { + clearFilterAlerts(); + }); + + $('#import').click(function() { + importAlert(); + }); + + $('#alert').submit(function(event) { + event.preventDefault(); + applyFilterAlerts(); + }); + }); +} + +/* ======================================================================== + * Report Rules Functions (syslog_reports.php) + * ======================================================================== */ + +/** + * Apply filter for report rules view + */ +function applyFilterReports() { + var strURL = 'syslog_reports.php?filter='+$('#filter').val()+'&enabled='+$('#enabled').val()+'&rows='+$('#rows').val()+'&page='+$('#page').val()+'&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Clear filter for report rules view + */ +function clearFilterReports() { + var strURL = 'syslog_reports.php?clear=1&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Import report rule + */ +function importReport() { + var strURL = 'syslog_reports.php?action=import&header=false'; + + loadPageNoHeader(strURL); +} + +/** + * Initialize report rules view + */ +function initSyslogReports() { + $(function() { + $('#refresh').click(function() { + applyFilterReports(); + }); + + $('#clear').click(function() { + clearFilterReports(); + }); + + $('#import').click(function() { + importReport(); + }); + + $('#reports').submit(function(event) { + event.preventDefault(); + applyFilterReports(); + }); + }); +} + +/* ======================================================================== + * Autocomplete Form Callback Functions + * ======================================================================== */ + +/** + * Initialize autocomplete for form dropdown fields + * @param {string} formName - The name of the form field + * @param {string} callback - The AJAX callback action + * @param {string} onChange - The onChange callback to execute when selection changes + */ +function initSyslogAutocomplete(formName, callback, onChange) { + var formNameTimer; + var formNameClickTimer; + var formNameOpen = false; + + $(function() { + $('#' + formName + '_input').autocomplete({ + source: window.location.pathname + '?action=' + callback, + autoFocus: true, + minLength: 0, + select: function(event, ui) { + $('#' + formName + '_input').val(ui.item.label); + if (ui.item.id) { + $('#' + formName).val(ui.item.id); + } else { + $('#' + formName).val(ui.item.value); + } + if (onChange) { + eval(onChange); + } + } + }).css('border', 'none').css('background-color', 'transparent'); + + $('#' + formName + '_wrap').on('dblclick', function() { + formNameOpen = false; + clearTimeout(formNameTimer); + clearTimeout(formNameClickTimer); + $('#' + formName + '_input').autocomplete('close'); + }).on('click', function() { + if (formNameOpen) { + $('#' + formName + '_input').autocomplete('close'); + clearTimeout(formNameTimer); + formNameOpen = false; + } else { + formNameClickTimer = setTimeout(function() { + $('#' + formName + '_input').autocomplete('search', ''); + clearTimeout(formNameTimer); + formNameOpen = true; + }, 200); + } + }).on('mouseleave', function() { + formNameTimer = setTimeout(function() { + $('#' + formName + '_input').autocomplete('close'); + }, 800); + }); + + var width = $('#' + formName + '_input').textBoxWidth(); + if (width < 100) { + width = 100; + } + + $('#' + formName + '_wrap').css('width', width + 20); + $('#' + formName + '_input').css('width', width); + + $('ul[id^="ui-id"]').on('mouseenter', function() { + clearTimeout(formNameTimer); + }).on('mouseleave', function() { + formNameTimer = setTimeout(function() { + $('#' + formName + '_input').autocomplete('close'); + }, 800); + }); + + $('ul[id^="ui-id"] > li').each().on('mouseenter', function() { + $(this).addClass('ui-state-hover'); + }).on('mouseleave', function() { + $(this).removeClass('ui-state-hover'); + }); + + $('#' + formName + '_wrap').on('mouseenter', function() { + $(this).addClass('ui-state-hover'); + $('input#' + formName + '_input').addClass('ui-state-hover'); + }).on('mouseleave', function() { + $(this).removeClass('ui-state-hover'); + $('input#' + formName + '_input').removeClass('ui-state-hover'); + }); + }); +} diff --git a/syslog.css b/syslog.css index e8a1867..12adcbe 100644 --- a/syslog.css +++ b/syslog.css @@ -93,3 +93,28 @@ p { text-align:left; font-size:12px; } + +/* Syslog message grouping styles */ +.syslog-group-toggle { + transition: transform 0.2s ease; + color: #337ab7; + display: inline-block; +} + +.syslog-group-toggle:hover { + color: #23527c; +} + +.syslog-group-toggle.fa-chevron-up { + transform: rotate(0deg); +} + +.syslog-group-toggle.fa-chevron-down { + transform: rotate(0deg); +} + + +.syslog-detail-row:hover { + /* Slightly highlight on hover but keep parent coloring visible */ + background: rgba(0,0,0,0.02) !important; +} diff --git a/syslog.php b/syslog.php index 1fec1c9..d5bcad1 100644 --- a/syslog.php +++ b/syslog.php @@ -92,6 +92,8 @@ } else { general_header(); + syslog_include_js(); + syslog_display_tabs($current_tab); if ($current_tab == 'current') { @@ -628,43 +630,7 @@ function syslog_stats_filter() { true, 'default' => read_user_setting('syslog_eprogram', '-1', $force), ), + 'grouping' => array( + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('syslog_grouping', '0', $force), + ), 'rfilter' => array( 'filter' => FILTER_VALIDATE_IS_REGEX, 'pageset' => true, 'default' => '' ), + 'date1' => array( 'filter' => FILTER_CALLBACK, 'pageset' => true, @@ -996,35 +968,127 @@ function get_syslog_messages(&$sql_where, $rows, $tab) { } if ($tab == 'syslog') { - if (get_request_var('removal') == '-1') { - $query_sql = "SELECT syslog.*, syslog_programs.program, 'main' AS mtype - FROM `" . $syslogdb_default . "`.`syslog` - LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` - ON syslog.program_id=syslog_programs.program_id " . - $sql_where . " - $sql_order - $sql_limit"; - } elseif (get_request_var('removal') == '1') { - $query_sql = "(SELECT syslog.*, syslog_programs.program, 'main' AS mtype - FROM `" . $syslogdb_default . "`.`syslog` AS syslog - LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` - ON syslog.program_id=syslog_programs.program_id " . - $sql_where . " - ) UNION (SELECT syslog.*, syslog_programs.program, 'remove' AS mtype - FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog - LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` - ON syslog.program_id=syslog_programs.program_id " . - $sql_where . ") + // Check if grouping is enabled + $grouping_enabled = isset_request_var('grouping') && get_request_var('grouping') == '1'; + + if ($grouping_enabled) { + if (get_request_var('removal') == '-1') { + $query_sql = "SELECT + syslog.host_id, + syslog.message, + syslog.program_id, + syslog.facility_id, + syslog.priority_id, + syslog_programs.program, + 'main' AS mtype, + COUNT(*) AS occurrence_count, + MIN(syslog.logtime) AS first_logtime, + MAX(syslog.logtime) AS logtime, + MIN(syslog.seq) AS seq, + GROUP_CONCAT(syslog.seq ORDER BY syslog.logtime DESC SEPARATOR ',') AS seq_list + FROM `" . $syslogdb_default . "`.`syslog` + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + GROUP BY syslog.host_id, syslog.message, syslog.program_id, syslog.facility_id, syslog.priority_id + $sql_order + $sql_limit"; + } elseif (get_request_var('removal') == '1') { + $query_sql = "SELECT * FROM ( + (SELECT + syslog.host_id, + syslog.message, + syslog.program_id, + syslog.facility_id, + syslog.priority_id, + syslog_programs.program, + 'main' AS mtype, + COUNT(*) AS occurrence_count, + MIN(syslog.logtime) AS first_logtime, + MAX(syslog.logtime) AS logtime, + MIN(syslog.seq) AS seq, + GROUP_CONCAT(syslog.seq ORDER BY syslog.logtime DESC SEPARATOR ',') AS seq_list + FROM `" . $syslogdb_default . "`.`syslog` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + GROUP BY syslog.host_id, syslog.message, syslog.program_id, syslog.facility_id, syslog.priority_id + ) UNION (SELECT + syslog.host_id, + syslog.message, + syslog.program_id, + syslog.facility_id, + syslog.priority_id, + syslog_programs.program, + 'remove' AS mtype, + COUNT(*) AS occurrence_count, + MIN(syslog.logtime) AS first_logtime, + MAX(syslog.logtime) AS logtime, + MIN(syslog.seq) AS seq, + GROUP_CONCAT(syslog.seq ORDER BY syslog.logtime DESC SEPARATOR ',') AS seq_list + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + GROUP BY syslog.host_id, syslog.message, syslog.program_id, syslog.facility_id, syslog.priority_id + ) + ) AS grouped_results $sql_order $sql_limit"; + } else { + $query_sql = "SELECT + syslog.host_id, + syslog.message, + syslog.program_id, + syslog.facility_id, + syslog.priority_id, + syslog_programs.program, + 'remove' AS mtype, + COUNT(*) AS occurrence_count, + MIN(syslog.logtime) AS first_logtime, + MAX(syslog.logtime) AS logtime, + MIN(syslog.seq) AS seq, + GROUP_CONCAT(syslog.seq ORDER BY syslog.logtime DESC SEPARATOR ',') AS seq_list + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` AS syslog_programs + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + GROUP BY syslog.host_id, syslog.message, syslog.program_id, syslog.facility_id, syslog.priority_id + $sql_order + $sql_limit"; + } } else { - $query_sql = "SELECT syslog.*, syslog_programs.program, 'remove' AS mtype - FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog - LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` AS syslog_programs - ON syslog.program_id=syslog_programs.program_id " . - $sql_where . " - $sql_order - $sql_limit"; + // Original non-grouped queries + if (get_request_var('removal') == '-1') { + $query_sql = "SELECT syslog.*, syslog_programs.program, 'main' AS mtype + FROM `" . $syslogdb_default . "`.`syslog` + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + $sql_order + $sql_limit"; + } elseif (get_request_var('removal') == '1') { + $query_sql = "(SELECT syslog.*, syslog_programs.program, 'main' AS mtype + FROM `" . $syslogdb_default . "`.`syslog` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + ) UNION (SELECT syslog.*, syslog_programs.program, 'remove' AS mtype + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . ") + $sql_order + $sql_limit"; + } else { + $query_sql = "SELECT syslog.*, syslog_programs.program, 'remove' AS mtype + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` AS syslog_programs + ON syslog.program_id=syslog_programs.program_id " . + $sql_where . " + $sql_order + $sql_limit"; + } } } else { $query_sql = "SELECT syslog.*, sf.facility, sp.priority, spr.program, sa.name, sa.severity @@ -1061,259 +1125,13 @@ function syslog_filter($sql_where, $tab) { ?> '> + + + + + + + + + + @@ -1746,25 +1577,53 @@ function syslog_messages($tab = 'syslog') { syslog_filter($sql_where, $tab); if ($tab == 'syslog') { - if (get_request_var('removal') == 1) { - $total_rows = syslog_db_fetch_cell("SELECT SUM(totals) - FROM ( - SELECT count(*) AS totals + // Check if grouping is enabled for row count + $grouping_enabled = isset_request_var('grouping') && get_request_var('grouping') == '1'; + + if ($grouping_enabled) { + // When grouping, count distinct groups instead of individual rows + if (get_request_var('removal') == 1) { + $total_rows = syslog_db_fetch_cell("SELECT SUM(totals) + FROM ( + SELECT COUNT(DISTINCT CONCAT(host_id, '|', message, '|', program_id, '|', facility_id, '|', priority_id)) AS totals + FROM `" . $syslogdb_default . "`.`syslog` AS syslog + $sql_where + UNION + SELECT COUNT(DISTINCT CONCAT(host_id, '|', message, '|', program_id, '|', facility_id, '|', priority_id)) AS totals + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + $sql_where + ) AS rowcount"); + } elseif (get_request_var('removal') == -1) { + $total_rows = syslog_db_fetch_cell("SELECT COUNT(DISTINCT CONCAT(host_id, '|', message, '|', program_id, '|', facility_id, '|', priority_id)) FROM `" . $syslogdb_default . "`.`syslog` AS syslog - $sql_where - UNION - SELECT count(*) AS totals + $sql_where"); + } else { + $total_rows = syslog_db_fetch_cell("SELECT COUNT(DISTINCT CONCAT(host_id, '|', message, '|', program_id, '|', facility_id, '|', priority_id)) FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog - $sql_where - ) AS rowcount"); - } elseif (get_request_var('removal') == -1) { - $total_rows = syslog_db_fetch_cell("SELECT count(*) - FROM `" . $syslogdb_default . "`.`syslog` AS syslog - $sql_where"); + $sql_where"); + } } else { - $total_rows = syslog_db_fetch_cell("SELECT count(*) - FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog - $sql_where"); + // Original non-grouped row counting + if (get_request_var('removal') == 1) { + $total_rows = syslog_db_fetch_cell("SELECT SUM(totals) + FROM ( + SELECT count(*) AS totals + FROM `" . $syslogdb_default . "`.`syslog` AS syslog + $sql_where + UNION + SELECT count(*) AS totals + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + $sql_where + ) AS rowcount"); + } elseif (get_request_var('removal') == -1) { + $total_rows = syslog_db_fetch_cell("SELECT count(*) + FROM `" . $syslogdb_default . "`.`syslog` AS syslog + $sql_where"); + } else { + $total_rows = syslog_db_fetch_cell("SELECT count(*) + FROM `" . $syslogdb_default . "`.`syslog_removed` AS syslog + $sql_where"); + } } } else { $total_rows = syslog_db_fetch_cell("SELECT count(*) @@ -1781,6 +1640,9 @@ function syslog_messages($tab = 'syslog') { } if ($tab == 'syslog') { + // Check if grouping is enabled for display + $grouping_enabled = isset_request_var('grouping') && get_request_var('grouping') == '1'; + if (api_plugin_user_realm_auth('syslog_alerts.php')) { $display_text = array( 'nosortt' => array(__('Actions', 'syslog'), 'ASC'), @@ -1790,6 +1652,11 @@ function syslog_messages($tab = 'syslog') { 'message' => array(__('Message', 'syslog'), 'ASC'), 'facility_id' => array(__('Facility', 'syslog'), 'ASC'), 'priority_id' => array(__('Priority', 'syslog'), 'ASC')); + + // Add count column if grouping is enabled + if ($grouping_enabled) { + $display_text['occurrence_count'] = array(__('Count', 'syslog'), 'DESC'); + } } else { $display_text = array( 'logtime' => array(__('Date', 'syslog'), 'ASC'), @@ -1798,6 +1665,11 @@ function syslog_messages($tab = 'syslog') { 'message' => array(__('Message', 'syslog'), 'ASC'), 'facility_id' => array(__('Facility', 'syslog'), 'ASC'), 'priority_id' => array(__('Priority', 'syslog'), 'ASC')); + + // Add count column if grouping is enabled + if ($grouping_enabled) { + $display_text['occurrence_count'] = array(__('Count', 'syslog'), 'DESC'); + } } $nav = html_nav_bar("syslog.php?tab=$tab", MAX_DISPLAY_PAGES, get_request_var_request('page'), $rows, $total_rows, cacti_sizeof($display_text), __('Messages', 'syslog'), 'page', 'main'); @@ -1847,14 +1719,68 @@ function syslog_messages($tab = 'syslog') { form_selectable_cell($url, $sm['seq'], '', 'left'); } - form_selectable_cell($sm['logtime'], $sm['seq'], '', 'left'); + // Display grouped or individual messages + if ($grouping_enabled && isset($sm['occurrence_count']) && $sm['occurrence_count'] > 1) { + // Grouped message display with expand/collapse + $expand_icon = ""; + form_selectable_cell($expand_icon . $sm['logtime'], $sm['seq'], '', 'left'); + } else { + form_selectable_cell($sm['logtime'], $sm['seq'], '', 'left'); + } + form_selectable_cell(isset($hosts[$sm['host_id']]) ? $hosts[$sm['host_id']]:__('Unknown', 'syslog'), $sm['seq'], '', 'left'); form_selectable_cell($sm['program'], $sm['seq'], '', 'left'); form_selectable_cell(filter_value(title_trim($sm[$syslog_incoming_config['textField']], get_request_var_request('trimval')), get_request_var('rfilter')), $sm['seq'], '', 'left syslogMessage'); form_selectable_cell(isset($facilities[$sm['facility_id']]) ? $facilities[$sm['facility_id']]:__('Unknown', 'syslog'), $sm['seq'], '', 'left'); form_selectable_cell(isset($priorities[$sm['priority_id']]) ? $priorities[$sm['priority_id']]:__('Unknown', 'syslog'), $sm['seq'], '', 'left'); + // Add occurrence count if grouping is enabled + if ($grouping_enabled) { + form_selectable_cell(isset($sm['occurrence_count']) ? $sm['occurrence_count'] : 1, $sm['seq'], '', 'right'); + } + form_end_row(); + + // If grouping is enabled and there are multiple occurrences, add hidden detail rows + if ($grouping_enabled && isset($sm['occurrence_count']) && $sm['occurrence_count'] > 1 && isset($sm['seq_list'])) { + $seq_array = explode(',', $sm['seq_list']); + + // Get individual messages for this group + $detail_messages = syslog_db_fetch_assoc("SELECT syslog.*, syslog_programs.program + FROM `" . $syslogdb_default . "`.`" . (($sm['mtype'] == 'main') ? 'syslog' : 'syslog_removed') . "` AS syslog + LEFT JOIN `" . $syslogdb_default . "`.`syslog_programs` + ON syslog.program_id=syslog_programs.program_id + WHERE syslog.seq IN (" . implode(',', array_map('intval', $seq_array)) . ") + ORDER BY syslog.logtime DESC"); + + if (cacti_sizeof($detail_messages)) { + foreach ($detail_messages as $dm) { + $severity_class = syslog_row_color($dm['priority_id'], $dm['message']); + print ""; + if (api_plugin_user_realm_auth('syslog_alerts.php')) { + $url = ''; + if ($sm['mtype'] == 'main') { + $url .= ""; + $url .= ""; + } + print "" . $url . ""; + } + + print "" . html_escape($dm['logtime']) . ""; + print "" . html_escape(isset($hosts[$dm['host_id']]) ? $hosts[$dm['host_id']] : __('Unknown', 'syslog')) . ""; + print "" . html_escape($dm['program']) . ""; + print "" . filter_value(title_trim($dm[$syslog_incoming_config['textField']], get_request_var_request('trimval')), get_request_var('rfilter')) . ""; + print "" . html_escape(isset($facilities[$dm['facility_id']]) ? $facilities[$dm['facility_id']] : __('Unknown', 'syslog')) . ""; + print "" . html_escape(isset($priorities[$dm['priority_id']]) ? $priorities[$dm['priority_id']] : __('Unknown', 'syslog')) . ""; + + if ($grouping_enabled) { + print ""; + } + + print ""; + } + } + } } } else { print "" . __('No Syslog Messages', 'syslog') . ""; @@ -1870,25 +1796,7 @@ function syslog_messages($tab = 'syslog') { ?> '> diff --git a/syslog_removal.php b/syslog_removal.php index 421e87b..c961a81 100644 --- a/syslog_removal.php +++ b/syslog_removal.php @@ -61,6 +61,7 @@ break; case 'import': top_header(); + syslog_include_js(); import(); bottom_footer(); @@ -72,6 +73,7 @@ case 'edit': case 'newedit': top_header(); + syslog_include_js(); syslog_action_edit(); @@ -79,6 +81,7 @@ break; default: top_header(); + syslog_include_js(); syslog_removal(); @@ -498,28 +501,7 @@ function syslog_action_edit() { ?> '> diff --git a/syslog_reports.php b/syslog_reports.php index 8354843..abf5bc8 100644 --- a/syslog_reports.php +++ b/syslog_reports.php @@ -51,6 +51,7 @@ break; case 'import': top_header(); + syslog_include_js(); import(); bottom_footer(); @@ -61,6 +62,7 @@ break; case 'edit': top_header(); + syslog_include_js(); syslog_action_edit(); @@ -68,6 +70,7 @@ break; default: top_header(); + syslog_include_js(); syslog_report(); @@ -614,41 +617,7 @@ function syslog_filter() { '>