Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

--- 2.6 ---

* security: Move the slow-query views' inline event handlers to CSP-safe bindings so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive: the charts/summary/details filter selects, Go/Clear buttons, time-shift icons and per-field clear glyph are bound in each view's ready block, and the confirmation Cancel button uses the `cactiReturnTo` class
* perf: Stream the slow query log a line at a time during import (`fgets()`) instead of slurping the whole file into memory with `file()`, so importing a multi-gigabyte slow query log no longer costs many times its own size in PHP array overhead - the detail rows were already flushed to the database in `SLOWLOG_IMPORT_BATCH_SIZE` batches as parsing proceeds
* bug: Distinguish a read error from end-of-file when streaming the import - `fgets()` returns `false` for both, so a mid-file I/O error was being treated as a clean EOF and the partial import was finalized (and post-processed) as successful. `import_logfile()` now checks `feof()` before closing the handle and fails the import (status 3, "Read Error - Import Aborted, Log May Be Truncated") instead of ingesting a truncated log
* perf: Drop the `SELECT DISTINCT sld.*` from the details list query - the association tables' composite primary keys already make every `plugin_slowlog_details` x method x table join row unique, so the DISTINCT never removed a row and only forced a filesort/hash over the `mediumtext` query columns on every page of the Details view
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ Very simple analysis. I use it to see the forest through the tree's.

Cacti 1.2.x

## Cacti compatibility

If you are running a version of Cacti below 1.2.31, please add the function
below to the `applySkin()` function in `include/layout.js` to enable the Cancel
buttons on forms to work:

```js
$(document).off('click.cactiReturnTo', '.cactiReturnTo')
.on('click.cactiReturnTo', '.cactiReturnTo', function(event) {
event.preventDefault();
cactiReturnTo($(this).attr('data-url'));
});
```

## Installation

Simply place in the plugins directory and install like any other Cacti Plugin.
Expand Down
2 changes: 1 addition & 1 deletion includes/slowlog_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3038,7 +3038,7 @@ function slowlog_details_filter_clear_glyph(string $field): string {
$defaults = slowlog_details_filter_defaults();
$url = slowlog_details_filter_url($field, $defaults[$field]) . '&header=false';

return " <a class='pic' href='#' onclick=\"loadPageNoHeader('" . $url . "');return false;\"><i class='fa fa-trash-alt pic' title='" . __esc('Clear this filter', 'slowlog') . "'></i></a>";
return " <a class='pic slowlogClearFilter' href='#' data-url='" . $url . "'><i class='fa fa-trash-alt pic' title='" . __esc('Clear this filter', 'slowlog') . "'></i></a>";
}

