mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-24 03:57:32 +09:00
* feat: expose RRDtool 1.11 graph axis controls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: constrain axis template inheritance checkboxes Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): exercise native aggregate validation and enforce coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: share changed-code coverage contracts and enforce shell policy Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ci): run request coverage with portable path matching Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: retain bootstrap coverage and validate request dispatch Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: synchronize native coverage fixture dispatch Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: measure coverage at the exact PR revision Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2174 lines
78 KiB
PHP
2174 lines
78 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(__DIR__ . '/CactiValidator.php');
|
|
|
|
/**
|
|
* Create or update aggregate graph.
|
|
* Save all graph definitions, but omit graph items. Wipe out host_id and graph_template_id.
|
|
*
|
|
* @param int $_local_graph_id The local graph ID.
|
|
* @param int $_graph_template_id The graph template ID.
|
|
* @param string $_graph_title The title of the graph.
|
|
* @param int $_aggregate_template_id The aggregate template ID (optional, default is 0).
|
|
* @param array $graph_data Additional graph data (optional).
|
|
*
|
|
* @return int The ID of the newly inserted graph.
|
|
*/
|
|
function aggregate_graph_save(int $_local_graph_id, int $_graph_template_id, string $_graph_title, int $_aggregate_template_id = 0, array $graph_data = []) : int {
|
|
// suppress warnings
|
|
error_reporting(E_ALL);
|
|
|
|
// install own error handler
|
|
set_error_handler('aggregate_error_handler');
|
|
|
|
cacti_log(__FUNCTION__ . ' local_graph: ' . $_local_graph_id . ' template: ' . $_graph_template_id . ' graph title: ' . $_graph_title . ' aggregate template: ' . $_aggregate_template_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// store basic graph info
|
|
$local_graph_id = aggregate_graph_local_save($_local_graph_id);
|
|
|
|
// store extra graph data
|
|
$graph_templates_graph_id = aggregate_graph_templates_graph_save($local_graph_id, $_graph_template_id, $_graph_title, $_aggregate_template_id, $graph_data);
|
|
|
|
// restore original error handler
|
|
restore_error_handler();
|
|
|
|
// return the id of the newly inserted graph
|
|
return $local_graph_id;
|
|
}
|
|
|
|
/**
|
|
* Creates or updates basic aggregate graph data in graph_local.
|
|
*
|
|
* @param int $id ID of existing aggregate graph if updating or 0 if creating a new one.
|
|
*
|
|
* @return int ID of graph.
|
|
*/
|
|
function aggregate_graph_local_save(int $id = 0) : int {
|
|
cacti_log(__FUNCTION__ . ' local_graph: ' . $id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// create or update entry: graph_local
|
|
$local_graph['id'] = $id;
|
|
$local_graph['graph_template_id'] = 0; // no templating
|
|
$local_graph['host_id'] = 0; // no host to be referred to
|
|
$local_graph['snmp_query_id'] = 0; // no templating
|
|
$local_graph['snmp_index'] = ''; // no templating, may hold string data
|
|
|
|
return sql_save($local_graph, 'graph_local');
|
|
}
|
|
|
|
/**
|
|
* Create or update aggregate graphs data in graph_templates_graph.
|
|
* Graph must already exist in graph_local eg. local_graph_id must never be 0
|
|
*
|
|
* @param int $local_graph_id ID of graph.
|
|
* @param int $graph_template_id Graph template this graph is based on.
|
|
* @param string $graph_title Title of graph. Used only for new graphs.
|
|
* @param int $aggregate_template_id ID of aggregate template this graph is based on (0 if not aggregate template based).
|
|
* @param array $new_data Key/value pairs with new graph data.
|
|
*
|
|
* @return int ID of record in graph_templates_graph
|
|
*/
|
|
function aggregate_graph_templates_graph_save(int $local_graph_id, int $graph_template_id, string $graph_title = '', int $aggregate_template_id = 0, array $new_data = []) : int {
|
|
cacti_log(__FUNCTION__ . ' local_graph: ' . $local_graph_id . ' template: ' . $graph_template_id . ' title: ' . $graph_title . ' aggregate template: ' . $aggregate_template_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// base graph must exist
|
|
if ($local_graph_id < 1) {
|
|
return 0;
|
|
}
|
|
|
|
$graph_data = [];
|
|
$existing_data = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$template_data = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id=0',
|
|
[$graph_template_id]);
|
|
|
|
if ($aggregate_template_id > 0) {
|
|
// override selected fields from template data with aggregate template data
|
|
$aggregate_data = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graph_templates_graph
|
|
WHERE aggregate_template_id = ?',
|
|
[$aggregate_template_id]);
|
|
|
|
if (cacti_sizeof($aggregate_data)) {
|
|
foreach ($aggregate_data as $field => $value) {
|
|
if (str_starts_with($field, 't_') && $value == 'on') {
|
|
$value_field_name = substr($field, 2);
|
|
$template_data[$value_field_name] = $aggregate_data[$value_field_name];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($existing_data) == 0) {
|
|
// this is a new graph, use template data
|
|
$graph_data = $template_data;
|
|
|
|
$graph_data['id'] = 0;
|
|
$graph_data['title'] = $graph_title;
|
|
$graph_data['title_cache'] = $graph_title;
|
|
} elseif ($aggregate_template_id > 0) {
|
|
/* this graph exists and is templated from aggregate template,
|
|
* use template data */
|
|
$graph_data = $template_data;
|
|
|
|
$graph_data['id'] = $existing_data['id'];
|
|
$graph_data['title'] = $existing_data['title'];
|
|
$graph_data['title_cache'] = $existing_data['title_cache'];
|
|
} else {
|
|
/* this is an existing graph and not templated from aggregate,
|
|
* reuse its old data */
|
|
$graph_data = $existing_data;
|
|
}
|
|
|
|
if ($aggregate_template_id == 0) {
|
|
// now use new data
|
|
$graph_data = array_merge($graph_data, $new_data);
|
|
}
|
|
|
|
// safety check - don't allow empty titles
|
|
if ($graph_title != '') {
|
|
$graph_data['title'] = $graph_title;
|
|
$graph_data['title_cache'] = $graph_title;
|
|
}
|
|
|
|
$graph_data['auto_padding'] = 'on';
|
|
$graph_data['local_graph_id'] = $local_graph_id;
|
|
$graph_data['local_graph_template_graph_id'] = 0; // no templating
|
|
$graph_data['graph_template_id'] = 0; // no templating
|
|
|
|
$graph_templates_graph_id = sql_save($graph_data, 'graph_templates_graph');
|
|
|
|
// update title cache
|
|
if (!empty($graph_templates_graph_id)) {
|
|
update_graph_title_cache($local_graph_id);
|
|
}
|
|
|
|
return $graph_templates_graph_id;
|
|
}
|
|
|
|
/**
|
|
* Inserts graph items from an old graph into a new graph.
|
|
*
|
|
* @param int $_new_graph_id The ID of the new graph.
|
|
* @param int $_old_graph_id The ID of the old graph.
|
|
* @param int $_graph_template_id The ID of the graph template.
|
|
* @param array $_skip Array of items to skip.
|
|
* @param array $_totali Array of total items.
|
|
* @param int $_graph_item_sequence The sequence number of the graph item.
|
|
* @param int $_selected_graph_index The index of the selected graph.
|
|
* @param array $_color_templates Array of color templates.
|
|
* @param array $_graph_item_types Array of graph item types.
|
|
* @param array $_cdefs Array of CDEFs.
|
|
* @param int $_graph_type The type of the graph.
|
|
* @param string $_gprint_prefix The prefix for GPRINT.
|
|
* @param string $_gprint_format The format for GPRINT.
|
|
* @param int $_total The total value.
|
|
* @param string $_total_type The type of the total (default is empty string).
|
|
* @param array $member_graphs Array of member graphs (default is empty array).
|
|
*
|
|
* @return int The next sequence number to be filled.
|
|
*/
|
|
function aggregate_graphs_insert_graph_items(int $_new_graph_id, int $_old_graph_id, int $_graph_template_id,
|
|
array $_skip, array $_totali, int $_graph_item_sequence, int $_selected_graph_index, array $_color_templates, array $_graph_item_types, array $_cdefs,
|
|
int $_graph_type, string $_gprint_prefix, string $_gprint_format, int $_total, string $_total_type = '', array $member_graphs = []) : int {
|
|
global $struct_graph_item, $graph_item_types;
|
|
|
|
// Remove filter item
|
|
unset($struct_graph_item['data_template_id']);
|
|
|
|
include_once(CACTI_PATH_LIBRARY . '/api_aggregate.php');
|
|
|
|
// suppress warnings
|
|
error_reporting(E_ALL);
|
|
|
|
// install own error handler
|
|
set_error_handler('aggregate_error_handler');
|
|
|
|
cacti_log(__FUNCTION__ . ' called. Insert example graph:' . $_old_graph_id . ' Graph Template:' . $_graph_template_id . ' into Graph:' . $_new_graph_id . ' at Sequence:' . $_graph_item_sequence . ' Graph_No:' . $_selected_graph_index . ' Type Action: ' . $_graph_type, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
cacti_log(__FUNCTION__ . ' skipping: ' . serialize($_skip), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// take graph item data from old one
|
|
if (!empty($_old_graph_id)) {
|
|
$graph_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$_old_graph_id]);
|
|
|
|
$graph_local = db_fetch_row_prepared('SELECT host_id, graph_template_id,
|
|
snmp_query_id, snmp_index
|
|
FROM graph_local
|
|
WHERE id = ?',
|
|
[$_old_graph_id]);
|
|
} else {
|
|
$graph_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
AND graph_template_id = ?
|
|
ORDER BY sequence',
|
|
[$_old_graph_id, $_graph_template_id]);
|
|
|
|
$graph_local = [];
|
|
}
|
|
|
|
// create new entry(s): graph_templates_item
|
|
$num_items = cacti_sizeof($graph_items);
|
|
|
|
$_hr = [];
|
|
|
|
if ($num_items > 0) {
|
|
// take care of items having a HR that shall be skipped
|
|
$i = 0;
|
|
|
|
for ($i; $i < $num_items; $i++) {
|
|
// remember existing hard returns (array must start at 1 to match $skipped_items
|
|
$_hr[$i + 1] = ($graph_items[$i]['hard_return'] != '');
|
|
}
|
|
// move 'skipped hard returns' to previous graph item
|
|
$_hr = auto_hr($_skip, $_hr);
|
|
|
|
// next entry will have to have a prepended text format
|
|
$prepend = true;
|
|
$prepend_cnt = 0;
|
|
$skip_graph = false;
|
|
$make0_cdef = aggregate_cdef_make0();
|
|
$i = 0;
|
|
|
|
foreach ($graph_items as $graph_item) {
|
|
// loop starts at 0, but $_skip starts at 1, so increment before comparing
|
|
$i++;
|
|
|
|
// go ahead, if this graph item has to be skipped
|
|
if (isset($_skip[$i]) && !empty($_skip[$i])) {
|
|
continue;
|
|
}
|
|
|
|
// HRULES will only appear in the total one way or nother
|
|
if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_HRULE) {
|
|
continue;
|
|
}
|
|
|
|
if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_COMMENT) {
|
|
if (preg_match('/(:bits:|:bytes:|\|sum:)/', $graph_item['text_format'])) {
|
|
// Only skip nth percentile COMMENT values
|
|
$parts = explode('|', $graph_item['text_format']);
|
|
|
|
if (isset($parts[1])) {
|
|
$pparts = explode(':', $parts[1]);
|
|
|
|
if (is_numeric($pparts[0])) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($_total == AGGREGATE_TOTAL_ONLY) {
|
|
// if we only need the totalling legend, ...
|
|
if (($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_LAST) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_MIN) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_MAX) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_AVERAGE) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_TEXTALIGN) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_TIC) ||
|
|
($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_COMMENT)) {
|
|
// and this is a legend entry (GPRINT, COMMENT), skip
|
|
continue;
|
|
} else {
|
|
// this is a graph entry, remove text to make it disappear
|
|
// do NOT skip!
|
|
// we need this entry as a DEF
|
|
// and as long as cacti does not provide for a 'pure DEF' graph item type
|
|
// we need this workaround
|
|
$graph_item['text_format'] = '';
|
|
|
|
// make sure, that this entry does not have a HR,
|
|
// else a single colored mark will be drawn
|
|
$graph_item['hard_return'] = '';
|
|
$_hr[$i] = '';
|
|
|
|
// make sure, that data of this item will be suppressed: make 0!
|
|
$graph_item['cdef_id'] = $make0_cdef;
|
|
|
|
// try to pick the best totaling cf id
|
|
$graph_item['consolidation_function_id'] = db_fetch_cell('SELECT consolidation_function_id
|
|
FROM graph_templates_item
|
|
WHERE color_id > 0
|
|
AND sequence = ' . $graph_item['sequence'] .
|
|
(cacti_sizeof($member_graphs) ? ' AND ' . array_to_sql_or($member_graphs, 'local_graph_id') : '') .
|
|
(cacti_sizeof($_skip) ? ' AND sequence NOT IN (' . implode(',', $_skip) . ')' : '') .
|
|
(cacti_sizeof($_totali) ? ' AND sequence IN (' . implode(',', $_totali) . ')' : '') . '
|
|
ORDER BY sequence ASC
|
|
LIMIT 1');
|
|
}
|
|
} elseif ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_AREA ||
|
|
$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE1 ||
|
|
$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE2 ||
|
|
$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE3 ||
|
|
$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_STACK) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) {
|
|
$graph_item['text_format'] = $_gprint_prefix;
|
|
} elseif ($_gprint_format != '') {
|
|
$graph_item['text_format'] = $_gprint_prefix . ' ' . $graph_item['text_format'];
|
|
} else {
|
|
$graph_item['text_format'] = $_gprint_prefix;
|
|
}
|
|
}
|
|
|
|
// use all data from 'old' graph ...
|
|
$save = $graph_item;
|
|
|
|
// now it's time for some 'special purpose' processing
|
|
// selected fields will need special treatment
|
|
|
|
// take care of color changes only if not set to None
|
|
if (isset($_color_templates[$i])) {
|
|
if ($_color_templates[$i] > 0) {
|
|
// get the size of the color templates array
|
|
// if number of colored items exceed array size, use round robin
|
|
$num_colors = db_fetch_cell_prepared('SELECT COUNT(color_id)
|
|
FROM color_template_items
|
|
WHERE color_template_id = ?',
|
|
[$_color_templates[$i]]);
|
|
|
|
// a color template with no items counts zero, and the round
|
|
// robin below divides by that count. PHP 8 raises
|
|
// DivisionByZeroError where PHP 7 warned and carried on.
|
|
if ($num_colors > 0) {
|
|
// templating required, get color for current graph item.
|
|
// The id is bound, as the count above already does; the
|
|
// offset is arithmetic on two integers, which no
|
|
// placeholder is needed for and MySQL will not bind in
|
|
// a LIMIT on every supported version.
|
|
$offset = $_selected_graph_index % $num_colors;
|
|
|
|
$save['color_id'] = db_fetch_cell_prepared('SELECT color_id
|
|
FROM color_template_items
|
|
WHERE color_template_id = ?
|
|
ORDER BY sequence
|
|
LIMIT ' . $offset . ',1',
|
|
[$_color_templates[$i]]);
|
|
} else {
|
|
$save['color_id'] = $graph_item['color_id'];
|
|
}
|
|
} else {
|
|
// set a color even if no color templating is required
|
|
$save['color_id'] = $graph_item['color_id'];
|
|
}
|
|
} // else: no color templating defined, e.g. GPRINT entry
|
|
|
|
// do we want to override cdef of this item
|
|
// certainly not if it was set to $make0_cdef above
|
|
if ($_cdefs[$i] > 0 && $graph_item['cdef_id'] != $make0_cdef) {
|
|
$save['cdef_id'] = $_cdefs[$i];
|
|
}
|
|
|
|
// take care of the graph_item_type
|
|
// user may want to override types (ex. LINEx to AREA)
|
|
// do this before we try start converting stuff to AREAs and such
|
|
if ($_graph_item_types[$i] > 0) {
|
|
$save['graph_type_id'] = $_graph_item_types[$i];
|
|
} else {
|
|
$save['graph_type_id'] = $graph_item['graph_type_id'];
|
|
}
|
|
|
|
// change graph types, if requested
|
|
$save['graph_type_id'] = aggregate_change_graph_type($_selected_graph_index, $save['graph_type_id'], $_graph_type);
|
|
|
|
// new item text format required?
|
|
if ($_total_type != '') {
|
|
if ($prepend) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL && $prepend_cnt > 0) {
|
|
continue;
|
|
}
|
|
|
|
// pointless to add any data source item name here, cause ALL are totaled
|
|
if ($_gprint_format != '') {
|
|
$save['text_format'] = $graph_item['text_format'];
|
|
} else {
|
|
$save['text_format'] = '';
|
|
}
|
|
|
|
// no more prepending until next line break is encountered
|
|
$prepend = false;
|
|
$prepend_cnt++;
|
|
} elseif (str_contains($save['text_format'], ':current:')) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) {
|
|
$save['text_format'] = str_replace(':current:', ':aggregate_sum:', $save['text_format']);
|
|
} elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) {
|
|
$save['text_format'] = str_replace(':current:', ':aggregate_current:', $save['text_format']);
|
|
} else {
|
|
cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for :current: in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
} elseif (str_contains($save['text_format'], ':max:')) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) {
|
|
$save['text_format'] = str_replace(':max:', ':aggregate_sum_peak:', $save['text_format']);
|
|
} elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) {
|
|
$save['text_format'] = str_replace(':max:', ':aggregate_current_peak:', $save['text_format']);
|
|
} else {
|
|
cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for :max: in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($save['text_format'] != '') {
|
|
$save['text_format'] = substitute_host_data($save['text_format'], '|', '|', ($graph_local['host_id'] ?? 0));
|
|
cacti_log(__FUNCTION__ . ' substituted:' . $save['text_format'], true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// if this is a data query graph type, try to substitute
|
|
if (isset($graph_local['snmp_query_id']) && $graph_local['snmp_query_id'] > 0 && $graph_local['snmp_index'] != '') {
|
|
$max_chars = intval(read_config_option('max_data_query_field_length'));
|
|
|
|
$save['text_format'] = substitute_snmp_query_data($save['text_format'], $graph_local['host_id'], $graph_local['snmp_query_id'], $graph_local['snmp_index'], $max_chars);
|
|
|
|
cacti_log(__FUNCTION__ . ' substituted:' . $save['text_format'] . ' for ' . $graph_local['host_id'] . ',' . $graph_local['snmp_query_id'] . ',' . $graph_local['snmp_index'], true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
}
|
|
}
|
|
|
|
// <HR> wanted?
|
|
if (isset($_hr[$i]) && $_hr[$i] > 0) {
|
|
$save['hard_return'] = 'on';
|
|
}
|
|
|
|
// if this item defines a line break, remember to prepend next line
|
|
if ($save['text_format'] != '') {
|
|
$prepend = ($save['hard_return'] == 'on');
|
|
}
|
|
|
|
// provide new sequence number
|
|
$save['sequence'] = $_graph_item_sequence;
|
|
cacti_log(__FUNCTION__ . ' hard return: ' . $save['hard_return'] . ' sequence: ' . $_graph_item_sequence, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
$save['id'] = 0;
|
|
$save['local_graph_template_item_id'] = 0; // disconnect this graph item from the graph template item
|
|
$save['local_graph_id'] = $_new_graph_id;
|
|
$save['graph_template_id'] = 0; // disconnect this graph item from the graph template
|
|
$save['hash'] = ''; // remove any template attribs
|
|
|
|
$graph_item_mappings[$graph_item['id']] = sql_save($save, 'graph_templates_item');
|
|
|
|
$_graph_item_sequence++;
|
|
}
|
|
}
|
|
|
|
// restore original error handler
|
|
restore_error_handler();
|
|
|
|
// return with next sequence number to be filled
|
|
return $_graph_item_sequence;
|
|
}
|
|
|
|
/**
|
|
* Insert or update aggregate graph items in DB tables
|
|
*
|
|
* @param array $items
|
|
* @param string $table
|
|
* @return bool true if save was successful, false otherwise
|
|
*/
|
|
function aggregate_graph_items_save(array $items, string $table) : bool {
|
|
$defaults = [];
|
|
|
|
if ($table == 'aggregate_graphs_graph_item') {
|
|
$defaults['aggregate_graph_id'] = null;
|
|
$id_field = 'aggregate_graph_id';
|
|
} elseif ($table == 'aggregate_graph_templates_item') {
|
|
$defaults['aggregate_template_id'] = null;
|
|
$id_field = 'aggregate_template_id';
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$defaults['graph_templates_item_id'] = null;
|
|
$defaults['sequence'] = 0;
|
|
$defaults['color_template'] = 0;
|
|
$defaults['t_graph_type_id'] = '';
|
|
$defaults['graph_type_id'] = 0;
|
|
$defaults['t_cdef_id'] = '';
|
|
$defaults['cdef_id'] = 0;
|
|
$defaults['item_skip'] = '';
|
|
$defaults['item_total'] = '';
|
|
|
|
$items_sql = [];
|
|
|
|
foreach ($items as $item) {
|
|
// substitute any missing fields with defaults
|
|
$item = array_merge($defaults, $item);
|
|
|
|
// remove any extra fields
|
|
$item = array_intersect_key($item, $defaults);
|
|
|
|
// without these graph item makes no sense
|
|
if (!isset($item[$id_field]) || !isset($item['graph_templates_item_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// convert to partial SQL statement
|
|
$items_sql[] = sprintf(
|
|
' (%d, %d, %d, %d, %s, %d, %s, %d, %s, %s)',
|
|
$item[$id_field],
|
|
$item['graph_templates_item_id'],
|
|
$item['sequence'],
|
|
$item['color_template'],
|
|
db_qstr($item['t_graph_type_id']),
|
|
$item['graph_type_id'],
|
|
db_qstr($item['t_cdef_id']),
|
|
$item['cdef_id'],
|
|
db_qstr($item['item_skip']),
|
|
db_qstr($item['item_total'])
|
|
);
|
|
}
|
|
|
|
$sql = 'INSERT INTO ' . $table;
|
|
$sql .= '(' . implode(', ', array_keys($defaults)) . ') VALUES ';
|
|
$sql .= implode(', ', $items_sql);
|
|
|
|
cacti_log(__FUNCTION__ . ' called. SQL: ' . $sql, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// remove all old items
|
|
if (isset($items[0][$id_field])) {
|
|
db_execute("DELETE FROM $table WHERE " . $id_field . '=' . $items[0][$id_field]);
|
|
}
|
|
|
|
if (db_execute($sql) == 1) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate extra graph parameters posted from graph edit form.
|
|
* You can check for validation errors with cacti function is_error_message
|
|
*
|
|
* @param array $posted values posted from form
|
|
* @param bool $has_override form had override checkboxes
|
|
*
|
|
* @return array - cleaned up graph parameters
|
|
*/
|
|
function aggregate_validate_graph_params(array $posted, bool $has_override = false) : array {
|
|
$check_post_params = [
|
|
'alt_y_grid' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'auto_padding' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'auto_scale' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'auto_scale_log' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'auto_scale_opts' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => ''],
|
|
'auto_scale_rigid' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'base_value' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^[0-9]+$'],
|
|
'dynamic_labels' => ['type' => 'str', 'allow_empty' => true, 'default' => 0, 'regex' => ''],
|
|
'force_rules_legend' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'grouping' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'height' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => '^[0-9]+$'],
|
|
'image_format_id' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => ''],
|
|
'left_axis_format' => ['type' => 'int', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'],
|
|
'left_axis_formatter' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'legend_direction' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'legend_position' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'lower_limit' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?)|U$'],
|
|
'no_gridfit' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'right_axis' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'right_axis_format' => ['type' => 'int', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'],
|
|
'right_axis_formatter' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'right_axis_label_angle' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'right_axis_range' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'vertical_label_angle' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'right_axis_label' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'scale_log_units' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'slope_mode' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'tab_width' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'],
|
|
'unit_exponent_value' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'unit_length' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'unit_value' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'upper_limit' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?)|U$'],
|
|
'vertical_label' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''],
|
|
'width' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => '^[0-9]+$']
|
|
];
|
|
|
|
$params_new = [];
|
|
|
|
// validate posted form fields
|
|
foreach ($check_post_params as $field => $defs) {
|
|
if ($has_override && !isset($posted['t_' . $field])) {
|
|
// override checkbox off - use default value
|
|
$params_new['t_' . $field] = '';
|
|
$params_new[$field] = $defs['default'];
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($has_override) {
|
|
// override checkbox was on
|
|
$params_new['t_' . $field] = 'on';
|
|
}
|
|
|
|
// validate value
|
|
if (in_array($field, ['vertical_label_angle', 'right_axis_label_angle', 'right_axis_range'], true)) {
|
|
$params_new[$field] = CactiValidator::validateRrdGraphAxisInput($posted[$field] ?? '', $field);
|
|
} elseif ($defs['type'] == 'bool') {
|
|
$params_new[$field] = (isset($posted[$field])) ? 'on' : '';
|
|
} else {
|
|
$params_new[$field] = (isset($posted[$field]) ? form_input_validate(htmle($posted[$field]), $field, $defs['regex'], $defs['allow_empty'], 3) : $defs['default']);
|
|
}
|
|
}
|
|
|
|
return $params_new;
|
|
}
|
|
|
|
/**
|
|
* Populate graph items array with posted values.
|
|
* $graph_items array must be keyed on graph item id.
|
|
*
|
|
* @param array $posted Values posted from form
|
|
* @param array $graph_items Reference to graph items array to update with form values
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_validate_graph_items(array $posted, array &$graph_items) : void {
|
|
foreach ($posted as $var => $val) {
|
|
// work on color_templates
|
|
if (preg_match('/^agg_color_([0-9]+)$/', $var, $matches)) {
|
|
// ================= input validation =================
|
|
input_validate_input_number($matches[1], 'agg_color');
|
|
// ====================================================
|
|
$graph_templates_item_id = str_replace('agg_color_', '', $var);
|
|
|
|
if (isset($graph_items[$graph_templates_item_id])) {
|
|
$graph_items[$graph_templates_item_id]['color_template'] = $val;
|
|
} else {
|
|
cacti_log('Something fubar in agg_color');
|
|
}
|
|
}
|
|
|
|
// work on checkboxed for skipping items
|
|
if (preg_match('/^agg_skip_([0-9]+)$/', $var, $matches)) {
|
|
// ================= input validation =================
|
|
input_validate_input_number($matches[1], 'agg_skip');
|
|
// ====================================================
|
|
$graph_templates_item_id = str_replace('agg_skip_', '', $var);
|
|
|
|
if (isset($graph_items[$graph_templates_item_id])) {
|
|
$graph_items[$graph_templates_item_id]['item_skip'] = $val;
|
|
} else {
|
|
cacti_log('Something fubar in agg_skip');
|
|
}
|
|
}
|
|
|
|
// work on checkboxed for totalling items
|
|
if (preg_match('/^agg_total_([0-9]+)$/', $var, $matches)) {
|
|
// ================= input validation =================
|
|
input_validate_input_number($matches[1], 'agg_total');
|
|
// ====================================================
|
|
$graph_templates_item_id = str_replace('agg_total_', '', $var);
|
|
|
|
if (isset($graph_items[$graph_templates_item_id])) {
|
|
$graph_items[$graph_templates_item_id]['item_total'] = $val;
|
|
} else {
|
|
cacti_log('Something fubar in agg_total');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleans up aggregated graphs.
|
|
*
|
|
* @param int $base The base graph ID.
|
|
* @param int $aggregate The aggregate graph ID.
|
|
* @param int $reorder The reorder flag.
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_graphs_cleanup(int $base, int $aggregate, int $reorder) : void {
|
|
include_once(CACTI_PATH_LIBRARY . '/api_aggregate.php');
|
|
|
|
cacti_log(__FUNCTION__ . ' called. Base ' . $base . ' Aggregate ' . $aggregate . ' Reorder: ' . $reorder, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// suppress warnings
|
|
error_reporting(E_ALL);
|
|
|
|
// install own error handler
|
|
set_error_handler('aggregate_error_handler');
|
|
|
|
// restore original error handler
|
|
restore_error_handler();
|
|
}
|
|
|
|
/**
|
|
* Reorders the data source graph for a given aggregate.
|
|
*
|
|
* @param int $base The base ID for the operation.
|
|
* @param string $graph_template_id The ID of the graph template.
|
|
* @param int $aggregate The aggregate ID.
|
|
* @param int $reorder The new order position.
|
|
* @param int $graph_type The type of the graph.
|
|
*
|
|
* @return bool
|
|
*/
|
|
function aggregate_reorder_ds_graph(int $base, string $graph_template_id, int $aggregate, int $reorder, int $graph_type) : bool {
|
|
cacti_log(__FUNCTION__ . ' called. Base Graph ' . $base . ' Graph Template ' . $graph_template_id . ' Aggregate Graph ' . $aggregate . ' Reorder: ' . $reorder, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// suppress warnings
|
|
error_reporting(E_ALL);
|
|
|
|
// install own error handler
|
|
set_error_handler('aggregate_error_handler');
|
|
|
|
$new_seq = 1;
|
|
$base_handler = false;
|
|
$updates = [];
|
|
|
|
// Get the order of items to re-arrange on the graph
|
|
if ($reorder == AGGREGATE_ORDER_NONE) {
|
|
restore_error_handler();
|
|
|
|
return true;
|
|
}
|
|
|
|
$sql_order = '';
|
|
|
|
if ($reorder == AGGREGATE_ORDER_DS_GRAPH) {
|
|
$sql_order = 'dtr.data_source_name, gti.sequence';
|
|
} elseif ($reorder == AGGREGATE_ORDER_BASE_GRAPH) {
|
|
$base_handler = true;
|
|
} else {
|
|
$sql_order = 'gtg.title_cache, gti.sequence';
|
|
}
|
|
|
|
if ($base_handler) {
|
|
/* get all different local_data_template_rrd_id's
|
|
* respecting the order that the aggregated graph has
|
|
*/
|
|
$sql_where = "WHERE gti.local_graph_id = $base" . ($base == 0 ? " AND gti.graph_template_id = $graph_template_id" : '');
|
|
$sql_id_column = ($base == 0 ? 'id' : 'local_data_template_rrd_id');
|
|
$sql = "SELECT DISTINCT dtr.$sql_id_column AS local_data_template_rrd_id
|
|
FROM data_template_rrd AS dtr
|
|
LEFT JOIN graph_templates_item AS gti
|
|
ON gti.task_item_id = dtr.id
|
|
$sql_where
|
|
ORDER BY gti.sequence";
|
|
|
|
$ds_ids = db_fetch_assoc($sql);
|
|
|
|
foreach ($ds_ids as $ds_id) {
|
|
cacti_log('local_data_template_rrd_id: ' . $ds_id['local_data_template_rrd_id'], false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
/* get all different task_item_id's
|
|
* respecting the order that the aggregated graph has
|
|
*/
|
|
$sql = "SELECT gti.id, gti.task_item_id
|
|
FROM graph_templates_item AS gti
|
|
LEFT JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id = dtr.id
|
|
WHERE gti.local_graph_id = $aggregate
|
|
AND dtr.local_data_template_rrd_id = " . $ds_id['local_data_template_rrd_id'] . '
|
|
ORDER BY sequence';
|
|
|
|
cacti_log(__FUNCTION__ . ' sql: ' . $sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
$items = db_fetch_assoc($sql);
|
|
|
|
foreach ($items as $item) {
|
|
// accumulate the updates to avoid interfering the next loops
|
|
$updates[] = 'UPDATE graph_templates_item SET sequence = ' . $new_seq++ . ' WHERE id = ' . $item['id'];
|
|
}
|
|
}
|
|
} else {
|
|
$aggregate_graph_id = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$aggregate]);
|
|
|
|
$list = array_rekey(
|
|
db_fetch_assoc_prepared("SELECT DISTINCT dtr.local_data_id
|
|
FROM data_template_rrd AS dtr
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gti.task_item_id = dtr.id
|
|
INNER JOIN aggregate_graphs_items AS agi
|
|
ON gti.local_graph_id = agi.local_graph_id
|
|
INNER JOIN graph_templates_graph AS gtg
|
|
ON gtg.local_graph_id = gti.local_graph_id
|
|
WHERE agi.aggregate_graph_id = ?
|
|
ORDER BY $sql_order",
|
|
[$aggregate_graph_id]),
|
|
'local_data_id', 'local_data_id'
|
|
);
|
|
|
|
if (cacti_sizeof($list)) {
|
|
$sql_order = 'FIELD(dtr.local_data_id, ' . implode(', ', $list) . '), gti.sequence';
|
|
}
|
|
|
|
$sql = "SELECT gti.id, gti.task_item_id, graph_type_id
|
|
FROM graph_templates_item AS gti
|
|
LEFT JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id = dtr.id
|
|
WHERE gti.local_graph_id = $aggregate
|
|
AND dtr.local_data_id IN (" . implode(', ', $list) . ")
|
|
ORDER BY $sql_order";
|
|
|
|
if ($reorder != AGGREGATE_ORDER_NONE && $reorder != AGGREGATE_ORDER_DS_GRAPH) {
|
|
$color_ids = db_fetch_assoc("SELECT color_id
|
|
FROM graph_templates_item AS gti
|
|
LEFT JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id = dtr.id
|
|
WHERE gti.local_graph_id = $aggregate
|
|
AND dtr.local_data_id IN (" . implode(', ', $list) . ')
|
|
ORDER BY gti.sequence');
|
|
} else {
|
|
$color_ids = [];
|
|
}
|
|
|
|
cacti_log(__FUNCTION__ . ' sql: ' . $sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
$items = db_fetch_assoc($sql);
|
|
$i = 0;
|
|
|
|
foreach ($items as $item) {
|
|
$new_graph_type = aggregate_change_graph_type($i, $item['graph_type_id'], $graph_type);
|
|
|
|
if (isset($color_ids[$i])) {
|
|
$color_id = $color_ids[$i]['color_id'];
|
|
} else {
|
|
$color_id = '';
|
|
}
|
|
|
|
// accumulate the updates to avoid interfering the next loops
|
|
$updates[] = 'UPDATE graph_templates_item
|
|
SET sequence = ' . $new_seq . ",
|
|
graph_type_id = $new_graph_type " .
|
|
($color_id != '' ? ', color_id = ' . $color_id : '') . '
|
|
WHERE id = ' . $item['id'];
|
|
|
|
$i++;
|
|
$new_seq++;
|
|
}
|
|
}
|
|
|
|
// now get all 'empty' local_data_template_rrd_id's
|
|
// = those graph items without associated data source (e.g. COMMENT)
|
|
$sql = "SELECT id
|
|
FROM graph_templates_item AS gti
|
|
WHERE gti.local_graph_id = $aggregate
|
|
AND gti.task_item_id = 0
|
|
ORDER BY sequence";
|
|
|
|
cacti_log($sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
$empty_task_items = db_fetch_assoc($sql);
|
|
|
|
// now add those 'empty' one's to the end
|
|
foreach ($empty_task_items as $item) {
|
|
// accumulate the updates to avoid interfering the next loops
|
|
$updates[] = 'UPDATE graph_templates_item SET sequence = ' . $new_seq++ . ' WHERE id = ' . $item['id'];
|
|
}
|
|
|
|
// now run all updates
|
|
if (cacti_sizeof($updates)) {
|
|
foreach ($updates as $update) {
|
|
cacti_log(__FUNCTION__ . ' update: ' . $update, false, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
db_execute($update);
|
|
}
|
|
}
|
|
|
|
// restore original error handler
|
|
restore_error_handler();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Pushes out aggregate graph data based on the provided aggregate template ID or local graph ID.
|
|
*
|
|
* @param int $aggregate_template_id The ID of the aggregate template to use for generating the graph data.
|
|
* @param int $local_graph_id The ID of the local graph to use for generating the graph data. Default is 0.
|
|
*
|
|
* @return bool True when every selected aggregate was updated.
|
|
*/
|
|
function push_out_aggregates(int $aggregate_template_id, int $local_graph_id = 0) : bool {
|
|
$attribs = [];
|
|
$attribs['skipped_items'] = [];
|
|
$attribs['total_items'] = [];
|
|
$attribs['color_templates'] = [];
|
|
$attribs['graph_item_types'] = [];
|
|
$attribs['cdefs'] = [];
|
|
$member_graphs = [];
|
|
|
|
if ($local_graph_id > 0 && $aggregate_template_id == 0) {
|
|
$id = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$attribs['graph_title'] = db_fetch_cell_prepared('SELECT title_format
|
|
FROM aggregate_graphs
|
|
WHERE id = ?',
|
|
[$id]);
|
|
|
|
$attribs['skipped_items'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE item_skip="on"
|
|
AND aggregate_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$id]),
|
|
'sequence', 'sequence'
|
|
);
|
|
|
|
$attribs['total_items'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE item_total="on"
|
|
AND aggregate_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$id]),
|
|
'sequence', 'sequence'
|
|
);
|
|
|
|
$attribs['color_templates'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, color_template
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE color_template>=0
|
|
AND aggregate_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$id]),
|
|
'sequence', 'color_template'
|
|
);
|
|
|
|
$attribs['graph_item_types'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, graph_type_id
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE t_graph_type_id="on"
|
|
AND aggregate_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$id]),
|
|
'sequence', 'graph_type_id'
|
|
);
|
|
|
|
$attribs['cdefs'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, cdef_id
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE t_cdef_id="on"
|
|
AND aggregate_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$id]),
|
|
'sequence', 'cdef_id'
|
|
);
|
|
|
|
$attribs['aggregate_graph_id'] = $aggregate_template_id;
|
|
$attribs['template_propogation'] = '';
|
|
|
|
$template_data = db_fetch_row_prepared('SELECT * FROM aggregate_graphs WHERE id = ?', [$id]);
|
|
$attribs['graph_template_id'] = $template_data['graph_template_id'];
|
|
$attribs['gprint_prefix'] = $template_data['gprint_prefix'];
|
|
$attribs['gprint_format'] = $template_data['gprint_format'];
|
|
$attribs['graph_type'] = $template_data['graph_type'];
|
|
$attribs['total'] = $template_data['total'];
|
|
$attribs['total_type'] = $template_data['total_type'];
|
|
$attribs['total_prefix'] = $template_data['total_prefix'];
|
|
$attribs['reorder'] = $template_data['order_type'];
|
|
$attribs['item_no'] = db_fetch_cell_prepared('SELECT COUNT(*) FROM aggregate_graphs_graph_item WHERE aggregate_graph_id = ?', [$id]);
|
|
} else {
|
|
$attribs['graph_title'] = '';
|
|
|
|
$attribs['skipped_items'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence
|
|
FROM aggregate_graph_templates_item
|
|
WHERE item_skip="on"
|
|
AND aggregate_template_id = ?
|
|
ORDER BY sequence',
|
|
[$aggregate_template_id]),
|
|
'sequence', 'sequence'
|
|
);
|
|
|
|
$attribs['total_items'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence
|
|
FROM aggregate_graph_templates_item
|
|
WHERE item_total="on"
|
|
AND aggregate_template_id = ?
|
|
ORDER BY sequence',
|
|
[$aggregate_template_id]),
|
|
'sequence', 'sequence'
|
|
);
|
|
|
|
$attribs['color_templates'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, color_template
|
|
FROM aggregate_graph_templates_item
|
|
WHERE color_template>=0
|
|
AND aggregate_template_id = ?
|
|
ORDER BY sequence',
|
|
[$aggregate_template_id]),
|
|
'sequence', 'color_template'
|
|
);
|
|
|
|
$attribs['graph_item_types'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, graph_type_id
|
|
FROM aggregate_graph_templates_item
|
|
WHERE t_graph_type_id="on"
|
|
AND aggregate_template_id = ?
|
|
ORDER BY sequence',
|
|
[$aggregate_template_id]),
|
|
'sequence', 'graph_type_id'
|
|
);
|
|
|
|
$attribs['cdefs'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sequence, cdef_id
|
|
FROM aggregate_graph_templates_item
|
|
WHERE t_cdef_id="on"
|
|
AND aggregate_template_id = ?
|
|
ORDER BY sequence',
|
|
[$aggregate_template_id]),
|
|
'sequence', 'cdef_id'
|
|
);
|
|
|
|
$attribs['aggregate_template_id'] = $aggregate_template_id;
|
|
|
|
$template_data = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graph_templates
|
|
WHERE id = ?',
|
|
[$aggregate_template_id]);
|
|
|
|
$attribs['template_propogation'] = 'on';
|
|
$attribs['graph_template_id'] = $template_data['graph_template_id'];
|
|
$attribs['gprint_prefix'] = $template_data['gprint_prefix'];
|
|
$attribs['gprint_format'] = $template_data['gprint_format'];
|
|
$attribs['graph_type'] = $template_data['graph_type'];
|
|
$attribs['total'] = $template_data['total'];
|
|
$attribs['total_type'] = $template_data['total_type'];
|
|
$attribs['total_prefix'] = $template_data['total_prefix'];
|
|
$attribs['reorder'] = $template_data['order_type'];
|
|
|
|
$attribs['item_no'] = db_fetch_cell_prepared('SELECT COUNT(*)
|
|
FROM aggregate_graph_templates_item
|
|
WHERE aggregate_template_id = ?',
|
|
[$aggregate_template_id]);
|
|
}
|
|
|
|
$aggregate_graphs = [];
|
|
|
|
if ($local_graph_id > 0) {
|
|
$aggregate_graphs[] = $local_graph_id;
|
|
} else {
|
|
$graphs = db_fetch_assoc_prepared('SELECT local_graph_id
|
|
FROM aggregate_graphs
|
|
WHERE aggregate_template_id = ?',
|
|
[$aggregate_template_id]);
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $g) {
|
|
$aggregate_graphs[] = $g['local_graph_id'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($aggregate_graphs)) {
|
|
foreach ($aggregate_graphs as $ag) {
|
|
$member_graphs = [];
|
|
|
|
$graphs = db_fetch_assoc_prepared('SELECT DISTINCT agi.local_graph_id
|
|
FROM aggregate_graphs AS ag
|
|
INNER JOIN aggregate_graphs_items AS agi
|
|
ON ag.id=agi.aggregate_graph_id
|
|
WHERE ag.local_graph_id = ?',
|
|
[$ag]);
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $mg) {
|
|
$member_graphs[] = $mg['local_graph_id'];
|
|
}
|
|
|
|
if (!aggregate_create_update($ag, $member_graphs, $attribs)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates or updates an aggregate based on criteria
|
|
*
|
|
* @param int $local_graph_id The ID of the local graph to create or update.
|
|
* @param array $member_graphs An array of member graphs to include in the aggregate.
|
|
* @param array $attribs An array of attributes for the aggregate graph.
|
|
* @param bool $manage_transaction Start and finish the transaction in this function.
|
|
*
|
|
* @return bool True when the aggregate update was committed.
|
|
*/
|
|
function aggregate_create_update(int &$local_graph_id, array $member_graphs, array $attribs, bool $manage_transaction = true) : bool {
|
|
cacti_log(__FUNCTION__ . ' called. Graph id: ' . $local_graph_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// suppress warnings
|
|
error_reporting(E_ALL);
|
|
|
|
// install own error handler
|
|
set_error_handler('aggregate_error_handler');
|
|
$original_local_graph_id = $local_graph_id;
|
|
|
|
if ($manage_transaction && !db_begin_transaction()) {
|
|
raise_message('aggregate_transaction_failed', __('Unable to start the aggregate graph update.'), MESSAGE_LEVEL_ERROR);
|
|
restore_error_handler();
|
|
|
|
return false;
|
|
}
|
|
|
|
if (cacti_sizeof($member_graphs)) {
|
|
$graph_title = ($attribs['graph_title'] ?? '');
|
|
$aggregate_template = ($attribs['aggregate_template_id'] ?? 0);
|
|
$graph_template_id = ($attribs['graph_template_id'] ?? 0);
|
|
$aggregate_graph = ($attribs['aggregate_graph_id'] ?? 0);
|
|
$template_propogation = ($attribs['template_propogation'] ?? 'on');
|
|
$gprint_prefix = ($attribs['gprint_prefix'] ?? '');
|
|
$gprint_format = ($attribs['gprint_format'] ?? '');
|
|
$_graph_type = ($attribs['graph_type'] ?? 0);
|
|
$_total = ($attribs['total'] ?? 0);
|
|
$_total_type = ($attribs['total_type'] ?? 0);
|
|
$_total_prefix = ($attribs['total_prefix'] ?? '');
|
|
$_reorder = ($attribs['reorder'] ?? 0);
|
|
$item_no = ($attribs['item_no'] ?? 0);
|
|
$color_templates = (is_array($attribs['color_templates']) ? $attribs['color_templates'] : []);
|
|
$graph_item_types = (is_array($attribs['graph_item_types']) ? $attribs['graph_item_types'] : []);
|
|
$cdefs = (is_array($attribs['cdefs']) ? $attribs['cdefs'] : []);
|
|
$skipped_items = (is_array($attribs['skipped_items']) ? $attribs['skipped_items'] : []);
|
|
$total_items = (is_array($attribs['total_items']) ? $attribs['total_items'] : []);
|
|
$example_graph_id = 0;
|
|
|
|
// save the aggregate information
|
|
$save1 = [];
|
|
|
|
if ($local_graph_id == 0) {
|
|
// create new graph based on first graph selected
|
|
$local_graph_id = aggregate_graph_save($example_graph_id, $graph_template_id, $graph_title, $aggregate_template);
|
|
$save1['id'] = '';
|
|
$new_aggregate = true;
|
|
} else {
|
|
// update graph params of existing aggregate graph
|
|
$local_graph_id = aggregate_graph_save($local_graph_id, $graph_template_id, $graph_title, $aggregate_template);
|
|
|
|
$save1['id'] = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$new_aggregate = false;
|
|
}
|
|
|
|
$save1['aggregate_template_id'] = $aggregate_template;
|
|
$save1['template_propogation'] = $template_propogation;
|
|
|
|
if ($graph_title != '') {
|
|
$save1['title_format'] = $graph_title;
|
|
} else {
|
|
$save1['title_format'] = 'New Graph';
|
|
}
|
|
|
|
$save1['local_graph_id'] = $local_graph_id;
|
|
$save1['graph_template_id'] = $graph_template_id;
|
|
$save1['gprint_prefix'] = $gprint_prefix;
|
|
$save1['gprint_format'] = $gprint_format;
|
|
$save1['graph_type'] = $_graph_type;
|
|
$save1['total'] = $_total;
|
|
$save1['total_type'] = $_total_type;
|
|
$save1['total_prefix'] = $_total_prefix;
|
|
$save1['order_type'] = $_reorder;
|
|
$save1['user_id'] = $_SESSION[SESS_USER_ID];
|
|
$aggregate_graph_id = sql_save($save1, 'aggregate_graphs');
|
|
|
|
// sequence number of next graph item to be added, index starts at 1
|
|
$next_item_sequence = 1;
|
|
$j = 1;
|
|
$i = 0;
|
|
|
|
// remove all old graph items first
|
|
if ($local_graph_id > 0) {
|
|
db_execute_prepared('DELETE FROM graph_templates_item
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
}
|
|
|
|
/* now add the graphs one by one to the newly created graph
|
|
* program flow is governed by
|
|
* - totalling
|
|
* - new graph type: convert graph to e.g. AREA
|
|
*/
|
|
// loop for all selected graphs
|
|
foreach ($member_graphs as $graph_id) {
|
|
// insert all graph items of selected graph
|
|
// next items to be inserted have to be in sequence
|
|
$next_item_sequence = aggregate_graphs_insert_graph_items(
|
|
$local_graph_id,
|
|
$graph_id,
|
|
$graph_template_id,
|
|
$skipped_items,
|
|
$total_items,
|
|
$next_item_sequence,
|
|
$i,
|
|
$color_templates,
|
|
$graph_item_types,
|
|
$cdefs,
|
|
$_graph_type,
|
|
$gprint_prefix,
|
|
$gprint_format,
|
|
$_total,
|
|
'',
|
|
$member_graphs);
|
|
|
|
db_execute_prepared('REPLACE INTO aggregate_graphs_items
|
|
(aggregate_graph_id, local_graph_id, sequence)
|
|
VALUES (?, ?, ?)',
|
|
[$aggregate_graph_id, $graph_id, $j]);
|
|
|
|
$j++;
|
|
$i++;
|
|
}
|
|
|
|
cacti_log(__FUNCTION__ . ' all items inserted, next item seq: ' . $next_item_sequence . ' selGraph: ' . $i, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
/* post processing for pure LINEx graphs
|
|
* if we convert to AREA/STACK, the function aggregate_graphs_insert_graph_items
|
|
* created a pure STACK graph (see comments in that function)
|
|
* so let's find out, if we have a pure STACK now ...
|
|
*/
|
|
if (aggregate_is_pure_stacked_graph($local_graph_id)) {
|
|
// ... and convert to AREA
|
|
aggregate_conditional_convert_graph_type($local_graph_id, GRAPH_ITEM_TYPE_STACK, GRAPH_ITEM_TYPE_AREA);
|
|
}
|
|
|
|
if (aggregate_is_stacked_graph($local_graph_id)) {
|
|
/* reorder graph items, if requested
|
|
* for STACKed graphs, reorder before adding totals */
|
|
aggregate_reorder_ds_graph(
|
|
$example_graph_id,
|
|
$graph_template_id,
|
|
$local_graph_id,
|
|
$_reorder,
|
|
$_graph_type);
|
|
}
|
|
|
|
$_orig_graph_type = $_graph_type;
|
|
|
|
// special code to add totalling graph items
|
|
switch ($_total) {
|
|
case AGGREGATE_TOTAL_NONE: // no totalling
|
|
// do NOT add any totalling items
|
|
|
|
break;
|
|
case AGGREGATE_TOTAL_ALL: // any totalling option was selected ...
|
|
$_graph_type = GRAPH_ITEM_TYPE_LINE1;
|
|
|
|
$cf_id = db_fetch_cell_prepared('SELECT consolidation_function_id
|
|
FROM graph_templates_item
|
|
WHERE color_id > 0' .
|
|
(cacti_sizeof($member_graphs) > 0 ? ' AND ' . array_to_sql_or($member_graphs, 'local_graph_id') : '') .
|
|
(cacti_sizeof($skipped_items) > 0 ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . '
|
|
ORDER BY sequence ASC
|
|
LIMIT 1', array_values($skipped_items));
|
|
|
|
// add an empty line before total items
|
|
db_execute_prepared("INSERT INTO graph_templates_item
|
|
(local_graph_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence)
|
|
VALUES (?, 1, ?, '', '', 'on', 2, ?)", [$local_graph_id, $cf_id, $next_item_sequence++]);
|
|
|
|
// Fall through: all totals also need the common totalling graph items.
|
|
case AGGREGATE_TOTAL_ONLY:
|
|
// use the prefix for totalling GPRINTs as given by the user
|
|
switch ($_total_type) {
|
|
case AGGREGATE_TOTAL_TYPE_SIMILAR:
|
|
case AGGREGATE_TOTAL_TYPE_ALL:
|
|
$gprint_prefix = $_total_prefix;
|
|
|
|
break;
|
|
}
|
|
|
|
// now skip all items, that are
|
|
// - explicitly marked as skipped (based on $skipped_items)
|
|
// - OR NOT marked as 'totalling' items
|
|
for ($k = 1; $k <= $item_no; $k++) {
|
|
cacti_log(__FUNCTION__ . ' old skip: ' . ($skipped_items[$k] ?? ''), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// skip all items, that shall not be totalled
|
|
if (!isset($total_items[$k])) {
|
|
$skipped_items[$k] = $k;
|
|
}
|
|
|
|
cacti_log(__FUNCTION__ . ' new skip: ' . ($skipped_items[$k] ?? ''), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
|
|
// add the 'templating' graph to the new graph, honoring skipped, hr and color
|
|
aggregate_graphs_insert_graph_items(
|
|
$local_graph_id,
|
|
$example_graph_id,
|
|
$graph_template_id,
|
|
$skipped_items,
|
|
$total_items,
|
|
$next_item_sequence,
|
|
$i,
|
|
$color_templates,
|
|
$graph_item_types,
|
|
$cdefs,
|
|
$_graph_type, // TODO: user may choose LINEx instead of assuming LINE1
|
|
$gprint_prefix,
|
|
$gprint_format,
|
|
AGGREGATE_TOTAL_ALL, // now add the totalling line(s)
|
|
$_total_type,
|
|
$member_graphs);
|
|
|
|
// now pay attention to CDEFs
|
|
// next_item_sequence still points to the first totalling graph item
|
|
if (!aggregate_cdef_totalling(
|
|
$local_graph_id,
|
|
$next_item_sequence,
|
|
$_total_type)) {
|
|
if ($manage_transaction) {
|
|
db_rollback_transaction();
|
|
$local_graph_id = $original_local_graph_id;
|
|
}
|
|
raise_message('aggregate_invalid_cdef', __('Unable to create aggregate totals because a referenced CDEF is invalid or empty.'), MESSAGE_LEVEL_ERROR);
|
|
restore_error_handler();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* post processing for pure LINEx graphs
|
|
* if we convert to AREA/STACK, the function aggregate_graphs_insert_graph_items
|
|
* created a pure STACK graph (see comments in that function)
|
|
* so let's find out, if we have a pure STACK now ...
|
|
*/
|
|
if (aggregate_is_pure_stacked_graph($local_graph_id)) {
|
|
// ... and convert to AREA
|
|
aggregate_conditional_convert_graph_type($local_graph_id, GRAPH_ITEM_TYPE_STACK, GRAPH_ITEM_TYPE_AREA);
|
|
}
|
|
|
|
if (!aggregate_is_stacked_graph($local_graph_id)) {
|
|
/* reorder graph items, if requested
|
|
* for non-STACKed graphs, we want to reorder the totals as well */
|
|
aggregate_reorder_ds_graph(
|
|
$example_graph_id,
|
|
$graph_template_id,
|
|
$local_graph_id,
|
|
$_reorder,
|
|
$_graph_type);
|
|
}
|
|
|
|
// Handle stacked lines properly
|
|
aggregate_handle_stacked_lines($local_graph_id, $_orig_graph_type, $_total, $_total_type, $_total_prefix);
|
|
|
|
// Handle aggregate nth percentiles
|
|
if ($_total != AGGREGATE_TOTAL_NONE) {
|
|
aggregate_handle_ptile_type($member_graphs, $skipped_items, $local_graph_id, $_total, $_total_type);
|
|
}
|
|
}
|
|
|
|
if ($manage_transaction && !db_commit_transaction()) {
|
|
db_rollback_transaction();
|
|
$local_graph_id = $original_local_graph_id;
|
|
raise_message('aggregate_commit_failed', __('Unable to commit the aggregate graph update.'), MESSAGE_LEVEL_ERROR);
|
|
restore_error_handler();
|
|
|
|
return false;
|
|
}
|
|
|
|
// restore original error handler
|
|
restore_error_handler();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handles the percentile type aggregation for member graphs.
|
|
*
|
|
* @param array $member_graphs An array of member graphs to be aggregated.
|
|
* @param array $skipped_items An array of items that were skipped during the aggregation process.
|
|
* @param int $local_graph_id The ID of the local graph being processed.
|
|
* @param int $_total The total value calculated during the aggregation.
|
|
* @param string $_total_type The type of total calculation (e.g., 'sum', 'average').
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_handle_ptile_type(array $member_graphs, array $skipped_items, int $local_graph_id, int $_total, string $_total_type) : void {
|
|
$special_comments = null;
|
|
$special_hrules = null;
|
|
$graph_template_id = 0;
|
|
|
|
$agg_info = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (cacti_sizeof($agg_info)) {
|
|
$comments_hrules = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE graph_type_id IN (?, ?)
|
|
AND graph_template_id = ?
|
|
AND local_graph_id = 0' .
|
|
(cacti_sizeof($skipped_items) ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . '
|
|
AND (text_format != "" || value != "")
|
|
ORDER BY sequence ASC',
|
|
[GRAPH_ITEM_TYPE_COMMENT, GRAPH_ITEM_TYPE_HRULE, $agg_info['graph_template_id'], ...array_values($skipped_items)]);
|
|
|
|
$graph_template_id = $agg_info['graph_template_id'];
|
|
} else {
|
|
if (cacti_sizeof($member_graphs)) {
|
|
$template_graph[] = $member_graphs[0];
|
|
} else {
|
|
$template_graph = [];
|
|
}
|
|
|
|
$comments_hrules = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE graph_type_id IN(' . GRAPH_ITEM_TYPE_COMMENT . ',' . GRAPH_ITEM_TYPE_HRULE . ')' .
|
|
(cacti_sizeof($template_graph) ? ' AND ' . array_to_sql_or($template_graph, 'local_graph_id') : '') .
|
|
(cacti_sizeof($skipped_items) ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . '
|
|
AND (text_format != "" || value != "")
|
|
ORDER BY local_graph_id, sequence ASC', array_values($skipped_items));
|
|
|
|
if (cacti_sizeof($comments_hrules)) {
|
|
$graph_template_id = $comments_hrules[0]['graph_template_id'];
|
|
}
|
|
}
|
|
|
|
$next_item_sequence = db_fetch_cell_prepared('SELECT MAX(sequence)
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (cacti_sizeof($comments_hrules)) {
|
|
foreach ($comments_hrules as $item) {
|
|
switch($item['graph_type_id']) {
|
|
case GRAPH_ITEM_TYPE_COMMENT:
|
|
if (!isset($special_comments[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']])) {
|
|
if (preg_match('/(:bits:|:bytes:)/', $item['text_format'])) {
|
|
$special_comments[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']] = true;
|
|
|
|
$parts = explode('|', $item['text_format']);
|
|
|
|
if (isset($parts[1])) {
|
|
$pparts = explode(':', $parts[1]);
|
|
|
|
if (isset($pparts[3])) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) {
|
|
$pparts[3] = str_replace('current', 'aggregate_sum', $pparts[3]);
|
|
$pparts[3] = str_replace('max', 'aggregate_sum_peak', $pparts[3]);
|
|
} elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) {
|
|
$pparts[3] = str_replace('current', 'aggregate_current', $pparts[3]);
|
|
$pparts[3] = str_replace('max', 'aggregate_current_peak', $pparts[3]);
|
|
} else {
|
|
cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for pparts[3] in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
|
|
switch($pparts[3]) {
|
|
case 'current':
|
|
$new_ppart = 'current';
|
|
|
|
break;
|
|
case 'total':
|
|
$new_ppart = 'aggregate_sum';
|
|
|
|
break;
|
|
case 'max':
|
|
$new_ppart = 'max';
|
|
|
|
break;
|
|
case 'total_peak':
|
|
$new_ppart = 'total_peak';
|
|
|
|
break;
|
|
case 'all_max_current':
|
|
case 'all_max_peak':
|
|
case 'aggregate_max':
|
|
$new_ppart = 'aggregate_peak';
|
|
|
|
break;
|
|
case 'aggregate_sum':
|
|
case 'aggregate_sum_peak':
|
|
case 'aggregate_current':
|
|
case 'aggregate_current_peak':
|
|
case 'aggregate':
|
|
case 'aggregate_peak':
|
|
$new_ppart = $pparts[3];
|
|
|
|
break;
|
|
default:
|
|
$new_ppart = 'max';
|
|
|
|
break;
|
|
}
|
|
|
|
$pparts[3] = $new_ppart;
|
|
|
|
$parts[1] = implode(':', $pparts);
|
|
$item['text_format'] = implode('|', $parts);
|
|
}
|
|
}
|
|
|
|
db_execute_prepared("INSERT INTO graph_templates_item
|
|
(graph_template_id, local_graph_id, task_item_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence)
|
|
VALUES (?, ?, ?, ?, 1, ?, '', ?, 2, ?)", [
|
|
$graph_template_id,
|
|
$local_graph_id,
|
|
$item['task_item_id'],
|
|
GRAPH_ITEM_TYPE_COMMENT,
|
|
$item['text_format'],
|
|
$item['hard_return'],
|
|
$next_item_sequence++
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case GRAPH_ITEM_TYPE_HRULE:
|
|
if (!isset($special_hrules[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']])) {
|
|
if (preg_match('/(:bits:|:bytes:)/', $item['value'])) {
|
|
$special_hrules[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']] = true;
|
|
|
|
$parts = explode('|', $item['value']);
|
|
|
|
if (isset($parts[1])) {
|
|
$pparts = explode(':', $parts[1]);
|
|
|
|
if (isset($pparts[3])) {
|
|
if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) {
|
|
$pparts[3] = str_replace('current', 'aggregate_sum', $pparts[3]);
|
|
$pparts[3] = str_replace('max', 'aggregate_sum_peak', $pparts[3]);
|
|
} elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) {
|
|
$pparts[3] = str_replace('current', 'aggregate_current', $pparts[3]);
|
|
$pparts[3] = str_replace('max', 'aggregate_current_peak', $pparts[3]);
|
|
} else {
|
|
cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for pparts[3] in value', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
|
|
switch($pparts[3]) {
|
|
case 'current':
|
|
$new_ppart = 'current';
|
|
|
|
break;
|
|
case 'total':
|
|
$new_ppart = 'aggregate_sum';
|
|
|
|
break;
|
|
case 'max':
|
|
$new_ppart = 'max';
|
|
|
|
break;
|
|
case 'total_peak':
|
|
$new_ppart = 'total_peak';
|
|
|
|
break;
|
|
case 'all_max_current':
|
|
case 'all_max_peak':
|
|
case 'aggregate_max':
|
|
$new_ppart = 'aggregate_peak';
|
|
|
|
break;
|
|
case 'aggregate_peak':
|
|
case 'aggregate_sum':
|
|
case 'aggregate_sum_peak':
|
|
case 'aggregate_current':
|
|
case 'aggregate_current_peak':
|
|
case 'aggregate':
|
|
$new_ppart = $pparts[3];
|
|
|
|
break;
|
|
default:
|
|
$new_ppart = 'max';
|
|
|
|
break;
|
|
}
|
|
|
|
$pparts[3] = $new_ppart;
|
|
|
|
$parts[1] = implode(':', $pparts);
|
|
$item['value'] = implode('|', $parts);
|
|
}
|
|
}
|
|
|
|
// add an empty line before nth percentile for the first item only
|
|
if (cacti_sizeof($special_hrules) == 1) {
|
|
db_execute_prepared("INSERT INTO graph_templates_item
|
|
(graph_template_id, local_graph_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence)
|
|
VALUES (?, ?, 1, 1, '', '', 'on', 2, ?)", [$graph_template_id, $local_graph_id, $next_item_sequence++]);
|
|
}
|
|
|
|
db_execute_prepared("INSERT INTO graph_templates_item
|
|
(graph_template_id, local_graph_id, task_item_id, graph_type_id, color_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence)
|
|
VALUES (?, ?, ?, ?, ?, 1, ?, ?, '', 2, ?)", [
|
|
$graph_template_id,
|
|
$local_graph_id,
|
|
$item['task_item_id'],
|
|
GRAPH_ITEM_TYPE_HRULE,
|
|
$item['color_id'],
|
|
$item['text_format'],
|
|
$item['value'],
|
|
$next_item_sequence++
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the aggregation of stacked lines for a given graph.
|
|
*
|
|
* @param int $local_graph_id The ID of the local graph.
|
|
* @param string $_orig_graph_type The original type of the graph.
|
|
* @param int $_total The total value to be aggregated.
|
|
* @param string $_total_type The type of the total value.
|
|
* @param string $_total_prefix The prefix for the total value.
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_handle_stacked_lines(int $local_graph_id, string $_orig_graph_type, int $_total, string $_total_type, string $_total_prefix) : void {
|
|
// Handle the stacked line cases switch line widths
|
|
$width = '0.01';
|
|
$special_type = '';
|
|
$special_line = false;
|
|
|
|
switch ($_orig_graph_type) {
|
|
case AGGREGATE_GRAPH_TYPE_LINE1_STACK:
|
|
$width = '1.00';
|
|
$special_type = GRAPH_ITEM_TYPE_LINE1;
|
|
$special_line = true;
|
|
|
|
break;
|
|
case AGGREGATE_GRAPH_TYPE_LINE2_STACK:
|
|
$width = '2.00';
|
|
$special_type = GRAPH_ITEM_TYPE_LINE2;
|
|
$special_line = true;
|
|
|
|
break;
|
|
case AGGREGATE_GRAPH_TYPE_LINE3_STACK:
|
|
$width = '3.00';
|
|
$special_type = GRAPH_ITEM_TYPE_LINE3;
|
|
$special_line = true;
|
|
|
|
break;
|
|
}
|
|
|
|
if ($special_line) {
|
|
if ($_total == AGGREGATE_TOTAL_NONE) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET line_width = ?
|
|
WHERE local_graph_id = ?
|
|
AND graph_type_id IN (?)',
|
|
[$width, $local_graph_id, GRAPH_ITEM_TYPE_LINESTACK]);
|
|
}
|
|
|
|
// Handle special case total prefix
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET graph_type_id = ?
|
|
WHERE local_graph_id = ?
|
|
AND text_format = ?',
|
|
[$special_type, $local_graph_id, $_total_prefix]);
|
|
|
|
if ($_total == AGGREGATE_TOTAL_ALL) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET graph_type_id = ?, line_width = "0.01"
|
|
WHERE local_graph_id = ?
|
|
AND graph_type_id = ?
|
|
AND text_format != ?',
|
|
[
|
|
GRAPH_ITEM_TYPE_LINESTACK,
|
|
$local_graph_id,
|
|
$special_type,
|
|
$_total_prefix
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
// handle any missing lines
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET line_width="0.01"
|
|
WHERE graph_type_id = ?
|
|
AND line_width="0.00"
|
|
AND local_graph_id = ?',
|
|
[GRAPH_ITEM_TYPE_LINESTACK, $local_graph_id]);
|
|
}
|
|
|
|
/**
|
|
* Retrieves data sources for aggregation.
|
|
*
|
|
* @param array $graph_array Array of graphs to aggregate.
|
|
* @param mixed $data_sources Array to store the retrieved data sources.
|
|
* @param mixed $graph_template Template for the graphs.
|
|
* @param string $message Optional. Message to store any errors or information.
|
|
*
|
|
* @return bool True on success, false on failure.
|
|
*/
|
|
function aggregate_get_data_sources(array &$graph_array, mixed &$data_sources, mixed &$graph_template, string &$message = '') : bool {
|
|
if (cacti_sizeof($graph_array)) {
|
|
// fetch all data sources for all selected graphs
|
|
$data_sources = db_fetch_assoc('SELECT dtd.local_data_id, dtd.name_cache
|
|
FROM data_template_rrd AS dtr
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON dtr.id = gti.task_item_id
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dtr.local_data_id = dtd.local_data_id
|
|
WHERE ' . array_to_sql_or($graph_array, 'gti.local_graph_id') . '
|
|
AND dtd.local_data_id > 0
|
|
GROUP BY dtd.local_data_id
|
|
ORDER BY dtd.name_cache');
|
|
|
|
// verify, that only a single graph template is used, else
|
|
// aggregate will look funny
|
|
$templates = db_fetch_assoc('SELECT DISTINCT gl.graph_template_id AS id
|
|
FROM graph_local AS gl
|
|
WHERE ' . array_to_sql_or($graph_array, 'gl.id'));
|
|
|
|
if (cacti_sizeof($templates) > 1) {
|
|
$message = __('The Graphs chosen for the Aggregate Graph below represent Graphs from multiple Graph Templates. Aggregate does not support creating Aggregate Graphs from multiple Graph Templates.');
|
|
|
|
return false;
|
|
}
|
|
|
|
if (cacti_sizeof($templates) == 1) {
|
|
if ($templates[0]['id'] == 0) {
|
|
// selected graphs do not use templates
|
|
$message = __('The Graphs chosen for the Aggregate Graph do not use Graph Templates. Aggregate does not support creating Aggregate Graphs from non-templated graphs.');
|
|
|
|
return false;
|
|
} else {
|
|
$graph_template = $templates[0]['id'];
|
|
|
|
return true;
|
|
}
|
|
} else {
|
|
$message = __('There was some error in MySQL/MariaDB. Can not continue.');
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
$message = __('You must pass at least a single Graph to this function');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws the list of aggregate graph items.
|
|
*
|
|
* @param int $_graph_id The ID of the graph. Default is 0.
|
|
* @param int $_graph_template_id The ID of the graph template. Default is 0.
|
|
* @param array $_object An array of objects related to the graph. Default is an empty array.
|
|
*
|
|
* @return void
|
|
*/
|
|
function draw_aggregate_graph_items_list(int $_graph_id = 0, int $_graph_template_id = 0, array $_object = []) : void {
|
|
global $graph_item_types, $consolidation_functions;
|
|
|
|
cacti_log(__FUNCTION__ . ' called. graph: ' . $_graph_id . ' template: ' . $_graph_template_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
if ($_graph_id == 0 && $_graph_template_id == 0) {
|
|
return;
|
|
}
|
|
|
|
$item_list_where = '';
|
|
|
|
// fetch graph items
|
|
if ($_graph_id == 0) {
|
|
$item_list_where = "gti.local_graph_id=0 AND gti.graph_template_id=$_graph_template_id ";
|
|
}
|
|
|
|
if ($_graph_template_id == 0) {
|
|
$item_list_where = "gti.local_graph_id=$_graph_id ";
|
|
}
|
|
|
|
$item_list = db_fetch_assoc("SELECT
|
|
gti.id, gti.text_format, gti.value, gti.hard_return, gti.graph_type_id,
|
|
gti.consolidation_function_id, cdef.name as cdef_name, colors.hex,
|
|
vdef.name AS vdef_name, gtgp.name AS gprint_name
|
|
FROM graph_templates_item AS gti
|
|
LEFT JOIN graph_templates_gprint AS gtgp
|
|
ON gti.gprint_id=gtgp.id
|
|
LEFT JOIN cdef
|
|
ON gti.cdef_id=cdef.id
|
|
LEFT JOIN vdef
|
|
ON gti.vdef_id=vdef.id
|
|
LEFT JOIN colors
|
|
ON gti.color_id=colors.id
|
|
WHERE $item_list_where
|
|
ORDER BY gti.sequence");
|
|
|
|
// fetch color templates
|
|
$color_templates = db_fetch_assoc('SELECT color_template_id, name FROM color_templates ORDER BY name');
|
|
|
|
$current_vals = [];
|
|
$is_edit = false;
|
|
$is_templated = false;
|
|
|
|
if (cacti_sizeof($_object) > 0 && $_object['id'] > 0) {
|
|
// drawing items for existing aggregate graph/template
|
|
$is_edit = true;
|
|
|
|
// fetch existing item values
|
|
if (isset($_object['aggregate_template_id']) && $_object['aggregate_template_id'] == 0) {
|
|
// this is aggregate graph with no aggregate template
|
|
$current_vals = db_fetch_assoc_prepared('SELECT *
|
|
FROM aggregate_graphs_graph_item
|
|
WHERE aggregate_graph_id = ?',
|
|
[$_object['id']]);
|
|
|
|
$item_editor_link_param = 'aggregate_graph_id=' . $_object['id'] . '&local_graph_id=' . $_object['local_graph_id'];
|
|
$is_templated = false;
|
|
} elseif (isset($_object['aggregate_template_id'])) {
|
|
// this is aggregate graph from aggregate template
|
|
$current_vals = db_fetch_assoc_prepared('SELECT *
|
|
FROM aggregate_graph_templates_item
|
|
WHERE aggregate_template_id = ?',
|
|
[$_object['aggregate_template_id']]);
|
|
|
|
$item_editor_link_param = 'aggregate_template_id=' . $_object['aggregate_template_id'];
|
|
$is_templated = true;
|
|
} else {
|
|
// this is aggregate template
|
|
$current_vals = db_fetch_assoc_prepared('SELECT *
|
|
FROM aggregate_graph_templates_item
|
|
WHERE aggregate_template_id = ?',
|
|
[$_object['id']]);
|
|
|
|
$item_editor_link_param = 'aggregate_template_id=' . $_object['id'];
|
|
$is_templated = true;
|
|
}
|
|
// key results on item id
|
|
$current_vals = array_rekey(
|
|
$current_vals,
|
|
'graph_templates_item_id',
|
|
['color_template', 'item_skip', 'item_total', 't_graph_type_id', 'graph_type_id']
|
|
);
|
|
}
|
|
|
|
// draw list of graph items
|
|
html_start_box(($is_templated ? __('Graph Template Items') : __('Graph Items')), '100%', false, 3, 'center', '');
|
|
|
|
// print column header
|
|
print "<tr class='tableHeader'>";
|
|
DrawMatrixHeaderItem(__('Graph Item'), '', 1);
|
|
DrawMatrixHeaderItem(__('Data Source'), '',1);
|
|
DrawMatrixHeaderItem(__('Graph Item Type'), '', 1);
|
|
DrawMatrixHeaderItem(__('CF Type'), '', 1);
|
|
DrawMatrixHeaderItem(__('GPrint'), '', 1);
|
|
DrawMatrixHeaderItem(__('CDEF'), '', 1);
|
|
DrawMatrixHeaderItem(__('VDEF'), '', 1);
|
|
DrawMatrixHeaderItem(__('Item Color'), '', 2);
|
|
DrawMatrixHeaderItem(__('Color Template'), '', 1);
|
|
DrawMatrixHeaderItem(__('Skip'), '', 1);
|
|
DrawMatrixHeaderItem(__('Total'), '', 1);
|
|
print '</tr>';
|
|
|
|
$group_counter = 0;
|
|
$_graph_type_name = '';
|
|
$i = 0;
|
|
|
|
if (cacti_sizeof($item_list)) {
|
|
foreach ($item_list as $item) {
|
|
// graph grouping display logic
|
|
$this_row_style = '';
|
|
$use_custom_class = false;
|
|
$hard_return = '';
|
|
$matrix_title = '';
|
|
$customClass = '';
|
|
|
|
if (!preg_match('/(GPRINT|TEXTALIGN|HRULE|VRULE|TICK)/', $graph_item_types[$item['graph_type_id']])) {
|
|
$this_row_style = 'font-weight: bold;';
|
|
$use_custom_class = true;
|
|
|
|
if ($group_counter % 2 == 0) {
|
|
$customClass = 'graphItem';
|
|
} else {
|
|
$customClass = 'graphItemAlternate';
|
|
}
|
|
|
|
$group_counter++;
|
|
}
|
|
|
|
// column "Data Source"
|
|
$_graph_type_name = $graph_item_types[$item['graph_type_id']];
|
|
$force_skip = false;
|
|
|
|
switch (true) {
|
|
case preg_match('/(TEXTALIGN)/', $_graph_type_name):
|
|
$matrix_title = 'TEXTALIGN: ' . ucfirst($item['textalign']);
|
|
|
|
break;
|
|
case preg_match('/(TICK)/', $_graph_type_name):
|
|
$matrix_title = '(' . $item['data_source_name'] . '): ' . $item['text_format'];
|
|
|
|
break;
|
|
case preg_match('/(AREA|STACK|GPRINT|LINE[123])/', $_graph_type_name):
|
|
$matrix_title = $item['text_format'];
|
|
|
|
break;
|
|
case preg_match('/(HRULE)/', $_graph_type_name):
|
|
$matrix_title = 'HRULE: ' . $item['value'];
|
|
|
|
if (preg_match('/(:bits:|:bytes:|\|sum:)/', $item['value'])) {
|
|
$force_skip = false;
|
|
} else {
|
|
$force_skip = true;
|
|
}
|
|
|
|
break;
|
|
case preg_match('/(VRULE)/', $_graph_type_name):
|
|
$force_skip = true;
|
|
$matrix_title = 'VRULE: ' . $item['value'];
|
|
|
|
break;
|
|
case preg_match('/(COMMENT)/', $_graph_type_name):
|
|
$matrix_title = 'COMMENT: ' . $item['text_format'];
|
|
|
|
if (preg_match('/(:bits:|:bytes:|\|sum:)/', $item['text_format'])) {
|
|
$force_skip = false;
|
|
} elseif ($item['text_format'] != '') {
|
|
$force_skip = false;
|
|
} else {
|
|
$force_skip = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// values can be overridden in aggregate graph/template
|
|
if ($is_edit && isset($current_vals[$item['id']]['t_graph_type_id']) && $current_vals[$item['id']]['t_graph_type_id'] == 'on') {
|
|
$item['graph_type_id'] = $current_vals[$item['id']]['graph_type_id'];
|
|
}
|
|
|
|
// alternating row color
|
|
if ($use_custom_class == false) {
|
|
form_alternate_row();
|
|
} else {
|
|
print "<tr class='tableRow odd $customClass'>";
|
|
}
|
|
|
|
// column 'Graph Item'
|
|
print '<td title="' . __esc('Aggregate Items are not modifiable') . '">';
|
|
|
|
if ($is_edit == false) {
|
|
// no existing aggregate graph/template
|
|
print __('Item # %d', ($i + 1));
|
|
} elseif (isset($_object['template_propogation']) && $_object['template_propogation']) {
|
|
// existing aggregate graph with template propagation enabled
|
|
print __('Item # %d', ($i + 1));
|
|
} else {
|
|
// existing aggregate template or graph with no templating
|
|
// create a link to graph item editor
|
|
print '<span title="' . __esc('Aggregate Items are not editable') . '">' . __('Item # %d', ($i + 1)) . '</span>';
|
|
}
|
|
print '</td>';
|
|
|
|
if ($item['hard_return'] == 'on') {
|
|
$hard_return = '<strong><span style="color:#FF0000;"><HR></span></strong>';
|
|
}
|
|
|
|
print "<td style='$this_row_style'>" . htmle($matrix_title) . $hard_return . '</td>';
|
|
|
|
// column 'Graph Item Type'
|
|
print "<td style='$this_row_style'>" . $graph_item_types[$item['graph_type_id']] . '</td>';
|
|
|
|
// column 'CF Type'
|
|
print "<td style='$this_row_style'>" . $consolidation_functions[$item['consolidation_function_id']] . '</td>';
|
|
|
|
// column 'GPrint'
|
|
print "<td style='$this_row_style'>" . $item['gprint_name'] . '</td>';
|
|
|
|
// column 'CDEF'
|
|
print "<td style='$this_row_style'>" . $item['cdef_name'] . '</td>';
|
|
|
|
// column 'CDEF'
|
|
print "<td style='$this_row_style'>" . $item['vdef_name'] . '</td>';
|
|
|
|
// column 'Item Color'
|
|
print "<td style='width:1%;" . ((!empty($item['hex'])) ? 'background-color:#' . $item['hex'] . ";'" : "'") . '> </td>';
|
|
print "<td style='$this_row_style'>" . $item['hex'] . '</td>';
|
|
|
|
// column 'Color Template'
|
|
print '<td>';
|
|
|
|
if (!empty($item['hex'])) {
|
|
print "<select id='agg_color_" . $item['id'] . "' name='agg_color_" . $item['id'] . "'>";
|
|
print "<option value='0' selected>None</option>";
|
|
html_create_list($color_templates, 'name', 'color_template_id', ($is_edit && isset($current_vals[$item['id']]['color_template']) ? $current_vals[$item['id']]['color_template'] : ''));
|
|
print '</select>';
|
|
}
|
|
print '</td>';
|
|
|
|
// column "Skip"
|
|
if (!$force_skip) {
|
|
print "<td style='width:1%;text-align:center;'>";
|
|
print "<input class='checkbox' id='agg_skip_" . $item['id'] . "' type='checkbox' name='agg_skip_" . $item['id'] . "' title='" . htmle($item['text_format']) . "' " . ($is_edit && (!isset($current_vals[$item['id']]['item_total']) || (isset($current_vals[$item['id']]['item_skip']) && $current_vals[$item['id']]['item_skip'] == 'on')) ? 'checked' : '') . "><label class='formCheckboxLabel' for='agg_skip_" . $item['id'] . "'>";
|
|
print '</td>';
|
|
|
|
// column 'Total'
|
|
print "<td style='width:1%;text-align:center;'>";
|
|
print "<input class='checkbox' id='agg_total_" . ($item['id']) . "' type='checkbox' name='agg_total_" . ($item['id']) . "' title='" . htmle($item['text_format']) . "' " . ($is_edit && isset($current_vals[$item['id']]['item_total']) && $current_vals[$item['id']]['item_total'] == 'on' ? 'checked' : '') . "><label class='formCheckboxLabel' for='agg_total_" . $item['id'] . "'>";
|
|
print '</td>';
|
|
} else {
|
|
print "<td style='width:1%;text-align:center;'><input class='checkbox' id='dummy_" . $item['id'] . "' disabled='disabled' type='checkbox' name='dummy_" . $item['id'] . "'" . ($is_edit ? 'checked' : '') . '></td>';
|
|
print "<td style='width:1%;text-align:center;'><input class='checkbox' id='dummy1_" . $item['id'] . "' disabled='disabled' type='checkbox' name='dummy1_" . $item['id'] . "'>";
|
|
print "<input style='display:none;' class='checkbox' id='agg_skip_" . $item['id'] . "' type='checkbox' name='agg_skip_" . $item['id'] . "' title='" . htmle($item['text_format']) . "' " . ($is_edit ? 'checked' : '') . "><label class='formCheckboxLabel' for='agg_skip_" . $item['id'] . "'>";
|
|
print "<input style='display:none;' class='checkbox' id='agg_total_" . ($item['id']) . "' type='checkbox' name='agg_total_" . ($item['id']) . "' title='" . htmle($item['text_format']) . "'><label class='formCheckboxLabel' for='agg_total_" . ($item['id']) . "'></label></td>";
|
|
}
|
|
|
|
print '</tr>';
|
|
|
|
$i++;
|
|
}
|
|
} else {
|
|
print "<tr class='tableRow odd'><td colspan='7'><em>" . __('No Items') . '</em></td></tr>';
|
|
}
|
|
|
|
html_end_box();
|
|
|
|
form_hidden_box('item_no', cacti_sizeof($item_list), cacti_sizeof($item_list));
|
|
}
|
|
|
|
/**
|
|
* Draw graph configuration form so user can override some graph template parameters
|
|
*
|
|
* @param int $aggregate_template_id The ID of the aggregate template.
|
|
* @param int $graph_template_id The ID of the graph template.
|
|
*
|
|
* @return void
|
|
*/
|
|
function draw_aggregate_template_graph_config(int $aggregate_template_id, int $graph_template_id) : void {
|
|
global $struct_graph;
|
|
|
|
html_start_box(__('Graph Configuration'), '100%', true, 3, 'center', '');
|
|
|
|
$aggregate_templates_graph = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graph_templates_graph
|
|
WHERE aggregate_template_id = ?',
|
|
[$aggregate_template_id]);
|
|
|
|
$graph_templates_graph = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_id]);
|
|
|
|
$form_array = [];
|
|
|
|
foreach ($struct_graph as $field_name => $field_array) {
|
|
if ($field_name == 'title') {
|
|
continue;
|
|
}
|
|
|
|
if ($field_array['method'] != 'spacer') {
|
|
$form_array += [$field_name => $struct_graph[$field_name]];
|
|
|
|
/* value from graph template or aggregate graph template
|
|
(based on value of t_$field_name of aggregate_template_graph) */
|
|
if (cacti_sizeof($aggregate_templates_graph) && $aggregate_templates_graph['t_' . $field_name] == 'on') {
|
|
$value = $aggregate_templates_graph[$field_name];
|
|
} else {
|
|
$value = $graph_templates_graph[$field_name];
|
|
}
|
|
|
|
$form_array[$field_name]['value'] = $value;
|
|
$form_array[$field_name]['sub_checkbox'] = [
|
|
'name' => 't_' . $field_name,
|
|
'friendly_name' => __('Override this Value') . '<br>',
|
|
'value' => (cacti_sizeof($aggregate_templates_graph) ? $aggregate_templates_graph['t_' . $field_name] : ''),
|
|
'on_change' => 'toggleFieldEnabled(this.id);'
|
|
];
|
|
} else {
|
|
$form_array += [$field_name => $struct_graph[$field_name]];
|
|
}
|
|
}
|
|
|
|
draw_edit_form(
|
|
[
|
|
'config' => ['no_form_tag' => true],
|
|
'fields' => $form_array
|
|
]
|
|
);
|
|
|
|
html_end_box(false, true);
|
|
|
|
// some javascript do dynamically disable non-overridden fields
|
|
?>
|
|
<script type='text/javascript'>
|
|
|
|
$(function() {
|
|
setFieldsDisabled();
|
|
});
|
|
|
|
// disable all items with sub-checkboxes except
|
|
// where sub-checkbox checked
|
|
function setFieldsDisabled() {
|
|
$('input[id^="t_"]').each(function() {
|
|
if (!$(this).is(':checked')) {
|
|
var fieldId = $(this).attr('id').substr(2);
|
|
|
|
$('#'+fieldId).prop('disabled', true);
|
|
$('#'+fieldId).addClass('ui-state-disabled');
|
|
|
|
if ($('#'+fieldId).selectmenu('instance')) {
|
|
$('#'+fieldId).selectmenu('disable');
|
|
} else if ($('#'+fieldId).hasClass('select2-hidden-accessible')) {
|
|
$('#'+fieldId).trigger('change.select2');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// enable or disable form field based on state of corresponding checkbox
|
|
function toggleFieldEnabled(toggleFieldId) {
|
|
fieldId = toggleFieldId.substr(2);
|
|
|
|
if ($('#'+fieldId).hasClass('ui-state-disabled')) {
|
|
$('#'+fieldId).prop('disabled', false).removeClass('ui-state-disabled');
|
|
|
|
if ($('#'+fieldId).selectmenu('instance')) {
|
|
$('#'+fieldId).selectmenu('enable');
|
|
} else if ($('#'+fieldId).hasClass('select2-hidden-accessible')) {
|
|
$('#'+fieldId).trigger('change.select2');
|
|
}
|
|
} else {
|
|
$('#'+fieldId).prop('disabled', true).addClass('ui-state-disabled');
|
|
|
|
if ($('#'+fieldId).selectmenu('instance')) {
|
|
$('#'+fieldId).selectmenu('disable');
|
|
} else if ($('#'+fieldId).hasClass('select2-hidden-accessible')) {
|
|
$('#'+fieldId).trigger('change.select2');
|
|
}
|
|
}
|
|
}
|
|
|
|
</script>
|
|
<?php
|
|
}
|