mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
feat: expose RRDtool 1.11 graph axis controls (#8356)
* 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>
This commit is contained in:
1 parent
930b085703
commit
4d11070ced
12 files changed
+361
-34
No files matched your search
@@ -67,6 +67,12 @@ CREATE TABLE `aggregate_graph_templates_graph` (
|
||||
`lower_limit` varchar(20) NOT NULL default '0',
|
||||
`t_vertical_label` char(2) default '',
|
||||
`vertical_label` varchar(200) default '',
|
||||
t_vertical_label_angle char(2) default '',
|
||||
vertical_label_angle varchar(3) NOT NULL default '',
|
||||
t_right_axis_label_angle char(2) default '',
|
||||
right_axis_label_angle varchar(3) NOT NULL default '',
|
||||
t_right_axis_range char(2) default '',
|
||||
right_axis_range varchar(64) NOT NULL default '',
|
||||
`t_slope_mode` char(2) default '',
|
||||
`slope_mode` char(2) default 'on',
|
||||
`t_auto_scale` char(2) default '',
|
||||
@@ -1846,6 +1852,12 @@ CREATE TABLE `graph_templates_graph` (
|
||||
lower_limit varchar(20) NOT NULL default '0',
|
||||
t_vertical_label char(2) default '',
|
||||
vertical_label varchar(200) default NULL,
|
||||
t_vertical_label_angle char(2) default '',
|
||||
vertical_label_angle varchar(3) NOT NULL default '',
|
||||
t_right_axis_label_angle char(2) default '',
|
||||
right_axis_label_angle varchar(3) NOT NULL default '',
|
||||
t_right_axis_range char(2) default '',
|
||||
right_axis_range varchar(64) NOT NULL default '',
|
||||
t_slope_mode char(2) default '',
|
||||
slope_mode char(2) default 'on',
|
||||
t_auto_scale char(2) default '',
|
||||
|
||||
@@ -245,6 +245,12 @@ function form_save() : void {
|
||||
$save2['lower_limit'] = CactiValidator::validateInput(gnrv('lower_limit'), 'lower_limit', [new Assert\Regex('/^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?|U)\z/')], 3);
|
||||
$save2['t_vertical_label'] = CactiValidator::validateInput((isrv('t_vertical_label') ? gnrv('t_vertical_label') : ''), 't_vertical_label', [], 3);
|
||||
$save2['vertical_label'] = CactiValidator::validateInput(gnrv('vertical_label'), 'vertical_label', [], 3);
|
||||
$save2['t_vertical_label_angle'] = CactiValidator::validateInput((isrv('t_vertical_label_angle') ? gnrv('t_vertical_label_angle') : ''), 't_vertical_label_angle', [new Assert\Choice(['', 'on'])], 3);
|
||||
$save2['vertical_label_angle'] = CactiValidator::validateRrdGraphAxisInput((isrv('vertical_label_angle') ? gnrv('vertical_label_angle') : ''), 'vertical_label_angle');
|
||||
$save2['t_right_axis_label_angle'] = CactiValidator::validateInput((isrv('t_right_axis_label_angle') ? gnrv('t_right_axis_label_angle') : ''), 't_right_axis_label_angle', [new Assert\Choice(['', 'on'])], 3);
|
||||
$save2['right_axis_label_angle'] = CactiValidator::validateRrdGraphAxisInput((isrv('right_axis_label_angle') ? gnrv('right_axis_label_angle') : ''), 'right_axis_label_angle');
|
||||
$save2['t_right_axis_range'] = CactiValidator::validateInput((isrv('t_right_axis_range') ? gnrv('t_right_axis_range') : ''), 't_right_axis_range', [new Assert\Choice(['', 'on'])], 3);
|
||||
$save2['right_axis_range'] = CactiValidator::validateRrdGraphAxisInput((isrv('right_axis_range') ? gnrv('right_axis_range') : ''), 'right_axis_range');
|
||||
$save2['t_slope_mode'] = CactiValidator::validateInput((isrv('t_slope_mode') ? gnrv('t_slope_mode') : ''), 't_slope_mode', [], 3);
|
||||
$save2['slope_mode'] = CactiValidator::validateInput((isrv('slope_mode') ? gnrv('slope_mode') : ''), 'slope_mode', [], 3);
|
||||
$save2['t_auto_scale'] = CactiValidator::validateInput((isrv('t_auto_scale') ? gnrv('t_auto_scale') : ''), 't_auto_scale', [], 3);
|
||||
|
||||
@@ -397,6 +397,9 @@ function form_save() : void {
|
||||
$save2['upper_limit'] = CactiValidator::validateInput(gnrv('upper_limit'), 'upper_limit', [new Assert\Regex('/^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?|U)\z/')], 3);
|
||||
$save2['lower_limit'] = CactiValidator::validateInput(gnrv('lower_limit'), 'lower_limit', [new Assert\Regex('/^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?|U)\z/')], 3);
|
||||
$save2['vertical_label'] = CactiValidator::validateInput(gnrv('vertical_label'), 'vertical_label', [], 3);
|
||||
$save2['vertical_label_angle'] = CactiValidator::validateRrdGraphAxisInput((isrv('vertical_label_angle') ? gnrv('vertical_label_angle') : ''), 'vertical_label_angle');
|
||||
$save2['right_axis_label_angle'] = CactiValidator::validateRrdGraphAxisInput((isrv('right_axis_label_angle') ? gnrv('right_axis_label_angle') : ''), 'right_axis_label_angle');
|
||||
$save2['right_axis_range'] = CactiValidator::validateRrdGraphAxisInput((isrv('right_axis_range') ? gnrv('right_axis_range') : ''), 'right_axis_range');
|
||||
$save2['slope_mode'] = CactiValidator::validateInput((isrv('slope_mode') ? gnrv('slope_mode') : ''), 'slope_mode', [], 3);
|
||||
$save2['auto_scale'] = CactiValidator::validateInput((isrv('auto_scale') ? gnrv('auto_scale') : ''), 'auto_scale', [], 3);
|
||||
$save2['auto_scale_opts'] = CactiValidator::validateInput(gnrv('auto_scale_opts'), 'auto_scale_opts', [], 3);
|
||||
|
||||
@@ -773,6 +773,13 @@ $struct_graph = [
|
||||
'description' => __('Should be set to 1024 for memory and 1000 for traffic measurements.'),
|
||||
'size' => '12'
|
||||
],
|
||||
'vertical_label_angle' => [
|
||||
'friendly_name' => __('Vertical Label Angle (--vertical-label-angle)'),
|
||||
'method' => 'drop_array',
|
||||
'array' => ['' => __('RRDtool Default'), '0' => '0', '90' => '90', '180' => '180', '270' => '270'],
|
||||
'default' => '',
|
||||
'description' => __('Label rotation in degrees counter clockwise. Requires RRDtool 1.11 or newer. Blank uses the RRDtool default. Angles other than 90 and 270 may overlap nearby content.'),
|
||||
],
|
||||
'slope_mode' => [
|
||||
'friendly_name' => __('Slope Mode (--slope-mode)'),
|
||||
'method' => 'checkbox',
|
||||
@@ -912,6 +919,21 @@ $struct_graph = [
|
||||
'size' => '30',
|
||||
'description' => __('The label for the right axis.'),
|
||||
],
|
||||
'right_axis_label_angle' => [
|
||||
'friendly_name' => __('Right Axis Label Angle (--right-axis-label-angle)'),
|
||||
'method' => 'drop_array',
|
||||
'array' => ['' => __('RRDtool Default'), '0' => '0', '90' => '90', '180' => '180', '270' => '270'],
|
||||
'default' => '',
|
||||
'description' => __('Right label rotation in degrees counter clockwise. Requires RRDtool 1.11 or newer. Blank follows the vertical label angle. Angles other than 90 and 270 may overlap nearby content.'),
|
||||
],
|
||||
'right_axis_range' => [
|
||||
'friendly_name' => __('Right Axis Label Range (--right-axis-range)'),
|
||||
'method' => 'textbox',
|
||||
'max_length' => '64',
|
||||
'default' => '',
|
||||
'size' => '20',
|
||||
'description' => __('Only show right axis labels within min:max, for example 0:100. Either bound may be blank. This does not change the axis scale. Requires RRDtool 1.11 or newer. Blank leaves labels unrestricted.'),
|
||||
],
|
||||
'right_axis_format' => [
|
||||
'friendly_name' => __('Right Axis Format (--right-axis-format <format>)'),
|
||||
'method' => 'drop_sql',
|
||||
|
||||
@@ -83,6 +83,13 @@ function upgrade_to_1_3_0() : void {
|
||||
db_install_add_column('user_domains_ldap', ['name' => 'tls_certificate', 'type' => 'tinyint(3)', 'unsigned' => true, 'NULL' => false, 'default' => '3']);
|
||||
}
|
||||
|
||||
foreach (['graph_templates_graph', 'aggregate_graph_templates_graph'] as $table) {
|
||||
foreach (['vertical_label_angle' => 3, 'right_axis_label_angle' => 3, 'right_axis_range' => 64] as $field => $length) {
|
||||
db_install_add_column($table, ['name' => 't_' . $field, 'type' => 'char(2)', 'default' => '']);
|
||||
db_install_add_column($table, ['name' => $field, 'type' => 'varchar(' . $length . ')', 'NULL' => false, 'default' => '']);
|
||||
}
|
||||
}
|
||||
|
||||
db_install_add_column('graph_templates_graph', ['name' => 't_left_axis_format', 'type' => 'char(2)', 'default' => '', 'after' => 'right_axis_formatter']);
|
||||
db_install_add_column('graph_templates_graph', ['name' => 'left_axis_format', 'type' => 'mediumint(8)', 'NULL' => true, 'after' => 't_left_axis_format']);
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
require_once(__DIR__ . '/rrd_graph_axis.php');
|
||||
|
||||
class CactiValidator {
|
||||
private static ?ValidatorInterface $validator = null;
|
||||
|
||||
@@ -186,6 +188,17 @@ class CactiValidator {
|
||||
]);
|
||||
}
|
||||
|
||||
/** Apply the same axis validation to graph, template and aggregate saves. */
|
||||
public static function validateRrdGraphAxisInput(mixed $value, string $name): mixed {
|
||||
return self::validateInput($value, $name, [new Assert\Callback(
|
||||
static function (mixed $value, \Symfony\Component\Validator\Context\ExecutionContextInterface $context) use ($name): void {
|
||||
if (!rrd_graph_axis_option_is_valid($name, $value)) {
|
||||
$context->buildViolation(__('Invalid RRDtool axis option.'))->addViolation();
|
||||
}
|
||||
}
|
||||
)], 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common input validation helper.
|
||||
* Populates $_SESSION with the value and any error states.
|
||||
|
||||
+39
-32
@@ -22,6 +22,8 @@
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
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.
|
||||
@@ -575,37 +577,40 @@ function aggregate_graph_items_save(array $items, string $table) : bool {
|
||||
*/
|
||||
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' => ['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]+$']
|
||||
'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 = [];
|
||||
@@ -626,7 +631,9 @@ function aggregate_validate_graph_params(array $posted, bool $has_override = fal
|
||||
}
|
||||
|
||||
// validate value
|
||||
if ($defs['type'] == 'bool') {
|
||||
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']);
|
||||
|
||||
+6
-2
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/rrd_graph_item.php');
|
||||
require_once(__DIR__ . '/rrd_graph_axis.php');
|
||||
|
||||
use phpseclib4\Crypt\Rijndael;
|
||||
use phpseclib4\Crypt\RSA;
|
||||
@@ -2174,6 +2175,8 @@ function rrd_function_process_graph_options(int $graph_start, int $graph_end, ar
|
||||
}
|
||||
}
|
||||
|
||||
$graph_opts .= rrd_graph_axis_options($graph, $version);
|
||||
|
||||
foreach ($graph as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$value = rrdtool_resolve_graph_text($value, $graph);
|
||||
@@ -2557,12 +2560,13 @@ function rrdtool_function_graph(int $local_graph_id, mixed $rra_id, array $graph
|
||||
}
|
||||
|
||||
$graph = db_fetch_row_prepared('SELECT gl.id AS local_graph_id, gl.host_id,
|
||||
gl.snmp_query_id, gl.snmp_index, gtg.title_cache, gtg.vertical_label,
|
||||
gl.snmp_query_id, gl.snmp_index, gtg.title_cache, gtg.vertical_label, gtg.vertical_label_angle,
|
||||
gtg.slope_mode, gtg.auto_scale, gtg.auto_scale_opts, gtg.auto_scale_log,
|
||||
gtg.scale_log_units, gtg.auto_scale_rigid, gtg.auto_padding, gtg.base_value,
|
||||
gtg.upper_limit, gtg.lower_limit, gtg.height, gtg.width, gtg.image_format_id,
|
||||
gtg.unit_value, gtg.unit_exponent_value, gtg.alt_y_grid,
|
||||
gtg.right_axis, gtg.right_axis_label, gtg.right_axis_format, gtg.no_gridfit,
|
||||
gtg.right_axis, gtg.right_axis_label, gtg.right_axis_label_angle,
|
||||
gtg.right_axis_range, gtg.right_axis_format, gtg.no_gridfit,
|
||||
gtg.unit_length, gtg.tab_width, gtg.dynamic_labels, gtg.force_rules_legend,
|
||||
gtg.legend_position, gtg.legend_direction, gtg.right_axis_formatter,
|
||||
gtg.left_axis_format, gtg.left_axis_formatter,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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/ |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/** Validate optional RRDtool 1.11 axis settings, including imported values. */
|
||||
function rrd_graph_axis_option_is_valid(string $field, mixed $value) : bool {
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($value === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($field === 'vertical_label_angle' || $field === 'right_axis_label_angle') {
|
||||
return in_array($value, ['0', '90', '180', '270'], true);
|
||||
}
|
||||
|
||||
if ($field !== 'right_axis_range' || strlen($value) > 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$number = '-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+\-]?[0-9]+)?';
|
||||
|
||||
if (!preg_match('/\A(' . $number . ')?:(' . $number . ')?\z/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$minimum, $maximum] = explode(':', $value);
|
||||
|
||||
if ($minimum === '' && $maximum === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ([$minimum, $maximum] as $bound) {
|
||||
if ($bound !== '' && !is_finite((float) $bound)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $minimum === '' || $maximum === '' || (float) $minimum <= (float) $maximum;
|
||||
}
|
||||
|
||||
/** Blank defaults preserve RRDtool's layout; older releases omit these flags. */
|
||||
function rrd_graph_axis_options(array $graph, string $version) : string {
|
||||
if (version_compare($version, '1.11.0', '<')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$options = '';
|
||||
|
||||
foreach (['vertical_label_angle', 'right_axis_label_angle', 'right_axis_range'] as $field) {
|
||||
$value = $graph[$field] ?? '';
|
||||
|
||||
if ($value !== '' && rrd_graph_axis_option_is_valid($field, $value)) {
|
||||
$options .= '--' . str_replace('_', '-', $field) . ' ' . cacti_escapeshellarg($value) . RRD_NL;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
@@ -25463,3 +25463,35 @@ msgstr ""
|
||||
#: lib/Auth/LocalAuthLoginProvider.php:164
|
||||
msgid "Access Denied! Login Failed."
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Vertical Label Angle (--vertical-label-angle)"
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "RRDtool Default"
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Label rotation in degrees counter clockwise. Requires RRDtool 1.11 or newer. Blank uses the RRDtool default. Angles other than 90 and 270 may overlap nearby content."
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Right Axis Label Angle (--right-axis-label-angle)"
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Right label rotation in degrees counter clockwise. Requires RRDtool 1.11 or newer. Blank follows the vertical label angle. Angles other than 90 and 270 may overlap nearby content."
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Right Axis Label Range (--right-axis-range)"
|
||||
msgstr ""
|
||||
|
||||
#: include/global_form.php
|
||||
msgid "Only show right axis labels within min:max, for example 0:100. Either bound may be blank. This does not change the axis scale. Requires RRDtool 1.11 or newer. Blank leaves labels unrestricted."
|
||||
msgstr ""
|
||||
|
||||
#: lib/CactiValidator.php
|
||||
msgid "Invalid RRDtool axis option."
|
||||
msgstr ""
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later.
|
||||
require_once dirname(__DIR__, 4) . '/lib/CactiValidator.php';
|
||||
require_once dirname(__DIR__, 4) . '/lib/rrd.php';
|
||||
require_once dirname(__DIR__, 4) . '/lib/api_aggregate.php';
|
||||
require_once dirname(__DIR__, 3) . '/Helpers/SourceSlice.php';
|
||||
require_once dirname(__DIR__, 3) . '/fixtures/Core/Rrd/AxisOptions.php';
|
||||
cacti_fixture_source_function(dirname(__DIR__, 4) . '/lib/rrd.php', 'rrd_function_process_graph_options', 'CactiAxisFixture');
|
||||
cacti_fixture_source_function(dirname(__DIR__, 4) . '/lib/template.php', 'push_out_graph', 'CactiAxisFixture');
|
||||
|
||||
test('axis defaults and older configured versions preserve graph commands', function () {
|
||||
$fields = ['vertical_label_angle' => '90', 'right_axis_label_angle' => '270', 'right_axis_range' => '0:100'];
|
||||
|
||||
foreach (['1.5.0', '1.9.0', '1.10.0'] as $version) {
|
||||
expect(rrd_graph_axis_options($fields, $version))->toBe('');
|
||||
}
|
||||
expect(rrd_graph_axis_options([], '1.11.0'))->toBe('')
|
||||
->and(rrd_graph_axis_options(array_fill_keys(array_keys($fields), ''), '1.11.0'))->toBe('');
|
||||
});
|
||||
|
||||
test('new and future versions emit independent angles including zero', function () {
|
||||
foreach (['1.11.0', '1.12.0'] as $version) {
|
||||
$options = rrd_graph_axis_options(['vertical_label_angle' => '0', 'right_axis_label_angle' => '270', 'right_axis_range' => '0:100'], $version);
|
||||
expect($options)->toBe('--vertical-label-angle ' . cacti_escapeshellarg('0') . RRD_NL .
|
||||
'--right-axis-label-angle ' . cacti_escapeshellarg('270') . RRD_NL .
|
||||
'--right-axis-range ' . cacti_escapeshellarg('0:100') . RRD_NL);
|
||||
}
|
||||
});
|
||||
|
||||
test('right axis bounds accept open endpoints and finite numeric values', function (string $range) {
|
||||
expect(rrd_graph_axis_option_is_valid('right_axis_range', $range))->toBeTrue();
|
||||
})->with(['', '0:100', '0:', ':100', '-2.5:3.25', '-1e2:1e2', '.5:1.', '0:0']);
|
||||
|
||||
test('invalid imported axis values cannot reach the graph command', function (string $field, mixed $value) {
|
||||
expect(rrd_graph_axis_option_is_valid($field, $value))->toBeFalse()
|
||||
->and(rrd_graph_axis_options([$field => $value], '1.11.0'))->toBe('');
|
||||
})->with([
|
||||
['vertical_label_angle', '45'], ['right_axis_label_angle', '270;touch /tmp/axis'],
|
||||
['vertical_label_angle', ['90']], ['right_axis_range', '100:0'],
|
||||
['right_axis_range', ':'], ['right_axis_range', 'NaN:100'],
|
||||
['right_axis_range', '0:INF'], ['right_axis_range', '1e999:'],
|
||||
['right_axis_range', "0:100\n"], ['right_axis_range', '0:100 --title injected'],
|
||||
['right_axis_range', str_repeat('1', 65) . ':'],
|
||||
]);
|
||||
|
||||
test('graph rendering includes the new options only at the configured capability level', function () {
|
||||
global $image_types;
|
||||
$image_types = [1 => 'PNG'];
|
||||
$graph = [
|
||||
'auto_scale' => '', 'upper_limit' => '', 'lower_limit' => '',
|
||||
'auto_scale_log' => '', 'scale_log_units' => '', 'auto_scale_rigid' => '',
|
||||
'unit_value' => '', 'unit_exponent_value' => '', 'height' => '100', 'width' => '200',
|
||||
'image_format_id' => 1, 'vertical_label_angle' => '90',
|
||||
'right_axis_label_angle' => '270', 'right_axis_range' => ':100',
|
||||
];
|
||||
$data = [];
|
||||
|
||||
foreach (['1.10.0', '1.11.0'] as $version) {
|
||||
$GLOBALS['axis_fixture_version'] = $version;
|
||||
$options = CactiAxisFixture\rrd_function_process_graph_options(1700000000, 1700003600, $graph, $data);
|
||||
|
||||
foreach (['vertical-label-angle', 'right-axis-label-angle', 'right-axis-range'] as $flag) {
|
||||
expect(substr_count($options, '--' . $flag . ' '))->toBe($version === '1.11.0' ? 1 : 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('aggregate overrides retain zero and reject invalid axis values', function () {
|
||||
$_SESSION[SESS_ERROR_FIELDS] = [];
|
||||
$posted = ['t_vertical_label_angle' => 'on', 'vertical_label_angle' => '0',
|
||||
't_right_axis_range' => 'on', 'right_axis_range' => '100:0'];
|
||||
$params = aggregate_validate_graph_params($posted, true);
|
||||
expect($params['vertical_label_angle'])->toBe('0')
|
||||
->and($params['t_vertical_label_angle'])->toBe('on')
|
||||
->and($params['right_axis_label_angle'])->toBe('')
|
||||
->and($params['t_right_axis_label_angle'])->toBe('')
|
||||
->and($_SESSION[SESS_ERROR_FIELDS])->toHaveKey('right_axis_range');
|
||||
unset($_SESSION[SESS_ERROR_FIELDS], $_SESSION[SESS_FIELD_VALUES], $_SESSION['messages']);
|
||||
});
|
||||
|
||||
test('new template fields propagate while per graph overrides remain intact', function () {
|
||||
global $struct_graph;
|
||||
$previous = $struct_graph ?? null;
|
||||
require dirname(__DIR__, 4) . '/include/global_arrays.php';
|
||||
require dirname(__DIR__, 4) . '/include/global_form.php';
|
||||
$GLOBALS['axis_fixture_template'] = [
|
||||
'id' => 42, 'graph_template_id' => 7,
|
||||
't_vertical_label_angle' => '', 'vertical_label_angle' => '90',
|
||||
't_right_axis_label_angle' => 'on', 'right_axis_label_angle' => '270',
|
||||
't_right_axis_range' => '', 'right_axis_range' => '0:100',
|
||||
];
|
||||
$GLOBALS['axis_fixture_updates'] = [];
|
||||
|
||||
try {
|
||||
expect(CactiAxisFixture\push_out_graph(42, false))->toBeTrue();
|
||||
$updates = $GLOBALS['axis_fixture_updates'];
|
||||
expect($updates)->toHaveCount(2)
|
||||
->and($updates[0][0])->toContain('SET vertical_label_angle = ?')
|
||||
->and($updates[0][1])->toBe(['90', 42])
|
||||
->and($updates[1][0])->toContain('SET right_axis_range = ?')
|
||||
->and($updates[1][1])->toBe(['0:100', 42]);
|
||||
} finally {
|
||||
$struct_graph = $previous;
|
||||
unset($GLOBALS['axis_fixture_template'], $GLOBALS['axis_fixture_updates']);
|
||||
}
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later.
|
||||
namespace CactiAxisFixture;
|
||||
|
||||
// These boundaries are confined to this fixture namespace.
|
||||
function get_rrdtool_version(): string {
|
||||
return $GLOBALS['axis_fixture_version'];
|
||||
}
|
||||
function read_config_option(string $name): string {
|
||||
return '';
|
||||
}
|
||||
function rrdtool_resolve_graph_text(string $value, array $graph): string {
|
||||
return $value;
|
||||
}
|
||||
function rrdtool_function_format_graph_date(array $data): string {
|
||||
return '';
|
||||
}
|
||||
function rrdtool_function_theme_font_options(array $data): string {
|
||||
return '';
|
||||
}
|
||||
class CactiValidator extends \CactiValidator {
|
||||
}
|
||||
|
||||
function db_fetch_row_prepared(string $sql, array $params): array {
|
||||
return $GLOBALS['axis_fixture_template'];
|
||||
}
|
||||
function db_fetch_cell_prepared(string $sql, array $params): int {
|
||||
return 1;
|
||||
}
|
||||
function db_execute_prepared(string $sql, array $params): bool {
|
||||
$GLOBALS['axis_fixture_updates'][] = [$sql, $params];
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user