mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* Port #8166 SNMP credential cache + procedural SNMPv3 to develop Ports the shared SNMPv3 credential cache and procedural SNMPv3 routing from the 1.2.x feature (merged as #8166) onto develop, adapted to develop's refactored SNMP layer. Parts of the 1.2.x change that do not apply to develop are omitted: the cacti_exec() idle-timeout rewrite (develop has no cacti_exec()), the PHP floor bump and composer/doc changes (develop is already 8.3+), and the cmd.php max_oids batching (develop's cmd.php already collects SNMP in-process through persistent sessions, so the per-item spawn the batching removed does not occur). - lib/cache.php: new cross-process \Cacti\Cache\SharedCache (file + POSIX shared-memory + process-local tiers, optional AES-256-GCM sealing). Loaded via lib/snmp.php (the Cacti\Cache\ namespace is not PSR-4 autoloaded). The crypto helpers it seals with already live in develop's lib/auth.php, loaded by global.php, so no duplicate was added. - lib/snmp.php: snmp_get_method() now routes SNMPv3 get/getnext to the procedural snmp3_*() calls when the running PHP supports the configured auth/privacy tokens and no context/engine id is set (SHA-224/384 and AES-192/256[C] stay on the binary before PHP 8.6); the three single-get functions gain the procedural v3 path with \Throwable handling; adds the credential-cache subsystem, protocol-option/MD5-DES helpers, snmp_native_protocol(), snmp_format_agent() and cacti_snmp_get_multi(); the binary v3 auth builder consults the cache when enabled. - poller.php / script_server.php: prime / load the shared cache. - poller_maintenance.php: daily out-of-band cache rebuild on every collector (per-poller lastrun marker). - lib/api_device.php / lib/utility.php: bump snmp_cred_version only on v3 credential add/change (and propagate the token to the affected remote poller). - include/global.php: resolve and create the private cache/ directory. - include/global_settings.php: add 'Enable Credential Cache' and 'Enable MD5 and DES for SNMPv3' settings (both off by default); default new-device v3 auth/privacy to SHA/AES instead of legacy MD5/DES. - host.php / automation_snmp.php: drop MD5/DES from the pickers when disabled and default new records to SHA/AES on upgraded installs. - include/config.php.dist: document $config['cache_dir']. - tests/Unit/Core/Cache/SharedCacheTest.php: SharedCache behaviour coverage. - tests/security/baselines: baseline the two new lib/cache.php sinks. * Incorporate #8171: binary fallback for unsupported SNMPv3 protocols Layers PR #8171 onto the develop port so an SNMPv3 device whose auth/privacy protocols the running PHP's ext-snmp cannot service natively (e.g. AES-256 before PHP 8.6) no longer fatals the poller or device-edit screen. - lib/snmp.php: add CactiSnmpBinarySession, a session stand-in exposing the get/getnext/walk/close interface the cacti_snmp_session_*() helpers use and delegating each call to cacti_snmp_*() -> the Net-SNMP binary. cacti_snmp_session() returns it when php-snmp is absent or the v3 protocol is not natively serviceable, so cmd.php/host.php collect these devices via the binary on pre-8.6 and switch to the in-process path on 8.6 with no caller change. v1/v2 snmpget/snmp2_get now go through snmp_format_agent(). The ERRNO_TIMEOUT check tolerates php-snmp being absent. Drop the vendored classSNMP.php compatibility shim and its include (and the Yandex attribution). - cmd.php: open_snmp_session() returns the down sentinel when a host is already marked down this run, instead of returning an unset (null) [] that cacti_snmp_session_get() then dereferenced (TypeError). - lib/ping.php: ping_snmp() runs the availability check through the procedural cacti_snmp_get()/getnext() path so an unsupported protocol falls back to the binary instead of raising a ValueError in SNMP::setSecurity(). * Incorporate #8172: retire include/vendor/phpsnmp, relocate into lib Follow-up to #8171 on the develop port. After classSNMP.php was removed, the include/vendor/phpsnmp/ directory held only Cacti-owned files; this retires it. - lib/snmp.php: the ext-snmp subclass that carried Cacti's bulk_walk_size / value_output_format (previously vendor/phpsnmp/extension.php) is now declared directly as CactiSnmpNativeSession, guarded by CACTI_PHP_SNMP; the include and the 'use phpsnmp\SNMP' alias are gone, and new SNMP(...) / SNMP::VERSION_* now reference the native \SNMP and the new subclass. - lib/mib_parser.php: moved from include/vendor/phpsnmp/mib_parser.php; lib/mib_cache.php includes it via __DIR__. - Remove include/vendor/phpsnmp/extension.php and index.php (directory retired). - tests: SnmpCoverageTest uses \SNMP::ERRNO_TIMEOUT now that phpsnmp\SNMP is gone; drop the stale include/vendor/phpsnmp/ entry from the vendor-policy allowlist. develop's sink baseline tracks constant-based includes differently than 1.2.x, so no baseline refresh was needed here. * Address PR #8174 review: snmp_get_method params, v3 walk, getnext agent, cache bump, CHANGELOG - lib/snmp.php: restore $php_snmp as the 6th snmp_get_method() parameter and move the new $auth_proto/$priv_proto after it (callers pass them by name), so the positional $php_snmp used by SnmpCoverageTest and others keeps working. - lib/snmp.php: keep every SNMPv3 operation except get/getnext on the Net-SNMP binary - the native walk branch implements only v1/v2, so a v3 walk reaching the procedural gate would have silently returned empty when path_snmpbulkwalk is missing. - lib/snmp.php: cacti_snmp_getnext() v1/v2 branches now use snmp_format_agent() like get/get_raw, so an IPv6 literal / non-default port is bracketed. - lib/api_device.php: bump snmp_cred_version only when the host's v3 membership or one of the cached credential fields actually changed, instead of on every save of a v3 device. - CHANGELOG: move the #8171 entry into the issue block and the #8166 entries into the feature block, matching the established section ordering. * Fix CI: unbracket IPv4/DNS SNMP targets, inline auth-cache SQL columns, apply phpcsfixer standard * Refresh sink inventory baseline after short-array normalization * Restore lib/snmp.php 100% coverage after aligning v3 routing to 1.2.x - update the three SnmpCoverageTest assertions that still encoded develop's pre-port behavior: v3 snmp_get_method now returns PHP, an unsupported- protocol v3 session returns the binary-delegating session object, and the v3 cacti_snmp_get path runs natively - add a v3 noAuthNoPriv user to the snmp-coverage snmpd fixture so native snmp3_get is exercised deterministically - add tests covering the ported subsystems to 100%: shared auth cache, binary-delegating session, protocol pickers, v3 credential args, cacti_snmp_get_multi, and the cacti_get_snmpv3_auth cache path - snmp_auth_cache_load uses a resettable process guard so its enabled and disabled branches are both coverable in a single process * Resolve phpstan errors in the relocated SNMP MIB parser and ported snmp.php - lib/mib_parser.php: type the MibParser properties, add parameter/return types, guard file_get_contents()/preg_split() against false, correct the parse_bracket_token delimiter params (string, not integer), and drop the unreachable return after trigger_error(E_USER_ERROR) - now phpstan clean - lib/mib_cache.php: drop the redundant isset($mp->oids) guard (the typed property is always initialized; $mp->mib is the real check) - lib/snmp.php: type the CactiSnmpBinarySession and CactiSnmpNativeSession properties/methods and pass bool to snmp_set_quick_print() - .phpstan.neon: ignore greaterOrEqual.alwaysFalse alongside the existing greater.alwaysFalse (the PHP_VERSION_ID >= 80600 forward-compat gate) - regenerate phpstan-baseline.neon for the irreducible ext-snmp stub strictness (snmp3_get literal-union protocols, int<-1,max> timeouts) in the ported procedural v3 path, the new lib/cache.php shmop stubs, and the $config array-shape offsets in include/global.php * Make the v3 getter catch coverage net-snmp-build-independent The DES-rejection trick that drove the procedural snmp3_get()/snmp3_getnext() try/catch blocks only works on net-snmp builds compiled without DES; CI's Debian net-snmp accepts DES, so those catches went uncovered (98.3%). - extend the existing native_get injection seam (already on cacti_snmp_get) to cacti_snmp_get_raw, cacti_snmp_getnext, and cacti_snmp_get_multi, and drive the catch blocks with a throwing native getter in the test - mark the seam-less cacti_snmp_session() setSecurity catch with @codeCoverageIgnore (it only throws for a protocol the running net-snmp rejects, which is not portably reproducible in the fixture) * Exclude ext-snmp/PHP-8.6-gated defensive branches from snmp coverage Two branches in lib/snmp.php are unreachable on the PHP 8.3 + ext-snmp coverage runner and were only "covered" locally via opcache dead-code elimination: - snmp_get_method()'s !function_exists('snmp3_get') fallback (ext-snmp always provides snmp3_get) - snmp_php_v3_protocols_supported()'s PHP_VERSION_ID >= 80600 block (SHA-224/384 and AES-192/256 tokens are only accepted on PHP 8.6+) Mark both with @codeCoverageIgnore so the 100% gate is deterministic across runners regardless of opcache settings. * Account for the #8166 SNMPv3 credential-cache gate query in the batching test poller_update_poller_cache_from_buffer() now runs one SELECT per flush to decide whether the flush touched SNMPv3 poller items before bumping the shared credential-cache version (matching 1.2.x). UpdateDataQueryCacheBatchingIntegrationTest counted 4 statements per flush; update it to 5 (and 3 per-row flushes to 15, 1 batched flush to 5). The guard's intent - batching issues fewer statements than per-row flushes - still holds. * Port #8175: group SNMP options under an SNMP Behavior section Mirror the 1.2.x settings reorganization (#8175) on develop. develop already keeps the SNMP options in a dedicated SNMP tab, so add an "SNMP Behavior" spacer there, order the behavior options as md5/des, credential cache, max get size, bulkwalk fetch size, and move "Disable increasing OID Check" out of the Poller tab to sit last in that group. Display-only; no settings added or removed and no behavior change. Adds the "SNMP Behavior" heading to cacti.pot. * fix: complete dependabot htmx 2.0.10 to 2.0.11 bump Dependabot bumped package.json and package-lock.json to htmx.org 2.0.11 (PR 8179) but left the vendored include/js/htmx.js, its version marker, and the pinned SRI constant at 2.0.10. The htmx-filters CI workflow runs npm ci, which regenerates include/js/htmx.js from the 2.0.11 package, so HtmxTest's runtime sha384 no longer matches the HTMX_2_0_10_SRI constant and the integrity assertion fails. Complete the bump: regenerate the vendored include/js/htmx.js to the 2.0.11 build, bump include/js/htmx.js.version to 2.0.11, rename HTMX_2_0_10_SRI to HTMX_2_0_11_SRI with the recomputed sha384 hash, and update the version/integrity assertions in tests/Unit/Ui/Html/HtmxTest.php and tests/HandOff/HtmxLoaderHandOffTest.php. package.json and package-lock.json match the values dependabot already landed on develop. * settings: move SNMP Agent Support under the SNMP Behavior spacer Relocate the enable_snmp_agent setting (SNMP Agent Support Enabled) so it is the first item under the SNMP Behavior spacer instead of sitting in the Poller General group, grouping it with the other SNMP behavior options. * Address PR #8174 review: keep SHA-224/384 on the binary, honor port on transport targets Resolves the two still-valid Copilot review findings on lib/snmp.php that were left unresolved (only marked outdated after later commits shifted their lines). - snmp_php_v3_protocols_supported(): drop SHA224/SHA384 from the native auth allowlist. ext-snmp's SNMP::setSecurity()/snmp3_*() only map MD5/SHA/SHA256/ SHA512 - there is no SHA-224/SHA-384 mapping on any build, including PHP 8.6 (verified against php-src ext/snmp/snmp.c), so routing those tokens to the procedural path raised a ValueError instead of using the binary. They now always fall back to the Net-SNMP binary. The AES-192/256 privacy tokens that ext-snmp does add on 8.6 stay gated behind PHP_VERSION_ID >= 80600. - snmp_format_agent(): a udp/tcp transport-qualified target that omits a port now has the configured non-default port appended (udp6:[2001:db8::1] + 1161 -> udp6:[2001:db8::1]:1161), matching the Net-SNMP binary path. Targets that already carry a port, and unix: sockets (no port), are left untouched. - SnmpCoverageTest: cover the new transport-target branches; lib/snmp.php stays at 100%.
3541 lines
122 KiB
PHP
3541 lines
122 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/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* Update hash stored in settings table to inform remote pollers to update their caches
|
|
*
|
|
* @param int $poller_id The ID of the poller for which the device cache CRC is being updated.
|
|
* @param string $variable The base name of the variable to store the hash. Defaults to 'poller_replicate_device_cache_crc'.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_cache_crc_update(int $poller_id, string $variable = 'poller_replicate_device_cache_crc') : void {
|
|
$hash = hash('ripemd160', date('Y-m-d H:i:s') . random_int(0, mt_getrandmax()) . "$poller_id");
|
|
|
|
db_execute_prepared("REPLACE INTO settings SET value = ?, name='$variable" . '_' . "$poller_id'", [$hash]);
|
|
}
|
|
|
|
/**
|
|
* Removes a device from the system.
|
|
*
|
|
* @param int $device_id The ID of the device to be removed.
|
|
* @return void
|
|
*/
|
|
function api_device_remove(int $device_id) : void {
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id
|
|
FROM host WHERE id = ?',
|
|
[$device_id]);
|
|
|
|
api_plugin_hook_function('device_remove', [$device_id]);
|
|
|
|
/**
|
|
* Get the object totals by object type for later updating
|
|
*/
|
|
object_cache_get_totals('device_delete', $device_id);
|
|
|
|
if (!db_begin_transaction()) {
|
|
raise_message('device_remove_failed_' . $device_id, __esc('Unable to remove Device %s because a database transaction could not be started.', $device_id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$graphs = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_local
|
|
WHERE host_id = ?',
|
|
[$device_id]),
|
|
'id', 'id'
|
|
);
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
$graph_ids = $graphs;
|
|
api_delete_graphs($graphs, 2);
|
|
|
|
$remaining_graphs = db_fetch_cell('SELECT COUNT(*)
|
|
FROM graph_local
|
|
WHERE ' . db_in_clause('id', $graph_ids));
|
|
|
|
if ($remaining_graphs === false || $remaining_graphs > 0) {
|
|
db_rollback_transaction();
|
|
raise_message('device_graph_remove_failed_' . $device_id, __esc('Unable to remove Device %s because Graph cleanup did not complete.', $device_id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
$cleanup_queries = [
|
|
['DELETE FROM host_graph WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM host_snmp_query WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM host_snmp_cache WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM host_value_cache WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM poller_item WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM poller_reindex WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM graph_tree_items WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM reports_items WHERE host_id = ?', [$device_id]],
|
|
['DELETE FROM poller_command WHERE command LIKE ?', [$device_id . ':%']]
|
|
];
|
|
|
|
foreach ($cleanup_queries as [$sql, $params]) {
|
|
if (db_execute_prepared($sql, $params) === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_failed_' . $device_id, __esc('Unable to remove Device %s because dependent database cleanup failed.', $device_id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($poller_id == 1) {
|
|
$removed = db_execute_prepared('DELETE FROM host WHERE id = ?', [$device_id]);
|
|
} else {
|
|
$removed = db_execute_prepared('UPDATE host SET deleted = "on" WHERE id = ?', [$device_id]);
|
|
}
|
|
|
|
if ($removed === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_failed_' . $device_id, __esc('Unable to remove Device %s because its primary database record could not be updated.', $device_id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!db_commit_transaction()) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_failed_' . $device_id, __esc('Unable to remove Device %s because its database transaction could not be committed.', $device_id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($poller_id > 1) {
|
|
api_device_purge_from_remote($device_id, $poller_id);
|
|
}
|
|
|
|
api_device_cache_crc_update($poller_id);
|
|
|
|
/**
|
|
* Save the last time a device/site was created/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_device', time());
|
|
set_config_option('time_last_change_site_device', time());
|
|
|
|
/**
|
|
* Set the object totals by object type for later updating
|
|
*/
|
|
object_cache_update_totals('delete');
|
|
}
|
|
|
|
/**
|
|
* Removes a device from a remote data collectors
|
|
*
|
|
* @param array|int $device_ids An array of device IDs or a single device ID to be purged.
|
|
* @param int $poller_id The ID of the remote poller. Default is 0.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_purge_from_remote(array|int $device_ids, int $poller_id = 0) : void {
|
|
if (!is_array($device_ids)) {
|
|
$device_ids = [$device_ids];
|
|
}
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($poller_id, true)) !== false) {
|
|
$int_device_ids = array_map('intval', $device_ids);
|
|
|
|
db_execute('DELETE FROM host WHERE ' . db_in_clause('id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM host_graph WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM host_snmp_query WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM host_snmp_cache WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM host_value_cache WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
poller_item_delete_for_host($int_device_ids, $rcnn_id, false);
|
|
db_execute('DELETE FROM poller_reindex WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM graph_tree_items WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM reports_items WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
|
|
db_execute('DELETE FROM poller_command
|
|
WHERE ' . db_in_clause('SUBSTRING_INDEX(command, ":", 1)', $int_device_ids), true, $rcnn_id);
|
|
|
|
db_execute('DELETE FROM data_local WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
db_execute('DELETE FROM graph_local WHERE ' . db_in_clause('host_id', $int_device_ids), true, $rcnn_id);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
|
|
foreach ($device_ids as $id) {
|
|
db_execute_prepared('INSERT INTO poller_command
|
|
(poller_id, time, action, command)
|
|
VALUES (?, NOW(), ?, ?)
|
|
ON DUPLICATE KEY UPDATE time=VALUES(time)',
|
|
[$poller_id, POLLER_COMMAND_PURGE, $id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove any devices from the database that are marked for deletion.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_purge_deleted_devices() : void {
|
|
$devices = db_fetch_assoc_prepared('SELECT id, poller_id
|
|
FROM host
|
|
WHERE deleted = "on"
|
|
AND UNIX_TIMESTAMP(last_updated) < UNIX_TIMESTAMP()-500');
|
|
|
|
if (cacti_sizeof($devices)) {
|
|
foreach ($devices as $d) {
|
|
db_execute_prepared('DELETE FROM host WHERE id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM host_graph WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM host_snmp_query WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM host_snmp_cache WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM host_value_cache WHERE host_id = ?', [$d['id']]);
|
|
poller_item_delete_for_host($d['id']);
|
|
db_execute_prepared('DELETE FROM poller_reindex WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM graph_tree_items WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM reports_items WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM poller_command WHERE command LIKE ?', [$d['id'] . ':%']);
|
|
db_execute_prepared('DELETE FROM data_local WHERE host_id = ?', [$d['id']]);
|
|
db_execute_prepared('DELETE FROM graph_local WHERE host_id = ?', [$d['id']]);
|
|
|
|
api_device_purge_from_remote($d['id'], $d['poller_id']);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes multiple devices in one call
|
|
*
|
|
* @param array $device_ids An array of device IDs to be removed.
|
|
* @param int $delete_type The type of deletion to perform
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_remove_multi(array $device_ids, int $delete_type = 2) : void {
|
|
if (cacti_sizeof($device_ids)) {
|
|
/**
|
|
* Get the object totals by object type for later updating
|
|
*/
|
|
if ($delete_type == 2) {
|
|
object_cache_get_totals('device_delete', $device_ids);
|
|
} else {
|
|
object_cache_get_totals('device_leave', $device_ids);
|
|
}
|
|
|
|
api_plugin_hook_function('device_remove', $device_ids);
|
|
|
|
$data_sources = [];
|
|
$graphs = [];
|
|
|
|
$int_device_ids = array_map('intval', $device_ids);
|
|
$devices_to_delete = implode(', ', $int_device_ids);
|
|
|
|
$data_sources = array_rekey(
|
|
db_fetch_assoc('SELECT id
|
|
FROM data_local
|
|
WHERE ' . db_in_clause('host_id', $int_device_ids)),
|
|
'id', 'id'
|
|
);
|
|
|
|
$graphs = array_rekey(
|
|
db_fetch_assoc('SELECT id
|
|
FROM graph_local
|
|
WHERE ' . db_in_clause('host_id', $int_device_ids)),
|
|
'id', 'id'
|
|
);
|
|
|
|
$poller_ids = get_remote_poller_ids_from_devices($devices_to_delete);
|
|
|
|
if (!db_begin_transaction()) {
|
|
raise_message('device_remove_multi_failed', __('Unable to remove the selected Devices because a database transaction could not be started.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($delete_type == 2) {
|
|
$graph_ids = $graphs;
|
|
api_delete_graphs($graphs, $delete_type, false);
|
|
|
|
if (cacti_sizeof($graph_ids)) {
|
|
$remaining_graphs = db_fetch_cell('SELECT COUNT(*)
|
|
FROM graph_local
|
|
WHERE ' . db_in_clause('id', $graph_ids));
|
|
|
|
if ($remaining_graphs === false || $remaining_graphs > 0) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_graph_failed', __('Unable to remove the selected Devices because Graph cleanup did not complete.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// poller commands go one at a time due to trashy logic
|
|
foreach ($device_ids as $device_id) {
|
|
if (db_execute_prepared('DELETE FROM poller_command WHERE command LIKE ?', [$device_id . ':%']) === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_failed', __('Unable to remove the selected Devices because dependent database cleanup failed.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (db_execute('DELETE FROM poller_item WHERE ' . db_in_clause('host_id', $int_device_ids)) === false ||
|
|
db_execute('DELETE FROM poller_reindex WHERE ' . db_in_clause('host_id', $int_device_ids)) === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_failed', __('Unable to remove the selected Devices because poller cleanup failed.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$cleanup_queries = [
|
|
'DELETE FROM host_graph WHERE ' . db_in_clause('host_id', $int_device_ids),
|
|
'DELETE FROM host_snmp_query WHERE ' . db_in_clause('host_id', $int_device_ids),
|
|
'DELETE FROM host_snmp_cache WHERE ' . db_in_clause('host_id', $int_device_ids),
|
|
'DELETE FROM host_value_cache WHERE ' . db_in_clause('host_id', $int_device_ids),
|
|
'DELETE FROM graph_tree_items WHERE ' . db_in_clause('host_id', $int_device_ids),
|
|
'DELETE FROM reports_items WHERE ' . db_in_clause('host_id', $int_device_ids)
|
|
];
|
|
|
|
foreach ($cleanup_queries as $sql) {
|
|
if (db_execute($sql) === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_failed', __('Unable to remove the selected Devices because dependent database cleanup failed.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($delete_type != 2) {
|
|
api_data_source_disable_multi($data_sources);
|
|
|
|
if (db_execute('UPDATE graph_local SET host_id = 0 WHERE ' . db_in_clause('host_id', $int_device_ids)) === false ||
|
|
db_execute('UPDATE data_local SET host_id = 0 WHERE ' . db_in_clause('host_id', $int_device_ids)) === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_detach_failed', __('Unable to remove the selected Devices because Graph or Data Source detachment failed.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remove the primary records only after dependent cleanup succeeds.
|
|
if (db_execute('DELETE FROM host WHERE ' . db_in_clause('id', $int_device_ids) . ' AND poller_id = 1') === false ||
|
|
db_execute("UPDATE host SET deleted = 'on' WHERE " . db_in_clause('id', $int_device_ids) . ' AND poller_id != 1') === false) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_failed', __('Unable to update the primary records for the selected Devices.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!db_commit_transaction()) {
|
|
db_rollback_transaction();
|
|
raise_message('device_remove_multi_failed', __('Unable to remove the selected Devices because the database transaction could not be committed.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
if (cacti_sizeof($poller_ids)) {
|
|
foreach ($poller_ids as $poller_id) {
|
|
api_device_cache_crc_update($poller_id);
|
|
api_device_purge_from_remote($device_ids, $poller_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the object totals by object type for later updating
|
|
*/
|
|
object_cache_update_totals('delete');
|
|
|
|
/**
|
|
* Save the last time a device/site was created/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_device', time());
|
|
set_config_option('time_last_change_site_device', time());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable an array of device ids
|
|
*
|
|
* @param array $device_ids An array of device IDs to be disabled.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_disable_devices(array $device_ids) : void {
|
|
$raised = [];
|
|
|
|
foreach ($device_ids as $device_id) {
|
|
db_execute_prepared("UPDATE host
|
|
SET disabled = 'on', status = 0
|
|
WHERE id = ?
|
|
AND (deleted = '' OR (deleted = 'on' AND disabled = ''))",
|
|
[$device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared("UPDATE host
|
|
SET disabled='on'
|
|
WHERE id = ?
|
|
AND (deleted = '' OR (deleted = 'on' AND disabled = ''))",
|
|
[$device_id], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable an array of device ids
|
|
*
|
|
* @param array $device_ids An array of device IDs to be enabled.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_enable_devices(array $device_ids) : void {
|
|
$raised = [];
|
|
|
|
foreach ($device_ids as $device_id) {
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$device_id]);
|
|
|
|
db_execute_prepared("UPDATE host
|
|
SET disabled = ''
|
|
WHERE id = ?
|
|
AND deleted = ''",
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
$poller_cache = 0;
|
|
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared("UPDATE host
|
|
SET disabled = ''
|
|
WHERE id = ?",
|
|
[$device_id], true, $rcnn_id);
|
|
|
|
$poller_cache = db_fetch_cell_prepared('SELECT COUNT(local_data_id)
|
|
FROM poller_item
|
|
WHERE host_id = ?',
|
|
[$device_id], '', true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} else {
|
|
$poller_cache = db_fetch_cell_prepared('SELECT COUNT(local_data_id)
|
|
FROM poller_item
|
|
WHERE host_id = ?',
|
|
[$device_id]);
|
|
}
|
|
|
|
/**
|
|
* Only reprime poller cache if empty this allows support for pre 1.2.16
|
|
* enable behavior.
|
|
*/
|
|
if (!cacti_sizeof($poller_cache)) {
|
|
// update poller cache
|
|
$data_sources = db_fetch_assoc_prepared('SELECT id
|
|
FROM data_local
|
|
WHERE host_id = ?',
|
|
[$device_id]);
|
|
|
|
$poller_items = $local_data_ids = [];
|
|
|
|
if (cacti_sizeof($data_sources)) {
|
|
foreach ($data_sources as $data_source) {
|
|
$local_data_ids[] = $data_source['id'];
|
|
|
|
// $poller_id was already looked up above for this $device_id
|
|
$poller_items = array_merge($poller_items, update_poller_cache($data_source['id'], false, $poller_id));
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($local_data_ids)) {
|
|
poller_update_poller_cache_from_buffer($local_data_ids, $poller_items, $poller_id);
|
|
}
|
|
}
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
poller_push_reindex_data_to_poller($device_id, 0, true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given an array of device ids and the post variable, update a series of Device settings.
|
|
*
|
|
* @param array $device_ids An array of device IDs to update.
|
|
* @param array $post An associative array containing the POST data with the new options.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_change_options(array $device_ids, array $post) : void {
|
|
global $fields_host_edit;
|
|
|
|
$previous_poller = -1;
|
|
$poller_ids = [];
|
|
$raised = [];
|
|
|
|
foreach ($device_ids as $device_id) {
|
|
foreach ($fields_host_edit as $field_name => $field_array) {
|
|
if (isset($post["t_$field_name"])) {
|
|
if ($field_name == 'poller_id') {
|
|
$old_poller = db_fetch_cell_prepared('SELECT poller_id
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$device_id]);
|
|
|
|
if ($old_poller > 1 && $old_poller != gnrv($field_name)) {
|
|
$previous_poller = gnrv($field_name);
|
|
|
|
api_device_purge_from_remote($device_id, $old_poller);
|
|
}
|
|
|
|
// Update the local device and replicate
|
|
if ($old_poller != gnrv($field_name) && gnrv($field_name) > 1) {
|
|
api_device_replicate_out($device_id, gnrv($field_name));
|
|
}
|
|
}
|
|
|
|
db_execute_prepared("UPDATE host
|
|
SET $field_name = ?
|
|
WHERE id = ?
|
|
AND deleted = ''",
|
|
[gnrv($field_name), $device_id]);
|
|
|
|
if (!isset($poller_ids[$device_id])) {
|
|
$poller_ids[$device_id] = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
}
|
|
|
|
$poller_id = $poller_ids[$device_id];
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared("UPDATE host
|
|
SET $field_name = ?
|
|
WHERE id = ?
|
|
AND deleted = ''",
|
|
[gnrv($field_name), $device_id], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
|
|
if ($field_name == 'host_template_id') {
|
|
api_device_update_host_template($device_id, gnrv($field_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
push_out_host($device_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all device level statistics and reset as if the device was new in Cacti
|
|
*
|
|
* @param array $device_ids An array of device IDs for which the statistics need to be cleared.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_clear_statistics(array $device_ids) : void {
|
|
$raised = [];
|
|
|
|
foreach ($device_ids as $device_id) {
|
|
db_execute_prepared("UPDATE host
|
|
SET min_time = '9.99999', max_time = '0', cur_time = '0', avg_time = '0',
|
|
total_polls = '0', failed_polls = '0', availability = '100.00'
|
|
WHERE id = ?
|
|
AND deleted = ''",
|
|
[$device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared("UPDATE host
|
|
SET min_time = '9.99999', max_time = '0', cur_time = '0', avg_time = '0',
|
|
total_polls = '0', failed_polls = '0', availability = '100.00'
|
|
WHERE id = ?
|
|
AND deleted = ''",
|
|
[$device_id], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* api_device_sync_device_templates - Sync an array of device ids with their
|
|
* parent Device Template
|
|
*
|
|
* @param array $device_ids An array of device ids
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_sync_device_templates(array $device_ids) : void {
|
|
foreach ($device_ids as $device_id) {
|
|
$device_template_id = db_fetch_cell_prepared('SELECT host_template_id
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$device_id]);
|
|
|
|
if ($device_template_id > 0) {
|
|
api_device_update_host_template($device_id, $device_template_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a device->data query mapping
|
|
*
|
|
* @param int $device_id The ID of the device to which the data query is being added.
|
|
* @param int $data_query_id The ID of the data query to be added.
|
|
* @param string $reindex_method The method used for reindexing.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_dq_add(int $device_id, int $data_query_id, string $reindex_method) : void {
|
|
db_execute_prepared('REPLACE INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)',
|
|
[$device_id, $data_query_id, $reindex_method]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('REPLACE INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)',
|
|
[$device_id, $data_query_id, $reindex_method], true, $rcnn_id);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
|
|
// recache snmp data
|
|
run_data_query($device_id, $data_query_id);
|
|
}
|
|
|
|
/**
|
|
* Removes a device->data query mapping
|
|
*
|
|
* @param int $device_id The ID of the device.
|
|
* @param int $data_query_id The ID of the data query to be removed.
|
|
* @return void
|
|
*/
|
|
function api_device_dq_remove(int $device_id, int $data_query_id) : void {
|
|
db_execute_prepared('DELETE FROM host_snmp_cache
|
|
WHERE snmp_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id]);
|
|
|
|
db_execute_prepared('DELETE FROM host_snmp_query
|
|
WHERE snmp_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id]);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE data_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('DELETE FROM host_snmp_cache
|
|
WHERE snmp_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id], true, $rcnn_id);
|
|
|
|
db_execute_prepared('DELETE FROM host_snmp_query
|
|
WHERE snmp_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id], true, $rcnn_id);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE data_query_id = ?
|
|
AND host_id = ?',
|
|
[$data_query_id, $device_id], true, $rcnn_id);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes a device->data query mapping
|
|
*
|
|
* @param int $device_id The ID of the device.
|
|
* @param int $data_query_id The ID of the data query.
|
|
* @param string $reindex_method The reindex method to be used.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_dq_change(int $device_id, int $data_query_id, string $reindex_method) : void {
|
|
db_execute_prepared('INSERT INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE reindex_method=VALUES(reindex_method)',
|
|
[$device_id, $data_query_id, $reindex_method]);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE data_query_id = ?
|
|
AND host_id = ?', [$data_query_id, $device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('INSERT INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE reindex_method=VALUES(reindex_method)',
|
|
[$device_id, $data_query_id, $reindex_method], true, $rcnn_id);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE data_query_id = ?
|
|
AND host_id = ?', [$data_query_id, $device_id], true, $rcnn_id);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
|
|
// finally rerun the data query
|
|
run_data_query($device_id, $data_query_id);
|
|
}
|
|
|
|
/**
|
|
* Removes a device->graph template mapping
|
|
*
|
|
* @param int $device_id The ID of the device.
|
|
* @param int $graph_template_id The ID of the graph template.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_gt_remove(int $device_id, int $graph_template_id) : void {
|
|
db_execute_prepared('DELETE FROM host_graph
|
|
WHERE graph_template_id = ?
|
|
AND host_id = ?',
|
|
[$graph_template_id, $device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('DELETE FROM host_graph
|
|
WHERE graph_template_id = ?
|
|
AND host_id = ?',
|
|
[$graph_template_id, $device_id], true, $rcnn_id);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace device settings to the remote data collectors
|
|
*
|
|
* @param int $device_id The ID of the device to replicate.
|
|
* @param int $poller_id The ID of the poller to replicate to. Defaults to 1.
|
|
*
|
|
* @return bool Returns true on success, false on failure.
|
|
*/
|
|
function api_device_replicate_out(int $device_id, int $poller_id = 1) : bool {
|
|
$rcnn_id = false;
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
$rcnn_id = poller_connect_to_remote($poller_id);
|
|
}
|
|
}
|
|
|
|
if ($rcnn_id === false) {
|
|
return false;
|
|
}
|
|
|
|
// Update poller id where applicable
|
|
db_execute_prepared('UPDATE host
|
|
SET poller_id = ?
|
|
WHERE id = ?
|
|
AND deleted = ""',
|
|
[$poller_id, $device_id]);
|
|
|
|
db_execute_prepared('UPDATE poller_item
|
|
SET poller_id = ?
|
|
WHERE host_id = ?',
|
|
[$poller_id, $device_id]);
|
|
|
|
// Start Push Replication
|
|
$data = db_fetch_assoc_prepared('SELECT hsq.*
|
|
FROM host_snmp_query AS hsq
|
|
INNER JOIN host AS h
|
|
ON h.id=hsq.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'host_snmp_query', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT pi.*
|
|
FROM poller_item AS pi
|
|
WHERE pi.host_id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'poller_item', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT h.*
|
|
FROM host AS h
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'host', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT hsc.*
|
|
FROM host_snmp_cache AS hsc
|
|
INNER JOIN host AS h
|
|
ON h.id=hsc.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'host_snmp_cache', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT hsc.*
|
|
FROM host_value_cache AS hsc
|
|
INNER JOIN host AS h
|
|
ON h.id=hsc.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'host_value_cache', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT pri.*
|
|
FROM poller_reindex AS pri
|
|
INNER JOIN host AS h
|
|
ON h.id=pri.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'poller_reindex', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT dl.*
|
|
FROM data_local AS dl
|
|
INNER JOIN host AS h
|
|
ON h.id=dl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'data_local', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT gl.*
|
|
FROM graph_local AS gl
|
|
INNER JOIN host AS h
|
|
ON h.id=gl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'graph_local', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT dtd.*
|
|
FROM data_template_data AS dtd
|
|
INNER JOIN data_local AS dl
|
|
ON dtd.local_data_id=dl.id
|
|
INNER JOIN host AS h
|
|
ON h.id=dl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'data_template_data', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT dtr.*
|
|
FROM data_template_rrd AS dtr
|
|
INNER JOIN data_local AS dl
|
|
ON dtr.local_data_id=dl.id
|
|
INNER JOIN host AS h
|
|
ON h.id=dl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'data_template_rrd', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT gti.*
|
|
FROM graph_templates_item AS gti
|
|
INNER JOIN graph_local AS gl
|
|
ON gti.local_graph_id=gl.id
|
|
INNER JOIN host AS h
|
|
ON h.id=gl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'graph_templates_item', $poller_id);
|
|
}
|
|
|
|
$data = db_fetch_assoc_prepared('SELECT did.*
|
|
FROM data_input_data AS did
|
|
INNER JOIN data_template_data AS dtd
|
|
ON did.data_template_data_id=dtd.id
|
|
INNER JOIN data_local AS dl
|
|
ON dl.id=dtd.local_data_id
|
|
INNER JOIN host AS h
|
|
ON h.id=dl.host_id
|
|
WHERE h.id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
replicate_table_to_poller($rcnn_id, $data, 'data_input_data', $poller_id);
|
|
}
|
|
|
|
api_plugin_hook_function('replicate_out', ['remote_poller_id' => $poller_id, 'rcnn_id' => $rcnn_id, 'class' => 'all']);
|
|
|
|
$stats = db_fetch_row_prepared('SELECT
|
|
SUM(CASE WHEN action=0 THEN 1 ELSE 0 END) AS snmp,
|
|
SUM(CASE WHEN action=1 THEN 1 ELSE 0 END) AS script,
|
|
SUM(CASE WHEN action=2 THEN 1 ELSE 0 END) AS server
|
|
FROM poller_item
|
|
WHERE poller_id = ?',
|
|
[$poller_id]);
|
|
|
|
if (cacti_sizeof($stats)) {
|
|
db_execute_prepared('UPDATE poller
|
|
SET snmp = ?, script = ?, server = ?
|
|
WHERE id = ?',
|
|
[$stats['snmp'], $stats['script'], $stats['server'], $poller_id]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save a device and update the poller cache for the device is required.
|
|
* The function will determine if the poller cache needs updating by reviewing the changed
|
|
* settings. If no settings changed that require an update of the poller cache, the
|
|
* device level settings will simply be updated, otherwise the poller cache will be refreshed
|
|
* for the device.
|
|
*
|
|
* @param int $id The ID of the device. If 0, a new device will be created.
|
|
* @param int $device_template_id The ID of the device template.
|
|
* @param string $description The description of the device.
|
|
* @param string $hostname The hostname of the device.
|
|
* @param string $snmp_community The SNMP community string.
|
|
* @param int $snmp_version The SNMP version.
|
|
* @param string $snmp_username The SNMP username (for SNMP v3).
|
|
* @param string $snmp_password The SNMP password (for SNMP v3).
|
|
* @param int $snmp_port The SNMP port.
|
|
* @param int $snmp_timeout The SNMP timeout.
|
|
* @param string $disabled Whether the device is disabled ('on' or '').
|
|
* @param int $availability_method The availability method.
|
|
* @param int $ping_method The ping method.
|
|
* @param int $ping_port The ping port.
|
|
* @param int $ping_timeout The ping timeout.
|
|
* @param int $ping_retries The number of ping retries.
|
|
* @param string $notes Notes about the device.
|
|
* @param string $snmp_auth_protocol The SNMP authentication protocol (for SNMP v3).
|
|
* @param string $snmp_priv_passphrase The SNMP privacy passphrase (for SNMP v3).
|
|
* @param string $snmp_priv_protocol The SNMP privacy protocol (for SNMP v3).
|
|
* @param string $snmp_context The SNMP context (for SNMP v3).
|
|
* @param string $snmp_engine_id The SNMP engine ID (for SNMP v3).
|
|
* @param int $max_oids The maximum number of OIDs.
|
|
* @param int $device_threads The number of device threads.
|
|
* @param int $poller_id The poller ID.
|
|
* @param int $site_id The site ID.
|
|
* @param string $external_id The external ID.
|
|
* @param string $location The location of the device.
|
|
* @param int $bulk_walk_size The bulk walk size.
|
|
* @param int $snmp_options The SNMP options.
|
|
* @param int $snmp_retries The number of SNMP retries.
|
|
*
|
|
* @return int The ID of the saved device.
|
|
*/
|
|
function api_device_save(int $id, int $device_template_id, string $description, string $hostname, string $snmp_community, int $snmp_version,
|
|
string $snmp_username, string $snmp_password, int $snmp_port, int $snmp_timeout, string $disabled,
|
|
int $availability_method, int $ping_method, int $ping_port, int $ping_timeout, int $ping_retries,
|
|
string $notes, string $snmp_auth_protocol, string $snmp_priv_passphrase, string $snmp_priv_protocol, string $snmp_context, string $snmp_engine_id,
|
|
int $max_oids = 5, int $device_threads = 1, int $poller_id = 1, int $site_id = 1, string $external_id = '', string $location = '', int $bulk_walk_size = -1,
|
|
int $snmp_options = 0, int $snmp_retries = 3) : int {
|
|
include_once(CACTI_PATH_LIBRARY . '/utility.php');
|
|
include_once(CACTI_PATH_LIBRARY . '/variables.php');
|
|
include_once(CACTI_PATH_LIBRARY . '/data_query.php');
|
|
|
|
if ($id > 0) {
|
|
$previous = db_fetch_row_prepared('SELECT *
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$id]);
|
|
|
|
if (cacti_sizeof($previous)) {
|
|
$previous_poller = $previous['poller_id'];
|
|
} else {
|
|
$previous_poller = 0;
|
|
}
|
|
} else {
|
|
$previous = [];
|
|
|
|
$previous_poller = 0;
|
|
}
|
|
|
|
// fetch some cache variables
|
|
if (empty($id)) {
|
|
$_host_template_id = 0;
|
|
} else {
|
|
$_host_template_id = db_fetch_cell_prepared('SELECT host_template_id
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$id]);
|
|
}
|
|
|
|
$raised = false;
|
|
|
|
$save['id'] = form_input_validate($id, 'id', '^[0-9]+$', false, 3);
|
|
$save['host_template_id'] = form_input_validate($device_template_id, 'host_template_id', '^[0-9]+$', true, 3);
|
|
|
|
$save['poller_id'] = form_input_validate($poller_id, 'poller_id', '^[0-9]+$', true, 3);
|
|
$save['site_id'] = form_input_validate($site_id, 'site_id', '^[0-9]+$', true, 3);
|
|
$save['external_id'] = form_input_validate($external_id, 'external_id', '', true, 3);
|
|
|
|
$save['description'] = form_input_validate($description, 'description', '', false, 3);
|
|
$save['hostname'] = form_input_validate(trim($hostname), 'hostname', '', false, 3);
|
|
$save['notes'] = form_input_validate($notes, 'notes', '', true, 3);
|
|
$save['location'] = form_input_validate($location, 'location', '', true, 3);
|
|
|
|
$save['snmp_options'] = form_input_validate($snmp_options, 'snmp_options', '^[0-9]+$', false, 3);
|
|
$save['snmp_version'] = form_input_validate($snmp_version, 'snmp_version', '', true, 3);
|
|
// GHSA-m3fh-gxqj-76hq: SNMP credentials reach the net-snmp command line; strip control characters the same way snmp_community's sibling requires.
|
|
$save['snmp_community'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_community, 'snmp_community', '', true, 3));
|
|
|
|
if ($save['snmp_version'] == 3) {
|
|
$save['snmp_username'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_username, 'snmp_username', '', true, 3));
|
|
$save['snmp_password'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_password, 'snmp_password', '', true, 3));
|
|
$save['snmp_auth_protocol'] = form_input_validate($snmp_auth_protocol, 'snmp_auth_protocol', "^(\[None\]|MD5|SHA|SHA224|SHA256|SHA384|SHA512)$", true, 3);
|
|
$save['snmp_priv_passphrase'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_priv_passphrase, 'snmp_priv_passphrase', '', true, 3));
|
|
$save['snmp_priv_protocol'] = form_input_validate($snmp_priv_protocol, 'snmp_priv_protocol', "^(\[None\]|DES|AES|AES128|AES192|AES192C|AES256|AES256C)$", true, 3);
|
|
$save['snmp_context'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_context, 'snmp_context', '', true, 3));
|
|
$save['snmp_engine_id'] = preg_replace('/[\x00-\x1f\x7f]/', '', (string) form_input_validate($snmp_engine_id, 'snmp_engine_id', '', true, 3));
|
|
|
|
if (strlen($save['snmp_password']) < 8 && $snmp_auth_protocol != '[None]') {
|
|
raise_message(32);
|
|
$_SESSION[SESS_ERROR_FIELDS]['snmp_password'] = 'snmp_password';
|
|
}
|
|
} else {
|
|
$save['snmp_username'] = '';
|
|
$save['snmp_password'] = '';
|
|
$save['snmp_auth_protocol'] = '';
|
|
$save['snmp_priv_passphrase'] = '';
|
|
$save['snmp_priv_protocol'] = '';
|
|
$save['snmp_context'] = '';
|
|
$save['snmp_engine_id'] = '';
|
|
}
|
|
|
|
$save['snmp_port'] = form_input_validate($snmp_port, 'snmp_port', '^[0-9]+$', false, 3);
|
|
$save['snmp_timeout'] = form_input_validate($snmp_timeout, 'snmp_timeout', '^[0-9]+$', false, 3);
|
|
$save['snmp_retries'] = form_input_validate($snmp_retries, 'snmp_retries', '^[0-9]+$', false, 3);
|
|
|
|
/* disabled = 'on' => regexp '^on$'
|
|
* not disabled = '' => no regexp, but allow nulls */
|
|
$save['disabled'] = form_input_validate($disabled, 'disabled', '^on$', true, 3);
|
|
|
|
if ($save['disabled'] == 'on') {
|
|
if ($save['id'] > 0) {
|
|
api_device_disable_devices([$save['id']]);
|
|
}
|
|
}
|
|
|
|
$quick_save = api_device_quick_save($save);
|
|
|
|
$save['availability_method'] = form_input_validate($availability_method, 'availability_method', '^[0-9]+$', false, 3);
|
|
$save['ping_method'] = form_input_validate($ping_method, 'ping_method', '^[0-9]+$', false, 3);
|
|
$save['ping_port'] = form_input_validate($ping_port, 'ping_port', '^[0-9]+$', true, 3);
|
|
$save['ping_timeout'] = form_input_validate($ping_timeout, 'ping_timeout', '^[0-9]+$', true, 3);
|
|
$save['ping_retries'] = form_input_validate($ping_retries, 'ping_retries', '^[0-9]+$', true, 3);
|
|
$save['max_oids'] = form_input_validate($max_oids, 'max_oids', '^[0-9]+$', false, 3);
|
|
$save['bulk_walk_size'] = form_input_validate($bulk_walk_size, 'bulk_walk_size', '^[-0-9]+$', false, 3);
|
|
$save['device_threads'] = form_input_validate($device_threads, 'device_threads', '^[0-9]+$', true, 3);
|
|
|
|
$device_id = 0;
|
|
|
|
if (!is_error_message()) {
|
|
$save = api_plugin_hook_function('api_device_save', $save);
|
|
|
|
$device_id = sql_save($save, 'host');
|
|
|
|
if ($device_id) {
|
|
if ($previous_poller > 1 && $poller_id != $previous_poller) {
|
|
if (remote_poller_up($previous_poller)) {
|
|
api_device_purge_from_remote($device_id, $previous_poller);
|
|
} else {
|
|
raise_message('poller_down_' . $save['id'], __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $previous_poller), MESSAGE_LEVEL_WARN);
|
|
$raised = true;
|
|
}
|
|
}
|
|
|
|
raise_message(1);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
$save['id'] = $device_id;
|
|
sql_save($save, 'host', 'id', true, $rcnn_id);
|
|
} elseif (!$raised) {
|
|
raise_message('poller_down_' . $save['id'], __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised = true;
|
|
}
|
|
} elseif (!$raised) {
|
|
raise_message('poller_down_' . $save['id'], __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised = true;
|
|
}
|
|
}
|
|
|
|
// clear the host session data for poller cache repopulation
|
|
if (isset($_SESSION['sess_host_cache_array'][$device_id])) {
|
|
unset($_SESSION['sess_host_cache_array'][$device_id]);
|
|
}
|
|
|
|
// change reindex method for 'None' for non-snmp devices
|
|
if ($save['snmp_version'] == 0) {
|
|
db_execute_prepared('UPDATE host_snmp_query
|
|
SET reindex_method = 0
|
|
WHERE host_id = ?',
|
|
[$device_id]);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE host_id = ?',
|
|
[$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('UPDATE host_snmp_query
|
|
SET reindex_method = 0
|
|
WHERE host_id = ?',
|
|
[$device_id], true, $rcnn_id);
|
|
|
|
db_execute_prepared('DELETE FROM poller_reindex
|
|
WHERE host_id = ?',
|
|
[$device_id], true, $rcnn_id);
|
|
} elseif (!$raised) {
|
|
raise_message('poller_down_' . $save['id'], __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} elseif (!$raised) {
|
|
raise_message('poller_down_' . $save['id'], __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
}
|
|
|
|
api_device_cache_crc_update($save['poller_id']);
|
|
|
|
// push out relevant fields to data sources using this host
|
|
if (!$quick_save) {
|
|
push_out_host($device_id, 0);
|
|
}
|
|
|
|
// the host substitution cache is now stale; purge it
|
|
kill_session_var('sess_host_cache_array');
|
|
|
|
// update title cache for graph and data source
|
|
update_data_source_title_cache_from_host($device_id);
|
|
update_graph_title_cache_from_host($device_id);
|
|
|
|
if (empty($id)) {
|
|
/**
|
|
* Save the last time a device/site was created/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_device', time());
|
|
set_config_option('time_last_change_site_device', time());
|
|
}
|
|
} else {
|
|
raise_message(2);
|
|
}
|
|
|
|
// if the user changes the host template, add each snmp query associated with it
|
|
if ($device_template_id > 0 && $device_template_id != $_host_template_id) {
|
|
api_device_update_host_template($device_id, $device_template_id);
|
|
}
|
|
}
|
|
|
|
if ($device_id > 0) {
|
|
/* The shared credential cache holds only SNMPv3 tuples, keyed by the exact
|
|
* credential fields. Bump the version only when this host's v3 membership or
|
|
* one of those credential fields actually changed, so saving an unrelated
|
|
* field (notes, availability, ...) does not force a needless cache rebuild. */
|
|
$was_v3 = isset($previous['snmp_version']) && $previous['snmp_version'] == 3;
|
|
$is_v3 = $save['snmp_version'] == 3;
|
|
$cred_changed = ($was_v3 != $is_v3);
|
|
|
|
if ($is_v3 && $was_v3) {
|
|
foreach (['snmp_community', 'snmp_username', 'snmp_password', 'snmp_auth_protocol', 'snmp_priv_passphrase', 'snmp_priv_protocol'] as $field) {
|
|
if ((string) ($previous[$field] ?? '') !== (string) ($save[$field] ?? '')) {
|
|
$cred_changed = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($cred_changed) {
|
|
set_config_option('snmp_cred_version', uniqid('', true));
|
|
}
|
|
|
|
if (read_config_option('extended_paths') == 'on') {
|
|
$pattern = read_config_option('extended_paths_type');
|
|
$maxdirs = read_config_option('extended_paths_hashes');
|
|
|
|
if (empty($maxdirs) || $maxdirs < 0 || !is_numeric($maxdirs)) {
|
|
$maxdirs = 100;
|
|
}
|
|
|
|
$hash_id = $device_id % $maxdirs;
|
|
|
|
if ($pattern == 'device' || $pattern == '') {
|
|
$host_dir = CACTI_PATH_RRA . "/$device_id";
|
|
} elseif ($pattern == 'device_dq') {
|
|
$host_dir = CACTI_PATH_RRA . "/$device_id";
|
|
} elseif ($pattern == 'hash_device') {
|
|
$host_dir = CACTI_PATH_RRA . "/$hash_id/$device_id";
|
|
} elseif ($pattern == 'hash_device_dq') {
|
|
$host_dir = CACTI_PATH_RRA . "/$hash_id/$device_id";
|
|
} else {
|
|
$host_dir = CACTI_PATH_RRA . "/$device_id";
|
|
}
|
|
|
|
if (!is_dir($host_dir)) {
|
|
if (is_writable(CACTI_PATH_RRA)) {
|
|
if (mkdir($host_dir, 0775, true)) {
|
|
if (CACTI_SERVER_OS != 'win32') {
|
|
$owner_id = fileowner(CACTI_PATH_RRA);
|
|
$group_id = filegroup(CACTI_PATH_RRA);
|
|
|
|
if ($owner_id !== false && $group_id !== false &&
|
|
chown($host_dir, $owner_id) && chgrp($host_dir, $group_id)) {
|
|
// permissions set ok
|
|
} else {
|
|
cacti_log("ERROR: Unable to set directory permissions for '" . $host_dir . "'", false);
|
|
}
|
|
}
|
|
} else {
|
|
cacti_log("ERROR: Unable to create directory '" . $host_dir . "'", false);
|
|
}
|
|
} else {
|
|
cacti_log("ERROR: Unable to create directory due to missing write permissions '" . $host_dir . "'", false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// now that we have the id of the new host, we may plugin postprocessing code
|
|
$save['id'] = $device_id;
|
|
|
|
snmpagent_api_device_new($save);
|
|
|
|
automation_execute_device_create_tree($device_id);
|
|
|
|
api_plugin_hook_function('api_device_new', $save);
|
|
}
|
|
|
|
/**
|
|
* Update the caches for various meta-data changes
|
|
* This is to overcome changes in various caching aspects.
|
|
*/
|
|
if (cacti_sizeof($previous) && cacti_sizeof($save)) {
|
|
if ($save['site_id'] != $previous['site_id']) {
|
|
db_execute_prepared('UPDATE sites SET devices = devices + 1 WHERE id = ?', [$save['site_id']]);
|
|
db_execute_prepared('UPDATE sites SET devices = devices - 1 WHERE id = ?', [$previous['site_id']]);
|
|
}
|
|
|
|
if ($save['poller_id'] != $previous['poller_id']) {
|
|
db_execute_prepared('UPDATE poller SET devices = devices + 1 WHERE id = ?', [$save['poller_id']]);
|
|
db_execute_prepared('UPDATE poller SET devices = devices - 1 WHERE id = ?', [$previous['poller_id']]);
|
|
}
|
|
|
|
if ($save['host_template_id'] != $previous['host_template_id']) {
|
|
db_execute_prepared('UPDATE host_template SET devices = devices + 1 WHERE id = ?', [$save['host_template_id']]);
|
|
db_execute_prepared('UPDATE host_template SET devices = devices - 1 WHERE id = ?', [$previous['host_template_id']]);
|
|
}
|
|
}
|
|
|
|
return $device_id;
|
|
}
|
|
|
|
/**
|
|
* Checks if the poller cache needs to be rebuilt as a part of a device save.
|
|
*
|
|
* @param array $save An associative array containing device information to be saved.
|
|
*
|
|
* @return bool Returns true if the device information has not changed, false otherwise.
|
|
*/
|
|
function api_device_quick_save(array &$save) : bool {
|
|
if ($save['id'] > 0) {
|
|
$device = db_fetch_row_prepared('SELECT *
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$save['id']]);
|
|
|
|
$compare = [
|
|
'poller_id',
|
|
'disabled',
|
|
'hostname',
|
|
'snmp_community',
|
|
'snmp_version',
|
|
'snmp_username',
|
|
'snmp_password',
|
|
'snmp_auth_protocol',
|
|
'snmp_priv_passphrase',
|
|
'snmp_priv_protocol',
|
|
'snmp_context',
|
|
'snmp_engine_id',
|
|
'snmp_port',
|
|
'snmp_timeout'
|
|
];
|
|
|
|
foreach ($compare as $c) {
|
|
if ($save[$c] != $device[$c]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the host template of a host
|
|
*
|
|
* @param int $device_id The ID of the device to update.
|
|
* @param int $device_template_id The ID of the new device template to assign to the device.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_update_host_template(int $device_id, int $device_template_id) : void {
|
|
static $raised = [];
|
|
|
|
if ($device_id > 0) {
|
|
object_cache_get_totals('device_state', $device_id);
|
|
}
|
|
|
|
db_execute_prepared('UPDATE host
|
|
SET host_template_id = ?
|
|
WHERE id = ?
|
|
AND deleted = ""',
|
|
[$device_template_id, $device_id]);
|
|
|
|
$poller_id = db_fetch_cell_prepared('SELECT poller_id FROM host WHERE id = ?', [$device_id]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('UPDATE host
|
|
SET host_template_id = ?
|
|
WHERE id = ?
|
|
AND deleted = ""',
|
|
[$device_template_id, $device_id], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
|
|
// add all new snmp queries assigned to the device template
|
|
$snmp_queries = db_fetch_assoc_prepared('SELECT snmp_query_id
|
|
FROM host_template_snmp_query AS htsq
|
|
WHERE host_template_id = ?
|
|
AND htsq.snmp_query_id NOT IN (SELECT snmp_query_id FROM host_snmp_cache WHERE host_id = ?)',
|
|
[$device_template_id, $device_id]);
|
|
|
|
if (cacti_sizeof($snmp_queries)) {
|
|
foreach ($snmp_queries as $snmp_query) {
|
|
db_execute_prepared('REPLACE INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)',
|
|
[$device_id, $snmp_query['snmp_query_id'], read_config_option('reindex_method')]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('REPLACE INTO host_snmp_query
|
|
(host_id, snmp_query_id, reindex_method)
|
|
VALUES (?, ?, ?)',
|
|
[$device_id, $snmp_query['snmp_query_id'], read_config_option('reindex_method')], true, $rcnn_id);
|
|
} elseif ($raised[$poller_id]) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
|
|
// recache snmp data
|
|
run_data_query($device_id, $snmp_query['snmp_query_id']);
|
|
}
|
|
}
|
|
|
|
// add all graph templates assigned to the device template
|
|
$graph_templates = db_fetch_assoc_prepared('SELECT graph_template_id
|
|
FROM host_template_graph AS hg
|
|
WHERE host_template_id = ?
|
|
AND hg.graph_template_id NOT IN (SELECT graph_template_id FROM host_graph WHERE host_id = ?)',
|
|
[$device_template_id, $device_id]);
|
|
|
|
if (cacti_sizeof($graph_templates)) {
|
|
foreach ($graph_templates as $graph_template) {
|
|
db_execute_prepared('REPLACE INTO host_graph
|
|
(host_id, graph_template_id)
|
|
VALUES (?, ?)',
|
|
[$device_id, $graph_template['graph_template_id']]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('REPLACE INTO host_graph
|
|
(host_id, graph_template_id)
|
|
VALUES (?, ?)',
|
|
[$device_id, $graph_template['graph_template_id']], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
|
|
automation_hook_graph_template($device_id, $graph_template['graph_template_id']);
|
|
|
|
api_plugin_hook_function('add_graph_template_to_host',
|
|
['host_id' => $device_id, 'graph_template_id' => $graph_template['graph_template_id']]);
|
|
}
|
|
}
|
|
|
|
// remove unused graph templates not assigned to the device template
|
|
$unused_graph_templates = db_fetch_assoc_prepared('SELECT DISTINCT
|
|
hg.graph_template_id AS id, gt.name, result.gtid
|
|
FROM host_graph AS hg
|
|
LEFT JOIN graph_templates AS gt
|
|
ON gt.id=hg.graph_template_id
|
|
LEFT JOIN (
|
|
SELECT DISTINCT graph_template_id AS gtid
|
|
FROM graph_local AS gl
|
|
WHERE gl.host_id = ?
|
|
AND snmp_query_id = 0
|
|
UNION
|
|
SELECT DISTINCT graph_template_id AS gtid
|
|
FROM host_template_graph AS htg
|
|
WHERE htg.host_template_id = ?
|
|
) AS result
|
|
ON hg.graph_template_id=result.gtid
|
|
WHERE gt.id NOT IN (SELECT graph_template_id FROM snmp_query_graph)
|
|
HAVING gtid IS NULL
|
|
ORDER BY gt.name',
|
|
[$device_id, $device_template_id]
|
|
);
|
|
|
|
if (cacti_sizeof($unused_graph_templates)) {
|
|
foreach ($unused_graph_templates as $unused_graph_template) {
|
|
db_execute_prepared('DELETE
|
|
FROM host_graph
|
|
WHERE host_id = ?
|
|
AND graph_template_id = ?',
|
|
[$device_id, $unused_graph_template['id']]);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($device_id)) !== false) {
|
|
db_execute_prepared('DELETE
|
|
FROM host_graph
|
|
WHERE host_id = ?
|
|
AND graph_template_id = ?',
|
|
[$device_id, $unused_graph_template['id']], true, $rcnn_id);
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
} elseif (!isset($raised[$poller_id])) {
|
|
raise_message('poller_down_' . $poller_id, __esc('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
$raised[$poller_id] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$data = ['device_id' => $device_id, 'device_template_id' => $device_template_id];
|
|
|
|
api_plugin_hook_function('device_template_change', $data);
|
|
|
|
if ($device_id > 0) {
|
|
object_cache_get_totals('device_state', $device_id, true);
|
|
object_cache_update_totals('diff');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the global $device_change_fields array against the field name
|
|
* and returns true or false if it matches the rule
|
|
*
|
|
* @param string $field_name The name of the field to check against the rules.
|
|
*
|
|
* @return bool Returns true if the field name matches any rule, otherwise false.
|
|
*/
|
|
function api_device_change_field_match(string $field_name) : bool {
|
|
global $device_change_fields;
|
|
|
|
$matches = false;
|
|
|
|
foreach ($device_change_fields as $rule_type => $rules) {
|
|
foreach ($rules as $field_rule) {
|
|
if ($rule_type == 'preg_field') {
|
|
if (preg_match($field_rule, $field_name)) {
|
|
$matches = true;
|
|
|
|
break 2;
|
|
}
|
|
} elseif ($rule_type == 'match_field') {
|
|
if ($field_rule == $field_name) {
|
|
$matches = true;
|
|
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $matches;
|
|
}
|
|
|
|
/**
|
|
* Updates the device template mapping for all devices mapped to a template
|
|
*
|
|
* @param int $device_template The ID of the device template to synchronize.
|
|
* @param array|string $device_ids An array or comma-separated string of device IDs to update. Default is an empty string.
|
|
* @param bool $down_devices Whether to include down devices in the synchronization. Default is false.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_template_sync_template(int $device_template, array|string $device_ids = '', bool $down_devices = false) : void {
|
|
if ($down_devices == true) {
|
|
$status_where = '';
|
|
} else {
|
|
$status_where = ' AND status IN(3,2)';
|
|
}
|
|
|
|
$device_ids_supplied = $device_ids !== '' && $device_ids !== [];
|
|
|
|
if (!is_array($device_ids)) {
|
|
$device_ids = preg_split('/,/', $device_ids, -1, PREG_SPLIT_NO_EMPTY);
|
|
}
|
|
|
|
$requested_device_count = cacti_sizeof($device_ids);
|
|
$device_ids = array_values(array_filter($device_ids, static function (mixed $id) : bool {
|
|
return ctype_digit((string) $id) && (int) $id > 0;
|
|
}));
|
|
|
|
if ($device_ids_supplied && cacti_sizeof($device_ids) != $requested_device_count) {
|
|
return;
|
|
}
|
|
|
|
if (cacti_sizeof($device_ids)) {
|
|
$device_ids = array_map('intval', $device_ids);
|
|
$status_where .= ' AND ' . db_in_clause('host.id', $device_ids);
|
|
}
|
|
|
|
$devices = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT id
|
|
FROM host
|
|
WHERE host_template_id = ?' .
|
|
$status_where,
|
|
[$device_template]),
|
|
'id', 'id'
|
|
);
|
|
|
|
if (cacti_sizeof($devices)) {
|
|
foreach ($devices as $device) {
|
|
api_device_update_host_template($device, $device_template);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a device id and optional indicator of where the ping request came from, ping the device.
|
|
* The ping results are echoed to standard output for the browser
|
|
*
|
|
* @param string|null $device_id The ID of the device to ping. If null or empty, an error message is printed.
|
|
* @param bool $from_remote Indicates if the request is from a remote source. Default is false.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_ping_device(string|null $device_id, bool $from_remote = false) : void {
|
|
global $snmp_error;
|
|
|
|
if (empty($device_id)) {
|
|
print __('ERROR: Device ID is Blank');
|
|
|
|
return;
|
|
}
|
|
|
|
$host = db_fetch_row_prepared('SELECT *
|
|
FROM host
|
|
WHERE id = ?',
|
|
[$device_id]);
|
|
|
|
if (!cacti_sizeof($host)) {
|
|
if ($from_remote) {
|
|
print __('ERROR: Device[' . $device_id . '] not found. Please perform Full Sync!');
|
|
} else {
|
|
print __('ERROR: Device[' . $device_id . '] not found. Please check database for errors.');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$am = $host['availability_method'];
|
|
$anym = false;
|
|
|
|
if ($host['poller_id'] != POLLER_ID && $from_remote == false) {
|
|
$url = CACTI_PATH_URL . 'remote_agent.php?action=ping&host_id=' . $host['id'];
|
|
|
|
$results = call_remote_data_collector($host['poller_id'], $url);
|
|
|
|
if ($results != '') {
|
|
$results = (string) preg_replace('/<br\s*\/?\s*>/i', PHP_EOL, $results);
|
|
print nl2br(htmle(strip_tags($results)));
|
|
} else {
|
|
print __('ERROR: Failed to connect to remote collector.');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ($host['disabled'] == 'on') {
|
|
print __('Device is Disabled') . '<br>';
|
|
print __('Device Availability Check Bypassed') . '<br>';
|
|
} elseif ($am == AVAIL_SNMP || $am == AVAIL_SNMP_GET_NEXT ||
|
|
$am == AVAIL_SNMP_GET_SYSDESC || $am == AVAIL_SNMP_AND_PING ||
|
|
$am == AVAIL_SNMP_OR_PING) {
|
|
$anym = true;
|
|
|
|
print '<span><b>' . __('SNMP Information') . '</b></span><br>';
|
|
print "<span class='monoSpace'>";
|
|
|
|
if (($host['snmp_community'] == '' && $host['snmp_username'] == '') || $host['snmp_version'] == 0) {
|
|
print "<span style='color: #ab3f1e; font-weight: bold;'>" . __('SNMP not in use') . '</span>';
|
|
} else {
|
|
$snmp_error = '';
|
|
$session = cacti_snmp_session($host['hostname'], $host['snmp_community'], $host['snmp_version'],
|
|
$host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'],
|
|
$host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_engine_id'], $host['snmp_port'],
|
|
$host['snmp_timeout'], $host['ping_retries'], $host['max_oids']);
|
|
|
|
if ($session === false || $snmp_error != '') {
|
|
print "<span class='hostDown'>" . __('Session') . ' ' . __('SNMP error');
|
|
|
|
if ($snmp_error != '') {
|
|
print ' - ' . htmle($snmp_error);
|
|
} else {
|
|
print ' - ' . __('No session');
|
|
}
|
|
print '</span>';
|
|
} else {
|
|
$snmp_system = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.1.0');
|
|
|
|
if ($snmp_system === false || $snmp_system == 'U' || $snmp_error != '') {
|
|
print "<span class='hostDown'>" . __('System') . ' ' . __('SNMP error');
|
|
|
|
if ($snmp_error != '') {
|
|
print ' - ' . htmle($snmp_error);
|
|
}
|
|
print '</span>';
|
|
} else {
|
|
if ($host['status'] != 3) {
|
|
db_execute_prepared('UPDATE host
|
|
SET status = 3
|
|
WHERE id = ?',
|
|
[$device_id]);
|
|
}
|
|
|
|
// modify for some system descriptions
|
|
// 0000937: System output in host.php poor for Alcatel
|
|
if (substr_count($snmp_system, '00:')) {
|
|
$snmp_system = str_replace('00:', '', $snmp_system);
|
|
$snmp_system = str_replace(':', ' ', $snmp_system);
|
|
}
|
|
|
|
// Some devices (Dell iDRAC, Fortigate, etc.) may have an empty system value. This causes a false down status
|
|
$snmp_engine_time = cacti_snmp_session_get($session, '.1.3.6.1.6.3.10.2.1.3.0');
|
|
$snmp_system_uptime = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.3.0');
|
|
$snmp_uptime = cacti_snmp_select_uptime($snmp_system_uptime, $snmp_engine_time);
|
|
|
|
if ($snmp_system == '' && $snmp_uptime === false) {
|
|
print "<span class='hostDown'>" . __('Host') . ' ' . __('SNMP error');
|
|
|
|
if ($snmp_error != '') {
|
|
print ' - ' . htmle($snmp_error);
|
|
}
|
|
|
|
print '</span>';
|
|
} else {
|
|
$snmp_hostname = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.5.0');
|
|
$snmp_location = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.6.0');
|
|
$snmp_contact = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.4.0');
|
|
|
|
print '<b>' . __('System:') . '</b> ' . html_split_string(htmle($snmp_system), 150) . '<br>';
|
|
|
|
if ($snmp_uptime === false) {
|
|
$snmp_uptime = 'U';
|
|
print '<b>' . __('Uptime:') . '</b> ' . htmle($snmp_uptime) . '<br>';
|
|
} else {
|
|
$snmp_uptime_ticks = intval($snmp_uptime);
|
|
$days = intval($snmp_uptime_ticks / (60 * 60 * 24 * 100));
|
|
$remainder = $snmp_uptime_ticks % (60 * 60 * 24 * 100);
|
|
$hours = intval($remainder / (60 * 60 * 100));
|
|
$remainder %= 60 * 60 * 100;
|
|
$minutes = intval($remainder / (60 * 100));
|
|
$snmp_uptime = (string) $snmp_uptime;
|
|
print '<b>' . __('Uptime:') . '</b> ' . htmle($snmp_uptime);
|
|
print ' (' . $days . __('days') . ', ' . $hours . __('hours') . ', ' . $minutes . __('minutes') . ')<br>';
|
|
}
|
|
print '<b>' . __('Hostname:') . '</b> ' . htmle($snmp_hostname) . '<br>';
|
|
print '<b>' . __('Location:') . '</b> ' . htmle($snmp_location) . '<br>';
|
|
print '<b>' . __('Contact:') . '</b> ' . htmle($snmp_contact) . '<br>';
|
|
}
|
|
}
|
|
|
|
$session->close();
|
|
}
|
|
}
|
|
print '</span>';
|
|
}
|
|
|
|
if ($am == AVAIL_PING || $am == AVAIL_SNMP_AND_PING || $am == AVAIL_SNMP_OR_PING) {
|
|
$anym = true;
|
|
|
|
// create new ping socket for host pinging
|
|
$ping = new Net_Ping;
|
|
|
|
$ping->host = $host;
|
|
$ping->port = $host['ping_port'];
|
|
|
|
// perform the appropriate ping check of the host
|
|
$ping_results = $ping->ping(AVAIL_PING, $host['ping_method'], $host['ping_timeout'], $host['ping_retries']);
|
|
|
|
if ($ping_results == true) {
|
|
$host_down = false;
|
|
$class = 'hostUp';
|
|
} else {
|
|
$host_down = true;
|
|
$class = 'hostDown';
|
|
}
|
|
|
|
print __('Ping Results') . "<br>\n";
|
|
print "<span class='" . $class . "'>" . htmle($ping->ping_response) . "</span>\n";
|
|
}
|
|
|
|
if ($anym == false && $host['disabled'] != 'on') {
|
|
print __('No Ping or SNMP Availability Check in Use') . "<br><br>\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Duplicates a device template.
|
|
*
|
|
* @param int $_host_template_id The ID of the host template to duplicate.
|
|
* @param string $host_template_title The title of the new host template.
|
|
*
|
|
* @return int|bool The result of the duplication process.
|
|
*/
|
|
function api_duplicate_device_template(int $_host_template_id, string $host_template_title) : int|bool {
|
|
global $fields_host_template_edit;
|
|
|
|
$host_template = db_fetch_row_prepared('SELECT * FROM host_template WHERE id = ?', [$_host_template_id]);
|
|
$host_template_graphs = db_fetch_assoc_prepared('SELECT * FROM host_template_graph WHERE host_template_id = ?', [$_host_template_id]);
|
|
$host_template_data_queries = db_fetch_assoc_prepared('SELECT * FROM host_template_snmp_query WHERE host_template_id = ?', [$_host_template_id]);
|
|
|
|
if (cacti_sizeof($host_template)) {
|
|
// substitute the title variable
|
|
$host_template['name'] = str_replace('<template_title>', $host_template['name'], $host_template_title);
|
|
|
|
// create new entry: host_template
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_host_template(0);
|
|
|
|
foreach ($fields_host_template_edit as $field => $array) {
|
|
if (!preg_match('/^hidden/', $array['method'])) {
|
|
$save[$field] = $host_template[$field];
|
|
}
|
|
}
|
|
|
|
$host_template_id = sql_save($save, 'host_template');
|
|
|
|
// create new entry(s): host_template_graph
|
|
if (cacti_sizeof($host_template_graphs)) {
|
|
foreach ($host_template_graphs as $host_template_graph) {
|
|
db_execute_prepared('INSERT INTO host_template_graph
|
|
(host_template_id,graph_template_id)
|
|
VALUES (?, ?)',
|
|
[$host_template_id, $host_template_graph['graph_template_id']]);
|
|
}
|
|
}
|
|
|
|
// create new entry(s): host_template_snmp_query
|
|
if (cacti_sizeof($host_template_data_queries)) {
|
|
foreach ($host_template_data_queries as $host_template_data_query) {
|
|
db_execute_prepared('INSERT INTO host_template_snmp_query
|
|
(host_template_id,snmp_query_id)
|
|
VALUES (?, ?)',
|
|
[$host_template_id, $host_template_data_query['snmp_query_id']]);
|
|
}
|
|
}
|
|
|
|
return $host_template_id;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays a clone specific log message if there to CLI and the Cacti log
|
|
*
|
|
* @param string $message The message to be logged.
|
|
* @param bool $force If true, forces the message to be logged regardless of the debug setting. Default is false.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_clone_message($message, $force = false) : void {
|
|
global $debug;
|
|
|
|
if ($debug || $force) {
|
|
if (!CACTI_WEB) {
|
|
print trim($message) . PHP_EOL;
|
|
}
|
|
|
|
cacti_log($message, false, 'DTCLONE');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a unique name for a cacti object based upon the table and column name
|
|
*
|
|
* @param string $name The base name to check for uniqueness.
|
|
* @param string $table The name of the database table to check.
|
|
* @param string $column The name of the column in the table to check for the name. Default is 'name'.
|
|
*
|
|
* @return string|false The unique name if found, or false if a unique name could not be generated within 20 attempts.
|
|
*/
|
|
function api_clone_get_unique_name(string $name, string $table, string $column = 'name') : string|false {
|
|
$i = 0;
|
|
|
|
while ($i < 20) {
|
|
if ($i > 0) {
|
|
$check_name = $name . " ($i)";
|
|
} else {
|
|
$check_name = $name;
|
|
}
|
|
|
|
$exists = db_fetch_cell_prepared("SELECT $column
|
|
FROM $table
|
|
WHERE $column = ?",
|
|
[$check_name]);
|
|
|
|
if ($exists == '') {
|
|
return $check_name;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a unique file name for a Cacti object based upon the file name
|
|
*
|
|
* @param string $file_name The original file name to be used as the base for generating a unique filename.
|
|
*
|
|
* @return string|false The unique filename if found, or false if no unique filename could be generated within 20 attempts.
|
|
*/
|
|
function api_clone_get_unique_filename(string $file_name) : string|false {
|
|
$i = 1;
|
|
|
|
$file_data = pathinfo($file_name);
|
|
$file_base = $file_data['dirname'] . '/' . basename($file_data['basename'], $file_data['extension']);
|
|
$file_ext = $file_data['extension'];
|
|
|
|
while ($i < 20) {
|
|
$id = substr('00' . $i, -2);
|
|
$check_name = $file_base . "_$id" . '.' . $file_ext;
|
|
|
|
if (!file_exists($check_name)) {
|
|
return $check_name;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* This function will validate the input and return warnings and errors before allowing users to proceed.
|
|
* This option is skipped when using the quiet option.
|
|
*
|
|
* @param int $device_template_id - The ID of the device template to clone.
|
|
* @param string $device_template_name - The name of the device template to clone.
|
|
* @param string $include_gt - Whether to include graph templates ('all', comma-separated list of IDs, or empty).
|
|
* @param string $clone_gt - Whether to clone graph templates ('all', comma-separated list of IDs, or empty).
|
|
* @param string $include_dq - Whether to include data queries ('all', comma-separated list of IDs, or empty).
|
|
* @param string $clone_dq - Whether to clone data queries ('all', comma-separated list of IDs, or empty).
|
|
* @param string $include_dt - Whether to include data templates ('all', comma-separated list of IDs, or empty).
|
|
* @param string $clone_dt - Whether to clone data templates ('all', comma-separated list of IDs, or empty).
|
|
* @param string &$suffix - The suffix to append to cloned items.
|
|
* @param bool &$clone_xml - Whether to clone XML files.
|
|
* @param bool &$clone_script - Whether to clone script files.
|
|
*
|
|
* @return array - An array containing 'warnings' and 'errors' keys with respective messages.
|
|
*/
|
|
function api_clone_device_template_check_for_errors(int $device_template_id, string $device_template_name, string $include_gt, string $clone_gt,
|
|
string $include_dq, string $clone_dq, string $include_dt, string $clone_dt, string &$suffix, bool &$clone_xml, bool &$clone_script) : array {
|
|
$return = [
|
|
'warnings' => [],
|
|
'errors' => []
|
|
];
|
|
|
|
$device_template = db_fetch_row_prepared('SELECT *
|
|
FROM host_template
|
|
WHERE id = ?',
|
|
[$device_template_id]);
|
|
|
|
// first error check
|
|
if (!cacti_sizeof($device_template)) {
|
|
$return['errors'][] = sprintf('FATAL: Device Template %s does not exist!', $device_template_id);
|
|
|
|
return $return;
|
|
} else {
|
|
$objects = api_clone_device_template_get_objects($device_template['id']);
|
|
}
|
|
|
|
// second error check
|
|
if (!cacti_sizeof($objects)) {
|
|
$return['errors'][] = sprintf('FATAL: Device Template %s has no Objects!', $device_template_id);
|
|
|
|
return $return;
|
|
}
|
|
|
|
$errors = 0;
|
|
$warnings = 0;
|
|
$include_gt = cacti_strtolower($include_gt);
|
|
$include_dq = cacti_strtolower($include_dq);
|
|
$include_dt = cacti_strtolower($include_dt);
|
|
$clone_gt = cacti_strtolower($clone_gt);
|
|
$clone_dq = cacti_strtolower($clone_dq);
|
|
$clone_dt = cacti_strtolower($clone_dt);
|
|
|
|
printf('Cloning Criteria for Device Template are:' . PHP_EOL);
|
|
printf('---------------------------------------------------------------------------' . PHP_EOL);
|
|
printf('NOTE: Include Graph Template: ' . ($include_gt != '' ? $include_gt : 'none') . PHP_EOL);
|
|
printf('NOTE: Include Data Query: ' . ($include_dq != '' ? $include_dq : 'none') . PHP_EOL);
|
|
printf('NOTE: Include Data Template: ' . ($include_dt != '' ? $include_dt : 'none') . PHP_EOL);
|
|
printf('NOTE: Clone Graph Template: ' . ($clone_gt != '' ? $clone_gt : 'none') . PHP_EOL);
|
|
printf('NOTE: Clone Data Query: ' . ($clone_dq != '' ? $clone_dq : 'none') . PHP_EOL);
|
|
printf('NOTE: Clone Data Template: ' . ($clone_dt != '' ? $clone_dt : 'none') . PHP_EOL);
|
|
printf('NOTE: Clone XML Files: ' . ($clone_xml != '' ? 'yes' : 'no') . PHP_EOL);
|
|
printf('NOTE: Clone Script Files: ' . ($clone_script != '' ? 'yes' : 'no') . PHP_EOL);
|
|
printf('---------------------------------------------------------------------------' . PHP_EOL);
|
|
|
|
if ($include_gt == 'all' && $clone_gt != '') {
|
|
$return['errors'][] = sprintf('FATAL: Include Graph Templates can not be \'all\' when Clone Graph Templates is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_dq == 'all' && $clone_dq != '') {
|
|
$return['errors'][] = sprintf('FATAL: Include Data Queries can not be \'all\' when Clone Data Queries is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_dt == 'all' && $clone_dt != '') {
|
|
$return['errors'][] = sprintf('FATAL: Include Data Templates can not be \'all\' when Clone Data Templates is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_gt != '' && $clone_gt == 'all') {
|
|
$return['errors'][] = sprintf('FATAL: Clone Graph Templates can not be \'all\' when Include Graph Templates is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_dq != '' && $clone_dq == 'all') {
|
|
$return['errors'][] = sprintf('FATAL: Clone Data Queries can not be \'all\' when Include Data Queries is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_dt != '' && $clone_dt == 'all') {
|
|
$return['errors'][] = sprintf('FATAL: Clone Data Templates can not be \'all\' when Include Data Templates is in use!');
|
|
$errors++;
|
|
}
|
|
|
|
if ($include_gt != '' && $include_gt != 'all') {
|
|
$gts = explode(',', $include_gt);
|
|
|
|
foreach ($gts as $gt) {
|
|
if (!is_numeric($gt) || $gt <= 0) {
|
|
$errors++;
|
|
|
|
$return['errors'][] = sprintf('FATAL: Graph Template to be included %s is not numeric', $gt);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($include_dq != '' && $include_dq != 'all') {
|
|
$dqs = explode(',', $include_dq);
|
|
|
|
foreach ($dqs as $dq) {
|
|
if (!is_numeric($dq) || $dq <= 0) {
|
|
$errors++;
|
|
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be included %s is not numeric', $dq);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($include_dt != '' && $include_dt != 'all') {
|
|
$dts = explode(',', $include_dt);
|
|
|
|
foreach ($dts as $dt) {
|
|
if (!is_numeric($dt) || $dt <= 0) {
|
|
$errors++;
|
|
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be included %s is not numeric', $dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
$graph_templates = array_merge(array_keys($objects['graph_templates']), array_keys($objects['data_query_graph_templates']));
|
|
$data_templates = array_keys($objects['data_templates']);
|
|
$data_queries = array_keys($objects['data_queries']);
|
|
|
|
if ($include_gt != '' && $include_gt != 'all') {
|
|
$gti = explode(',', $include_gt);
|
|
|
|
foreach ($gts as $gt) {
|
|
if (array_search($gt, $graph_templates, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Graph Template to be included %s does not exist in Device Template', $gt);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clone_gt != '' && $clone_gt != 'all') {
|
|
$gtc = explode(',', $clone_gt);
|
|
|
|
foreach ($gtc as $gt) {
|
|
if (array_search($gt, $graph_templates, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Graph Template to be cloned %s does not exist in Device Template', $gt);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($include_dq != '' && $include_dq != 'all') {
|
|
$dqi = explode(',', $include_dq);
|
|
|
|
foreach ($dqi as $dq) {
|
|
if (array_search($dq, $data_queries, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be included %s does not exist in Device Template', $dq);
|
|
}
|
|
}
|
|
}
|
|
|
|
$clone_dq_proceed = false;
|
|
$dqc = [];
|
|
|
|
if ($clone_dq != '' && $clone_dq != 'all') {
|
|
$clone_dq_proceed = true;
|
|
|
|
$dqc = explode(',', $clone_dq);
|
|
|
|
foreach ($dqc as $dq) {
|
|
if (!is_numeric($dq) || $dq <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be cloned %s is not numeric', $dq);
|
|
} elseif (array_search($dq, $data_queries, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be cloned %s does not exist in Device Template', $dq);
|
|
}
|
|
}
|
|
} elseif ($clone_dq == 'all') {
|
|
$clone_dq_proceed = true;
|
|
}
|
|
|
|
if ($include_dt != '' && $include_dt != 'all') {
|
|
$dti = explode(',', $include_dt);
|
|
|
|
foreach ($dti as $dt) {
|
|
if (!is_numeric($dt) || $dt <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be cloned %s is not numeric', $dt);
|
|
} elseif (array_search($dt, $data_templates, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be included %s does not exist in Device Template', $dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
$clone_dt_proceed = false;
|
|
$dtc = [];
|
|
|
|
if ($clone_dt != '' && $clone_dt != 'all') {
|
|
$clone_dt_proceed = true;
|
|
|
|
$dtc = explode(',', $clone_dt);
|
|
|
|
foreach ($dtc as $dt) {
|
|
if (!is_numeric($dt) || $dt <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be cloned %s is not numeric', $dt);
|
|
} elseif (array_search($dt, $data_templates, true) === false) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be cloned %s does not exist in Device Template', $dt);
|
|
}
|
|
}
|
|
} elseif ($clone_dt == 'all') {
|
|
$clone_dt_proceed = true;
|
|
}
|
|
|
|
// now check for name collision xml files and scripts
|
|
if ($clone_xml) {
|
|
if ($clone_dq_proceed) {
|
|
if ($clone_dq == 'all') {
|
|
$clone_data_queries = $objects['data_queries'];
|
|
} else {
|
|
$clone_data_queries = array_intersect_key($objects['data_queries'], array_flip($dqc));
|
|
}
|
|
|
|
foreach ($clone_data_queries as $id => $data_query) {
|
|
if (!is_numeric($id) || $id <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be cloned %s is not numeric', $id);
|
|
} elseif ($data_query['xml_path'] != '') {
|
|
$xml_path = $data_query['xml_path'];
|
|
$xml_base = trim(str_replace(CACTI_PATH_BASE, '', $xml_path), '/');
|
|
|
|
if (!is_file($xml_path) || !is_readable($xml_path)) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query XML Base path \'%s\' for \'%s\' is not readable!', $xml_base, $data_query['name']);
|
|
} elseif (!is_writable(dirname($xml_path))) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query XML Base directory for \'%s\' is not writable!', $data_query['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clone_script) {
|
|
if ($clone_dq_proceed) {
|
|
if ($clone_dq == 'all') {
|
|
$clone_data_queries = $objects['data_queries'];
|
|
} else {
|
|
$clone_data_queries = array_intersect_key($objects['data_queries'], array_flip($dqc));
|
|
}
|
|
|
|
foreach ($clone_data_queries as $id => $data_query) {
|
|
if (!is_numeric($id) || $id <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query to be cloned %s is not numeric', $id);
|
|
} elseif (isset($data_query['script_path']) && $data_query['script_path'] != '') {
|
|
$script_path = $data_query['script_path'];
|
|
$script_base = trim(str_replace(CACTI_PATH_BASE, '', $script_path), '/');
|
|
|
|
if (!is_file($script_path) || !is_readable($script_path)) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query Script Base path \'%s\' for \'%s\' is not readable!', $script_base, $data_query['name']);
|
|
} elseif (!is_writable(dirname($script_path))) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query Script Base directory for \'%s\' is not writable!', $data_query['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clone_dt_proceed) {
|
|
if ($clone_dt == 'all') {
|
|
$clone_data_templates = $objects['data_templates'];
|
|
} else {
|
|
$clone_data_templates = array_intersect_key($objects['data_templates'], array_flip($dtc));
|
|
}
|
|
|
|
foreach ($clone_data_templates as $id => $data_template) {
|
|
if (!is_numeric($id) || $id <= 0) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template to be cloned %s is not numeric', $id);
|
|
} elseif (isset($data_template['script_path'])) {
|
|
$script_path = $data_template['script_path'];
|
|
$script_base = trim(str_replace(CACTI_PATH_BASE, '', $script_path), '/');
|
|
|
|
if (!is_file($script_path) || !is_readable($script_path)) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template Script Base path \'%s\' for \'%s\' is not readable!', $script_base, $data_template['name']);
|
|
} elseif (!is_writable(dirname($script_path))) {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template Script Base directory for \'%s\' is not writable!', $data_template['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// not issue some warnings for things to be cloned
|
|
if ($device_template_name == '') {
|
|
$device_template_name = db_fetch_cell_prepared('SELECT name
|
|
FROM host_template
|
|
WHERE id = ?',
|
|
[$device_template_id]);
|
|
|
|
$device_template_name .= $suffix;
|
|
}
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM host_template
|
|
WHERE name = ?',
|
|
[$device_template_name]);
|
|
|
|
if ($exists) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Device Template \'%s\' already exists.', $device_template_name);
|
|
}
|
|
|
|
if ($clone_gt != '') {
|
|
if ($clone_gt == 'all') {
|
|
$gts = array_keys($objects['graph_templates']);
|
|
} else {
|
|
$gts = explode(',', $clone_gt);
|
|
}
|
|
|
|
foreach ($gts as $gt_id) {
|
|
$name = $objects['graph_templates'][$gt_id]['name'];
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM graph_templates
|
|
WHERE name = ?',
|
|
[$name . $suffix]);
|
|
|
|
if ($exists > 0) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Graph Template \'%s\' already exists.', $name . $suffix);
|
|
}
|
|
}
|
|
}
|
|
|
|
// not issue some warnings for things to be cloned
|
|
if ($clone_xml && !$clone_dq_proceed) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Ignoring --clone-xml as no Data Queries were selected to be cloned.');
|
|
$clone_xml = false;
|
|
}
|
|
|
|
if ($clone_script && (!$clone_dq_proceed && !$clone_dt_proceed)) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Ignoring --clone-script as no Data Queries or Templates were selected to be cloned.');
|
|
$clone_script = false;
|
|
}
|
|
|
|
if ($clone_dq_proceed) {
|
|
$ndq = [];
|
|
|
|
if ($clone_dq == 'all') {
|
|
$dqs = array_keys($objects['data_queries']);
|
|
} else {
|
|
$dqs = explode(',', $clone_dq);
|
|
}
|
|
|
|
if (cacti_sizeof($dqs)) {
|
|
foreach ($dqs as $dq) {
|
|
$ndq[$dq] = $dq;
|
|
}
|
|
|
|
$dqs = $ndq;
|
|
}
|
|
|
|
foreach ($dqs as $dq_id) {
|
|
if (is_numeric($dq_id) && $dq_id > 0 && isset($objects['data_queries'][$dq_id])) {
|
|
$name = $objects['data_queries'][$dq_id]['name'];
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM snmp_query
|
|
WHERE name = ?',
|
|
[$name . $suffix]);
|
|
|
|
if ($exists > 0) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Data Query \'%s\' already exists.', $name . $suffix);
|
|
}
|
|
} else {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Query ID %s to be cloned is not numeric.', $dq_id);
|
|
}
|
|
}
|
|
|
|
foreach ($objects['data_query_graph_templates'] as $graph_template) {
|
|
$name = $graph_template['name'];
|
|
$snmp_query_id = $graph_template['snmp_query_id'];
|
|
|
|
if (isset($dqs[$snmp_query_id])) {
|
|
$dq_name = $objects['data_queries'][$snmp_query_id]['name'];
|
|
|
|
api_clone_message(sprintf('NOTE: Checking GT:\'%s\' and DQ Name:\'%s\'.', $name, $dq_name));
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM graph_templates
|
|
WHERE name = ?',
|
|
[$name . $suffix]);
|
|
|
|
if ($exists > 0) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Graph Template \'%s\' for Data Query \'%s\' already exists.', $name . $suffix, $dq_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clone_dt_proceed) {
|
|
if ($clone_dt == 'all') {
|
|
$dts = array_keys($objects['data_templates']);
|
|
} else {
|
|
$dts = explode(',', $clone_dt);
|
|
}
|
|
|
|
foreach ($dts as $dt_id) {
|
|
if (is_numeric($dt_id) && $dt_id > 0 && isset($objects['data_templates'][$dt_id])) {
|
|
$name = $objects['data_templates'][$dt_id]['name'];
|
|
$dihash = $objects['data_templates'][$dt_id]['dihash'];
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template
|
|
WHERE name = ?',
|
|
[$name . $suffix]);
|
|
|
|
if ($exists > 0) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Data Template \'%s\' already exists.', $name . $suffix);
|
|
}
|
|
|
|
$name = db_fetch_cell_prepared('SELECT name
|
|
FROM data_input
|
|
WHERE hash = ?',
|
|
[$dihash]);
|
|
|
|
$exists = db_fetch_cell_prepared('SELECT id
|
|
FROM data_input
|
|
WHERE name = ?',
|
|
[$name . $suffix]);
|
|
|
|
if ($exists > 0) {
|
|
$warnings++;
|
|
$return['warnings'][] = sprintf('WARNING: Data Template Data Input Method \'%s\' already exists.', $name . $suffix);
|
|
}
|
|
} else {
|
|
$errors++;
|
|
$return['errors'][] = sprintf('FATAL: Data Template ID %s to be cloned is not numeric.', $dt_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// print_r($objects);
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* This function returns the core components from the Device Template for validating cloning actions.
|
|
* Once these values are returned, the device template API will be able to clone the Device
|
|
* Template without errors.
|
|
*
|
|
* @param int $device_template_id The ID of the device template to retrieve objects for.
|
|
*
|
|
* @return array An associative array containing the following keys:
|
|
*/
|
|
function api_clone_device_template_get_objects(int $device_template_id) : array {
|
|
$objects = [
|
|
'graph_templates' => [],
|
|
'data_templates' => [],
|
|
'data_queries' => [],
|
|
'data_query_graph_templates' => [],
|
|
'data_query_data_templates' => []
|
|
];
|
|
|
|
$objects['graph_templates'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT gt.id, gt.name, gt.hash
|
|
FROM host_template_graph AS ht
|
|
INNER JOIN graph_templates AS gt
|
|
ON ht.graph_template_id = gt.id
|
|
WHERE ht.host_template_id = ?',
|
|
[$device_template_id]),
|
|
'id', ['name', 'hash']
|
|
);
|
|
|
|
$objects['data_queries'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT sq.id, sq.name, sq.hash, sq.data_input_id, di.hash AS dihash,
|
|
REPLACE(sq.xml_path, "<path_cacti>", ?) AS xml_path,
|
|
REPLACE(di.input_string, "<path_cacti>", ?) AS input_string
|
|
FROM host_template_snmp_query AS htsq
|
|
INNER JOIN snmp_query AS sq
|
|
ON sq.id = htsq.snmp_query_id
|
|
INNER JOIN data_input AS di
|
|
ON di.id = sq.data_input_id
|
|
WHERE host_template_id = ?',
|
|
[CACTI_PATH_BASE, CACTI_PATH_BASE, $device_template_id]),
|
|
'id', ['name', 'hash', 'dihash', 'data_input_id', 'xml_path', 'input_string']
|
|
);
|
|
|
|
if (cacti_sizeof($objects['data_queries'])) {
|
|
foreach ($objects['data_queries'] as $id => $data_query) {
|
|
$snmp_query_data = get_data_query_array($id);
|
|
|
|
if (isset($snmp_query_data['script_path'])) {
|
|
$objects['data_queries'][$id]['script_path'] = str_replace('|path_cacti|', CACTI_PATH_BASE, $snmp_query_data['script_path']);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($objects['graph_templates'])) {
|
|
$objects['data_templates'] = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT DISTINCT dt.id, dt.name, dt.hash, dtd.data_input_id, di.hash AS dihash,
|
|
REPLACE(di.input_string, "<path_cacti>", "' . CACTI_PATH_BASE . '") AS input_string
|
|
FROM data_template AS dt
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dt.id = dtd.data_template_id
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dt.id = dtr.data_template_id
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON dtr.id = gti.task_item_id
|
|
INNER JOIN data_input AS di
|
|
ON di.id = dtd.data_input_id
|
|
WHERE dtr.local_data_id = 0
|
|
AND gti.local_graph_id = 0
|
|
AND gti.graph_template_id IN (
|
|
SELECT graph_template_id
|
|
FROM host_template_graph
|
|
WHERE host_template_id = ?
|
|
)',
|
|
[$device_template_id]),
|
|
'id', ['name', 'hash', 'dihash', 'data_input_id', 'input_string']
|
|
);
|
|
|
|
if (cacti_sizeof($objects['data_templates'])) {
|
|
foreach ($objects['data_templates'] as $id => $data_template) {
|
|
// peel the script from the input_string
|
|
if (isset($data_template['input_string'])) {
|
|
$parts = explode(' ', $data_template['input_string']);
|
|
|
|
foreach ($parts as $p) {
|
|
if (str_contains($p, CACTI_PATH_BASE)) {
|
|
if (file_exists($p)) {
|
|
$objects['data_templates'][$id]['script_path'] = $p;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// let's get the list of graph templates that need updating
|
|
$graph_templates = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT DISTINCT graph_template_id AS id
|
|
FROM graph_templates_item AS gti
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id = dtr.id
|
|
WHERE local_graph_id = 0
|
|
AND local_data_id = 0
|
|
AND dtr.data_template_id = ?',
|
|
[$id]),
|
|
'id', 'id'
|
|
);
|
|
|
|
$objects['data_templates'][$id]['graph_template_ids'] = $graph_templates;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($objects['data_queries'])) {
|
|
$objects['data_query_graph_templates'] = array_rekey(
|
|
db_fetch_assoc('SELECT gt.id, gt.name, gt.hash, sqg.snmp_query_id, sqg.name AS sqname
|
|
FROM graph_templates AS gt
|
|
INNER JOIN snmp_query_graph AS sqg
|
|
ON gt.id = sqg.graph_template_id
|
|
WHERE ' . array_to_sql_or(array_keys($objects['data_queries']), 'sqg.snmp_query_id')),
|
|
'id', ['name', 'hash', 'snmp_query_id', 'sqname']
|
|
);
|
|
|
|
$objects['data_query_data_templates'] = array_rekey(
|
|
db_fetch_assoc('SELECT DISTINCT dt.id, dt.name, dt.hash, dtd.data_input_id, sq.id AS snmp_query_id
|
|
FROM data_template AS dt
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dt.id = dtd.data_template_id
|
|
INNER JOIN snmp_query_graph_rrd AS sqgr
|
|
ON dt.id = sqgr.data_template_id
|
|
INNER JOIN snmp_query_graph AS sqg
|
|
ON sqgr.snmp_query_graph_id = sqg.id
|
|
INNER JOIN snmp_query AS sq
|
|
ON sq.id = sqg.snmp_query_id
|
|
WHERE dtd.local_data_id = 0
|
|
AND ' . array_to_sql_or(array_keys($objects['data_queries']), 'sq.id')),
|
|
'id', ['name', 'hash', 'data_input_id', 'snmp_query_id']
|
|
);
|
|
}
|
|
|
|
return $objects;
|
|
}
|
|
|
|
/**
|
|
* Clones a device template and in some cases also updates duplicates Graph Templates,
|
|
* Data Templates, Data Input Methods and making copies of scripts, and XML files as well.
|
|
*
|
|
* @param int $template_id The ID of the template to clone.
|
|
* @param string $template_name The name for the new template. If empty, the original name with a suffix will be used.
|
|
* @param string $include_gt Comma-separated list of graph template IDs to include. If 'all', includes all graph templates.
|
|
* @param string $clone_gt Comma-separated list of graph template IDs to clone. If 'all', clones all graph templates.
|
|
* @param string $include_dq Comma-separated list of data query IDs to include. If 'all', includes all data queries.
|
|
* @param string $clone_dq Comma-separated list of data query IDs to clone. If 'all', clones all data queries.
|
|
* @param string $include_dt Comma-separated list of data template IDs to include. If 'all', includes all data templates.
|
|
* @param string $clone_dt Comma-separated list of data template IDs to clone. If 'all', clones all data templates.
|
|
* @param string $suffix The suffix to append to the new template name if no name is provided.
|
|
* @param bool $clone_xml Whether to clone XML files associated with data queries.
|
|
* @param bool $clone_script Whether to clone script files associated with data queries.
|
|
* @param bool $cli Whether the function is being called from the command line interface.
|
|
*
|
|
* @return int|bool The ID of the newly created template.
|
|
*/
|
|
function api_clone_device_template(int $template_id, string $template_name, string $include_gt, string $clone_gt,
|
|
string $include_dq, string $clone_dq, string $include_dt, string $clone_dt, string $suffix, bool $clone_xml, bool $clone_script, bool $cli = false) : int|bool {
|
|
// The list of duplicated Data Templates. Dont do it more than once
|
|
$duped_graph_templates = [];
|
|
$duped_data_templates = [];
|
|
$duped_data_input_methods = [];
|
|
$duped_xmlfiles = [];
|
|
$duped_scripts = [];
|
|
$duped_data_query_graphs = [];
|
|
|
|
$start = microtime(true);
|
|
|
|
$device_template = db_fetch_row_prepared('SELECT *
|
|
FROM host_template
|
|
WHERE id = ?',
|
|
[$template_id]);
|
|
|
|
if (!cacti_sizeof($device_template)) {
|
|
api_clone_message(sprintf('ERROR: Device Template %s does not exist.', $template_id), true);
|
|
|
|
return false;
|
|
}
|
|
|
|
api_clone_message(sprintf('NOTE: Beginning Cloning Device Template %s.', $device_template['name']));
|
|
|
|
$device_template_hash = generate_hash();
|
|
|
|
if ($template_name == '') {
|
|
$new_name = $device_template['name'] . $suffix;
|
|
} else {
|
|
$new_name = $template_name;
|
|
}
|
|
|
|
$new_name = api_clone_get_unique_name($new_name, 'host_template', 'name');
|
|
|
|
if ($new_name === false) {
|
|
api_clone_message('ERROR: Unable to allocate a unique Device Template name.', true);
|
|
|
|
return false;
|
|
}
|
|
|
|
api_clone_message(sprintf('NOTE: Cloning Device Template \'%s\'to \'%s\'', $device_template['name'], $new_name), true);
|
|
|
|
$save = $device_template;
|
|
$save['id'] = 0;
|
|
$save['name'] = $new_name;
|
|
$save['hash'] = $device_template_hash;
|
|
|
|
/**
|
|
* This process follows the following algorithm
|
|
*
|
|
* Template Duplication Process
|
|
* --------------------------------------------------------------
|
|
* 1. Get all current Device Template Objects
|
|
*
|
|
* 2. Copy the Device Template
|
|
*
|
|
* 3. Handle Include Cases
|
|
* a. Include Graph Templates
|
|
* b. Include Data Queries
|
|
*
|
|
* 4. Handle Clone Cases for Graph Template case
|
|
* a. Clone Graph Templates
|
|
* b. Clone Data Templates
|
|
* c. Clone Data Input Method if Called for
|
|
*
|
|
* 5. Handle Clone Cases for Data Query Cases
|
|
* a. Clone Data Queries
|
|
* b. Clone Data Query Graph Templates
|
|
* c. Clone Data Query Data Templates
|
|
*
|
|
* 6. Handle Clone XML and Clone Script options
|
|
* a. Make copies of each XML file
|
|
* b. Make copyies of each script file
|
|
* c. Update references in Data Query XML files
|
|
* d. Make updates in Data Input Methods
|
|
*
|
|
* NOTES:
|
|
* --------------------------------------------------------------
|
|
* In the Case of the Data Queries, there is no cloning of the
|
|
* Data Input Method as they are static and will not change
|
|
*
|
|
* In the case of Cloning Graph Template Data Templates,
|
|
* we must also clone the Data Input method as it is assumed
|
|
* that the clone should include it as well.
|
|
*/
|
|
|
|
// get the list of exist Data Template Objects
|
|
$objects = api_clone_device_template_get_objects($template_id);
|
|
|
|
$selection_ids = static function (string $selection, array $available) : array|false {
|
|
if ($selection == 'all') {
|
|
return array_map('intval', array_keys($available));
|
|
}
|
|
|
|
if ($selection == '') {
|
|
return [];
|
|
}
|
|
|
|
$ids = explode(',', $selection);
|
|
|
|
foreach ($ids as $id) {
|
|
if (!ctype_digit($id) || (int) $id <= 0 || !array_key_exists((int) $id, $available)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique(array_map('intval', $ids)));
|
|
};
|
|
|
|
$included_graph_templates = $selection_ids($include_gt, $objects['graph_templates']);
|
|
$cloned_graph_templates = $selection_ids($clone_gt, $objects['graph_templates']);
|
|
$included_data_queries = $selection_ids($include_dq, $objects['data_queries']);
|
|
$cloned_data_queries = $selection_ids($clone_dq, $objects['data_queries']);
|
|
$included_data_templates = $selection_ids($include_dt, $objects['data_templates']);
|
|
$cloned_data_templates = $selection_ids($clone_dt, $objects['data_templates']);
|
|
|
|
if ($included_graph_templates === false || $cloned_graph_templates === false ||
|
|
$included_data_queries === false || $cloned_data_queries === false ||
|
|
$included_data_templates === false || $cloned_data_templates === false) {
|
|
api_clone_message('ERROR: One or more clone selections contain an invalid object ID.', true);
|
|
|
|
return false;
|
|
}
|
|
|
|
$selected_graph_templates = array_unique(array_merge(
|
|
$included_graph_templates,
|
|
$cloned_graph_templates
|
|
));
|
|
$selected_data_templates = array_unique(array_merge(
|
|
$included_data_templates,
|
|
$cloned_data_templates
|
|
));
|
|
|
|
foreach ($objects['data_templates'] as $data_template_id => $data_template) {
|
|
$graph_template_ids = array_map('intval', array_keys($data_template['graph_template_ids']));
|
|
|
|
if (array_intersect($selected_graph_templates, $graph_template_ids) &&
|
|
!in_array((int) $data_template_id, $selected_data_templates, true)) {
|
|
api_clone_message(sprintf('ERROR: Data Template \'%s\' is required by a selected Graph Template but was not included or cloned.', $data_template['name']), true);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$new_template = sql_save($save, 'host_template');
|
|
|
|
if (!$new_template) {
|
|
api_clone_message('ERROR: Unable to create the Device Template clone.', true);
|
|
|
|
return false;
|
|
}
|
|
|
|
// include graph templates
|
|
if ($include_gt != '' && $include_gt != 'all') {
|
|
$sql_where = 'AND ' . db_in_clause('graph_template_id', $included_graph_templates);
|
|
} elseif ($clone_gt == 'all') {
|
|
$sql_where = 'AND 1 = 0';
|
|
} else {
|
|
$sql_where = '';
|
|
}
|
|
|
|
$graph_templates = db_fetch_assoc_prepared("SELECT *
|
|
FROM host_template_graph
|
|
WHERE host_template_id = ?
|
|
$sql_where",
|
|
[$device_template['id']]);
|
|
|
|
if (cacti_sizeof($graph_templates)) {
|
|
api_clone_message(sprintf('NOTE: Including %s Graph Templates', cacti_sizeof($graph_templates)));
|
|
|
|
foreach ($graph_templates as $gt) {
|
|
db_execute_prepared('INSERT INTO host_template_graph
|
|
(host_template_id, graph_template_id)
|
|
VALUES (?, ?)',
|
|
[$new_template, $gt['graph_template_id']]);
|
|
}
|
|
} else {
|
|
api_clone_message('NOTE: No Graph Templates to be Included');
|
|
}
|
|
|
|
// include data queries
|
|
if ($include_dq != '' && $include_dq != 'all') {
|
|
$sql_where = 'AND ' . db_in_clause('snmp_query_id', $included_data_queries);
|
|
} elseif ($clone_dq == 'all') {
|
|
$sql_where = 'AND 1 = 0';
|
|
} else {
|
|
$sql_where = '';
|
|
}
|
|
|
|
$data_queries = db_fetch_assoc_prepared("SELECT *
|
|
FROM host_template_snmp_query
|
|
WHERE host_template_id = ?
|
|
$sql_where",
|
|
[$device_template['id']]);
|
|
|
|
if (cacti_sizeof($data_queries)) {
|
|
api_clone_message(sprintf('NOTE: Including %s Data Queries', cacti_sizeof($data_queries)));
|
|
|
|
foreach ($data_queries as $dq) {
|
|
db_execute_prepared('INSERT INTO host_template_snmp_query
|
|
(host_template_id, snmp_query_id)
|
|
VALUES (?, ?)',
|
|
[$new_template, $dq['snmp_query_id']]);
|
|
}
|
|
} else {
|
|
api_clone_message('NOTE: No Data Queries to be Included');
|
|
}
|
|
|
|
/**
|
|
* Handle the Data Query Clone Case
|
|
*
|
|
* 1. Duplicate the Data Query
|
|
* 2. If --clone-xml - Clone the XML file
|
|
* 3. If --clone-script - Clone the Script and update the XML file
|
|
* 4. If --clone-xml - Update the Data Query with the new XML path
|
|
*/
|
|
if ($clone_dq != '') {
|
|
api_clone_message('NOTE: Starting Data Query Clone Process');
|
|
|
|
foreach ($cloned_data_queries as $id) {
|
|
$old_name = $objects['data_queries'][$id]['name'];
|
|
$new_name = api_clone_get_unique_name($old_name, 'snmp_query', 'name');
|
|
$new_dq = data_query_duplicate($id, $new_name);
|
|
$new_xml = false;
|
|
$new_script = false;
|
|
|
|
api_clone_message(sprintf('NOTE: Cloning Data Query \'%s\' to \'%s\'', $objects['data_queries'][$id]['name'], $new_name), true);
|
|
|
|
db_execute_prepared('INSERT INTO host_template_snmp_query
|
|
(host_template_id, snmp_query_id)
|
|
VALUES (?, ?)',
|
|
[$new_template, $new_dq]);
|
|
|
|
if ($clone_xml) {
|
|
$old_xmlfile = $objects['data_queries'][$id]['xml_path'];
|
|
$old_xmlbase = str_replace(CACTI_PATH_BASE, '', $old_xmlfile);
|
|
$new_xmlfile = api_clone_get_unique_filename($old_xmlfile);
|
|
|
|
if ($new_xmlfile === false) {
|
|
api_clone_message(sprintf('ERROR: Unable to allocate a unique XML filename for \'%s\'.', $old_xmlbase), true);
|
|
|
|
return false;
|
|
}
|
|
|
|
$new_xmlbase = str_replace(CACTI_PATH_BASE, '', $new_xmlfile);
|
|
|
|
if (!isset($duped_xmlfiles[$old_xmlfile])) {
|
|
api_clone_message(sprintf('NOTE: Copying XML Base \'%s\' to \'%s\'', $old_xmlbase, $new_xmlbase));
|
|
|
|
$new_xml = copy($old_xmlfile, $new_xmlfile);
|
|
|
|
if (!$new_xml) {
|
|
api_clone_message(sprintf('ERROR: Unable to copy XML Base \'%s\' to \'%s\'.', $old_xmlbase, $new_xmlbase), true);
|
|
|
|
return false;
|
|
}
|
|
|
|
$duped_xmlfiles[$old_xmlfile] = $new_xmlfile;
|
|
|
|
if ($clone_script) {
|
|
if (isset($objects['data_queries'][$id]['script_path'])) {
|
|
$old_scriptfile = $objects['data_queries'][$id]['script_path'];
|
|
$old_scriptbase = str_replace(CACTI_PATH_BASE, '', $old_scriptfile);
|
|
$new_scriptfile = api_clone_get_unique_filename($old_scriptfile);
|
|
|
|
if ($new_scriptfile !== false) {
|
|
$new_scriptbase = str_replace(CACTI_PATH_BASE, '', $new_scriptfile);
|
|
|
|
if (!isset($duped_scripts[$old_scriptfile])) {
|
|
api_clone_message(sprintf('NOTE: Copying Script Base \'%s\' to \'%s\'', $old_scriptbase, $new_scriptbase));
|
|
|
|
$new_script = copy($old_scriptfile, $new_scriptfile);
|
|
|
|
if ($new_script) {
|
|
$duped_scripts[$old_scriptfile] = $new_scriptfile;
|
|
} else {
|
|
api_clone_message(sprintf('ERROR: Unable to copy Script Base \'%s\' to \'%s\'.', $old_scriptbase, $new_scriptbase), true);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
// skipping as we've already cloned
|
|
$new_script = true;
|
|
$new_scriptfile = $duped_scripts[$old_scriptfile];
|
|
}
|
|
} else {
|
|
api_clone_message(sprintf('ERROR: Unable to allocate a unique script filename for \'%s\'.', $old_scriptbase), true);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update the XML with new values
|
|
if ($new_script) {
|
|
api_clone_message(sprintf('NOTE: Updating \'%s\' with new values', $new_xmlfile));
|
|
|
|
$data = file_get_contents($new_xmlfile);
|
|
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
|
|
$data = str_replace($old_scriptfile, $new_scriptfile, $data);
|
|
|
|
if (file_put_contents($new_xmlfile, $data) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
// skipping as we've already cloned
|
|
$new_xmlfile = $duped_xmlfiles[$old_xmlfile];
|
|
}
|
|
|
|
db_execute_prepared('UPDATE snmp_query
|
|
SET xml_path = ?
|
|
WHERE id = ?',
|
|
[$new_xmlfile, $new_dq]);
|
|
}
|
|
|
|
// Clone Data Query Graph Templates now
|
|
$dqgt = $objects['data_query_graph_templates'];
|
|
|
|
foreach ($dqgt as $gt_id => $gt_data) {
|
|
if ($gt_data['snmp_query_id'] == $id) {
|
|
$old_name = $objects['data_query_graph_templates'][$gt_id]['name'];
|
|
$new_name = api_clone_get_unique_name($old_name, 'graph_templates', 'name');
|
|
$dqgt_name = $objects['data_query_graph_templates'][$gt_id]['sqname'];
|
|
|
|
if (!isset($duped_graph_templates[$gt_id])) {
|
|
api_clone_message(sprintf('NOTE: Cloning Data Query Graph Template \'%s\' to \'%s\'', $old_name, $new_name), true);
|
|
|
|
$new_gt = api_duplicate_graph(0, $gt_id, $new_name, false);
|
|
|
|
$duped_graph_templates[$gt_id] = $new_gt;
|
|
} else {
|
|
// skipping as we've already cloned
|
|
$new_gt = $duped_graph_templates[$gt_id];
|
|
}
|
|
|
|
/**
|
|
* update the snmp_query_graph entries
|
|
* Get the snmp_query_graph_id from the duplicated
|
|
* data query first to use to update the rest
|
|
*/
|
|
$snmp_query_graph_id = db_fetch_cell_prepared('SELECT id
|
|
FROM snmp_query_graph
|
|
WHERE snmp_query_id = ?
|
|
AND graph_template_id = ?',
|
|
[$new_dq, $gt_id]);
|
|
|
|
if ($snmp_query_graph_id > 0) {
|
|
db_execute_prepared('UPDATE snmp_query_graph
|
|
SET graph_template_id = ?, name = ?
|
|
WHERE id = ?',
|
|
[$new_gt, $dqgt_name, $snmp_query_graph_id]);
|
|
|
|
/**
|
|
* Since we clone the Data Query, we will clone the
|
|
* Data Template too. So, get the new SNMP Query Graphs
|
|
* Data Template ID
|
|
*/
|
|
$data_template_id = db_fetch_cell_prepared('SELECT data_template_id
|
|
FROM snmp_query_graph_rrd
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$snmp_query_graph_id]);
|
|
|
|
$old_snmp_query_graph_id = db_fetch_cell_prepared('SELECT id
|
|
FROM snmp_query_graph
|
|
WHERE snmp_query_id = ?
|
|
AND graph_template_id = ?',
|
|
[$id, $gt_id]);
|
|
|
|
$old_snmp_query_graph_rrds = db_fetch_assoc_prepared('SELECT *
|
|
FROM snmp_query_graph_rrd
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$old_snmp_query_graph_id]);
|
|
|
|
$old_snmp_query_graph_rrd_sv = db_fetch_assoc_prepared('SELECT *
|
|
FROM snmp_query_graph_rrd_sv
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$old_snmp_query_graph_id]);
|
|
|
|
$old_snmp_query_graph_sv = db_fetch_assoc_prepared('SELECT *
|
|
FROM snmp_query_graph_sv
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$old_snmp_query_graph_id]);
|
|
|
|
if ($data_template_id > 0) {
|
|
$old_name = db_fetch_cell_prepared('SELECT name
|
|
FROM data_template
|
|
WHERE id = ?',
|
|
[$data_template_id]);
|
|
|
|
if (!isset($duped_data_templates[$data_template_id])) {
|
|
api_clone_message(sprintf('NOTE: Cloning Data Template \'%s\' to \'%s\'', $old_name, $new_name), true);
|
|
|
|
$new_name = api_clone_get_unique_name($old_name, 'data_template', 'name');
|
|
$new_dt = api_data_source_duplicate(0, $data_template_id, $new_name);
|
|
|
|
if (cacti_sizeof($old_snmp_query_graph_rrds)) {
|
|
foreach ($old_snmp_query_graph_rrds as $rrd) {
|
|
$data_source_name = db_fetch_cell_prepared('SELECT data_source_name
|
|
FROM data_template_rrd
|
|
WHERE id = ?',
|
|
[$rrd['data_template_rrd_id']]);
|
|
|
|
$dt_rrd_id = db_fetch_cell_prepared('SELECT id FROM data_template_rrd
|
|
WHERE data_template_id = ?
|
|
AND data_source_name = ?
|
|
AND local_data_id = 0',
|
|
[$new_dt, $data_source_name]);
|
|
|
|
db_execute_prepared('INSERT INTO snmp_query_graph_rrd
|
|
(snmp_query_graph_id, data_template_id, data_template_rrd_id, snmp_field_name)
|
|
VALUES (?, ?, ?, ?)',
|
|
[
|
|
$snmp_query_graph_id,
|
|
$new_dt,
|
|
$dt_rrd_id,
|
|
$rrd['snmp_field_name']
|
|
]
|
|
);
|
|
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET task_item_id = ?
|
|
WHERE task_item_id = ?',
|
|
[$dt_rrd_id, $rrd['data_template_rrd_id']]);
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($old_snmp_query_graph_rrd_sv)) {
|
|
foreach ($old_snmp_query_graph_rrd_sv as $sv) {
|
|
unset($save);
|
|
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_data_query(0, 'data_query_sv_data_source');
|
|
$save['snmp_query_graph_id'] = $snmp_query_graph_id;
|
|
$save['data_template_id'] = $sv['data_template_id'];
|
|
$save['sequence'] = $sv['sequence'];
|
|
$save['field_name'] = $sv['field_name'];
|
|
$save['text'] = $sv['text'];
|
|
|
|
sql_save($save, 'snmp_query_graph_rrd_sv');
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($old_snmp_query_graph_sv)) {
|
|
foreach ($old_snmp_query_graph_sv as $sv) {
|
|
unset($save);
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_data_query(0, 'data_query_sv_graph');
|
|
$save['snmp_query_graph_id'] = $snmp_query_graph_id;
|
|
$save['sequence'] = $sv['sequence'];
|
|
$save['field_name'] = $sv['field_name'];
|
|
$save['text'] = $sv['text'];
|
|
|
|
sql_save($save, 'snmp_query_graph_sv');
|
|
}
|
|
}
|
|
|
|
$duped_data_templates[$data_template_id] = $new_dt;
|
|
} else {
|
|
// skipping as we've already cloned
|
|
db_execute_prepared('UPDATE snmp_query_graph_rrd
|
|
SET data_template_id = ?
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$duped_data_templates[$data_template_id], $snmp_query_graph_id]);
|
|
}
|
|
} else {
|
|
api_clone_message(sprintf('WARNING: Data Query Graph Template \'%s\' not mapped to a Data Template', $snmp_query_graph_id));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clone_gt != '') {
|
|
api_clone_message('NOTE: Cloning Non Data Query Graph Templates');
|
|
|
|
foreach ($cloned_graph_templates as $id) {
|
|
$old_name = $objects['graph_templates'][$id]['name'];
|
|
$new_name = api_clone_get_unique_name($old_name, 'graph_templates', 'name');
|
|
|
|
if (!isset($duped_graph_templates[$id])) {
|
|
api_clone_message(sprintf('NOTE: Cloning Graph Template \'%s\' to \'%s\'', $old_name, $new_name), true);
|
|
|
|
$new_gt = api_duplicate_graph(0, $id, $new_name, false);
|
|
|
|
$duped_graph_templates[$id] = $new_gt;
|
|
} else {
|
|
// skipping as we've already cloned
|
|
$new_gt = $duped_graph_templates[$id];
|
|
}
|
|
|
|
db_execute_prepared('INSERT INTO host_template_graph
|
|
(host_template_id, graph_template_id)
|
|
VALUES (?, ?)',
|
|
[$new_template, $new_gt]);
|
|
}
|
|
}
|
|
|
|
if ($clone_dt != '') {
|
|
api_clone_message('NOTE: Cloning Non Data Query Data Templates');
|
|
|
|
foreach ($cloned_data_templates as $id) {
|
|
$id = (int) $id;
|
|
$old_name = $objects['data_templates'][$id]['name'];
|
|
$new_name = api_clone_get_unique_name($old_name, 'data_template', 'name');
|
|
|
|
if ($new_name === false) {
|
|
return false;
|
|
}
|
|
|
|
api_clone_message(sprintf('NOTE: Cloning Data Template \'%s\' to \'%s\'', $old_name, $new_name), true);
|
|
|
|
$new_dt = api_data_source_duplicate(0, $id, $new_name);
|
|
|
|
if (!$new_dt) {
|
|
return false;
|
|
}
|
|
|
|
$duped_data_templates[$id] = $new_dt;
|
|
|
|
if ($clone_script && isset($objects['data_templates'][$id]['script_path'])) {
|
|
$old_data_input_id = (int) $objects['data_templates'][$id]['data_input_id'];
|
|
|
|
if (!isset($duped_data_input_methods[$old_data_input_id])) {
|
|
$old_scriptfile = $objects['data_templates'][$id]['script_path'];
|
|
$new_scriptfile = api_clone_get_unique_filename($old_scriptfile);
|
|
|
|
if ($new_scriptfile === false || !copy($old_scriptfile, $new_scriptfile)) {
|
|
return false;
|
|
}
|
|
|
|
$old_input_name = db_fetch_cell_prepared('SELECT name FROM data_input WHERE id = ?', [$old_data_input_id]);
|
|
$new_input_name = api_clone_get_unique_name($old_input_name, 'data_input', 'name');
|
|
|
|
if ($new_input_name === false) {
|
|
return false;
|
|
}
|
|
|
|
$new_data_input_id = api_data_input_duplicate($old_data_input_id, $new_input_name);
|
|
|
|
if (!$new_data_input_id) {
|
|
return false;
|
|
}
|
|
|
|
db_execute_prepared('UPDATE data_input
|
|
SET input_string = REPLACE(input_string, ?, ?)
|
|
WHERE id = ?',
|
|
[$old_scriptfile, $new_scriptfile, $new_data_input_id]);
|
|
|
|
$duped_data_input_methods[$old_data_input_id] = $new_data_input_id;
|
|
$duped_scripts[$old_scriptfile] = $new_scriptfile;
|
|
}
|
|
|
|
$new_data_input_id = $duped_data_input_methods[$old_data_input_id];
|
|
$new_dtd_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_data
|
|
WHERE data_template_id = ?
|
|
AND local_data_id = 0',
|
|
[$new_dt]);
|
|
|
|
db_execute_prepared('UPDATE data_template_data
|
|
SET data_input_id = ?
|
|
WHERE id = ?',
|
|
[$new_data_input_id, $new_dtd_id]);
|
|
|
|
$input_fields = db_fetch_assoc_prepared('SELECT old_field.id AS old_id, new_field.id AS new_id
|
|
FROM data_input_fields AS old_field
|
|
INNER JOIN data_input_fields AS new_field
|
|
ON new_field.data_name = old_field.data_name
|
|
AND new_field.input_output = old_field.input_output
|
|
WHERE old_field.data_input_id = ?
|
|
AND new_field.data_input_id = ?',
|
|
[$old_data_input_id, $new_data_input_id]);
|
|
|
|
foreach ($input_fields as $input_field) {
|
|
db_execute_prepared('UPDATE data_input_data
|
|
SET data_input_field_id = ?
|
|
WHERE data_template_data_id = ?
|
|
AND data_input_field_id = ?',
|
|
[$input_field['new_id'], $new_dtd_id, $input_field['old_id']]);
|
|
}
|
|
}
|
|
|
|
$old_rrds = db_fetch_assoc_prepared('SELECT id, data_source_name
|
|
FROM data_template_rrd
|
|
WHERE data_template_id = ?
|
|
AND local_data_id = 0',
|
|
[$id]);
|
|
|
|
foreach ($old_rrds as $old_rrd) {
|
|
$new_rrd_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_rrd
|
|
WHERE data_template_id = ?
|
|
AND data_source_name = ?
|
|
AND local_data_id = 0',
|
|
[$new_dt, $old_rrd['data_source_name']]);
|
|
|
|
if (!is_numeric($new_rrd_id)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($duped_graph_templates as $new_graph_template_id) {
|
|
if (!is_numeric($new_graph_template_id)) {
|
|
continue;
|
|
}
|
|
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET task_item_id = ?
|
|
WHERE graph_template_id = ?
|
|
AND task_item_id = ?',
|
|
[(int) $new_rrd_id, $new_graph_template_id, $old_rrd['id']]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $new_template;
|
|
}
|
|
|
|
/**
|
|
* Downloads device templates or archives as a compressed tar file.
|
|
*
|
|
* @param string $type The type of download, either 'templates' or 'archives'.
|
|
* @param array $ids An array of template or archive IDs to be included in the download.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_device_template_download(string $type, array $ids) : void {
|
|
if (!in_array($type, ['templates', 'archives'], true)) {
|
|
raise_message('invalid_device_download_type', __('Invalid Device Template download type.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$ids = array_values(array_unique(array_filter($ids, static function (mixed $id) : bool {
|
|
return ctype_digit((string) $id) && (int) $id > 0;
|
|
})));
|
|
|
|
if (!cacti_sizeof($ids)) {
|
|
raise_message('invalid_device_download_ids', __('No valid Device Templates were selected for download.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$name = 'unknown';
|
|
|
|
if (cacti_sizeof($ids) == 1) {
|
|
if ($type == 'templates') {
|
|
$name = clean_up_name((string) db_fetch_cell_prepared('SELECT name FROM host_template WHERE id = ?', $ids));
|
|
} else {
|
|
$name = clean_up_name((string) db_fetch_cell_prepared('SELECT name FROM host_template_archive WHERE id = ?', $ids));
|
|
}
|
|
|
|
$filename = 'device_package_' . cacti_strtolower($name) . '_download.tar';
|
|
} else {
|
|
$filename = 'device_package_multiple_download.tar';
|
|
}
|
|
|
|
$temp_directory = sys_get_temp_dir() . '/cacti-device-download-' . bin2hex(random_bytes(16));
|
|
|
|
if (!mkdir($temp_directory, 0700)) {
|
|
raise_message('device_download_temp_failed', __('Unable to create a temporary Device Template download directory.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$tmpfile = $temp_directory . '/download.tar';
|
|
$gzip_file = $tmpfile . '.gz';
|
|
$files_added = 0;
|
|
|
|
try {
|
|
$archive = new PharData($tmpfile);
|
|
|
|
foreach ($ids as $id) {
|
|
$data = [];
|
|
|
|
if ($type == 'archives') {
|
|
$data = db_fetch_row_prepared('SELECT * FROM host_template_archive WHERE id = ?', [$id]);
|
|
|
|
if (!cacti_sizeof($data)) {
|
|
continue;
|
|
}
|
|
|
|
$contents = base64_decode($data['archive'], true);
|
|
} else {
|
|
$data = db_fetch_row_prepared('SELECT * FROM host_template WHERE id = ?', [$id]);
|
|
|
|
if (!cacti_sizeof($data)) {
|
|
continue;
|
|
}
|
|
|
|
$contents = api_device_template_archive_for_export((int) $id);
|
|
}
|
|
|
|
if (!is_string($contents)) {
|
|
continue;
|
|
}
|
|
|
|
$entry_name = 'device_template_' . clean_up_name($data['name']) . '_' . (int) $id . '.tgz';
|
|
$archive->addFromString($entry_name, $contents);
|
|
$files_added++;
|
|
}
|
|
|
|
if (!$files_added) {
|
|
raise_message('device_download_empty', __('None of the selected Device Templates could be exported.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
$archive->compress(Phar::GZ);
|
|
$download = file_get_contents($gzip_file);
|
|
|
|
if ($download === false) {
|
|
raise_message('device_download_read_failed', __('The Device Template archive could not be read after it was created.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
header('Content-type: application/gzip');
|
|
header('Content-Disposition: attachment; filename=' . $filename . '.gz');
|
|
|
|
print $download;
|
|
} finally {
|
|
if (file_exists($tmpfile)) {
|
|
unlink($tmpfile);
|
|
}
|
|
|
|
if (file_exists($gzip_file)) {
|
|
unlink($gzip_file);
|
|
}
|
|
|
|
rmdir($temp_directory);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Archives a device template for export.
|
|
*
|
|
* @param int $id The ID of the device template to archive for export.
|
|
*
|
|
* @return mixed - The contents of the package file if successful, or false on failure.
|
|
*/
|
|
function api_device_template_archive_for_export(int $id) : mixed {
|
|
global $export_errors, $debug, $package_file;
|
|
|
|
$export_okay = false;
|
|
|
|
$host_template = db_fetch_row_prepared('SELECT *
|
|
FROM host_template
|
|
WHERE id = ?',
|
|
[$id]);
|
|
|
|
if (cacti_sizeof($host_template)) {
|
|
$xml_data = get_item_xml('host_template', $id, true);
|
|
|
|
$info = [];
|
|
$info['name'] = $host_template['name'];
|
|
$info['author'] = $host_template['author'];
|
|
$info['homepage'] = $host_template['homepage'];
|
|
$info['email'] = $host_template['email'];
|
|
$info['description'] = $host_template['name'] . ' Package';
|
|
$info['class'] = $host_template['class'];
|
|
$info['tags'] = $host_template['tags'];
|
|
$info['installation'] = $host_template['installation'];
|
|
$info['version'] = $host_template['version'];
|
|
$info['copyright'] = $host_template['copyright'];
|
|
|
|
// Let's store the Template information for subsequent exports
|
|
$hash = get_export_hash('host_template', $id);
|
|
|
|
$export_okay = save_packager_metadata($hash, $info);
|
|
|
|
$debug = '';
|
|
|
|
if ($export_okay) {
|
|
$files = find_dependent_files($xml_data);
|
|
|
|
// search xml files for scripts
|
|
if (cacti_sizeof($files)) {
|
|
foreach ($files as $file) {
|
|
if (str_contains($file['file'], '.xml')) {
|
|
$files = array_merge($files, find_dependent_files((string) file_get_contents($file['file'])));
|
|
}
|
|
}
|
|
}
|
|
|
|
$success = package_template($xml_data, $info, $files, $debug);
|
|
|
|
if ($export_errors || !$success) {
|
|
raise_message('package_error_' . $id, __esc('There were errors packaging your Device Template: %s. Errors Follow. ', $info['name']) . str_replace("\n", '<br>', $debug), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($package_file != '' && file_exists($package_file)) {
|
|
$output = file_get_contents($package_file);
|
|
|
|
unlink($package_file);
|
|
|
|
return $output;
|
|
} else {
|
|
raise_message("package_error_$id", __esc('Unable to find Package file for Device Template: %s.', $info['name']), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
raise_message("export_failed_$id", __esc('The Export Failed for %s!. Check the Cacti Log for details', $info['name']), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
raise_message("export_failed_$id", __esc('Export Could not find the Device Template with the ID %s!. Check the Cacti Log for details', $id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Archives a device template by exporting its data and saving it to the database.
|
|
*
|
|
* @param int $id The ID of the device template to archive.
|
|
* @param string $archive_note A note to include with the archive.
|
|
*
|
|
* @return bool Returns true if the device template was archived successfully, false otherwise.
|
|
*/
|
|
function api_device_template_archive(int $id, string $archive_note) : bool {
|
|
global $export_errors, $debug, $package_file;
|
|
|
|
$export_okay = false;
|
|
|
|
$host_template = db_fetch_row_prepared('SELECT *
|
|
FROM host_template
|
|
WHERE id = ?',
|
|
[$id]);
|
|
|
|
if (cacti_sizeof($host_template)) {
|
|
$xml_data = get_item_xml('host_template', $id, true);
|
|
|
|
$info = [];
|
|
$info['name'] = $host_template['name'];
|
|
$info['author'] = $host_template['author'];
|
|
$info['homepage'] = $host_template['homepage'];
|
|
$info['email'] = $host_template['email'];
|
|
$info['description'] = $host_template['name'] . ' Package';
|
|
$info['class'] = $host_template['class'];
|
|
$info['tags'] = $host_template['tags'];
|
|
$info['installation'] = $host_template['installation'];
|
|
$info['version'] = $host_template['version'];
|
|
$info['copyright'] = $host_template['copyright'];
|
|
|
|
// Let's store the Template information for subsequent exports
|
|
$hash = get_export_hash('host_template', $id);
|
|
|
|
$export_okay = save_packager_metadata($hash, $info);
|
|
|
|
$debug = '';
|
|
|
|
if ($export_okay) {
|
|
$files = find_dependent_files($xml_data);
|
|
|
|
// search xml files for scripts
|
|
if (cacti_sizeof($files)) {
|
|
foreach ($files as $file) {
|
|
if (str_contains($file['file'], '.xml')) {
|
|
$files = array_merge($files, find_dependent_files((string) file_get_contents($file['file'])));
|
|
}
|
|
}
|
|
}
|
|
|
|
$success = package_template($xml_data, $info, $files, $debug);
|
|
|
|
if ($export_errors || !$success) {
|
|
raise_message('package_error_' . $id, __esc('There were errors packaging your Device Template: %s. Errors Follow. ', $info['name']) . str_replace("\n", '<br>', $debug), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($package_file != '' && file_exists($package_file)) {
|
|
$archive = base64_encode((string) file_get_contents($package_file));
|
|
$md5sum = md5($archive);
|
|
|
|
db_execute_prepared('INSERT INTO host_template_archive
|
|
(host_template_id, hash, name, version, class, tags, author, email, homepage, copyright, installation, archive_note, archive_md5sum, archive_date, archive)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$id,
|
|
$hash,
|
|
$info['name'],
|
|
$info['version'],
|
|
$info['class'],
|
|
$info['tags'],
|
|
$info['author'],
|
|
$info['email'],
|
|
$info['homepage'],
|
|
$info['copyright'],
|
|
$info['installation'],
|
|
$archive_note,
|
|
$md5sum,
|
|
date('Y-m-d H:i:s'),
|
|
$archive
|
|
]
|
|
);
|
|
|
|
raise_message("package_success_$id", __esc('The Device Template %s was Archived Successfully.', $info['name']), MESSAGE_LEVEL_INFO);
|
|
unlink($package_file);
|
|
|
|
return true;
|
|
} else {
|
|
raise_message("package_error_$id", __esc('Unable to find Package file for Device Template: %s.', $info['name']), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
raise_message("export_failed_$id", __esc('The Export Failed for %s!. Check the Cacti Log for details', $info['name']), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
raise_message("export_failed_$id", __esc('Export Could not find the Device Template with the ID %s!. Check the Cacti Log for details', $id), MESSAGE_LEVEL_ERROR);
|
|
|
|
return false;
|
|
}
|
|
}
|