/**
Expand Down
66 changes: 50 additions & 16 deletions slowlog.php
Original file line number Diff line number Diff line change
Expand Up @@ -1544,8 +1544,8 @@ function slowlog_charts_filter(string $method, int $id): void {
</td>
<td>
<span>
<input class='button_go' type='submit' onClick='applyChartsFilter()' name='go' value='<?php print __('Go', 'slowlog'); ?>'>
<input class='button_clear' type='button' onClick='clearChartsFilter()' name='clear' value='<?php print __('Clear', 'slowlog'); ?>'>
<input class='button_go' type='submit' name='go' value='<?php print __('Go', 'slowlog'); ?>'>
<input class='button_clear' type='button' name='clear' value='<?php print __('Clear', 'slowlog'); ?>'>
</span>
</td>
</tr>
Expand All @@ -1569,6 +1569,10 @@ function clearChartsFilter() {
}

$(function() {
$('#chartsfilter .button_clear').click(function() {
clearChartsFilter();
});

var $scope = $('#chart_scope');

// Cacti core turns this select into a select2 (via the select2-multi-count
Expand Down Expand Up @@ -1977,7 +1981,7 @@ function slowlog_save_button(string $cancel_action = '', string $action = 'save'
<tr>
<td class='saveRow'>
<input type='hidden' name='action' value='<?php print $action; ?>'>
<input type='button' value='<?php print $calt; ?>' onClick='window.location.assign('<?php print htmlspecialchars($caction); ?>')' name='cancel'>
<input type='button' class='cactiReturnTo' data-url='<?php print htmlspecialchars($caction); ?>' value='<?php print $calt; ?>' name='cancel'>
<input type='submit' value='<?php print $salt; ?>' name='<?php print $sname; ?>'>
</td>
</tr>
Expand Down Expand Up @@ -2013,8 +2017,8 @@ function filter(): void {
</td>
<td>
<span>
<input class='button_go' type='submit' onClick='applyFilter()' name='go' value='Go'>
<input class='button_clear' type='button' onClick='clearFilter()' name='clear' value='Clear'>
<input class='button_go' type='submit' name='go' value='Go'>
<input class='button_clear' type='button' name='clear' value='Clear'>
</span>
</td>
</tr>
Expand All @@ -2037,6 +2041,10 @@ function clearFilter() {
event.preventDefault();
applyFilter();
});

$('#summary .button_clear').click(function() {
clearFilter();
});
});
</script>
</td>
Expand Down Expand Up @@ -2077,7 +2085,7 @@ function slowlog_details_filter(): void {
<?php print __('LogFile', 'slowlog'); ?>
</td>
<td>
<select id='logid' onChange='applyFilter()'>
<select id='logid'>
<option value='-1'<?php if (get_request_var('logid') == '-1') {?> selected<?php }?>><?php print __('Any', 'slowlog'); ?></option>
<?php
$logids = db_fetch_assoc_prepared('SELECT logid, CONCAT(description) AS name
Expand All @@ -2097,7 +2105,7 @@ function slowlog_details_filter(): void {
<?php print __('Method', 'slowlog'); ?><?php print slowlog_details_filter_clear_glyph('mmethod'); ?>
</td>
<td>
<select id='mmethod' onChange='applyFilter()'>
<select id='mmethod'>
<option value='-1'<?php if (get_request_var('mmethod') == '-1') {?> selected<?php }?>><?php print __('Any', 'slowlog'); ?></option>
<option value='-2'<?php if (get_request_var('mmethod') == '-2') {?> selected<?php }?>><?php print __('N/A', 'slowlog'); ?></option>
<?php
Expand All @@ -2114,7 +2122,7 @@ function slowlog_details_filter(): void {
<?php print __('Tables', 'slowlog'); ?><?php print slowlog_details_filter_clear_glyph('table'); ?>
</td>
<td>
<select id='table' onChange='applyFilter()'>
<select id='table'>
<option value='-1'<?php if (get_request_var('table') == '-1') {?> selected<?php }?>><?php print __('Any', 'slowlog'); ?></option>
<option value='-2'<?php if (get_request_var('table') == '-2') {?> selected<?php }?>><?php print __('Others', 'slowlog'); ?></option>
<option value='-3'<?php if (get_request_var('table') == '-3') {?> selected<?php }?>><?php print __('N/A', 'slowlog'); ?></option>
Expand Down Expand Up @@ -2144,7 +2152,7 @@ function slowlog_details_filter(): void {
<?php print __('Rows', 'slowlog'); ?>
</td>
<td>
<select id='rows' onChange='applyFilter()'>
<select id='rows'>
<option value='-1'<?php if (get_request_var_request('rows') == '-1') {?> selected<?php }?>><?php print __('Default', 'slowlog'); ?></option>
<?php
if (cacti_sizeof($item_rows)) {
Expand All @@ -2157,8 +2165,8 @@ function slowlog_details_filter(): void {
</td>
<td>
<span>
<input class='button_go' type='submit' onClick='applyFilter()' name='go' value='<?php print __('Go', 'slowlog'); ?>'>
<input class='button_clear' type='button' onClick='clearFilter()' name='clear' value='<?php print __('Clear', 'slowlog'); ?>'>
<input class='button_go' type='submit' name='go' value='<?php print __('Go', 'slowlog'); ?>'>
<input class='button_clear' type='button' name='clear' value='<?php print __('Clear', 'slowlog'); ?>'>
</span>
</td>
</tr>
Expand All @@ -2175,7 +2183,7 @@ function slowlog_details_filter(): void {
<?php print __('User', 'slowlog'); ?><?php print slowlog_details_filter_clear_glyph('user'); ?>
</td>
<td>
<select id='myuser' onChange='applyFilter()'>
<select id='myuser'>
<option value='-1'<?php if (get_request_var('user') == '-1') {?> selected<?php }?>><?php print __('Any', 'slowlog'); ?></option>
<?php
if (get_request_var('logid') > 0) {
Expand Down Expand Up @@ -2203,7 +2211,7 @@ function slowlog_details_filter(): void {
<?php print __('Host', 'slowlog'); ?><?php print slowlog_details_filter_clear_glyph('host'); ?>
</td>
<td>
<select id='host' onChange='applyFilter()'>
<select id='host'>
<option value='-1'<?php if (get_request_var('host') == '-1') {?> selected<?php }?>><?php print __('Any', 'slowlog'); ?></option>
<?php
if (get_request_var('logid') > 0) {
Expand Down Expand Up @@ -2251,7 +2259,7 @@ function slowlog_details_filter(): void {
</td>
<td>
<span>
<i class='shiftArrow fa fa-backward' onClick='timeshiftGraphFilterLeft()' title='<?php print __esc('Shift Time Backward', 'slowlog'); ?>'></i>
<i class='shiftArrow fa fa-backward' title='<?php print __esc('Shift Time Backward', 'slowlog'); ?>'></i>
<select id='predefined_timeshift' title='<?php print __esc('Define Shifting Interval', 'slowlog'); ?>'>
<?php
$start_val = 1;
Expand All @@ -2268,11 +2276,11 @@ function slowlog_details_filter(): void {
}
?>
</select>
<i class='shiftArrow fa fa-forward' onClick='timeshiftGraphFilterRight()' title='<?php print __esc('Shift Time Forward', 'slowlog'); ?>'></i>
<i class='shiftArrow fa fa-forward' title='<?php print __esc('Shift Time Forward', 'slowlog'); ?>'></i>
</span>
</td>
<td style='display:none;'>
<select id='predefined_timespan' onChange='applyGraphTimespan()'>
<select id='predefined_timespan'>
<?php
$graph_timespans = array_merge([GT_CUSTOM => __('Custom', 'slowlog')], $graph_timespans);

Expand Down Expand Up @@ -2350,6 +2358,32 @@ function timeshiftFilterRight() {
applyFilter();
});

$('#logid, #mmethod, #table, #rows, #myuser, #host').change(function() {
applyFilter();
});

$('#predefined_timespan').change(function() {
applyGraphTimespan();
});

$('#details .button_clear').click(function() {
clearFilter();
});

$(document).off('click.slowlogClear', '.slowlogClearFilter')
.on('click.slowlogClear', '.slowlogClearFilter', function(event) {
event.preventDefault();
loadPageNoHeader($(this).data('url'));
});

$('.shiftArrow.fa-backward').click(function() {
timeshiftGraphFilterLeft();
});

$('.shiftArrow.fa-forward').click(function() {
timeshiftGraphFilterRight();
});

$('#startDate').click(function() {
if (date1Open) {
date1Open = false;
Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/SlowlogClearGlyphTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
*/

/*
* Unit coverage for slowlog_details_filter_clear_glyph() in
* includes/slowlog_functions.php. The clear-filter glyph uses a CSP-safe
* slowlogClearFilter class and data-url attribute (bound via a delegated
* handler) instead of an inline onclick.
*/

uses(TestCase::class);

beforeEach(function () {
TestCase::loadPluginSource('includes/slowlog_functions.php');
});

it('renders the clear-filter glyph with CSP-safe markup and no inline handler', function () {
// The unit harness' get_request_var() returns '', which differs from the
// 'table' filter default ('-1'), so the filter counts as active and the
// glyph (the changed line) is rendered.
$output = slowlog_details_filter_clear_glyph('table');

expect($output)->toContain('slowlogClearFilter');
expect($output)->toContain('data-url=');
expect($output)->not->toContain('onclick');
});
Loading