Files
cacti/data_debug.php
T
1997691de0 Render RRDtool Tune Info as balanced div boxes and POST the Data Source repair actions (#8412)
* data_sources: render RRDtool Tune Info as balanced div boxes

rrdtool_tune() emitted an unbalanced mix of html_header() and
html_end_box() calls wrapped around raw table rows inside the single
box opened by data_sources.php.  The premature and extra closing tags
corrupted the DOM, which (depending on theme) clipped the Save button
at the bottom of the Data Source edit page while in Data Source Info
mode.

- Separate the execute, CLI and HTML code paths in rrdtool_tune() and,
  for HTML, render each section as a self contained, balanced div box.
- Extract and rename the helper print_leaves() to the standalone
  rrdtool_tune_print_leaves(), matching the rrdtool_tune_* naming
  convention, and emit div rows instead of table rows.
- Switch the data_sources.php wrapper box to div mode to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* data_debug: require POST for the Data Source repair actions

Applying RRDfile repairs and rerunning the debugger are state changing
operations, so they should not be reachable with a GET.  Driving them
through a GET also meant the follow up redirect was pulled into the
content area as a full page, losing the theme and navigation on
rerender.

Register data_debug.php run_repair and run_debug in the
cactiIsPostActionUrl() allowlist so loadUrl() upgrades them to a
token-bearing POST, and add the matching csrf_require_post() guard so
the browser transport and server side guard stay in sync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* rrd: split rrdtool_tune into testable render and execute helpers

Break rrdtool_tune() into small single-purpose helpers so each path can
be exercised directly by unit tests (the web display path is otherwise
unreachable under CACTI_CLI):

- rrdtool_tune_print_leaves() takes an explicit $html flag instead of
  branching on CACTI_CLI, so both the div and plain-text branches are
  testable.
- rrdtool_tune_execute() applies the tune/resize commands for a repair.
- rrdtool_tune_print_cli() prints the source commands as plain text.
- rrdtool_tune_print_html() renders the balanced div boxes.
- rrdtool_tune() becomes a thin dispatcher over the three.

Add tests/Unit/RrdTuneRenderTest.php covering the leaf printer, the HTML
and CLI printers, the execute path and the dispatcher, asserting the
output is balanced div markup with no table rows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* review: post Data Source Troubleshooter actions via cactiPostAction

Address PR review feedback:

- Switch the Apply Suggested Fixes and Rerun Analysis links to the
  existing cactiPostAction/data-url idiom (as data_sources.php already
  does for ds_repair) instead of upgrading a loadUrl() GET through the
  cactiIsPostActionUrl allowlist. The allowlist path replayed the
  query-free mutation URL into browser history, so a refresh reopened
  the list instead of the selected Data Source; a full cactiPostAction
  submission follows the server redirect to the read-only view URL and
  records that in history. The run_repair / run_debug csrf_require_post
  guards are retained, and the data_debug.php allowlist entry (and its
  now redundant click handlers) are removed.

- Add a callable $executor test seam to rrdtool_tune_execute() (matching
  rrdtool_dump_document) so the unit tests assert the tune and resize
  commands are actually executed, with resize.rrd produced only when the
  resize command runs, rather than passing on the rename alone.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* review: move RrdTuneRenderTest into the Unit/Core/Rrd category folder

Per AGENTS.md, new unit tests belong in a category subfolder rather than
directly under tests/Unit. Move RrdTuneRenderTest there and update the
UnitStubs include depth accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: TheWitness <copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-16 11:23:07 -04:00

1202 lines
32 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| 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/ |
+-------------------------------------------------------------------------+
*/
require_once('include/auth.php');
require_once('lib/rrd.php');
require_once('lib/dsdebug.php');
$actions = [
1 => __('Run Check'),
2 => __('Delete Check')
];
ini_set('memory_limit', '-1');
set_default_action();
draw_data_debug_filter(false);
if (in_array(grv('action'), ['run_repair', 'run_debug'], true)) {
csrf_require_post();
}
switch (grv('action')) {
case 'actions':
form_actions();
break;
case 'run_debug':
$id = gfrv('id');
if ($id > 0) {
$selected_items = [$id];
debug_delete($selected_items);
debug_rerun($selected_items);
raise_message('rerun', __('Data Source debug started.'), MESSAGE_LEVEL_INFO);
header('Location: data_debug.php?action=view&id=' . gfrv('id'));
} else {
raise_message('repair_error', __('Data Source debug received an invalid Data Source ID.'), MESSAGE_LEVEL_ERROR);
}
break;
case 'run_repair':
$id = gfrv('id');
if ($id > 0) {
if (dsdebug_run_repair($id)) {
raise_message('repair', __('All RRDfile repairs succeeded.'), MESSAGE_LEVEL_INFO);
} else {
raise_message('repair', __('One or more RRDfile repairs failed. See Cacti log for errors.'), MESSAGE_LEVEL_ERROR);
}
$selected_items = [$id];
debug_delete($selected_items);
debug_rerun($selected_items);
raise_message('rerun', __('Automatic Data Source debug being rerun after repair.'), MESSAGE_LEVEL_INFO);
header('Location: data_debug.php?action=view&id=' . gfrv('id'));
} else {
raise_message('repair_error', __('Data Source repair received an invalid Data Source ID.'), MESSAGE_LEVEL_ERROR);
}
break;
case 'view':
$id = gfrv('id');
$debug_status = debug_process_status($id);
if ($debug_status == 'notset') {
$selected_items = [$id];
debug_delete($selected_items);
debug_rerun($selected_items);
$debug_status = 'waiting';
}
if ($debug_status == 'waiting' || $debug_status == 'analysis') {
$refresh = [
'seconds' => 30,
'page' => 'data_debug.php?action=view&id=' . $id,
'logout' => 'false'
];
set_page_refresh($refresh);
}
top_header();
debug_view();
bottom_footer();
break;
case 'ajax_hosts':
$sql_where = '';
if (gfrv('site_id') > 0) {
$sql_where = 'site_id = ' . gfrv('site_id');
}
get_allowed_ajax_hosts(true, true, $sql_where);
break;
case 'ajax_hosts_noany':
$sql_where = '';
if (gfrv('site_id') > 0) {
$sql_where = 'site_id = ' . gfrv('site_id');
}
get_allowed_ajax_hosts(false, true, $sql_where);
break;
case 'runall':
debug_runall_filtered();
// Fall through: render the refreshed list after running the selected sources.
default:
$refresh = [
'seconds' => grv('refresh'),
'page' => 'data_debug.php',
'logout' => 'false'
];
set_page_refresh($refresh);
top_header();
debug_wizard();
bottom_footer();
break;
}
function debug_runall_filtered() : void {
$info = [
'rrd_folder_writable' => '',
'rrd_exists' => '',
'rrd_writable' => '',
'active' => '',
'owner' => '',
'runas_poller' => '',
'runas_website' => get_running_user(),
'last_result' => '',
'valid_data' => '',
'rra_timestamp' => '',
'rra_timestamp2' => '',
'rrd_match' => ''
];
$info = serialize($info);
$sql_where = '';
$sql_params = [];
$dd_join = '';
$now = time();
debug_get_filter($sql_where, $sql_params, $dd_join);
db_execute_prepared("DELETE dd
FROM data_debug AS dd
INNER JOIN data_local AS dl
ON dd.datasource = dl.id
INNER JOIN data_template_data AS dtd
ON dl.id=dtd.local_data_id
INNER JOIN data_template AS dt
ON dt.id=dl.data_template_id
INNER JOIN host AS h
ON h.id = dl.host_id
$sql_where",
$sql_params);
$new_params = [$now, $info, $_SESSION[SESS_USER_ID]];
$sql_params = $new_params + $sql_params;
db_execute_prepared("INSERT INTO data_debug
(started, done, info, user, datasource)
SELECT ?, '0', ?, ?, dl.id
FROM data_local AS dl
INNER JOIN data_template_data AS dtd
ON dl.id=dtd.local_data_id
INNER JOIN data_template AS dt
ON dt.id=dl.data_template_id
INNER JOIN host AS h
ON h.id = dl.host_id
$sql_where",
$sql_params);
}
function debug_process_status(int $id) : string {
$status = db_fetch_row_prepared('SELECT done, IFNULL(issue, "waiting") AS issue
FROM data_debug
WHERE datasource = ?',
[$id]);
if (cacti_sizeof($status) == 0) {
return 'notset';
}
if ($status['issue'] == 'waiting') {
return 'waiting';
}
if ($status['done'] == 1) {
return 'complete';
} else {
return 'analysis';
}
}
function form_actions() : void {
global $actions, $assoc_actions;
// ================= input validation =================
gfrv('id');
gfrv('drp_action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z0-9_]+)$/']]);
// ================= input validation =================
$selected_items = [];
if (isrv('save_list')) {
// loop through each of the lists selected on the previous page and get more info about them
foreach ($_POST as $var=>$val) {
if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) {
// ================= input validation =================
input_validate_input_number($matches[1], 'chk[1]');
// ====================================================
$selected_items[] = $matches[1];
}
}
// if we are to save this form, instead of display it
if (isrv('save_list')) {
if (grv('drp_action') == '2') { // delete
debug_delete($selected_items);
header('Location: data_debug.php?debug=-1');
} elseif (grv('drp_action') == '1') { // Rerun
debug_rerun($selected_items);
header('Location: data_debug.php?debug=1');
}
exit;
}
}
}
function debug_rerun(array $selected_items) : void {
$info = [
'rrd_folder_writable' => '',
'rrd_exists' => '',
'rrd_writable' => '',
'active' => '',
'owner' => '',
'runas_poller' => '',
'runas_website' => get_running_user(),
'last_result' => '',
'valid_data' => '',
'rra_timestamp' => '',
'rra_timestamp2' => '',
'rrd_match' => ''
];
$info = serialize($info);
if (!empty($selected_items)) {
foreach ($selected_items as $id) {
$exists = db_fetch_cell_prepared('SELECT id
FROM data_debug
WHERE datasource = ?',
[$id]);
if (!$exists) {
$save = [];
$save['id'] = 0;
$save['datasource'] = $id;
$save['info'] = $info;
$save['started'] = time();
$save['user'] = intval($_SESSION[SESS_USER_ID]);
$id = sql_save($save, 'data_debug');
} else {
$stime = time();
db_execute_prepared('UPDATE data_debug
SET started = ?,
done = 0,
info = ?,
issue = ""
WHERE id = ?',
[$stime, $info, $exists]);
}
}
}
}
function debug_delete(array $selected_items) : void {
if (!empty($selected_items)) {
foreach ($selected_items as $id) {
db_execute_prepared('DELETE
FROM data_debug
WHERE datasource = ?',
[$id]);
}
}
}
function debug_get_filter(string &$sql_where, array &$sql_params, string &$dd_join) : void {
// form the 'where' clause for our main sql query
if (grv('rfilter') != '') {
$sql_where = 'WHERE (dtd.name_cache RLIKE ? OR dtd.local_data_id RLIKE ? OR dt.name RLIKE ?)';
$sql_params[] = grv('rfilter');
$sql_params[] = grv('rfilter');
$sql_params[] = grv('rfilter');
}
if (ierv('host_id')) {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' (dl.host_id = 0 OR dl.host_id IS NULL)';
} elseif (grv('host_id') > 0) {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dl.host_id = ?';
$sql_params[] = grv('host_id');
}
if (ierv('site_id')) {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' (h.site_id = 0 OR h.site_id IS NULL)';
} elseif (grv('site_id') > 0) {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' h.site_id = ?';
$sql_params[] = grv('site_id');
}
if (grv('template_id') == '0') {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dtd.data_template_id = 0';
} elseif (grv('template_id') > 0) {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dtd.data_template_id = ?';
$sql_params[] = grv('template_id');
}
if (grv('profile') > '-1') {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dtd.data_source_profile_id = ?';
$sql_params[] = grv('profile');
}
if (grv('status') == '0') {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dd.issue != ""';
} elseif (grv('status') == '1') {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dtd.active = "on"';
} elseif (grv('status') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dtd.active = ""';
}
if (grv('debug') == '-1') {
$dd_join = 'LEFT';
} elseif (grv('debug') == 0) {
$dd_join = 'LEFT';
$sql_where .= ($sql_where != '' ? ' AND' : 'WHERE') . ' dd.datasource IS NULL';
} else {
$dd_join = 'INNER';
}
}
function debug_wizard() : void {
global $actions;
$display_text = [
'name_cache' => [
'display' => __('Data Source'),
'sort' => 'ASC',
'tip' => __('The Data Source to Debug'),
],
'username' => [
'display' => __('User'),
'sort' => 'ASC',
'tip' => __('The User who requested the Debug.'),
],
'started' => [
'display' => __('Started'),
'sort' => 'DESC',
'align' => 'right',
'tip' => __('The Date that the Debug was Started.'),
],
'local_data_id' => [
'display' => __('ID'),
'sort' => 'ASC',
'align' => 'right',
'tip' => __('The Data Source internal ID.'),
],
'nosort1' => [
'display' => __('Status'),
'sort' => 'ASC',
'align' => 'center',
'tip' => __('The Status of the Data Source Debug Check.'),
],
'nosort2' => [
'display' => __('Writable'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the Data Collector or the Web Site have Write access.'),
],
'nosort3' => [
'display' => __('Exists'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the Data Source is located in the Poller Cache.'),
],
'nosort4' => [
'display' => __('Active'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the Data Source is Enabled.'),
],
'nosort5' => [
'display' => __('RRD Match'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the RRDfile matches the Data Source Template.'),
],
'nosort6' => [
'display' => __('Valid Data'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the RRDfile has been getting good recent Data.'),
],
'nosort7' => [
'display' => __('RRD Updated'),
'align' => 'center',
'sort' => '',
'tip' => __('Determines if the RRDfile has been written to properly.'),
],
'nosort8' => [
'display' => __('Issues'),
'align' => 'right',
'sort' => '',
'tip' => __('Summary of issues found for the Data Source.'),
]
];
if (isrv('purge')) {
db_execute('TRUNCATE TABLE data_debug');
}
// fill in the current date for printing in the log
if (defined('CACTI_DATE_TIME_FORMAT')) {
$datefmt = CACTI_DATE_TIME_FORMAT;
} else {
$datefmt = 'Y-m-d H:i:s';
}
draw_data_debug_filter(true);
$total_rows = 0;
$checks = [];
if (grv('rows') == '-1') {
$rows = read_config_option('num_rows_table');
} else {
$rows = grv('rows');
}
$sql_where = '';
$sql_params = [];
$dd_join = '';
debug_get_filter($sql_where, $sql_params, $dd_join);
$total_rows = db_fetch_cell_prepared("SELECT COUNT(*)
FROM data_local AS dl
INNER JOIN data_template_data AS dtd
ON dl.id=dtd.local_data_id
INNER JOIN data_template AS dt
ON dt.id=dl.data_template_id
INNER JOIN host AS h
ON h.id = dl.host_id
$dd_join JOIN data_debug AS dd
ON dl.id = dd.datasource
$sql_where",
$sql_params);
$sql_order = get_order_string();
$sql_limit = ' LIMIT ' . ($rows * (grv('page') - 1)) . ',' . $rows;
$checks = db_fetch_assoc_prepared("SELECT dd.*, dtd.local_data_id,
dtd.name_cache, u.username
FROM data_local AS dl
INNER JOIN data_template_data AS dtd
ON dl.id=dtd.local_data_id
INNER JOIN data_template AS dt
ON dt.id=dl.data_template_id
INNER JOIN host AS h
ON h.id = dl.host_id
$dd_join JOIN data_debug AS dd
ON dl.id = dd.datasource
LEFT JOIN user_auth AS u
ON u.id = dd.user
$sql_where
$sql_order
$sql_limit",
$sql_params);
$nav = html_nav_bar('data_debug.php', MAX_DISPLAY_PAGES, grv('page'), $rows, $total_rows, cacti_sizeof($display_text) + 1, __('Data Sources'), 'page', 'main');
form_start('data_debug.php', 'chk');
print $nav;
html_start_box('', '100%', false, 3, 'center', '');
html_header_sort_checkbox($display_text, grv('sort_column'), grv('sort_direction'), false);
if (cacti_sizeof($checks)) {
foreach ($checks as $check) {
if (isset($check['info']) && $check['info'] != '') {
$info = cacti_unserialize($check['info']);
} else {
$info = '';
}
if (isset($check['issue']) && $check['issue'] != '') {
$issues = explode("\n", $check['issue']);
} else {
$issues = [];
}
$iline = '';
if (cacti_sizeof($issues)) {
$iline = $issues[0];
}
$issue_title = implode('<br/>',$issues);
form_alternate_row('line' . $check['local_data_id']);
$url = 'data_debug.php?action=view&id=' . $check['local_data_id'];
form_selectable_cell(filter_value($check['name_cache'], grv('rfilter'), $url), $check['local_data_id']);
if (!empty($check['datasource'])) {
form_selectable_ecell($check['username'], $check['local_data_id']);
form_selectable_cell(date($datefmt, $check['started']), $check['local_data_id'], '', 'right');
form_selectable_cell($check['local_data_id'], $check['local_data_id'], '', 'right');
form_selectable_cell(debug_icon(($check['done'] ? ($iline != '' ? 'off' : 'on') : '')), $check['local_data_id'], '', 'center');
form_selectable_cell(debug_icon($info['rrd_writable']), $check['local_data_id'], '', 'center');
form_selectable_cell(debug_icon($info['rrd_exists']), $check['local_data_id'], '', 'center');
form_selectable_cell(debug_icon($info['active']), $check['local_data_id'], '', 'center');
form_selectable_cell(debug_icon($info['rrd_match']), $check['local_data_id'], '', 'center');
form_selectable_cell(debug_icon($info['valid_data']), $check['local_data_id'], '', 'center');
if ($check['done'] && $info['rrd_writable'] == '') {
form_selectable_cell(debug_icon('blah'), $check['local_data_id'], '', 'center');
} else {
form_selectable_cell(debug_icon(($info['rra_timestamp2'] != '' ? 1 : '')), $check['local_data_id'], '', 'center');
}
form_selectable_cell(filter_value($iline != '' ? __('Issues') : __('N/A'), '', '#', $issue_title), $check['local_data_id'], '', 'right');
} else {
form_selectable_cell('-', $check['local_data_id']);
form_selectable_cell(__('Not Debugging'), $check['local_data_id'], '', 'right');
form_selectable_cell($check['local_data_id'], $check['local_data_id'], '', 'right');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'center');
form_selectable_cell('-', $check['local_data_id'], '', 'right');
}
form_checkbox_cell($check['local_data_id'], $check['local_data_id']);
form_end_row();
}
} else {
print "<tr class='tableRow odd'><td colspan='" . (cacti_sizeof($display_text) + 1) . "'><em>" . __('No Data Source Checks Found') . '</em></td></tr>';
}
html_end_box(false);
if (cacti_sizeof($checks)) {
print $nav;
}
form_hidden_box('save_list', '1', '');
// draw the dropdown containing a list of available actions for this form
draw_actions_dropdown($actions);
form_end();
}
function debug_view() : void {
global $refresh;
$refresh = 60;
$id = gfrv('id');
$check = db_fetch_row_prepared('SELECT *
FROM data_debug
WHERE datasource = ?',
[$id]);
$check_exists = cacti_sizeof($check);
if (cacti_sizeof($check)) {
$check['info'] = cacti_unserialize($check['info']);
}
$dtd = db_fetch_row_prepared('SELECT *
FROM data_template_data
WHERE local_data_id = ?',
[$id]);
if (cacti_sizeof($dtd)) {
$real_path = htmle(str_replace('<path_rra>', CACTI_PATH_RRA, $dtd['data_source_path']));
} else {
$real_path = __('Not Found');
}
$poller_data = [];
if (!empty($check['info']['last_result'])) {
foreach ($check['info']['last_result'] as $a => $l) {
$poller_data[] = "$a = $l";
}
}
$poller_data = implode('<br>', $poller_data);
$rra_updated = '';
if (isset($check['info']['rra_timestamp2'])) {
$rra_updated = $check['info']['rra_timestamp2'] != '' ? __('Yes') : '';
}
$rrd_exists = '';
if (isset($check['info']['rrd_exists'])) {
$rrd_exists = $check['info']['rrd_exists'] == '1' ? __('Yes') : __('Not Checked Yet');
}
$active = '';
if (isset($check['info']['active'])) {
$active = $check['info']['active'] == 'on' ? __('Yes') : __('Not Checked Yet');
}
$issue = '';
if (isset($check['issue'])) {
$issue = $check['issue'];
}
if ($check['done'] == 1) {
if ($issue != '') {
$issue_icon = debug_icon(0);
} else {
$issue_icon = debug_icon(1);
}
} else {
if (isset($check['info']['rrd_match_array']['ds'])) {
if ($check['info']['rrd_match'] == 0) {
$issue_icon = debug_icon('blah');
$issue = __('Issues found! Waiting on RRDfile update');
} else {
$issue_icon = debug_icon('');
$issue = __('No Initial found! Waiting on RRDfile update');
}
} else {
$issue_icon = debug_icon('');
$issue = __('Waiting on analysis and RRDfile update');
}
}
$fields = [
[
'name' => 'owner',
'title' => __('RRDfile Owner'),
'icon' => '-'
],
[
'name' => 'runas_website',
'title' => __('Website runs as'),
'icon' => '-'
],
[
'name' => 'runas_poller',
'title' => __('Poller runs as'),
'icon' => '-'
],
[
'name' => 'rrd_folder_writable',
'title' => __('Is RRA Folder writeable by poller?'),
'value' => dirname($real_path)
],
[
'name' => 'rrd_writable',
'title' => __('Is RRDfile writeable by poller?'),
'value' => $real_path
],
[
'name' => 'rrd_exists',
'title' => __('Does the RRDfile Exist?'),
'value' => $rrd_exists
],
[
'name' => 'active',
'title' => __('Is the Data Source set as Active?'),
'value' => $active
],
[
'name' => 'last_result',
'title' => __('Did the poller receive valid data?'),
'value' => $poller_data
],
[
'name' => 'rra_updated',
'title' => __('Was the RRDfile updated?'),
'value' => '',
'icon' => $rra_updated
],
[
'name' => 'rra_timestamp',
'title' => __('First Check TimeStamp'),
'icon' => '-'
],
[
'name' => 'rra_timestamp2',
'title' => __('Second Check TimeStamp'),
'icon' => '-'
],
[
'name' => 'convert_name',
'title' => __('Were we able to convert the title?'),
'value' => htmle(get_data_source_title($check['datasource']))
],
[
'name' => 'rrd_match',
'title' => __('Data Source matches the RRDfile?'),
'value' => ''
],
[
'name' => 'issue',
'title' => __('Issues'),
'value' => $issue,
'icon' => $issue_icon
],
];
$debug_status = debug_process_status($id);
if ($debug_status == 'waiting') {
html_start_box(__('Data Source Troubleshooter [ Auto Refreshing till Complete ] %s', '<i class="reloadquery ti ti-refresh icon-rotate" data-id="' . $id . '" title="' . __esc('Refresh Now') . '"></i>'), '100%', false, 3, 'center', '');
} elseif ($debug_status == 'analysis') {
html_start_box(__('Data Source Troubleshooter [ Auto Refreshing till RRDfile Update ] %s', '<i class="reloadquery ti ti-refresh icon-rotate" data-id="' . $id . '" title="' . __esc('Refresh Now') . '"></i>'), '100%', false, 3, 'center', '');
} else {
html_start_box(__('Data Source Troubleshooter [ Analysis Complete! %s ]', '<a href="#" class="cactiPostAction linkEditMain" data-url="' . html_escape_url('data_debug.php?action=run_debug&id=' . $id) . '" style="cursor:pointer;">' . __('Rerun Analysis') . '</a>'), '100%', false, 3, 'center', '');
}
html_header(
[
__('Check'),
__('Value'),
__('Results')
]
);
$i = 1;
foreach ($fields as $field) {
$field_name = $field['name'];
form_alternate_row('line' . $i);
form_selectable_ecell($field['title'], $i);
$value = __('<not set>');
$icon = '';
if (array_key_exists($field_name, $check['info'])) {
$value = $check['info'][$field_name];
if ($field_name == 'last_result') {
$icon = debug_icon_valid_result($value);
} else {
$icon = debug_icon($value);
}
}
if (array_key_exists('value', $field)) {
$value = $field['value'];
}
if (array_key_exists('icon', $field)) {
$icon = $field['icon'];
}
$value_title = htmle((string) $value);
if (strlen($value) > 100) {
$value = substr($value, 0, 100);
}
form_selectable_ecell($value, $i, '', '', $value_title);
form_selectable_cell($icon, $i);
form_end_row();
$i++;
}
html_end_box();
if ($check_exists > 0 && isset($check['info']['rrd_match_array']['ds']) && $check['info']['rrd_match'] == 0) {
html_start_box(__('Data Source Repair Recommendations'), '100%', false, 3, 'center', '');
html_header(
[
__('Data Source'),
__('Issue')
]
);
if (isset($check['info']['rrd_match_array']['ds'])) {
$i = 0;
foreach ($check['info']['rrd_match_array']['ds'] as $data_source => $details) {
form_alternate_row('line2_' . $i, true);
form_selectable_ecell($data_source, $i);
$output = '';
foreach ($details as $attribute => $recommendation) {
$output .= __('For attribute \'%s\', issue found \'%s\'', $attribute, $recommendation);
}
form_selectable_ecell($output, 'line_2' . $i);
form_end_row();
$i++;
}
}
html_end_box();
if (isset($check['info']['rrd_match_array']['tune'])) {
$path = get_data_source_path($id, true);
if (is_writeable($path)) {
html_start_box(__('Repair Steps [ %s ]', '<a href="#" class="cactiPostAction linkEditMain" data-url="' . html_escape_url('data_debug.php?action=run_repair&id=' . $id) . '" style="cursor:pointer;">' . __('Apply Suggested Fixes') . '</a>'), '100%', false, 3, 'center', '');
} else {
html_start_box(__('Repair Steps [ Run Fix from Command Line ]', $path), '100%', false, 3, 'center', '');
}
html_header([__('Command')]);
$rrdtool_path = read_config_option('path_rrdtool');
$i = 0;
foreach ($check['info']['rrd_match_array']['tune'] as $options) {
form_alternate_row('line3_' . $i, true);
form_selectable_ecell($rrdtool_path . ' tune ' . $options, 'line3_' . $i);
form_end_row();
$i++;
}
html_end_box();
}
} else {
html_start_box(__('Data Source Repair Recommendations'), '100%', false, 3, 'center', '');
form_alternate_row('line3_0', true);
form_selectable_cell(__('Waiting on Data Source Check to Complete'), 'line3_0');
form_end_row();
html_end_box();
}
?>
<script type='text/javascript'>
$(function() {
$('.reloadquery').click(function() {
id = $(this).attr('data-id');
loadUrl({url:'data_debug.php?action=view&id=' + id})
});
});
</script>
<?php
}
function debug_icon_valid_result(mixed $result) : string {
if ($result === '' || $result === false) {
return '<i class="ti ti-loader fa-pulse fa-fw"></i>';
}
if ($result === '-') {
return '<i class="ti ti-info-circle-filled"></i>';
}
if (is_array($result)) {
foreach ($result as $variable => $value) {
if (!prepare_validate_result($value)) {
return '<i class="ti ti-x" style="color:red"></i>';
}
}
return '<i class="ti ti-check" style="color:green"></i>';
}
if (prepare_validate_result($result)) {
return '<i class="ti ti-check" style="color:green"></i>';
} else {
return '<i class="ti ti-x" style="color:red"></i>';
}
}
function debug_icon(mixed $result) : string {
if ($result === '' || $result === false) {
return '<i class="ti ti-loader fa-pulse fa-fw"></i>';
}
if ($result === '-') {
return '<i class="ti ti-info-circle-filled"></i>';
}
if ($result === 1 || $result === 'on') {
return '<i class="ti ti-check" style="color:green"></i>';
}
if ($result === 0 || $result === 'off') {
return '<i class="ti ti-x" style="color:red"></i>';
}
return '<i class="ti ti-alert-triangle-filled" style="color:orange"></i>';
}
function create_data_debug_filter(string $session_var) : array {
global $item_rows, $page_refresh_interval;
$all = ['-1' => __('All')];
$any = ['-1' => __('Any')];
$none = ['0' => __('None')];
$deleted = ['-2' => __('Deleted/Invalid')];
$sites = array_rekey(
db_fetch_assoc('SELECT id, name
FROM sites
ORDER BY name'),
'id', 'name'
);
$sites = $any + $sites;
$profiles = array_rekey(
db_fetch_assoc('SELECT id, name
FROM data_source_profiles
ORDER BY name'),
'id', 'name'
);
$profiles = $all + $profiles;
$status = [
'-1' => __('All'),
'0' => __('Failed'),
'1' => __('Enabled'),
'2' => __('Disabled')
];
$debugging = [
'-1' => __('All'),
'1' => __('Debugging'),
'0' => __('Not Debugging')
];
unset($page_refresh_interval[5]);
unset($page_refresh_interval[10]);
unset($page_refresh_interval[20]);
$sql_where = '';
$sql_params = [];
if (isrv('host_id')) {
$host_id = grv('host_id');
} elseif (isset($_SESSION[$session_var . '_host_id'])) {
$host_id = $_SESSION[$session_var . '_host_id'];
} else {
$host_id = '-1';
}
if ($host_id > 0) {
// for the templates dropdown
$sql_where = 'AND h.id = ?';
$sql_params[] = $host_id;
$hostname = db_fetch_cell_prepared('SELECT description
FROM host
WHERE id = ?',
[$host_id]);
} elseif ($host_id == 0) {
$host_id = '0';
$hostname = __('None');
} else {
$host_id = '-1';
$hostname = __('Any');
}
if (grv('site_id') > 0) {
$sql_where = 'AND site_id = ?';
$sql_params[] = grv('site_id');
}
$templates = array_rekey(
db_fetch_assoc_prepared("SELECT DISTINCT dt.id, dt.name
FROM data_template AS dt
INNER JOIN data_template_data AS dtd
ON dt.id = dtd.data_template_id
LEFT JOIN data_local AS dl
ON dtd.local_data_id = dl.id
LEFT JOIN host AS h
ON dl.host_id = h.id
WHERE dtd.local_data_id > 0
$sql_where
ORDER BY dt.name",
$sql_params),
'id', 'name'
);
$templates = $any + $templates;
return [
'rows' => [
[
'site_id' => [
'method' => 'drop_array',
'friendly_name' => __('Site'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $sites,
'value' => '-1'
],
'host_id' => [
'method' => 'drop_callback',
'friendly_name' => __('Device'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'sql' => 'SELECT DISTINCT id, description AS name FROM host ORDER BY description',
'action' => 'ajax_hosts',
'id' => $host_id,
'value' => $hostname,
'on_change' => 'applyFilter()'
],
'template_id' => [
'method' => 'drop_array',
'friendly_name' => __('Template'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $templates,
'value' => '-1'
]
],
[
'profile' => [
'method' => 'drop_array',
'friendly_name' => __('Profile'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $profiles,
'value' => '-1'
],
'status' => [
'method' => 'drop_array',
'friendly_name' => __('Status'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $status,
'value' => '-1'
],
'debug' => [
'method' => 'drop_array',
'friendly_name' => __('Debug'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $debugging,
'value' => '-1'
],
'refresh' => [
'method' => 'drop_array',
'friendly_name' => __('Refresh'),
'filter' => FILTER_VALIDATE_INT,
'default' => '30',
'pageset' => true,
'array' => $page_refresh_interval,
'value' => '30'
]
],
[
'rfilter' => [
'method' => 'textbox',
'friendly_name' => __('Search'),
'filter' => FILTER_VALIDATE_IS_REGEX,
'placeholder' => __('Enter a search term'),
'size' => '55',
'default' => '',
'pageset' => true,
'max_length' => '120',
'value' => ''
],
'rows' => [
'method' => 'drop_array',
'friendly_name' => __('Attempts'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $item_rows,
'value' => '-1'
]
]
],
'buttons' => [
'go' => [
'method' => 'submit',
'display' => __('Go'),
'title' => __('Apply filter to table'),
],
'clear' => [
'method' => 'button',
'display' => __('Clear'),
'title' => __('Reset filter to default values'),
],
'purge' => [
'method' => 'button',
'display' => __('Purge'),
'action' => 'default',
'title' => __('Purge User log of all but the last login attempt'),
],
'runall' => [
'method' => 'button',
'display' => __('Run All'),
'action' => 'default',
'title' => __('Run a Debug Check on all Data Sources'),
]
],
'sort' => [
'sort_column' => 'name_cache',
'sort_direction' => 'DESC'
]
];
}
function draw_data_debug_filter(bool $render = false) : void {
$filters = create_data_debug_filter('sess_data_debug');
if (grv('host_id') > 0) {
$hostname = db_fetch_cell_prepared('SELECT CONCAT(description, " ( ", hostname, " )")
FROM host WHERE id = ?',
[grv('host_id')]);
} else {
$hostname = '';
}
if (empty($hostname)) {
if (grv('host_id') == -1) {
$header = __('All Devices');
} else {
$header = __('No Devices');
}
} else {
$header = htmle($hostname);
}
// create the page filter
$pageFilter = new CactiTableFilter($header, 'data_debug.php', 'form_data_debug', 'sess_data_debug');
$pageFilter->rows_label = __('Data Sources');
$pageFilter->set_filter_array($filters);
if ($render) {
$pageFilter->render();
} else {
$pageFilter->sanitize();
}
}