Files
cacti/host_templates.php
T
TheWitness 41e6d9e033 security: consolidate verified GHSA fixes for develop (batch 4) (#8145)
* security: consolidate verified GHSA fixes for develop (batch 4)

Develop counterparts of the 1.2.x batch-4 fixes in #8144, re-verified against
current develop HEAD.

- GHSA-4rmr-wvjq-qxc2: include/auth.php Basic Auth and guest branches fall
  through to the per-page realm check instead of returning early.
- GHSA-j67j-wpm4-9g3x: the pre-dispatch update_policy branch in user_admin.php
  and user_group_admin.php requires a CSRF-token POST. The denylist -> allowlist
  redesign is tracked for 1.3 in #8143.
- GHSA-2p3x-f5jm-rg6p: the View-Graphs -> poller RCE chain is broken by the
  realm check (4rmr), the update_policy guard (j67j) and shell-free webhits (x5pw).
- GHSA-x5pw-cg7j-mj4r: scripts/webhits.pl runs wc through a shell-free list-form open.
- GHSA-9737-rqh3-h7fh: names interpolated into raise_message() now use __esc()
  across the UI; URL- and HTML-bearing messages are excluded.
- GHSA-9x9h-2577-9w86: the colordropdown input is built as a node and the label
  is no longer round-tripped through parseHTML (the list item already used
  createTextNode on develop).
- GHSA-h96j-8xhc-2p3w: remote_agent.php authorizes on $_SERVER['REMOTE_ADDR'],
  not get_client_addr(), which honours client-settable proxy headers.

Regression guards under tests/Unit/Security/{Auth,Csrf,Xss,ShellInjection}.
php -l, check-changelog.py and the sink/architectural baselines all pass.

* chore: reference PR #8145 on the batch-4 1.3.0-dev CHANGELOG entries

* security: resolve develop merge - keep id-only xmlerror unescaped (matches #8141 h745 test)

* security(GHSA-j67j): use REQUEST_METHOD POST guard instead of raw \ (hardening-pattern-guard)

* security(GHSA-9737): fix review findings - drop double-encoding in __esc, escape missed name sinks, correct plugins format-arg count
2026-10-02 13:10:42 -04:00

1554 lines
47 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('./include/auth.php');
require_once(CACTI_PATH_LIBRARY . '/api_data_source.php');
require_once(CACTI_PATH_LIBRARY . '/api_device.php');
require_once(CACTI_PATH_LIBRARY . '/api_graph.php');
require_once(CACTI_PATH_LIBRARY . '/api_tree.php');
require_once(CACTI_PATH_LIBRARY . '/data_query.php');
require_once(CACTI_PATH_LIBRARY . '/export.php');
require_once(CACTI_PATH_LIBRARY . '/import.php');
require_once(CACTI_PATH_LIBRARY . '/package.php');
require_once(CACTI_PATH_LIBRARY . '/poller.php');
require_once(CACTI_PATH_LIBRARY . '/template.php');
require_once(CACTI_PATH_LIBRARY . '/utility.php');
require_once(CACTI_PATH_LIBRARY . '/xml.php');
require_once(CACTI_PATH_LIBRARY . '/CactiValidator.php');
use Symfony\Component\Validator\Constraints as Assert;
if (gnrv('action') == 'archives' || gnrv('action_type') == 'archives') {
$actions = [
1 => __('Delete'),
2 => __('Download'),
];
} else {
$actions = [
1 => __('Delete'),
2 => __('Duplicate'),
3 => __('Sync Devices'),
4 => __('Archive'),
5 => __('Download')
];
if (!file_exists(CACTI_PATH_PKI . '/package.pub')) {
unset($actions[4]);
unset($actions[5]);
}
}
// set default action
set_default_action();
api_plugin_hook('device_template_top');
switch (grv('action')) {
case 'save':
form_save();
break;
case 'actions':
form_actions();
break;
case 'download':
$ids = explode(',', gnrv('ids'));
$type = gnrv('action_type');
api_device_template_download($type, $ids);
break;
case 'item_add_gt':
template_item_add_gt();
header('Location: host_templates.php?action=edit&id=' . gfrv('host_template_id'));
break;
case 'item_remove_gt_confirm':
template_item_remove_gt_confirm();
break;
case 'item_remove_gt':
template_item_remove_gt();
header('Location: host_templates.php?action=edit&id=' . gfrv('host_template_id'));
break;
case 'item_add_dq':
template_item_add_dq();
header('Location: host_templates.php?action=edit&id=' . gfrv('host_template_id'));
break;
case 'item_remove_dq_confirm':
template_item_remove_dq_confirm();
break;
case 'item_remove_dq':
template_item_remove_dq();
header('Location: host_templates.php?action=edit&id=' . gfrv('host_template_id'));
break;
case 'edit':
top_header();
template_edit();
bottom_footer();
break;
case 'archives':
top_header();
device_archives();
bottom_footer();
break;
default:
top_header();
device_templates();
bottom_footer();
break;
}
function form_save() : void {
// ================= input validation =================
gfrv('id');
gfrv('host_template_id');
gfrv('snmp_query_id');
gfrv('graph_template_id');
// ====================================================
if (isrv('save_component_template')) {
$save['id'] = gnrv('id');
$save['hash'] = get_hash_host_template(gnrv('id'));
$save['name'] = CactiValidator::validateInput(gnrv('name'), 'name', [new Assert\NotBlank()], 3);
$save['version'] = CactiValidator::validateInput(gnrv('version'), 'version', [new Assert\NotBlank()], 3);
$save['class'] = CactiValidator::validateInput(gnrv('class'), 'class', [new Assert\NotBlank()], 3);
$save['tags'] = CactiValidator::validateInput(gnrv('tags'), 'tags', [], 3);
$save['author'] = CactiValidator::validateInput(gnrv('author'), 'author', [], 3);
$save['email'] = CactiValidator::validateInput(gnrv('email'), 'email', [new Assert\Email(mode: 'html5')], 3);
$save['homepage'] = CactiValidator::validateInput(gnrv('homepage'), 'homepage', [], 3);
$save['copyright'] = CactiValidator::validateInput(gnrv('copyright'), 'copyright', [new Assert\NotBlank()], 3);
$save['installation'] = CactiValidator::validateInput(gnrv('installation'), 'installation', [], 3);
if (!is_error_message()) {
$host_template_id = sql_save($save, 'host_template');
if ($host_template_id) {
raise_message(1);
} else {
raise_message(2);
}
}
header('Location: host_templates.php?action=edit&id=' . (empty($host_template_id) ? gnrv('id') : $host_template_id));
}
}
function template_item_add_dq() : void {
// ================= input validation =================
gfrv('host_template_id');
gfrv('snmp_query_id');
// ====================================================
db_execute_prepared('REPLACE INTO host_template_snmp_query
(host_template_id, snmp_query_id)
VALUES (?, ?)',
[grv('host_template_id'), grv('snmp_query_id')]
);
raise_message(41);
}
function template_item_add_gt() : void {
// ================= input validation =================
gfrv('host_template_id');
gfrv('graph_template_id');
// ====================================================
db_execute_prepared('REPLACE INTO host_template_graph
(host_template_id, graph_template_id)
VALUES (?, ?)',
[grv('host_template_id'), grv('graph_template_id')]
);
raise_message(41);
}
function form_actions() : void {
global $actions;
// ================= input validation =================
gfrv('drp_action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z0-9_]+)$/']]);
// ====================================================
// if we are to save this form, instead of display it
if (isrv('selected_items')) {
if (gnrv('action_type') == 'templates') {
$selected_items = sanitize_unserialize_selected_items(gnrv('selected_items'));
if ($selected_items != false) {
if (gnrv('drp_action') == '1') { // delete
db_execute('DELETE FROM host_template WHERE ' . array_to_sql_or($selected_items, 'id'));
db_execute('DELETE FROM host_template_snmp_query WHERE ' . array_to_sql_or($selected_items, 'host_template_id'));
db_execute('DELETE FROM host_template_graph WHERE ' . array_to_sql_or($selected_items, 'host_template_id'));
// "undo" any device that is currently using this template
db_execute('UPDATE host SET host_template_id = 0 WHERE deleted = "" AND ' . array_to_sql_or($selected_items, 'host_template_id'));
} elseif (gnrv('drp_action') == '2') { // duplicate
foreach ($selected_items as $id) {
api_duplicate_device_template($id, gnrv('title_format'));
}
} elseif (gnrv('drp_action') == '3') { // sync
foreach ($selected_items as $id) {
api_device_template_sync_template($id);
}
} elseif (gnrv('drp_action') == '4') { // archive template
$archive_note = grv('archive_note');
foreach ($selected_items as $id) {
api_device_template_archive($id, $archive_note);
}
} elseif (gnrv('drp_action') == '5') { // download package
top_header();
print '<script text="text/javascript">
function DownloadStart(url) {
document.getElementById("download_iframe").src = url;
setTimeout(function() {
loadUrl({ url: "host_templates.php?action_type=templates" });
Pace.stop();
}, 2000);
}
$(function() {
DownloadStart(\'host_templates.php?action=download&action_type=templates&ids=' . implode(',', $selected_items) . '\');
});
</script>
<iframe id="download_iframe" style="display:none;"></iframe>';
bottom_footer();
exit(0);
}
}
header('Location: host_templates.php?action=templates&action=templates');
} else {
$selected_items = sanitize_unserialize_selected_items(gnrv('selected_items'));
if ($selected_items != false) {
if (gnrv('drp_action') == '1') { // delete
foreach ($selected_items as $id) {
$name = db_fetch_cell_prepared('SELECT name
FROM host_template_archive
WHERE id = ?', [$id]);
db_execute_prepared('DELETE FROM host_template_archive WHERE id = ?', [$id]);
raise_message('archives_removed_' . $id, __esc('The Device Template Archive %s has been removed.', $name), MESSAGE_LEVEL_INFO);
}
} elseif (gnrv('drp_action') == 2) {
top_header();
print '<script text="text/javascript">
function DownloadStart(url) {
document.getElementById("download_iframe").src = url;
setTimeout(function() {
loadUrl({ url: "host_templates.php?action_type=archives" });
Pace.stop();
}, 1000);
}
$(function() {
DownloadStart(\'host_templates.php?action=download&action_type=archives&ids=' . implode(',', $selected_items) . '\');
});
</script>
<iframe id="download_iframe" style="display:none;"></iframe>';
bottom_footer();
exit(0);
}
}
header('Location: host_templates.php?action=archives');
}
exit;
} else {
$ilist = '';
$iarray = [];
// loop through each of the host templates selected on the previous page and get more info about them
foreach ($_POST as $var => $val) {
if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) {
// ================= input validation =================
input_validate_input_number($matches[1], 'chk[1]');
// ====================================================
if (gnrv('action_type') == 'templates') {
$ilist .= '<li>' . htmle(db_fetch_cell_prepared('SELECT name FROM host_template WHERE id = ?', [$matches[1]])) . '</li>';
} else {
$ilist .= '<li>' . htmle(db_fetch_cell_prepared('SELECT name FROM host_template_archive WHERE id = ?', [$matches[1]])) . '</li>';
}
$iarray[] = $matches[1];
}
}
if (gnrv('action_type') == 'templates') {
$form_data = [
'general' => [
'page' => 'host_templates.php?action_type=templates',
'actions' => $actions,
'optvar' => 'drp_action',
'item_array' => $iarray,
'item_list' => $ilist
],
'options' => [
1 => [
'smessage' => __('Click \'Continue\' to Delete the following Device Template.'),
'pmessage' => __('Click \'Continue\' to Delete following Device Templates.'),
'scont' => __('Delete Device Template'),
'pcont' => __('Delete Device Templates')
],
2 => [
'smessage' => __('Click \'Continue\' to Duplicate the following Device Template.'),
'pmessage' => __('Click \'Continue\' to Duplicate following Device Templates.'),
'scont' => __('Duplicate Device Template'),
'pcont' => __('Duplicate Device Templates'),
'extra' => [
'title_format' => [
'method' => 'textbox',
'title' => __('Title Format'),
'default' => '<template_title> (1)',
'width' => 25
]
]
],
3 => [
'smessage' => __('Click \'Continue\' to Sync Devices to the following Device Template.'),
'pmessage' => __('Click \'Continue\' to Sync Devices to the following Device Templates.'),
'scont' => __('Sync Device Template'),
'pcont' => __('Sync Device Templates')
],
4 => [
'smessage' => __('Click \'Continue\' to Archive the following Device Template.'),
'pmessage' => __('Click \'Continue\' to Archive the following Device Templates.'),
'scont' => __('Archive Device Template'),
'pcont' => __('Archive Device Templates'),
'extra' => [
'archive_note' => [
'method' => 'textarea',
'title' => __('Archive Note'),
'default' => '',
'textarea_rows' => 4,
'textarea_cols' => 80
]
]
],
5 => [
'smessage' => __('Click \'Continue\' to Download the following Device Template.'),
'pmessage' => __('Click \'Continue\' to Download the following Device Templates.'),
'scont' => __('Download Device Template'),
'pcont' => __('Download Device Templates')
],
]
];
} else {
$form_data = [
'general' => [
'page' => 'host_templates.php?action_type=archive',
'actions' => $actions,
'optvar' => 'drp_action',
'item_array' => $iarray,
'item_list' => $ilist
],
'options' => [
1 => [
'smessage' => __('Click \'Continue\' to Delete the following Device Template Archive.'),
'pmessage' => __('Click \'Continue\' to Delete following Device Template Archives.'),
'scont' => __('Delete Device Template Archive'),
'pcont' => __('Delete Device Templates Archives')
],
2 => [
'smessage' => __('Click \'Continue\' to Download the following Device Template Archive.'),
'pmessage' => __('Click \'Continue\' to Download following Device Template Archives.'),
'scont' => __('Download Device Template Archive'),
'pcont' => __('Download Device Templates Archives')
]
]
];
}
form_continue_confirmation($form_data);
}
}
function template_item_remove_gt_confirm() : void {
// ================= input validation =================
gfrv('id');
gfrv('host_template_id');
// ====================================================
form_start('host_templates.php?action=edit&id' . grv('host_template_id'));
html_start_box('', '100%', false, 3, 'center', '');
$template = db_fetch_row_prepared('SELECT *
FROM graph_templates
WHERE id = ?',
[grv('id')]);
?>
<tr>
<td class='topBoxAlt'>
<p><?php print __('Click \'Continue\' to delete the following Graph Template will be disassociated from the Device Template.'); ?></p>
<p><?php print __('Graph Template Name: %s', htmle($template['name'])); ?>'<br>
</td>
</tr>
<tr>
<td class='right'>
<button type='button' class='ui-button ui-corner-all ui-widget' id='cancel' onClick='$("#cdialog").dialog("close")' name='cancel'><?php print __esc('Cancel'); ?></button>
<button type='button' class='ui-button ui-corner-all ui-widget' id='continue' name='continue' title='<?php print __esc('Remove Graph Template'); ?>'><?php print __esc('Continue'); ?></button>
</td>
</tr>
<?php
html_end_box();
form_end();
?>
<script type='text/javascript'>
$('#continue').click(function(data) {
var options = {
url: 'host_templates.php?action=item_remove_gt',
noState: true,
}
var data = {
__csrf_magic: csrfMagicToken,
host_template_id: <?php print grv('host_template_id'); ?>,
id: <?php print grv('id'); ?>
}
postUrl(options, data);
});
</script>
<?php
}
function template_item_remove_gt() : void {
// ================= input validation =================
gfrv('id');
gfrv('host_template_id');
// ====================================================
db_execute_prepared('DELETE FROM host_template_graph
WHERE graph_template_id = ?
AND host_template_id = ?',
[grv('id'), grv('host_template_id')]
);
raise_message(41);
}
function template_item_remove_dq_confirm() : void {
// ================= input validation =================
gfrv('id');
gfrv('host_template_id');
// ====================================================
form_start('host_templates.php?action=edit&id' . grv('host_template_id'));
html_start_box('', '100%', false, 3, 'center', '');
$query = db_fetch_row_prepared('SELECT * FROM snmp_query WHERE id = ?', [grv('id')]);
?>
<tr>
<td class='topBoxAlt'>
<p><?php print __('Click \'Continue\' to delete the following Data Queries will be disassociated from the Device Template.'); ?></p>
<p><?php print __('Data Query Name: %s', htmle($query['name'])); ?>'<br>
</td>
</tr>
<tr>
<td class='right'>
<button type='button' class='ui-button ui-corner-all ui-widget' id='cancel' onClick='$("#cdialog").dialog("close")' name='cancel'><?php print __esc('Cancel'); ?></button>
<button type='button' class='ui-button ui-corner-all ui-widget' id='continue' name='continue' title='<?php print __esc('Remove Data Query'); ?>'><?php print __esc('Continue'); ?></button>
</td>
</tr>
<?php
html_end_box();
form_end();
?>
<script type='text/javascript'>
$('#continue').click(function(data) {
var options = {
url: 'host_templates.php?action=item_remove_dq',
noState: true,
}
var data = {
__csrf_magic: csrfMagicToken,
host_template_id: <?php print grv('host_template_id'); ?>,
id: <?php print grv('id'); ?>
}
postUrl(options, data);
});
</script>
<?php
}
function template_item_remove_dq() : void {
// ================= input validation =================
gfrv('id');
gfrv('host_template_id');
// ====================================================
db_execute_prepared('DELETE FROM host_template_snmp_query
WHERE snmp_query_id = ?
AND host_template_id = ?',
[grv('id'), grv('host_template_id')]
);
raise_message(41);
}
function template_edit() : void {
global $fields_host_template_edit, $copyrights;
// ================= input validation =================
gfrv('id');
// ====================================================
if (!ierv('id')) {
$host_template = db_fetch_row_prepared('SELECT *
FROM host_template
WHERE id = ?',
[grv('id')]
);
$header_label = __esc('Device Templates [edit: %s]', $host_template['name']);
} else {
$host_template = [];
$header_label = __('Device Templates [new]');
srv('id', 0);
}
if (!cacti_sizeof($host_template) || !isset($host_template['version']) || $host_template['version'] == '') {
$fields_host_template_edit['version']['default'] = CACTI_VERSION;
}
if (!cacti_sizeof($host_template) || !isset($host_template['author']) || $host_template['author'] == '') {
$fields_host_template_edit['author']['default'] = read_config_option('packaging_author');
}
if (!cacti_sizeof($host_template) || !isset($host_template['email']) || $host_template['email'] == '') {
$fields_host_template_edit['email']['default'] = read_config_option('packaging_email');
}
if (!cacti_sizeof($host_template) || !isset($host_template['copyright']) || $host_template['copyright'] == '') {
$fields_host_template_edit['copyright']['default'] = read_config_option('packaging_copyright');
}
form_start('host_templates.php', 'form_network');
html_start_box($header_label, '100%', true, 3, 'center', '');
draw_edit_form(
[
'config' => ['no_form_tag' => 'true'],
'fields' => inject_form_variables($fields_host_template_edit, $host_template)
]
);
/* we have to hide this button to make a form change in the main form trigger the correct
* submit action */
print "<div style='display:none;'><button type='submit' class='ui-button ui-corner-all ui-widget ui-state-active'>" . __esc('Default Submit Button') . '</button></div>';
html_end_box(true, true);
if (!ierv('id')) {
html_start_box(__('Associated Graph Templates'), '100%', false, 3, 'center', '');
$selected_graph_templates = db_fetch_assoc_prepared('SELECT graph_templates.id, graph_templates.name
FROM (graph_templates,host_template_graph)
WHERE graph_templates.id = host_template_graph.graph_template_id
AND host_template_graph.host_template_id = ?
ORDER BY graph_templates.name', [grv('id')]);
$i = 0;
if (cacti_sizeof($selected_graph_templates)) {
foreach ($selected_graph_templates as $item) {
form_alternate_row("gt$i", true);
?>
<td class='left'>
<strong><?php print $i; ?>)</strong> <?php print htmle($item['name']); ?>
</td>
<td class='right'>
<a class='delete deleteMarker ti ti-x' title='<?php print __esc('Delete'); ?>' href='<?php print htmle('host_templates.php?action=item_remove_gt_confirm&id=' . $item['id'] . '&host_template_id=' . grv('id')); ?>'></a>
</td>
<?php
form_end_row();
$i++;
}
} else {
print '<tr class="tableRow odd"><td colspan="2"><em>' . __('No Associated Graph Templates.') . '</em></td></tr>';
}
?>
<tr class='odd'>
<td colspan='2'>
<table>
<tr style='line-height:10px'>
<td class='nowrap templateAdd'>
<?php print __('Add Graph Template'); ?>
</td>
<td class='noHide'>
<?php form_dropdown('graph_template_id', db_fetch_assoc_prepared('SELECT gt.id, gt.name
FROM graph_templates AS gt
LEFT JOIN host_template_graph AS htg
ON gt.id=htg.graph_template_id
AND htg.host_template_id = ?
WHERE htg.host_template_id IS NULL
AND gt.id NOT IN (SELECT graph_template_id FROM snmp_query_graph)
ORDER BY gt.name', [grv('id')]), 'name', 'id', '', '', ''); ?>
</td>
<td class='noHide'>
<button type='button' class='ui-button ui-corner-all ui-widget' id='add_gt' title='<?php print __esc('Add Graph Template to Device Template'); ?>'><?php print __esc('Add'); ?></button>
</td>
</tr>
</table>
</td>
</tr>
<?php
html_end_box();
html_start_box(__('Associated Data Queries'), '100%', false, 3, 'center', '');
$selected_data_queries = db_fetch_assoc_prepared('SELECT snmp_query.id, snmp_query.name
FROM (snmp_query, host_template_snmp_query)
WHERE snmp_query.id = host_template_snmp_query.snmp_query_id
AND host_template_snmp_query.host_template_id = ?
ORDER BY snmp_query.name', [grv('id')]);
$i = 0;
if (cacti_sizeof($selected_data_queries)) {
foreach ($selected_data_queries as $item) {
form_alternate_row("dq$i", true);
?>
<td class='left'>
<strong><?php print $i; ?>)</strong> <?php print htmle($item['name']); ?>
</td>
<td class='right'>
<a class='delete deleteMarker ti ti-x' title='<?php print __esc('Delete'); ?>' href='<?php print htmle('host_templates.php?action=item_remove_dq_confirm&id=' . $item['id'] . '&host_template_id=' . grv('id')); ?>'></a>
</td>
<?php
form_end_row();
$i++;
}
} else {
print '<tr class="tableRow odd"><td colspan="2"><em>' . __('No Associated Data Queries.') . '</em></td></tr>';
}
?>
<tr class='odd'>
<td colspan='2'>
<table>
<tr style='line-height:10px;'>
<td class='nowrap queryAdd'>
<?php print __('Add Data Query'); ?>
</td>
<td class='noHide'>
<?php form_dropdown('snmp_query_id', db_fetch_assoc_prepared('SELECT snmp_query.id, snmp_query.name
FROM snmp_query LEFT JOIN host_template_snmp_query
ON (snmp_query.id = host_template_snmp_query.snmp_query_id AND host_template_snmp_query.host_template_id = ?)
WHERE host_template_snmp_query.host_template_id is null
ORDER BY snmp_query.name', [grv('id')]), 'name', 'id', '', '', ''); ?>
</td>
<td class='noHide'>
<button type='button' class='ui-button ui-corner-all ui-widget' id='add_dq' title='<?php print __esc('Add Data Query to Device Template'); ?>'><?php print __esc('Add'); ?></button>
</td>
</tr>
</table>
</td>
</tr>
<?php
html_end_box();
api_plugin_hook('device_template_edit');
}
form_save_button('host_templates.php', 'return');
?>
<script type='text/javascript'>
function deleteFinalize(options, data) {
$('#cdialog').dialog({
title: '<?php print __('Delete Item from Device Template'); ?>',
close: function() {
$('.delete').blur();
$('.selectable').removeClass('selected');
},
minHeight: 80,
minWidth: 500
});
}
$(function() {
$('#cdialog').remove();
$('#main').append("<div id='cdialog' class='cdialog'></div>");
$('.delete').click(function(event) {
event.preventDefault();
var options = {
url: $(this).attr('href'),
noState: true,
funcEnd: 'deleteFinalize',
elementId: 'cdialog',
};
loadUrl(options);
}).css('cursor', 'pointer');
$('#add_dq').click(function() {
var options = {
url: 'host_templates.php?action=item_add_dq',
noState: true,
}
var data = {
host_template_id: $('#id').val(),
snmp_query_id: $('#snmp_query_id').val(),
reindex_method: $('#reindex_method').val(),
__csrf_magic: csrfMagicToken
}
postUrl(options, data);
});
$('#add_gt').click(function() {
var options = {
url: 'host_templates.php?action=item_add_gt',
noState: true,
}
var data = {
host_template_id: $('#id').val(),
graph_template_id: $('#graph_template_id').val(),
__csrf_magic: csrfMagicToken
}
postUrl(options, data);
});
});
</script>
<?php
}
function create_template_filter() : array {
global $item_rows, $device_classes;
$all = ['-1' => __('All')];
$device_classes = $all + $device_classes;
$action_arr = [
'templates' => __('Templates'),
'archives' => __('Archives')
];
if (isrv('has_hosts')) {
$value = gnrv('has_hosts');
} else {
$value = read_config_option('default_has') == 'on' ? 'true' : 'false';
}
if (grv('class') == '-1' || ierv('class')) {
$graph_templates = db_fetch_assoc('SELECT DISTINCT rs.id, rs.name
FROM (
SELECT gt.id, gt.name
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT gt.id, gt.name
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
ORDER BY name');
} else {
$graph_templates = db_fetch_assoc_prepared('SELECT DISTINCT rs.id, rs.name
FROM (
SELECT gt.id, gt.name, htg.host_template_id
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT gt.id, gt.name, htsq.host_template_id
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
INNER JOIN host_template AS ht
ON rs.host_template_id = ht.id
WHERE ht.class = ?
ORDER BY name',
[grv('class')]);
}
$graph_templates = array_rekey($graph_templates, 'id', 'name');
$graph_templates = $all + $graph_templates;
return [
'rows' => [
[
'class' => [
'method' => 'drop_array',
'friendly_name' => __('Class'),
'filter' => FILTER_CALLBACK,
'filter_options' => ['options' => 'sanitize_search_string'],
'default' => '-1',
'pageset' => true,
'array' => $device_classes,
'value' => '-1'
],
'graph_template' => [
'method' => 'drop_array',
'friendly_name' => __('Graph Templates'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'description' => __('Search for Device Templates that use this specific Graph Template'),
'pageset' => true,
'array' => $graph_templates,
'value' => '-1'
],
'action' => [
'method' => 'drop_array',
'friendly_name' => __('View'),
'filter' => FILTER_CALLBACK,
'filter_options' => ['options' => 'sanitize_search_string'],
'default' => 'templates',
'pageset' => true,
'array' => $action_arr,
'value' => 'templates'
],
'has_hosts' => [
'method' => 'filter_checkbox',
'friendly_name' => __('Has Devices'),
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => ['options' => ['regexp' => '(true|false)']],
'default' => '',
'pageset' => true,
'value' => $value
]
],
[
'filter' => [
'method' => 'textbox',
'friendly_name' => __('Search'),
'filter' => FILTER_DEFAULT,
'placeholder' => __('Enter a search term'),
'size' => '30',
'default' => '',
'pageset' => true,
'max_length' => '120',
'value' => ''
],
'rows' => [
'method' => 'drop_array',
'friendly_name' => __('Templates'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $item_rows,
'value' => '-1'
]
]
],
'buttons' => [
'go' => [
'method' => 'submit',
'display' => __('Go'),
'title' => __('Apply Filter to Table'),
],
'clear' => [
'method' => 'button',
'display' => __('Clear'),
'title' => __('Reset Filter to Default Values'),
]
],
'sort' => [
'sort_column' => 'name',
'sort_direction' => 'DESC'
]
];
}
function draw_template_filter(bool $render = false) : void {
$filters = create_template_filter();
// create the page filter
$pageFilter = new CactiTableFilter(__('Device Templates'), 'host_templates.php', 'form_template', 'sess_ht', 'host_templates.php?action=edit');
$pageFilter->set_filter_array($filters);
if ($render) {
$pageFilter->render();
} else {
$pageFilter->sanitize();
}
}
function device_templates() : void {
global $actions, $item_rows, $device_classes;
draw_template_filter(true);
if (grv('rows') == '-1') {
$rows = read_config_option('num_rows_table');
} else {
$rows = grv('rows');
}
// form the 'where' clause for our main sql query
$sql_where = '';
$sql_params = [];
$sql_join = '';
if (grv('filter') != '') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'ht.name LIKE ?';
$sql_params[] = '%' . grv('filter') . '%';
}
if (grv('class') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'ht.class = ?';
$sql_params[] = grv('class');
}
if (grv('graph_template') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'gt_id = ?';
$sql_params[] = grv('graph_template');
$sql_join = 'INNER JOIN (
SELECT DISTINCT host_template_id, id AS gt_id
FROM (
SELECT htg.host_template_id, gt.id
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT htsq.host_template_id, gt.id
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
) AS htdata
ON htdata.host_template_id = ht.id';
}
if (grv('has_hosts') == 'true') {
$sql_having = 'HAVING hosts > 0';
} else {
$sql_having = '';
}
$total_rows = db_fetch_cell_prepared("SELECT COUNT(`rows`)
FROM (
SELECT
COUNT(ht.id) AS `rows`, COUNT(DISTINCT host.id) AS hosts
FROM host_template AS ht
$sql_join
LEFT JOIN host
ON host.host_template_id = ht.id
LEFT JOIN host_template_archive AS hta
ON hta.host_template_id = ht.id
$sql_where
GROUP BY ht.id
$sql_having
) AS rs",
$sql_params);
$sql_order = get_order_string();
$sql_limit = ' LIMIT ' . ($rows * (grv('page') - 1)) . ',' . $rows;
$template_list = db_fetch_assoc_prepared("SELECT
ht.id, ht.name, ht.class, ht.version, ht.author, ht.copyright, COUNT(DISTINCT host.id) AS hosts, COUNT(DISTINCT hta.id) AS `archives`
FROM host_template AS ht
$sql_join
LEFT JOIN host
ON host.host_template_id=ht.id
LEFT JOIN host_template_archive AS hta
ON hta.host_template_id = ht.id
$sql_where
GROUP BY ht.id
$sql_having
$sql_order
$sql_limit",
$sql_params);
$display_text = [
'name' => [
'display' => __('Device Template Name'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The name of this Device Template.')
],
'ht.class' => [
'display' => __('Device Class'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The Class of this Device Template. The Class Name should be representative of it\'s function.')
],
'ht.version' => [
'display' => __('Version'),
'align' => 'center',
'sort' => 'ASC',
'tip' => __('The version of this Device Template.')
],
'archives' => [
'display' => __('Archives'),
'align' => 'center',
'sort' => 'DESC',
'tip' => __('The number of Archived versions of this Device Template on disk.')
],
'ht.author' => [
'display' => __('Author'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The author of this Device Template.')
],
'ht.copyright' => [
'display' => __('Copyright'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The copyright of this Device Template.')
],
'ht.id' => [
'display' => __('ID'),
'align' => 'right',
'sort' => 'ASC',
'tip' => __('The internal database ID for this Device Template. Useful when performing automation or debugging.')
],
'nosort' => [
'display' => __('Deletable'),
'align' => 'right',
'sort' => '',
'tip' => __('Device Templates in use cannot be Deleted. In use is defined as being referenced by a Device.')
],
'hosts' => [
'display' => __('Devices Using'),
'align' => 'right',
'sort' => 'DESC',
'tip' => __('The number of Devices using this Device Template.')
]
];
$nav = html_nav_bar('host_templates.php?action=templates', MAX_DISPLAY_PAGES, grv('page'), $rows, $total_rows, cacti_sizeof($display_text) + 1, __('Device Templates'), 'page', 'main');
form_start('host_templates.php?action=templates', 'chk');
print $nav;
html_start_box('', '100%', false, 3, 'center', '');
html_header_sort_checkbox($display_text, grv('sort_column'), grv('sort_direction'), false, 'host_templates.php?action=template');
if (cacti_sizeof($template_list)) {
foreach ($template_list as $template) {
if ($template['hosts'] > 0) {
$disabled = true;
} else {
$disabled = false;
}
form_alternate_row('line' . $template['id'], true, $disabled);
form_selectable_cell(filter_value($template['name'], grv('filter'), 'host_templates.php?action=edit&id=' . $template['id']), $template['id']);
if ($template['class'] != '') {
form_selectable_cell($device_classes[$template['class']], $template['id']);
} else {
form_selectable_cell(__('Unassigned'), $template['id']);
}
$archive_url = 'host_templates.php?action=archives&host_template=' . $template['id'];
form_selectable_ecell($template['version'], $template['id'], '', 'center');
form_selectable_cell(filter_value($template['archives'], '', $archive_url), $template['id'], '', 'center');
form_selectable_ecell($template['author'], $template['id'], '', 'left');
form_selectable_ecell($template['copyright'], $template['copyright'], '', 'left');
form_selectable_cell($template['id'], $template['id'], '', 'right');
form_selectable_cell($disabled ? __('No') : __('Yes'), $template['id'], '', 'right');
$url = 'host.php?reset=true&host_template_id=' . $template['id'];
form_selectable_cell(filter_value(number_format_i18n($template['hosts'], -1), '', $url), $template['id'], '', 'right');
form_checkbox_cell($template['name'], $template['id'], $disabled);
form_end_row();
}
} else {
print "<tr class='tableRow odd'><td colspan='" . (cacti_sizeof($display_text) + 1) . "'><em>" . __('No Device Templates Found') . '</em></td></tr>';
}
html_end_box(false);
if (cacti_sizeof($template_list)) {
print $nav;
}
form_hidden_box('action_type', 'templates', '');
// draw the dropdown containing a list of available actions for this form
draw_actions_dropdown($actions);
form_end();
}
function create_archive_filter() : array {
global $item_rows, $device_classes;
$all = ['-1' => __('All')];
$device_classes = $all + $device_classes;
if (isrv('has_hosts')) {
$value = gnrv('has_hosts');
} else {
$value = read_config_option('default_has') == 'on' ? 'true' : 'false';
}
$action_arr = [
'templates' => __('Templates'),
'archives' => __('Archives')
];
if (grv('class') == '-1' || ierv('class')) {
$graph_templates = db_fetch_assoc('SELECT DISTINCT rs.id, rs.name
FROM (
SELECT gt.id, gt.name
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT gt.id, gt.name
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
ORDER BY name');
} else {
$graph_templates = db_fetch_assoc_prepared('SELECT DISTINCT rs.id, rs.name
FROM (
SELECT gt.id, gt.name, htg.host_template_id
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT gt.id, gt.name, htsq.host_template_id
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
INNER JOIN host_template AS ht
ON rs.host_template_id = ht.id
WHERE ht.class = ?
ORDER BY name',
[grv('class')]);
}
$graph_templates = array_rekey($graph_templates, 'id', 'name');
$graph_templates = $all + $graph_templates;
if (!ierv('class') && grv('class') != '-1') {
$sql_where = 'WHERE ht.class = ?';
$sql_params[] = grv('class');
} else {
$sql_where = '';
$sql_params = [];
}
$device_templates = array_rekey(
db_fetch_assoc_prepared("SELECT DISTINCT ht.id, ht.name
FROM host_template AS ht
INNER JOIN host_template_archive AS hta
ON ht.id = hta.host_template_id
$sql_where
ORDER BY ht.name ASC",
$sql_params),
'id', 'name'
);
$device_templates = $all + $device_templates;
return [
'rows' => [
[
'class' => [
'method' => 'drop_array',
'friendly_name' => __('Class'),
'filter' => FILTER_CALLBACK,
'filter_options' => ['options' => 'sanitize_search_string'],
'default' => '-1',
'pageset' => true,
'array' => $device_classes,
'value' => '-1'
],
'host_template' => [
'method' => 'drop_array',
'friendly_name' => __('Device Template'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'description' => __('Search for Device Templates that use this specific Graph Template'),
'pageset' => true,
'array' => $device_templates,
'value' => '-1'
],
'graph_template' => [
'method' => 'drop_array',
'friendly_name' => __('Graph Template'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'description' => __('Search for Device Templates that use this specific Graph Template'),
'pageset' => true,
'array' => $graph_templates,
'value' => '-1'
],
'action' => [
'method' => 'drop_array',
'friendly_name' => __('View'),
'filter' => FILTER_CALLBACK,
'filter_options' => ['options' => 'sanitize_search_string'],
'default' => 'templates',
'pageset' => true,
'array' => $action_arr,
'value' => 'templates'
],
'has_hosts' => [
'method' => 'filter_checkbox',
'friendly_name' => __('Has Devices'),
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => ['options' => ['regexp' => '(true|false)']],
'default' => '',
'pageset' => true,
'value' => $value
]
],
[
'filter' => [
'method' => 'textbox',
'friendly_name' => __('Search'),
'filter' => FILTER_DEFAULT,
'placeholder' => __('Enter a search term'),
'size' => '30',
'default' => '',
'pageset' => true,
'max_length' => '120',
'value' => ''
],
'rows' => [
'method' => 'drop_array',
'friendly_name' => __('Archives'),
'filter' => FILTER_VALIDATE_INT,
'default' => '-1',
'pageset' => true,
'array' => $item_rows,
'value' => '-1'
]
]
],
'buttons' => [
'go' => [
'method' => 'submit',
'display' => __('Go'),
'title' => __('Apply Filter to Table'),
],
'clear' => [
'method' => 'button',
'display' => __('Clear'),
'title' => __('Reset Filter to Default Values'),
]
],
'sort' => [
'sort_column' => 'name',
'sort_direction' => 'DESC'
]
];
}
function draw_archive_filter(bool $render = false) : void {
$filters = create_archive_filter();
// create the page filter
$pageFilter = new CactiTableFilter(__('Device Templates Archives'), 'host_templates.php', 'form_template', 'sess_hta');
$pageFilter->set_filter_array($filters);
if ($render) {
$pageFilter->render();
} else {
$pageFilter->sanitize();
}
}
function device_archives() : void {
global $actions, $item_rows, $device_classes;
draw_archive_filter(true);
if (grv('rows') == '-1') {
$rows = read_config_option('num_rows_table');
} else {
$rows = grv('rows');
}
// form the 'where' clause for our main sql query
$sql_where = '';
$sql_params = [];
$sql_join = '';
if (grv('filter') != '') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'ht.name LIKE ? OR ht.archive_note LIKE ?';
$sql_params[] = '%' . grv('filter') . '%';
$sql_params[] = '%' . grv('filter') . '%';
}
if (grv('class') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'ht.class = ?';
$sql_params[] = grv('class');
}
if (grv('host_template') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'ht.host_template_id = ?';
$sql_params[] = grv('host_template');
}
if (grv('graph_template') != '-1') {
$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'gt_id = ?';
$sql_params[] = grv('graph_template');
$sql_join = 'INNER JOIN (
SELECT DISTINCT host_template_id, id AS gt_id
FROM (
SELECT htg.host_template_id, gt.id
FROM graph_templates AS gt
INNER JOIN host_template_graph AS htg
ON htg.graph_template_id = gt.id
UNION
SELECT htsq.host_template_id, gt.id
FROM graph_templates AS gt
INNER JOIN snmp_query_graph AS sqg
ON gt.id = sqg.graph_template_id
INNER JOIN host_template_snmp_query AS htsq
ON sqg.snmp_query_id = htsq.snmp_query_id
) AS rs
) AS htdata
ON htdata.host_template_id = ht.id';
}
if (grv('has_hosts') == 'true') {
$sql_having = 'HAVING hosts > 0';
} else {
$sql_having = '';
}
$total_rows = db_fetch_cell_prepared("SELECT COUNT(`rows`)
FROM (
SELECT
COUNT(ht.id) AS `rows`, COUNT(DISTINCT host.id) AS hosts
FROM host_template_archive AS ht
$sql_join
LEFT JOIN host
ON host.host_template_id = ht.id
$sql_where
GROUP BY ht.id
$sql_having
) AS rs",
$sql_params);
$sql_order = get_order_string();
$sql_limit = ' LIMIT ' . ($rows * (grv('page') - 1)) . ',' . $rows;
$archives = db_fetch_assoc_prepared("SELECT
ht.id, ht.name, ht.class, ht.version, ht.author, ht.copyright, archive_note, archive_date, LENGTH(archive) AS size, COUNT(DISTINCT host.id) AS hosts
FROM host_template_archive AS ht
$sql_join
LEFT JOIN host
ON host.host_template_id=ht.id
$sql_where
GROUP BY ht.id
$sql_having
$sql_order
$sql_limit",
$sql_params);
$display_text = [
'name' => [
'display' => __('Device Template Name'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The name of this Device Template.')
],
'ht.class' => [
'display' => __('Device Class'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The Class of this Device Template. The Class Name should be representative of it\'s function.')
],
'ht.version' => [
'display' => __('Version'),
'align' => 'center',
'sort' => 'ASC',
'tip' => __('The version of this Device Template.')
],
'ht.author' => [
'display' => __('Author'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The author of this Device Template.')
],
'ht.copyright' => [
'display' => __('Copyright'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('The copyright of this Device Template.')
],
'ht.archive_note' => [
'display' => __('Archive Notes'),
'align' => 'left',
'sort' => 'ASC',
'tip' => __('Hover over the column to see the archive notes.')
],
'ht.id' => [
'display' => __('ID'),
'align' => 'right',
'sort' => 'ASC',
'tip' => __('The internal database ID for this Device Template. Useful when performing automation or debugging.')
],
'size' => [
'display' => __('Size'),
'align' => 'right',
'sort' => 'DESC',
'tip' => __('The size of the Device Template Archive in the Database.')
],
'ht.archive_date' => [
'display' => __('Archive Date'),
'align' => 'right',
'sort' => 'ASC',
'tip' => __('The internal database ID for this Device Template. Useful when performing automation or debugging.')
]
];
$nav = html_nav_bar('host_templates.php?action=archives', MAX_DISPLAY_PAGES, grv('page'), $rows, $total_rows, cacti_sizeof($display_text) + 1, __('Device Templates'), 'page', 'main');
form_start('host_templates.php?action=archives', 'chk');
print $nav;
html_start_box('', '100%', false, 3, 'center', '');
html_header_sort_checkbox($display_text, grv('sort_column'), grv('sort_direction'), false, 'host_templates.php?action=archives');
if (cacti_sizeof($archives)) {
foreach ($archives as $a) {
form_alternate_row('line' . $a['id'], true);
form_selectable_cell(filter_value($a['name'], grv('filter')), $a['id']);
if ($a['class'] != '') {
form_selectable_cell($device_classes[$a['class']], $a['id']);
} else {
form_selectable_cell(__('Unassigned'), $a['id']);
}
form_selectable_ecell($a['version'], $a['id'], '', 'center');
form_selectable_ecell($a['author'], $a['id'], '', 'left');
form_selectable_ecell($a['copyright'], $a['id'], '', 'left');
form_selectable_cell(filter_value(__('Notes'), '', '#', $a['archive_note']), $a['id'], '', 'left');
form_selectable_cell($a['id'], $a['id'], '', 'right');
form_selectable_cell(number_format_i18n($a['size'], 2, 1000), $a['id'], '', 'right');
form_selectable_cell($a['archive_date'], $a['id'], '', 'right');
form_checkbox_cell($a['name'], $a['id']);
form_end_row();
}
} else {
print "<tr class='tableRow odd'><td colspan='" . (cacti_sizeof($display_text) + 1) . "'><em>" . __('No Device Template Archives Found') . '</em></td></tr>';
}
html_end_box(false);
if (cacti_sizeof($archives)) {
print $nav;
}
form_hidden_box('action_type', 'archives', '');
// draw the dropdown containing a list of available actions for this form
draw_actions_dropdown($actions);
form_end();
}