diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ffda458..aba0f56 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,153 +4,111 @@ When generating code for this repository: -1. **Version Compatibility**: Respect versions and compatibility declared by this plugin and CI. -2. **Context Files**: If `.github/copilot/*` files are added later, prioritize them first. -3. **Codebase Patterns**: When no explicit guidance exists, follow established patterns in this repository. -4. **Architectural Consistency**: Preserve the current monolithic, procedural Cacti plugin architecture. -5. **Code Quality**: Prioritize maintainability, security, performance, and testability in ways already present in this codebase. +1. **Version Compatibility First**: Honor plugin and CI version constraints before all style preferences. +2. **Repository Context Files**: Prioritize `.github/copilot/*` docs if they are added later. +3. **Agent Profiles as Secondary Context**: Reuse conventions from `.github/agents/*.md` when applicable. +4. **Pattern Matching Over Reinvention**: Mirror existing plugin patterns in the same file/flow. +5. **Architectural Consistency**: Keep the plugin procedural and Cacti-native. -## Technology Version Detection +## Verified Runtime and Compatibility -Before generating code, detect and honor exact versions from repository metadata: +Use only capabilities compatible with observed project metadata: -- **Plugin metadata**: `INFO` +- **Plugin metadata (`INFO`)** + - `name = monitor` - `version = 2.8` - - `compat = 1.2.15` (Cacti compatibility) + - `compat = 1.2.15` - `requires = thold:1.2.1` -- **CI runtime matrix**: `.github/workflows/plugin-ci-workflow.yml` +- **CI matrix (`.github/workflows/plugin-ci-workflow.yml`)** - PHP: `8.1`, `8.2`, `8.3`, `8.4` + - OS: `ubuntu-latest` - MariaDB service: `10.6` -- **Language/frameworks observed**: - - PHP plugin code (`setup.php`, `monitor.php`, `poller_monitor.php`) - - CSS themes (`monitor.css`, `themes/*/monitor.css`) - - GitHub Actions workflow YAML +- **Observed technologies** + - Procedural PHP plugin files + - CSS theme overlays in `themes/*/monitor.css` - gettext localization (`locales/po/*.po`, `locales/LC_MESSAGES/*.mo`) + - GitHub Actions integration checks -Do not introduce APIs or syntax incompatible with the supported Cacti/plugin environment and CI matrix. +Do not introduce syntax or APIs that could fail under these versions. -## Context Files +## Architecture and File Responsibilities -If present in future, prioritize `.github/copilot` files in this order: +This is a single Cacti plugin with procedural flows split by responsibility: -- `architecture.md` -- `tech-stack.md` -- `coding-standards.md` -- `folder-structure.md` -- `exemplars.md` +- `setup.php`: plugin lifecycle, hook registration, config arrays/settings, install/upgrade table management. +- `monitor.php`: web entrypoint/bootstrap, includes, session/request setup. +- `monitor_controller.php`: action flow, filter handling, page orchestration. +- `monitor_render.php`: dashboard/group rendering and view-specific output. +- `db_functions.php`: SQL filter/join helpers and status/device query utilities. +- `poller_monitor.php`: CLI poller entrypoint (`--help`, `--version`, `--debug`). +- `poller_functions.php`: poller helper logic (uptime checks, notifications, email payload building). -If these files do not exist, use repository patterns directly. +Avoid OO/framework refactors unless explicitly requested. -## Architecture and Boundaries (Observed) +## Coding Patterns to Preserve -This repository is a **single Cacti plugin** with procedural PHP entrypoints and Cacti hook integration. +### Naming and Structure -- **Plugin lifecycle and hooks**: `setup.php` - - Registration via `api_plugin_register_hook()` and `api_plugin_register_realm()`. - - Upgrade/install logic via `plugin_monitor_install()`, `plugin_monitor_upgrade()`, `monitor_check_upgrade()`. -- **Web UI controller/rendering**: `monitor.php` - - Request routing by `action` switch. - - Rendering and UI state via Cacti helper functions. -- **CLI/poller processing**: `poller_monitor.php` - - Argument parsing (`--help`, `--version`, `--force`, `--debug`). - - Notification and uptime/reboot processing. -- **Presentation assets**: `monitor.css`, `themes/*/monitor.css`, `sounds/`, `images/`. -- **Localization assets**: gettext `.po/.mo` files under `locales/`. +- Use procedural functions with **lowerCamelCase** naming, matching current core files. +- Keep plugin hook callback names exactly synchronized between definitions and `api_plugin_register_hook()` registration strings. +- Keep top-level entrypoints lightweight; place reusable logic in helper files. -Do not refactor this plugin into OO/framework patterns unless the existing code in this repository does so first. +### Cacti Integration -## Codebase Scanning Instructions +- Prefer Cacti APIs already used in this plugin: + - Config/state: `read_config_option()`, `set_config_option()`, `read_user_setting()`, `set_user_setting()` + - Request helpers: `get_request_var()`, `get_filter_request_var()`, `get_nfilter_request_var()`, `set_request_var()`, `validateRequestVars()` + - DB helpers: `db_fetch_assoc()`, `db_fetch_row_prepared()`, `db_fetch_cell_prepared()`, `db_execute_prepared()` + - Plugin hooks/realms: `api_plugin_register_hook()`, `api_plugin_register_realm()` +- Use gettext calls with the `monitor` domain for user-facing strings: `__('Text', 'monitor')`. -For any new change: +### Data and Schema Safety -1. Find similar logic in the same entrypoint type (`setup.php` for hooks/config, `monitor.php` for UI routing/rendering, `poller_monitor.php` for CLI/poller flows). -2. Match these patterns exactly: - - Function naming: `monitor_*`, `plugin_monitor_*` - - Procedural flow with top-level includes and switch routing - - Cacti helper/database APIs (`db_fetch_*`, `db_execute*`, `read_config_option`, `set_config_option`) - - Localization calls: `__('Text', 'monitor')` -3. Reuse existing request handling and sanitization helpers before adding any new input handling. -4. Prefer existing table names and schema migration style in `monitor_check_upgrade()` and related setup functions. -5. Avoid introducing new architectural abstractions not currently used. +- Keep schema evolution in existing setup/upgrade flows (table creation/alter logic in setup lifecycle functions). +- Reuse existing table names and avoid introducing parallel schema variants. +- Prefer prepared DB calls when dynamic values are present. -## Code Quality Standards (Evidence-Based) +## Quality Expectations ### Maintainability -- Keep procedural style and naming consistent with existing files. -- Keep related behavior grouped by responsibility (install/upgrade/hooks in `setup.php`; UI rendering in `monitor.php`; poller logic in `poller_monitor.php`). -- Prefer small helper functions as seen throughout the codebase. +- Keep changes localized to the appropriate responsibility file. +- Favor small helper extractions for complex branches (pattern used in `poller_functions.php`). +- Do not rename public/plugin callback functions unless all call sites and hook strings are updated. ### Security -- Follow current input-handling patterns: - - `get_request_var()`, `get_nfilter_request_var()`, `get_filter_request_var()`, `set_request_var()` - - `validate_request_vars()` where appropriate -- Escape output using existing helpers such as `html_escape()` for HTML contexts. -- Use prepared database calls where parameters are dynamic (`db_fetch_*_prepared`, `db_execute_prepared`) following existing usage. +- Continue current request-validation patterns before consuming request values. +- Escape HTML output with existing helpers (e.g., `html_escape()`). +- Avoid direct string interpolation for dynamic SQL parameters when prepared variants exist. ### Performance -- Match existing data-access style: targeted SQL queries and batched operations. -- Preserve existing poller timing/stat collection behavior in `poller_monitor.php`. -- Avoid adding expensive repeated queries inside loops when existing code already provides reusable query patterns. +- Avoid repeated expensive queries inside loops. +- Follow current query-shaping patterns (precompute lists/maps, then iterate). +- Preserve current poller stat logging behavior and timing model. -### Testability +### Documentation -- Keep logic in discrete functions so behavior can be linted, statically analyzed, and integration-tested as in CI. -- Preserve CLI flags and deterministic output patterns used by workflow checks. +- Keep function docblocks descriptive where present, especially in `poller_functions.php`. +- Keep inline comments concise and only where intent is non-obvious. +- Do not add boilerplate comments for trivial statements. -## Documentation Requirements +## Validation and CI Alignment -Documentation level in this repository is **Standard**: +Before finalizing substantial PHP changes: -- File-level header blocks are consistently present in PHP and workflow files. -- Inline comments are concise and purpose-driven. -- Follow existing style: do not over-document trivial lines. -- Update `CHANGELOG.md` style only when project maintainers require release note updates. +1. Run PHP syntax checks (`php -l`) on modified plugin files. +2. Ensure compatibility with Cacti-driven lint/style checks used in CI (`lint`, `phpcsfixer` scripts run from Cacti workspace). +3. Preserve integration behavior expected by CI: + - Plugin install/enable via Cacti CLI + - Poller execution path and monitor stats logging -## Testing Approach (Observed) +Do not add a new local unit-test framework unless requested. -This repository relies on **integration + static checks** via GitHub Actions: +## Scope Rules for Future Changes -- PHP syntax lint (`php -l` over plugin files) -- Composer-based lint/style checks from Cacti workspace (`lint`, `phpcsfixer` scripts) -- Runtime integration checks by installing Cacti + plugin and running poller -- No repository-local unit test suite is currently present - -When generating code: - -- Ensure code is syntactically valid PHP. -- Keep style and lint compatibility with current Cacti-driven CI steps. -- Do not invent a new test framework in this plugin unless requested. - -## PHP-Specific Guidelines - -- Maintain procedural PHP structure and Cacti plugin API usage. -- Keep includes, globals, and helper calls consistent with existing patterns. -- Match array formatting and control-flow style used in current files. -- Continue using gettext domain `'monitor'` for user-facing strings. -- Preserve compatibility with CI-validated PHP versions and Cacti/plugin constraints. - -## Versioning and Change Tracking - -- Follow existing changelog conventions in `CHANGELOG.md` (sectioned by release, `issue#` / `feature#` bullets). -- Treat plugin version metadata in `INFO` as the authoritative plugin version declaration. - -## General Best Practices for This Repository - -- Prioritize consistency with existing code over introducing newer external patterns. -- Reuse existing Cacti APIs and plugin hooks instead of custom abstractions. -- Keep CSS/theme changes aligned with current theme folder structure. -- Keep localization updates aligned with existing gettext files and domain usage. -- If uncertain, mirror nearby code patterns in the same file first. - -## Project-Specific Guidance - -- Scan relevant files before generating code; do not assume patterns from unrelated projects. -- Respect current architectural boundaries: - - Hook/config lifecycle in `setup.php` - - UI/rendering workflow in `monitor.php` - - Poller/notification workflow in `poller_monitor.php` -- When conflicts arise, prefer patterns that are currently active in top-level runtime paths and CI-validated flows. -- Always prioritize compatibility and consistency with this repository over external “best practice” rewrites. +- Prefer minimal, surgical updates over broad rewrites. +- Keep CSS/theme updates limited to existing theme file layout. +- Keep localization changes aligned to existing gettext workflow/files. +- If guidance conflicts, prefer behavior already validated in runtime entrypoints and CI workflow. diff --git a/db_functions.php b/db_functions.php new file mode 100644 index 0000000..8443bbc --- /dev/null +++ b/db_functions.php @@ -0,0 +1,321 @@ + 0))"; + } else { // triggered + return "(td.thold_enabled='on' + AND ((td.thold_alert != 0 AND td.thold_fail_count >= td.thold_fail_trigger) + OR (td.bl_alert > 0 AND td.bl_fail_count >= td.bl_fail_trigger)))"; + } +} + +/** + * Get host ids currently associated with triggered/breached thresholds. + * + * @return array + */ +function checkTholds(): array +{ + $thold_hosts = []; + + if (api_plugin_is_enabled('thold')) { + return array_rekey( + db_fetch_assoc('SELECT DISTINCT dl.host_id + FROM thold_data AS td + INNER JOIN data_local AS dl + ON td.local_data_id=dl.id + WHERE ' . getTholdWhere()), + 'host_id', + 'host_id' + ); + } + + return $thold_hosts; +} + + +/** + * Append an IN-clause fragment to an existing SQL where string. + * + * @param string $sql_where SQL where fragment, updated in place. + * @param string $sql_join Join token used between predicates (e.g. AND/OR). + * @param string $sql_field Field name to compare with IN list. + * @param string $sql_data Comma-delimited values for the IN list. + * @param string $sql_suffix Optional suffix appended inside predicate. + * + * @return void + */ +function renderGroupConcat(string &$sql_where, string $sql_join, string $sql_field, string $sql_data, string $sql_suffix = ''): void +{ + // Remove empty entries if something was returned + if (!empty($sql_data)) { + $sql_data = trim(str_replace(',,', ',', $sql_data), ','); + + if (!empty($sql_data)) { + $sql_where .= ($sql_where != '' ? $sql_join : '') . "($sql_field IN($sql_data) $sql_suffix)"; + } + } +} + +/** + * Build core monitor host query join/where fragments from current filters. + * + * @param string $sql_where SQL where fragment, updated in place. + * @param string $sql_join SQL join fragment, updated in place. + * + * @return void + */ +function renderWhereJoin(string &$sql_where, string &$sql_join): void +{ + if (get_request_var('crit') > 0) { + $awhere = 'h.monitor_criticality >= ' . get_request_var('crit'); + } else { + $awhere = ''; + } + + if (get_request_var('grouping') == 'site') { + if (get_request_var('site') > 0) { + $awhere .= ($awhere == '' ? '' : ' AND ') . 'h.site_id = ' . get_request_var('site'); + } elseif (get_request_var('site') == -2) { + $awhere .= ($awhere == '' ? '' : ' AND ') . ' h.site_id = 0'; + } + } + + if (get_request_var('rfilter') != '') { + $awhere .= ($awhere == '' ? '' : ' AND ') . " h.description RLIKE '" . get_request_var('rfilter') . "'"; + } + + if (get_request_var('grouping') == 'tree') { + if (get_request_var('tree') > 0) { + $hlist = db_fetch_cell_prepared( + 'SELECT GROUP_CONCAT(DISTINCT host_id) + FROM graph_tree_items AS gti + INNER JOIN host AS h + ON h.id = gti.host_id + WHERE host_id > 0 + AND graph_tree_id = ? + AND h.deleted = ""', + [get_request_var('tree')] + ); + + renderGroupConcat($awhere, ' AND ', 'h.id', $hlist); + } elseif (get_request_var('tree') == -2) { + $hlist = db_fetch_cell('SELECT GROUP_CONCAT(DISTINCT h.id) + FROM host AS h + LEFT JOIN (SELECT DISTINCT host_id FROM graph_tree_items WHERE host_id > 0) AS gti + ON h.id = gti.host_id + WHERE gti.host_id IS NULL + AND h.deleted = ""'); + + renderGroupConcat($awhere, ' AND ', 'h.id', $hlist); + } + } + + if (!empty($awhere)) { + $awhere = ' AND ' . $awhere; + } + + if (get_request_var('status') == '0') { + $sql_join = ''; + $sql_where = 'WHERE h.disabled = "" + AND h.monitor = "on" + AND h.status < 3 + AND h.deleted = "" + AND (h.availability_method > 0 + OR h.snmp_version > 0 + OR (h.cur_time >= h.monitor_warn AND monitor_warn > 0) + OR (h.cur_time >= h.monitor_alert AND h.monitor_alert > 0) + )' . $awhere; + } elseif (get_request_var('status') == '1' || get_request_var('status') == 2) { + $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + + $sql_where = 'WHERE h.disabled = "" + AND h.monitor = "on" + AND h.deleted = "" + AND (h.status < 3 + OR ' . getTholdWhere() . ' + OR ((h.availability_method > 0 OR h.snmp_version > 0) + AND ((h.cur_time > h.monitor_warn AND h.monitor_warn > 0) + OR (h.cur_time > h.monitor_alert AND h.monitor_alert > 0)) + ))' . $awhere; + } elseif (get_request_var('status') == -1) { + $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + + $sql_where = 'WHERE h.disabled = "" + AND h.monitor = "on" + AND h.deleted = "" + AND (h.availability_method > 0 OR h.snmp_version > 0 + OR ((td.thold_enabled="on" AND td.thold_alert > 0) + OR td.id IS NULL) + )' . $awhere; + } elseif (get_request_var('status') == -2) { + $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + + $sql_where = 'WHERE h.disabled = "" + AND h.deleted = ""' . $awhere; + } elseif (get_request_var('status') == -3) { + $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + + $sql_where = 'WHERE h.disabled = "" + AND h.monitor = "" + AND h.deleted = "")' . $awhere; + } else { + $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + + $sql_where = 'WHERE (h.disabled = "" + AND h.deleted = "" + AND td.id IS NULL)' . $awhere; + } +} + +/** + * Return host ids that are down/triggered and visible to current user. + * + * @param bool $prescan Whether to use prescan severity threshold. + * + * @return array + */ +function getHostsDownOrTriggeredByPermission(bool $prescan): array +{ + global $render_style; + $PreScanValue = 2; + + if ($prescan) { + $PreScanValue = 3; + } + + $result = []; + + if (get_request_var('crit') > 0) { + $sql_add_where = 'monitor_criticality >= ' . get_request_var('crit'); + } else { + $sql_add_where = ''; + } + + if (get_request_var('grouping') == 'tree') { + if (get_request_var('tree') > 0) { + $devices = db_fetch_cell_prepared( + 'SELECT GROUP_CONCAT(DISTINCT host_id) AS hosts + FROM graph_tree_items AS gti + INNER JOIN host AS h + WHERE host_id > 0 + AND h.deleted = "" + AND graph_tree_id = ?', + [get_request_var('tree')] + ); + + renderGroupConcat($sql_add_where, ' OR ', 'h.id', $devices, 'AND h.status < 2'); + } + } + + if (get_request_var('status') > 0) { + $triggered = db_fetch_cell('SELECT GROUP_CONCAT(DISTINCT host_id) AS hosts + FROM host AS h + INNER JOIN thold_data AS td + ON td.host_id = h.id + WHERE ' . getTholdWhere() . ' + AND h.deleted = ""'); + + renderGroupConcat($sql_add_where, ' OR ', 'h.id', $triggered, 'AND h.status > 1'); + + $_SESSION['monitor_triggered'] = array_rekey( + db_fetch_assoc('SELECT td.host_id, COUNT(DISTINCT td.id) AS triggered + FROM thold_data AS td + INNER JOIN host AS h + ON td.host_id = h.id + WHERE ' . getTholdWhere() . ' + AND h.deleted = "" + GROUP BY td.host_id'), + 'host_id', + 'triggered' + ); + } + + $sql_where = "h.monitor = 'on' + AND h.disabled = '' + AND h.deleted = '' + AND ((h.status < " . $PreScanValue . ' AND (h.availability_method > 0 OR h.snmp_version > 0)) ' . + ($sql_add_where != '' ? ' OR (' . $sql_add_where . '))' : ')'); + + // do a quick loop through to pull the hosts that are down + $hosts = get_allowed_devices($sql_where); + + if (cacti_sizeof($hosts)) { + foreach ($hosts as $host) { + $result[] = $host['id']; + sort($result); + } + } + + return $result; +} + +/** + * Return non-tree monitor hosts after applying current filters. + * + * @return array + */ +function getHostNonTreeArray(): array +{ + $leafs = []; + + $sql_where = ''; + $sql_join = ''; + + renderWhereJoin($sql_where, $sql_join); + + $hierarchy = db_fetch_assoc("SELECT DISTINCT + h.*, gti.title, gti.host_id, gti.host_grouping_type, gti.graph_tree_id + FROM host AS h + LEFT JOIN graph_tree_items AS gti + ON h.id=gti.host_id + $sql_join + $sql_where + AND gti.graph_tree_id IS NULL + ORDER BY h.description"); + + if (cacti_sizeof($hierarchy) > 0) { + $leafs = []; + $branchleafs = 0; + + foreach ($hierarchy as $leaf) { + $leafs[$branchleafs] = $leaf; + $branchleafs++; + } + } + + return $leafs; +} diff --git a/images/index.php b/images/index.php index d0f1fcd..e085c75 100644 --- a/images/index.php +++ b/images/index.php @@ -1,4 +1,7 @@ { + callback(); + if (++x === repetitions) { + globalThis.clearInterval(intervalID); + setZoomErrorBackgrounds(); + } + }, delay); +} + +if (mbColor === '') { + $('.monitor_container').css('background-color', ''); + $('.cactiConsoleContentArea').css('background-color', ''); +} else { + let monoe = false; + + setZoomErrorBackgrounds(); + $('.monitor_errorzoom_title').css('font-size', monitorFont); + + setIntervalX(() => { + if (monoe) { + if (mbColor !== '') { + $('.monitor_container').css('background-color', ''); + $('.cactiConsoleContentArea').css('background-color', ''); + } + + monoe = false; + } else { + setZoomErrorBackgrounds(); + monoe = true; + } + }, 600, 8); +} + +function timeStep() { + const value = Number($('#timer').html()) - 1; + + if (value <= 0) { + applyFilter('refresh'); + } else { + $('#timer').html(value); + // What is a second, well if you are an + // imperial storm tropper, it's just a little more than a second. + myTimer = setTimeout(timeStep, 1284); + } +} + +function muteUnmuteAudio(mute) { + if (mute) { + $('audio').each(function() { + this.pause(); + this.currentTime = 0; + }); + } else if ($('#downhosts').val() === 'true') { + $('audio').each(function() { + this.play(); + }); + } +} + +function closeTip() { + $(document).tooltip('close'); +} + +function applyFilter(action = '') { + clearTimeout(myTimer); + $('.mon_icon').unbind(); + + let strURL; + + if (action === 'dashboard') { + strURL = `monitor.php?action=dbchange&header=false&dashboard=${$('#dashboard').val()}`; + } else { + strURL = 'monitor.php?header=false'; + + if (action >= '') { + strURL += `&action=${action}`; + } + + strURL += `&refresh=${$('#refresh').val()}`; + strURL += `&grouping=${$('#grouping').val()}`; + strURL += `&tree=${$('#tree').val()}`; + strURL += `&site=${$('#site').val()}`; + strURL += `&template=${$('#template').val()}`; + strURL += `&view=${$('#view').val()}`; + strURL += `&rows=${$('#rows').val()}`; + strURL += `&crit=${$('#crit').val()}`; + strURL += `&size=${$('#size').val()}`; + strURL += `&trim=${$('#trim').val()}`; + strURL += `&mute=${$('#mute').val()}`; + strURL += `&rfilter=${base64_encode($('#rfilter').val())}`; + strURL += `&status=${$('#status').val()}`; + } + + loadIt(strURL); +} + +function saveFilter() { + const url = 'monitor.php?action=save&header=false'; + + const post = { + dashboard: $('#dashboard').val(), + refresh: $('#refresh').val(), + grouping: $('#grouping').val(), + tree: $('#tree').val(), + site: $('#site').val(), + template: $('#template').val(), + view: $('#view').val(), + rows: $('#rows').val(), + crit: $('#crit').val(), + rfilter: base64_encode($('#rfilter').val()), + trim: $('#trim').val(), + mute: $('#mute').val(), + size: $('#size').val(), + status: $('#status').val(), + __csrf_magic: csrfMagicToken + }; + + $.post(url, post).done(() => { + $('#text').show().text(monitorMessages.filterSaved || '').fadeOut(2000); + }); +} + +function saveNewDashboard(action) { + const dashboard = action === 'new' ? '-1' : $('#dashboard').val(); + const url = 'monitor.php?header=false'; + + const post = { + action: 'saveDb', + dashboard, + name: $('#name').val(), + refresh: $('#refresh').val(), + grouping: $('#grouping').val(), + tree: $('#tree').val(), + site: $('#site').val(), + template: $('#template').val(), + view: $('#view').val(), + rows: $('#rows').val(), + crit: $('#crit').val(), + rfilter: base64_encode($('#rfilter').val()), + trim: $('#trim').val(), + size: $('#size').val(), + mute: $('#mute').val(), + status: $('#status').val(), + __csrf_magic: csrfMagicToken + }; + + $('#newdialog').dialog('close'); + postIt(url, post); +} + +function removeDashboard() { + const url = `monitor.php?action=remove&header=false&dashboard=${$('#dashboard').val()}`; + loadIt(url); +} + +function loadIt(url) { + if (typeof loadUrl === 'undefined') { + loadPageNoHeader(url); + } else { + loadUrl({ url }); + } +} + +function postIt(url, post, returnLocation) { + if (typeof postUrl === 'undefined') { + loadPageUsingPost(url, post); + } else { + postUrl( + { + url, + tabId: returnLocation, + type: 'loadPageUsingPost' + }, + post + ); + } +} + +function saveDashboard(action) { + const btnDialog = { + Cancel: { + text: monitorMessages.cancel || 'Cancel', + id: 'btnCancel', + click() { + $(this).dialog('close'); + } + }, + Save: { + text: monitorMessages.save || 'Save', + id: 'btnSave', + click() { + saveNewDashboard(action); + } + } + }; + + if ($('#newdialog').length === 0) { + $('body').append(monitorNewForm); + } + + $('#newdialog').dialog({ + title: monitorNewTitle, + minHeight: 80, + minWidth: 500, + buttons: btnDialog, + position: { at: 'center top+240px', of: globalThis }, + open() { + $('#name').val($('#dashboard option:selected').text()); + $('#btnSave').addClass('ui-state-active'); + $('#name').focus(); + $('#new_dashboard') + .off('submit') + .on('submit', (event) => { + event.preventDefault(); + saveNewDashboard('new'); + }); + } + }); +} + +$(() => { + if (dozoomRefresh) { + applyFilter('refresh'); + } + + const selectmenu = $('#grouping').selectmenu('instance') !== undefined; + + if ($('#view').val() === 'list') { + $('#grouping').prop('disabled', true); + if (selectmenu) { + $('#grouping').selectmenu('disable'); + } + } else { + $('#grouping').prop('disabled', false); + if (selectmenu) { + $('#grouping').selectmenu('enable'); + } + } + + clearTimeout(myTimer); + + $('#go').click((event) => { + event.preventDefault(); + applyFilter('go'); + }); + + $('#clear').click(() => { + loadIt('monitor.php?clear=1&header=false'); + }); + + $('#sound').click(() => { + if ($('#mute').val() === 'false') { + $('#mute').val('true'); + muteUnmuteAudio(true); + applyFilter('ajax_mute_all'); + } else { + $('#mute').val('false'); + muteUnmuteAudio(false); + applyFilter('ajax_unmute_all'); + } + }); + + $('#refresh, #view, #rows, #trim, #crit, #grouping, #size, #status, #tree, #site, #template').change(() => { + applyFilter('change'); + }); + + $('#dashboard').change(() => { + applyFilter('dashboard'); + }); + + $('#save').click(() => { + saveFilter(); + }); + + $('#new').click(() => { + saveDashboard('new'); + }); + + $('#rename').click(() => { + saveDashboard('rename'); + }); + + $('#delete').click(() => { + removeDashboard(); + }); + + $('.monitorFilterForm').submit((event) => { + event.preventDefault(); + applyFilter('change'); + }); + + $('.monitor_device_frame').find('i').tooltip({ + items: '.mon_icon', + open(event, ui) { + ajaxAnchors(); + + if (event.originalEvent === undefined) { + return false; + } + }, + close(event, ui) { + ui.tooltip.hover( + function() { + $(this).stop(true).fadeTo(400, 1); + }, + function() { + $(this).fadeOut('400'); + } + ); + }, + position: { my: 'left:15 top', at: 'right center' }, + content(callback) { + const id = $(this).attr('id'); + const size = $('#size').val(); + $.get(`monitor.php?action=ajax_status&size=${size}&id=${id}`, (data) => { + callback(data); + }); + } + }); + + myTimer = setTimeout(timeStep, 1000); + + $(globalThis).resize(() => { + $(document).tooltip('option', 'position', { my: '1eft:15 top', at: 'right center' }); + }); + + if ($('#mute').val() === 'true') { + muteUnmuteAudio(true); + } else { + muteUnmuteAudio(false); + } + + $('#main').css('margin-right', '15px'); +}); diff --git a/locales/LC_MESSAGES/index.php b/locales/LC_MESSAGES/index.php index 828ecbe..58b5ee0 100644 --- a/locales/LC_MESSAGES/index.php +++ b/locales/LC_MESSAGES/index.php @@ -1,4 +1,7 @@ __('Disabled', 'monitor'), - 1 => __('Low', 'monitor'), - 2 => __('Medium', 'monitor'), - 3 => __('High', 'monitor'), - 4 => __('Mission Critical', 'monitor') + 0 => __('Disabled', 'monitor'), + 1 => __('Low', 'monitor'), + 2 => __('Medium', 'monitor'), + 3 => __('High', 'monitor'), + 4 => __('Mission Critical', 'monitor') ]; $iclasses = [ - 0 => 'deviceUnknown', - 1 => 'deviceDown', - 2 => 'deviceRecovering', - 3 => 'deviceUp', - 4 => 'deviceThreshold', - 5 => 'deviceDownMuted', - 6 => 'deviceUnmonitored', - 7 => 'deviceWarning', - 8 => 'deviceAlert', - 9 => 'deviceThresholdMuted', + 0 => 'deviceUnknown', + 1 => 'deviceDown', + 2 => 'deviceRecovering', + 3 => 'deviceUp', + 4 => 'deviceThreshold', + 5 => 'deviceDownMuted', + 6 => 'deviceUnmonitored', + 7 => 'deviceWarning', + 8 => 'deviceAlert', + 9 => 'deviceThresholdMuted', ]; $icolorsdisplay = [ - 0 => __('Unknown', 'monitor'), - 1 => __('Down', 'monitor'), - 2 => __('Recovering', 'monitor'), - 3 => __('Up', 'monitor'), - 4 => __('Triggered', 'monitor'), - 9 => __('Triggered (Muted/Acked)', 'monitor'), - 5 => __('Down (Muted/Acked)', 'monitor'), - 6 => __('No Availability Check', 'monitor'), - 7 => __('Warning Ping', 'monitor'), - 8 => __('Alert Ping', 'monitor'), + 0 => __('Unknown', 'monitor'), + 1 => __('Down', 'monitor'), + 2 => __('Recovering', 'monitor'), + 3 => __('Up', 'monitor'), + 4 => __('Triggered', 'monitor'), + 9 => __('Triggered (Muted/Acked)', 'monitor'), + 5 => __('Down (Muted/Acked)', 'monitor'), + 6 => __('No Availability Check', 'monitor'), + 7 => __('Warning Ping', 'monitor'), + 8 => __('Alert Ping', 'monitor'), ]; $classes = [ - 'monitor_exsmall' => __('Extra Small', 'monitor'), - 'monitor_small' => __('Small', 'monitor'), - 'monitor_medium' => __('Medium', 'monitor'), - 'monitor_large' => __('Large', 'monitor'), - 'monitor_exlarge' => __('Extra Large', 'monitor'), - 'monitor_errorzoom' => __('Zoom', 'monitor') + 'monitor_exsmall' => __('Extra Small', 'monitor'), + 'monitor_small' => __('Small', 'monitor'), + 'monitor_medium' => __('Medium', 'monitor'), + 'monitor_large' => __('Large', 'monitor'), + 'monitor_exlarge' => __('Extra Large', 'monitor'), + 'monitor_errorzoom' => __('Zoom', 'monitor') ]; $monitor_status = [ - -2 => __('All Devices', 'monitor'), - -1 => __('All Monitored Devices', 'monitor'), - 0 => __('Not Up', 'monitor'), - 1 => __('Not Up or Triggered', 'monitor'), - 2 => __('Not Up, Triggered or Breached', 'monitor'), - -4 => __('Devices without Thresholds', 'monitor'), - -3 => __('Devices not Monitored', 'monitor'), + -2 => __('All Devices', 'monitor'), + -1 => __('All Monitored Devices', 'monitor'), + 0 => __('Not Up', 'monitor'), + 1 => __('Not Up or Triggered', 'monitor'), + 2 => __('Not Up, Triggered or Breached', 'monitor'), + -4 => __('Devices without Thresholds', 'monitor'), + -3 => __('Devices not Monitored', 'monitor'), ]; $monitor_view_type = [ - 'default' => __('Default', 'monitor'), - 'list' => __('List', 'monitor'), - 'names' => __('Names only', 'monitor'), - 'tiles' => __('Tiles', 'monitor'), - 'tilesadt' => __('Tiles & Time', 'monitor') + 'default' => __('Default', 'monitor'), + 'list' => __('List', 'monitor'), + 'names' => __('Names only', 'monitor'), + 'tiles' => __('Tiles', 'monitor'), + 'tilesadt' => __('Tiles & Time', 'monitor') ]; $monitor_grouping = [ - 'default' => __('Default', 'monitor'), - 'tree' => __('Tree', 'monitor'), - 'site' => __('Site', 'monitor'), - 'template' => __('Device Template', 'monitor') + 'default' => __('Default', 'monitor'), + 'tree' => __('Tree', 'monitor'), + 'site' => __('Site', 'monitor'), + 'template' => __('Device Template', 'monitor') ]; $monitor_trim = [ - 0 => __('Default', 'monitor'), - -1 => __('Full', 'monitor'), - 10 => __('10 Chars', 'monitor'), - 20 => __('20 Chars', 'monitor'), - 30 => __('30 Chars', 'monitor'), - 40 => __('40 Chars', 'monitor'), - 50 => __('50 Chars', 'monitor'), - 75 => __('75 Chars', 'monitor'), - 100 => __('100 Chars', 'monitor'), + 0 => __('Default', 'monitor'), + -1 => __('Full', 'monitor'), + 10 => __('10 Chars', 'monitor'), + 20 => __('20 Chars', 'monitor'), + 30 => __('30 Chars', 'monitor'), + 40 => __('40 Chars', 'monitor'), + 50 => __('50 Chars', 'monitor'), + 75 => __('75 Chars', 'monitor'), + 100 => __('100 Chars', 'monitor'), ]; global $thold_hosts, $maxchars; @@ -122,2619 +132,58 @@ $_SESSION['names'] = 0; if (!isset($_SESSION['monitor_muted_hosts'])) { - $_SESSION['monitor_muted_hosts'] = []; -} - -validate_request_vars(); - -if (!db_column_exists('host', 'monitor_icon')) { - monitor_setup_table(); -} - -$thold_hosts = check_tholds(); - -switch(get_nfilter_request_var('action')) { - case 'ajax_status': - ajax_status(); - - break; - case 'ajax_mute_all': - mute_all_hosts(); - draw_page(); - - break; - case 'ajax_unmute_all': - unmute_all_hosts(); - draw_page(); - - break; - case 'dbchange': - load_dashboard_settings(); - draw_page(); - - break; - case 'remove': - remove_dashboard(); - draw_page(); - - break; - case 'saveDb': - save_settings(); - draw_page(); - - break; - case 'save': - save_settings(); - - break; - default: - draw_page(); -} - -exit; - -function load_dashboard_settings() { - $dashboard = get_filter_request_var('dashboard'); - - if ($dashboard > 0) { - $db_settings = db_fetch_cell_prepared('SELECT url - FROM plugin_monitor_dashboards - WHERE id = ?', - [$dashboard]); - - if ($db_settings != '') { - $db_settings = str_replace('monitor.php?', '', $db_settings); - $settings = explode('&', $db_settings); - - if (cacti_sizeof($settings)) { - foreach ($settings as $setting) { - [$name, $value] = explode('=', $setting); - - set_request_var($name, $value); - } - } - } - } -} - -function draw_page() { - global $config, $iclasses, $icolorsdisplay, $mon_zoom_state, $dozoomrefresh, $dozoombgndcolor, $font_sizes; - global $new_form, $new_title; - - $errored_list = get_hosts_down_or_triggered_by_permission(true); - - if (cacti_sizeof($errored_list) && read_user_setting('monitor_error_zoom') == 'on') { - if ($_SESSION['monitor_zoom_state'] == 0) { - $mon_zoom_state = $_SESSION['monitor_zoom_state'] = 1; - $_SESSION['mon_zoom_hist_status'] = get_nfilter_request_var('status'); - $_SESSION['mon_zoom_hist_size'] = get_nfilter_request_var('size'); - $dozoomrefresh = true; - $dozoombgndcolor = true; - } - } elseif (isset($_SESSION['monitor_zoom_state']) && $_SESSION['monitor_zoom_state'] == 1) { - $_SESSION['monitor_zoom_state'] = 0; - $dozoomrefresh = true; - $dozoombgndcolor = false; - } - - $name = db_fetch_cell_prepared('SELECT name - FROM plugin_monitor_dashboards - WHERE id = ?', - [get_request_var('dashboard')]); - - if ($name == '') { - $name = __('New Dashboard', 'monitor'); - } - - $new_form = "
" . __('Enter the Dashboard Name and then press \'Save\' to continue, else press \'Cancel\'', 'monitor') . '
' . __('Dashboard', 'monitor') . "
"; - - $new_title = __('Create New Dashboard', 'monitor'); - - find_down_hosts(); - - general_header(); - - draw_filter_and_status(); - - print ''; - - // Default with permissions = default_by_permission - // Tree = group_by_tree - $function = 'render_' . get_request_var('grouping'); - - if (function_exists($function) && get_request_var('view') != 'list') { - if (get_request_var('grouping') == 'default' || get_request_var('grouping') == 'site') { - html_start_box(__('Monitored Devices', 'monitor'), '100%', true, '3', 'center', ''); - } else { - html_start_box('', '100%', true, '3', 'center', ''); - } - print $function(); - } else { - print render_default(); - } - - print ''; - - html_end_box(); - - if (read_user_setting('monitor_legend', read_config_option('monitor_legend'))) { - print "
"; - - foreach ($iclasses as $index => $class) { - print "
" . $icolorsdisplay[$index] . '
'; - } - - print '
'; - } - - // If the host is down, we need to insert the embedded wav file - $monitor_sound = get_monitor_sound(); - - if (is_monitor_audible()) { - if (read_user_setting('monitor_sound_loop', read_config_option('monitor_sound_loop'))) { - print ""; - } else { - print ""; - } - } - - print '
' . get_filter_text() . '
'; - - bottom_footer(); -} - -function is_monitor_audible() { - return get_monitor_sound() != ''; -} - -function get_monitor_sound() { - $sound = read_user_setting('monitor_sound', read_config_option('monitor_sound')); - clearstatcache(); - $file = __DIR__ . '/sounds/' . $sound; - $exists = file_exists($file); - - return $exists ? $sound : ''; -} - -function find_down_hosts() { - $dhosts = get_hosts_down_or_triggered_by_permission(false); - - if (cacti_sizeof($dhosts)) { - set_request_var('downhosts', 'true'); - - if (isset($_SESSION['monitor_muted_hosts'])) { - unmute_up_non_triggered_hosts($dhosts); - - $unmuted_hosts = array_diff($dhosts, $_SESSION['monitor_muted_hosts']); - - if (cacti_sizeof($unmuted_hosts)) { - unmute_user(); - } - } else { - set_request_var('mute', 'false'); - } - } else { - unmute_all_hosts(); - set_request_var('downhosts', 'false'); - } -} - -function unmute_up_non_triggered_hosts($dhosts) { - if (isset($_SESSION['monitor_muted_hosts'])) { - foreach ($_SESSION['monitor_muted_hosts'] as $index => $host_id) { - if (array_search($host_id, $dhosts, true) === false) { - unset($_SESSION['monitor_muted_hosts'][$index]); - } - } - } -} - -function mute_all_hosts() { - $_SESSION['monitor_muted_hosts'] = get_hosts_down_or_triggered_by_permission(false); - mute_user(); -} - -function unmute_all_hosts() { - $_SESSION['monitor_muted_hosts'] = []; - unmute_user(); -} - -function mute_user() { - set_request_var('mute', 'true'); - set_user_setting('monitor_mute', 'true'); -} - -function unmute_user() { - set_request_var('mute', 'false'); - set_user_setting('monitor_mute','false'); -} - -function get_thold_where() { - if (get_request_var('status') == '2') { // breached - return "(td.thold_enabled = 'on' - AND (td.thold_alert != 0 OR td.bl_alert > 0))"; - } else { // triggered - return "(td.thold_enabled='on' - AND ((td.thold_alert != 0 AND td.thold_fail_count >= td.thold_fail_trigger) - OR (td.bl_alert > 0 AND td.bl_fail_count >= td.bl_fail_trigger)))"; - } -} - -function check_tholds() { - $thold_hosts = []; - - if (api_plugin_is_enabled('thold')) { - return array_rekey( - db_fetch_assoc('SELECT DISTINCT dl.host_id - FROM thold_data AS td - INNER JOIN data_local AS dl - ON td.local_data_id=dl.id - WHERE ' . get_thold_where()), - 'host_id', 'host_id' - ); - } - - return $thold_hosts; -} - -function get_filter_text() { - $filter = '
'; - - switch(get_request_var('status')) { - case '-4': - $filter .= __('Devices without Thresholds', 'monitor'); - - break; - case '-3': - $filter .= __('Not Monitored Devices', 'monitor'); - - break; - case '-2': - $filter .= __('All Devices', 'monitor'); - - break; - case '-1': - $filter .= __('All Monitored Devices', 'monitor'); - - break; - case '0': - $filter .= __('Monitored Devices either Down or Recovering', 'monitor'); - - break; - case '1': - $filter .= __('Monitored Devices either Down, Recovering, or with Triggered Thresholds', 'monitor'); - - break; - case '2': - $filter .= __('Monitored Devices either Down, Recovering, or with Breached or Triggered Thresholds', 'monitor'); - - break; - default: - $filter .= __('Unknown monitoring status (%s)', get_request_var('status'), 'monitor'); - } - - switch(get_request_var('crit')) { - case '0': - $filter .= __(', and All Criticalities', 'monitor'); - - break; - case '1': - $filter .= __(', and of Low Criticality or Higher', 'monitor'); - - break; - case '2': - $filter .= __(', and of Medium Criticality or Higher', 'monitor'); - - break; - case '3': - $filter .= __(', and of High Criticality or Higher', 'monitor'); - - break; - case '4': - $filter .= __(', and of Mission Critical Status', 'monitor'); - - break; - } - - $filter .= __('
Remember to first select eligible Devices to be Monitored from the Devices page!
', 'monitor'); - - return $filter; -} - -function draw_filter_dropdown($id, $title, $settings = [], $value = null) { - if ($value == null) { - $value = get_nfilter_request_var($id); - } - - if (cacti_sizeof($settings)) { - print '' . html_escape($title) . ''; - print '' . PHP_EOL; - } else { - print "" . PHP_EOL; - } -} - -function draw_filter_and_status() { - global $criticalities, $page_refresh_interval, $classes, $monitor_grouping; - global $monitor_view_type, $monitor_status, $monitor_trim; - global $dozoombgndcolor, $dozoomrefresh, $zoom_hist_status, $zoom_hist_size, $mon_zoom_state; - global $new_form, $new_title, $item_rows; - - $header = __('Monitor Filter [ Last Refresh: %s ]', date('g:i:s a', time()), 'monitor') . (get_request_var('refresh') < 99999 ? __(' [ Refresh Again in %d Seconds ]', get_request_var('refresh'), 'monitor') : '') . (get_request_var('view') == 'list' ? __('[ Showing only first 30 Devices ]', 'monitor') : '') . ''; - - html_start_box($header, '100%', false, '3', 'center', ''); - - print '' . PHP_EOL; - print '
' . PHP_EOL; - - // First line of filter - print '' . PHP_EOL; - print '' . PHP_EOL; - - $dashboards[0] = __('Unsaved', 'monitor'); - $dashboards += array_rekey( - db_fetch_assoc_prepared('SELECT id, name - FROM plugin_monitor_dashboards - WHERE user_id = 0 OR user_id = ? - ORDER BY name', - [$_SESSION['sess_user_id']]), - 'id', 'name' - ); - - $name = db_fetch_cell_prepared('SELECT name - FROM plugin_monitor_dashboards - WHERE id = ?', - [get_request_var('dashboard')]); - - $mon_zoom_status = null; - $mon_zoom_size = null; - - if (isset($_SESSION['monitor_zoom_state'])) { - if ($_SESSION['monitor_zoom_state'] == 1) { - $mon_zoom_status = 2; - $mon_zoom_size = 'monitor_errorzoom'; - $dozoombgndcolor = true; - } else { - if (isset($_SESSION['mon_zoom_hist_status'])) { - $mon_zoom_status = $_SESSION['mon_zoom_hist_status']; - } else { - $mon_zoom_status = null; - } - - if (isset($_SESSION['mon_zoom_hist_size'])) { - $currentddsize = get_nfilter_request_var('size'); - - if ($currentddsize != $_SESSION['mon_zoom_hist_size'] && $currentddsize != 'monitor_errorzoom') { - $_SESSION['mon_zoom_hist_size'] = $currentddsize; - } - - $mon_zoom_size = $_SESSION['mon_zoom_hist_size']; - } else { - $mon_zoom_size = null; - } - } - } - - draw_filter_dropdown('dashboard', __('Layout', 'monitor'), $dashboards); - draw_filter_dropdown('status', __('Status', 'monitor'), $monitor_status, $mon_zoom_status); - draw_filter_dropdown('view', __('View', 'monitor'), $monitor_view_type); - draw_filter_dropdown('grouping', __('Grouping', 'monitor'), $monitor_grouping); - draw_filter_dropdown('rows', __('Devices', 'monitor'), $item_rows); - - // Buttons - print ''; - print ''; - print '
' . PHP_EOL; - - print '' . PHP_EOL; - - print '' . PHP_EOL; - - print '' . PHP_EOL; - - print '' . PHP_EOL; - - if (get_request_var('dashboard') > 0) { - print '' . PHP_EOL; - } - - if (get_request_var('dashboard') > 0) { - print '' . PHP_EOL; - } - - print '' . PHP_EOL; - print '' . PHP_EOL; - print '
'; - - // Second line of filter - print '' . PHP_EOL; - print '' . PHP_EOL; - print ''; - print ''; - - draw_filter_dropdown('crit', __('Criticality', 'monitor'), $criticalities); - - if (get_request_var('view') != 'list') { - draw_filter_dropdown('size', __('Size', 'monitor'), $classes, $mon_zoom_size); - } - - if (get_request_var('view') == 'default' || get_request_var('view') == 'names') { - draw_filter_dropdown('trim', __('Trim', 'monitor'), $monitor_trim); - } - - if (get_nfilter_request_var('grouping') == 'tree') { - $trees = []; - - if (get_request_var('grouping') == 'tree') { - $trees_allowed = array_rekey(get_allowed_trees(), 'id', 'name'); - - if (cacti_sizeof($trees_allowed)) { - $trees_prefix = [-1 => __('All Trees', 'monitor')]; - $trees_suffix = [-2 => __('Non-Tree Devices', 'monitor')]; - - $trees = $trees_prefix + $trees_allowed + $trees_suffix; - } - } - - draw_filter_dropdown('tree', __('Tree', 'monitor'), $trees); - } - - if (get_nfilter_request_var('grouping') == 'site') { - $sites = []; - - if (get_request_var('grouping') == 'site') { - $sites = array_rekey( - db_fetch_assoc('SELECT id, name - FROM sites - ORDER BY name'), - 'id', 'name' - ); - - if (cacti_sizeof($sites)) { - $sites_prefix = [-1 => __('All Sites', 'monitor')]; - $sites_suffix = [-2 => __('Non-Site Devices', 'monitor')]; - - $sites = $sites_prefix + $sites + $sites_suffix; - } - } - - draw_filter_dropdown('site', __('Sites', 'monitor'), $sites); - } - - if (get_request_var('grouping') == 'template') { - $templates = []; - $templates_allowed = array_rekey( - db_fetch_assoc('SELECT ht.id, ht.name, COUNT(gl.id) AS graphs - FROM host_template AS ht - INNER JOIN host AS h - ON h.host_template_id = ht.id - INNER JOIN graph_local AS gl - ON h.id = gl.host_id - GROUP BY ht.id - HAVING graphs > 0'), - 'id', 'name' - ); - - if (cacti_sizeof($templates_allowed)) { - $templates_prefix = [-1 => __('All Templates', 'monitor')]; - $templates_suffix = [-2 => __('Non-Templated Devices', 'monitor')]; - - $templates = $templates_prefix + $templates_allowed + $templates_suffix; - } - - draw_filter_dropdown('template', __('Template', 'monitor'), $templates); - } - - draw_filter_dropdown('refresh', __('Refresh', 'monitor'), $page_refresh_interval); - - if (get_request_var('grouping') != 'tree') { - print '' . PHP_EOL; - } - - if (get_request_var('grouping') != 'site') { - print '' . PHP_EOL; - } - - if (get_request_var('grouping') != 'template') { - print '' . PHP_EOL; - } - - if (get_request_var('view') == 'list') { - print '' . PHP_EOL; - } - - if (get_request_var('view') != 'default') { - print '' . PHP_EOL; - } - - print ''; - print '
' . __('Search', 'monitor') . '
'; - print '
' . PHP_EOL; - - html_end_box(); - - if ($dozoombgndcolor) { - $mbcolora = db_fetch_row_prepared('SELECT * - FROM colors - WHERE id = ?', - [read_user_setting('monitor_error_background')]); - - $monitor_error_fontsize = read_user_setting('monitor_error_fontsize') . 'px'; - - if (cacti_sizeof($mbcolora)) { - $mbcolor = '#' . $mbcolora['hex']; - } else { - $mbcolor = 'snow'; - } - } else { - $mbcolor = ''; - $monitor_error_fontsize = '10px'; - } - - ?> - - $value) { - switch($var) { - case 'dashboard': - set_user_setting('monitor_rfilter', get_request_var('dashboard')); - - break; - case 'rfilter': - set_user_setting('monitor_rfilter', get_request_var('rfilter')); - - break; - case 'refresh': - set_user_setting('monitor_refresh', get_request_var('refresh')); - - break; - case 'grouping': - set_user_setting('monitor_grouping', get_request_var('grouping')); - - break; - case 'view': - set_user_setting('monitor_view', get_request_var('view')); - - break; - case 'rows': - set_user_setting('monitor_rows', get_request_var('rows')); - - break; - case 'crit': - set_user_setting('monitor_crit', get_request_var('crit')); - - break; - case 'mute': - set_user_setting('monitor_mute', get_request_var('mute')); - - break; - case 'size': - set_user_setting('monitor_size', get_request_var('size')); - - break; - case 'trim': - set_user_setting('monitor_trim', get_request_var('trim')); - - break; - case 'status': - set_user_setting('monitor_status', get_request_var('status')); - - break; - case 'tree': - set_user_setting('monitor_tree', get_request_var('tree')); - - break; - case 'mute': - set_user_setting('monitor_mute', get_request_var('mute')); - - break; - case 'site': - set_user_setting('monitor_site', get_request_var('site')); - - break; - } - } - } - } else { - $url = 'monitor.php' . - '?refresh=' . get_request_var('refresh') . - '&grouping=' . get_request_var('grouping') . - '&view=' . get_request_var('view') . - '&rows=' . get_request_var('rows') . - '&crit=' . get_request_var('crit') . - '&size=' . get_request_var('size') . - '&trim=' . get_request_var('trim') . - '&status=' . get_request_var('status') . - '&tree=' . get_request_var('tree') . - '&site=' . get_request_var('site'); - - if (!isset_request_var('user')) { - $user = $_SESSION['sess_user_id']; - } else { - $user = get_request_var('user'); - } - - $id = get_request_var('dashboard'); - $name = get_nfilter_request_var('name'); - - $save = []; - $save['id'] = $id; - $save['name'] = $name; - $save['user_id'] = $user; - $save['url'] = $url; - - $id = sql_save($save, 'plugin_monitor_dashboards'); - - if (!empty($id)) { - raise_message('monitorsaved', __('Dashboard \'%s\' has been Saved!', $name, 'monitor'), MESSAGE_LEVEL_INFO); - set_request_var('dashboard', $id); - } else { - raise_message('monitornotsaved', __('Dashboard \'%s\' could not be Saved!', $name, 'monitor'), MESSAGE_LEVEL_INFO); - set_request_var('dashboard', '0'); - } - } - - validate_request_vars(true); + $_SESSION['monitor_muted_hosts'] = []; } -function validate_request_vars($force = false) { - // ================= input validation and session storage ================= - $filters = [ - 'refresh' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => read_user_setting('monitor_refresh', read_config_option('monitor_refresh'), $force) - ], - 'dashboard' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_dashboard', '0', $force) - ], - 'rfilter' => [ - 'filter' => FILTER_VALIDATE_IS_REGEX, - 'pageset' => true, - 'default' => read_user_setting('monitor_rfilter', '', $force) - ], - 'name' => [ - 'filter' => FILTER_CALLBACK, - 'options' => ['options' => 'sanitize_search_string'], - 'default' => '' - ], - 'mute' => [ - 'filter' => FILTER_CALLBACK, - 'options' => ['options' => 'sanitize_search_string'], - 'default' => read_user_setting('monitor_mute', 'false', $force) - ], - 'grouping' => [ - 'filter' => FILTER_CALLBACK, - 'options' => ['options' => 'sanitize_search_string'], - 'pageset' => true, - 'default' => read_user_setting('monitor_grouping', read_config_option('monitor_grouping'), $force) - ], - 'view' => [ - 'filter' => FILTER_CALLBACK, - 'options' => ['options' => 'sanitize_search_string'], - 'pageset' => true, - 'default' => read_user_setting('monitor_view', read_config_option('monitor_view'), $force) - ], - 'rows' => [ - 'filter' => FILTER_VALIDATE_INT, - 'options' => ['options' => 'sanitize_search_string'], - 'default' => read_user_setting('monitor_rows', read_config_option('num_rows_table'), $force) - ], - 'size' => [ - 'filter' => FILTER_CALLBACK, - 'options' => ['options' => 'sanitize_search_string'], - 'default' => read_user_setting('monitor_size', 'monitor_medium', $force) - ], - 'trim' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => read_user_setting('monitor_trim', read_config_option('monitor_trim'), $force) - ], - 'crit' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_crit', '-1', $force) - ], - 'status' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_status', '-1', $force) - ], - 'tree' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_tree', '-1', $force) - ], - 'site' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_site', '-1', $force) - ], - 'template' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => read_user_setting('monitor_template', '-1', $force) - ], - 'id' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => '-1' - ], - 'page' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => '1' - ], - 'sort_column' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'status', - 'options' => ['options' => 'sanitize_search_string'] - ], - 'sort_direction' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'ASC', - 'options' => ['options' => 'sanitize_search_string'] - ] - ]; - validate_store_request_vars($filters, 'sess_monitor'); - // ================= input validation ================= -} +include_once __DIR__ . '/db_functions.php'; +include_once __DIR__ . '/monitor_render.php'; +include_once __DIR__ . '/monitor_controller.php'; -function render_group_concat(&$sql_where, $sql_join, $sql_field, $sql_data, $sql_suffix = '') { - // Remove empty entries if something was returned - if (!empty($sql_data)) { - $sql_data = trim(str_replace(',,',',',$sql_data), ','); +validateRequestVars(); - if (!empty($sql_data)) { - $sql_where .= ($sql_where != '' ? $sql_join : '') . "($sql_field IN($sql_data) $sql_suffix)"; - } - } +if (!db_column_exists('host', 'monitor_icon')) { + monitorSetupTable(); } -function render_where_join(&$sql_where, &$sql_join) { - if (get_request_var('crit') > 0) { - $awhere = 'h.monitor_criticality >= ' . get_request_var('crit'); - } else { - $awhere = ''; - } - - if (get_request_var('grouping') == 'site') { - if (get_request_var('site') > 0) { - $awhere .= ($awhere == '' ? '' : ' AND ') . 'h.site_id = ' . get_request_var('site'); - } elseif (get_request_var('site') == -2) { - $awhere .= ($awhere == '' ? '' : ' AND ') . ' h.site_id = 0'; - } - } +$thold_hosts = checkTholds(); - if (get_request_var('rfilter') != '') { - $awhere .= ($awhere == '' ? '' : ' AND ') . " h.description RLIKE '" . get_request_var('rfilter') . "'"; - } +switch (get_nfilter_request_var('action')) { + case 'ajax_status': + ajaxStatus(); - if (get_request_var('grouping') == 'tree') { - if (get_request_var('tree') > 0) { - $hlist = db_fetch_cell_prepared('SELECT GROUP_CONCAT(DISTINCT host_id) - FROM graph_tree_items AS gti - INNER JOIN host AS h - ON h.id = gti.host_id - WHERE host_id > 0 - AND graph_tree_id = ? - AND h.deleted = ""', - [get_request_var('tree')]); + break; + case 'ajax_mute_all': + muteAllHosts(); + drawPage(); - render_group_concat($awhere, ' AND ', 'h.id', $hlist); - } elseif (get_request_var('tree') == -2) { - $hlist = db_fetch_cell('SELECT GROUP_CONCAT(DISTINCT h.id) - FROM host AS h - LEFT JOIN (SELECT DISTINCT host_id FROM graph_tree_items WHERE host_id > 0) AS gti - ON h.id = gti.host_id - WHERE gti.host_id IS NULL - AND h.deleted = ""'); + break; + case 'ajax_unmute_all': + unmuteAllHosts(); + drawPage(); - render_group_concat($awhere, ' AND ', 'h.id', $hlist); - } - } + break; + case 'dbchange': + loadDashboardSettings(); + drawPage(); - if (!empty($awhere)) { - $awhere = ' AND ' . $awhere; - } + break; + case 'remove': + removeDashboard(); + drawPage(); - if (get_request_var('status') == '0') { - $sql_join = ''; - $sql_where = 'WHERE h.disabled = "" - AND h.monitor = "on" - AND h.status < 3 - AND h.deleted = "" - AND (h.availability_method > 0 - OR h.snmp_version > 0 - OR (h.cur_time >= h.monitor_warn AND monitor_warn > 0) - OR (h.cur_time >= h.monitor_alert AND h.monitor_alert > 0) - )' . $awhere; - } elseif (get_request_var('status') == '1' || get_request_var('status') == 2) { - $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + break; + case 'saveDb': + saveSettings(); + drawPage(); - $sql_where = 'WHERE h.disabled = "" - AND h.monitor = "on" - AND h.deleted = "" - AND (h.status < 3 - OR ' . get_thold_where() . ' - OR ((h.availability_method > 0 OR h.snmp_version > 0) - AND ((h.cur_time > h.monitor_warn AND h.monitor_warn > 0) - OR (h.cur_time > h.monitor_alert AND h.monitor_alert > 0)) - ))' . $awhere; - } elseif (get_request_var('status') == -1) { - $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; + break; + case 'save': + saveSettings(); - $sql_where = 'WHERE h.disabled = "" - AND h.monitor = "on" - AND h.deleted = "" - AND (h.availability_method > 0 OR h.snmp_version > 0 - OR ((td.thold_enabled="on" AND td.thold_alert > 0) - OR td.id IS NULL) - )' . $awhere; - } elseif (get_request_var('status') == -2) { - $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; - - $sql_where = 'WHERE h.disabled = "" - AND h.deleted = ""' . $awhere; - } elseif (get_request_var('status') == -3) { - $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; - - $sql_where = 'WHERE h.disabled = "" - AND h.monitor = "" - AND h.deleted = "")' . $awhere; - } else { - $sql_join = 'LEFT JOIN thold_data AS td ON td.host_id=h.id'; - - $sql_where = 'WHERE (h.disabled = "" - AND h.deleted = "" - AND td.id IS NULL)' . $awhere; - } + break; + default: + drawPage(); } -// Render functions -function render_default() { - global $maxchars; - - $result = ''; - - $sql_where = ''; - $sql_join = ''; - $sql_limit = ''; - $sql_order = 'ORDER BY description'; - - $rows = get_request_var('rows'); - - if ($rows == '-1') { - $rows = read_user_setting('monitor_rows'); - } - - if (!is_numeric($rows)) { - $rows = read_config_option('num_rows_table'); - } - - if (get_request_var('view') == 'list') { - $sql_order = get_order_string(); - } - - render_where_join($sql_where, $sql_join); - - $poller_interval = read_config_option('poller_interval'); - - $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ',' . $rows; - - $hosts_sql = ("SELECT DISTINCT h.*, IFNULL(s.name,' " . __('Non-Site Device', 'monitor') . " ') AS site_name, - CAST(IF(availability_method = 0, '0', - IF(status_event_count > 0 AND status IN (1, 2), status_event_count*$poller_interval, - IF(UNIX_TIMESTAMP(status_rec_date) < 943916400 AND status IN (0, 3), total_polls*$poller_interval, - IF(UNIX_TIMESTAMP(status_rec_date) > 943916400, UNIX_TIMESTAMP() - UNIX_TIMESTAMP(status_rec_date), - IF(snmp_sysUptimeInstance>0 AND snmp_version > 0, snmp_sysUptimeInstance/100, UNIX_TIMESTAMP() - ))))) AS unsigned) AS instate - FROM host AS h - LEFT JOIN sites AS s - ON h.site_id = s.id - $sql_join - $sql_where - $sql_order - $sql_limit"); - - $hosts = db_fetch_assoc($hosts_sql); - - $total_rows = db_fetch_cell("SELECT COUNT(DISTINCT h.id) - FROM host AS h - LEFT JOIN sites AS s - ON h.site_id = s.id - $sql_join - $sql_where"); - - if (cacti_sizeof($hosts)) { - // Determine the correct width of the cell - $maxlen = 10; - - if (get_request_var('view') == 'default') { - $maxlen = db_fetch_cell("SELECT MAX(LENGTH(description)) - FROM host AS h - $sql_join - $sql_where"); - } - - $maxlen = get_monitor_trim_length($maxlen); - - $function = 'render_header_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_header_ function - $result .= $function($hosts, $total_rows, $rows); - } - - $count = 0; - - foreach ($hosts as $host) { - if (is_device_allowed($host['id'])) { - $result .= render_host($host, true, $maxlen); - } - - $count++; - } - - $function = 'render_footer_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_footer_ function - $result .= $function($hosts, $total_rows, $rows); - } - } - - return $result; -} - -function render_site() { - global $maxchars; - - $result = ''; - - $sql_where = ''; - $sql_join = ''; - $sql_limit = ''; - - $rows = get_request_var('rows'); - - if ($rows == '-1') { - $rows = read_user_setting('monitor_rows'); - } - - if (!is_numeric($rows)) { - $rows = read_config_option('num_rows_table'); - } - - render_where_join($sql_where, $sql_join); - - $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ',' . $rows; - - $hosts_sql = ("SELECT DISTINCT h.*, IFNULL(s.name,' " . __('Non-Site Devices', 'monitor') . " ') AS site_name - FROM host AS h - LEFT JOIN sites AS s - ON s.id = h.site_id - $sql_join - $sql_where - ORDER BY site_name, description - $sql_limit"); - - $hosts = db_fetch_assoc($hosts_sql); - - $ctemp = -1; - $ptemp = -1; - - if (cacti_sizeof($hosts)) { - $suppressGroups = false; - $function = 'render_suppressgroups_' . get_request_var('view'); - - if (function_exists($function)) { - $suppressGroups = $function($hosts); - } - - $function = 'render_header_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_header_ function - $result .= $function($hosts); - $suppressGroups = true; - } - - foreach ($hosts as $index => $host) { - if (is_device_allowed($host['id'])) { - $host_ids[] = $host['id']; - } else { - unset($hosts[$index]); - } - } - - // Determine the correct width of the cell - $maxlen = 10; - - if (get_request_var('view') == 'default') { - $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) - FROM host AS h - WHERE id IN (' . implode(',', $host_ids) . ')'); - } - $maxlen = get_monitor_trim_length($maxlen); - - $class = get_request_var('size'); - $csuffix = get_request_var('view'); - - if ($csuffix == 'default') { - $csuffix = ''; - } - - foreach ($hosts as $host) { - $ctemp = $host['site_id']; - - if (!$suppressGroups) { - if ($ctemp != $ptemp && $ptemp > 0) { - $result .= ''; - } - - if ($ctemp != $ptemp) { - $result .= "
- -
-
"; - } - } - - $result .= render_host($host, true, $maxlen); - - if ($ctemp != $ptemp) { - $ptemp = $ctemp; - } - } - - if ($ptemp == $ctemp && !$suppressGroups) { - $result .= '
'; - } - - $function = 'render_footer_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_footer_ function - $result .= $function($hosts); - } - } - - return $result; -} - -function render_template() { - global $maxchars; - - $result = ''; - - $sql_where = ''; - $sql_join = ''; - $sql_limit = ''; - - $rows = get_request_var('rows'); - - if ($rows == '-1') { - $rows = read_user_setting('monitor_rows'); - } - - if (!is_numeric($rows)) { - $rows = read_config_option('num_rows_table'); - } - - render_where_join($sql_where, $sql_join); - - $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ',' . $rows; - - if (get_request_var('template') > 0) { - $sql_where .= ($sql_where == '' ? '' : 'AND ') . 'ht.id = ' . get_request_var('template'); - } - - $sql_template = 'INNER JOIN host_template AS ht ON h.host_template_id=ht.id '; - - if (get_request_var('template') == -2) { - $sql_where .= ($sql_where == '' ? '' : 'AND ') . 'ht.id IS NULL'; - $sql_template = 'LEFT JOIN host_template AS ht ON h.host_template_id=ht.id '; - } - - $hosts = db_fetch_assoc("SELECT DISTINCT - h.*, ht.name AS host_template_name - FROM host AS h - $sql_template - $sql_join - $sql_where - ORDER BY ht.name, h.description - $sql_limit"); - - $ctemp = -1; - $ptemp = -1; - - if (cacti_sizeof($hosts)) { - $suppressGroups = false; - $function = 'render_suppressgroups_' . get_request_var('view'); - - if (function_exists($function)) { - $suppressGroups = $function($hosts); - } - - $function = 'render_header_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_header_ function - $result .= $function($hosts); - $suppressGroups = true; - } - - foreach ($hosts as $index => $host) { - if (is_device_allowed($host['id'])) { - $host_ids[] = $host['id']; - } else { - unset($hosts[$index]); - } - } - - // Determine the correct width of the cell - $maxlen = 10; - - if (get_request_var('view') == 'default') { - $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) - FROM host AS h - WHERE id IN (' . implode(',', $host_ids) . ')'); - } - $maxlen = get_monitor_trim_length($maxlen); - - $class = get_request_var('size'); - $csuffix = get_request_var('view'); - - if ($csuffix == 'default') { - $csuffix = ''; - } - - foreach ($hosts as $host) { - $ctemp = $host['host_template_id']; - - if (!$suppressGroups) { - if ($ctemp != $ptemp && $ptemp > 0) { - $result .= ''; - } - - if ($ctemp != $ptemp) { - $result .= "
- -
-
"; - } - } - - $result .= render_host($host, true, $maxlen); - - if ($ctemp != $ptemp) { - $ptemp = $ctemp; - } - } - - if ($ptemp == $ctemp && !$suppressGroups) { - $result .= '
'; - } - - $function = 'render_footer_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_footer_ function - $result .= $function($hosts); - } - } - - return $result; -} - -function render_tree() { - global $maxchars; - - $result = ''; - - $leafs = []; - - if (get_request_var('tree') > 0) { - $sql_where = 'gt.id=' . get_request_var('tree'); - } else { - $sql_where = ''; - } - - if (get_request_var('tree') != -2) { - $tree_list = get_allowed_trees(false, false, $sql_where, 'sequence'); - } else { - $tree_list = []; - } - - $function = 'render_header_' . get_request_var('view'); - - if (function_exists($function)) { - $hosts = []; - - // Call the custom render_header_ function - $result .= $function($hosts); - } - - if (cacti_sizeof($tree_list)) { - $ptree = ''; - - foreach ($tree_list as $tree) { - $tree_ids[$tree['id']] = $tree['id']; - } - - render_where_join($sql_where, $sql_join); - - $branchWhost_SQL = ("SELECT DISTINCT gti.graph_tree_id, gti.parent - FROM graph_tree_items AS gti - INNER JOIN graph_tree AS gt - ON gt.id = gti.graph_tree_id - INNER JOIN host AS h - ON h.id = gti.host_id - $sql_join - $sql_where - AND gti.host_id > 0 - AND gti.graph_tree_id IN (" . implode(',', $tree_ids) . ') - ORDER BY gt.sequence, gti.position'); - - // cacti_log($branchWhost_SQL); - - $branchWhost = db_fetch_assoc($branchWhost_SQL); - - // Determine the correct width of the cell - $maxlen = 10; - - if (get_request_var('view') == 'default') { - $maxlen = db_fetch_cell("SELECT MAX(LENGTH(description)) - FROM host AS h - INNER JOIN graph_tree_items AS gti - ON gti.host_id = h.id - WHERE disabled = '' - AND deleted = ''"); - } - - $maxlen = get_monitor_trim_length($maxlen); - - if (cacti_sizeof($branchWhost)) { - foreach ($branchWhost as $b) { - if ($ptree != $b['graph_tree_id']) { - $titles[$b['graph_tree_id'] . ':0'] = __('Root Branch', 'monitor'); - $ptree = $b['graph_tree_id']; - } - - if ($b['parent'] > 0) { - $titles[$b['graph_tree_id'] . ':' . $b['parent']] = db_fetch_cell_prepared('SELECT title - FROM graph_tree_items - WHERE id = ? - AND graph_tree_id = ? - ORDER BY position', - [$b['parent'], $b['graph_tree_id']]); - } - } - - $ptree = ''; - - foreach ($titles as $index => $title) { - [$graph_tree_id, $parent] = explode(':', $index); - - $oid = $parent; - - $sql_where = ''; - $sql_join = ''; - - render_where_join($sql_where, $sql_join); - - $hosts_sql = "SELECT h.*, IFNULL(s.name,' " . __('Non-Site Device', 'monitor') . " ') AS site_name - FROM host AS h - LEFT JOIN sites AS s - ON h.site_id = s.id - INNER JOIN graph_tree_items AS gti - ON h.id = gti.host_id - $sql_join - $sql_where - AND parent = ? - AND graph_tree_id = ? - GROUP BY h.id - ORDER BY gti.position"; - - // cacti_log($hosts_sql); - - $hosts = db_fetch_assoc_prepared($hosts_sql, [$oid, $graph_tree_id]); - - $tree_name = db_fetch_cell_prepared('SELECT name - FROM graph_tree - WHERE id = ?', - [$graph_tree_id]); - - if ($ptree != $tree_name) { - if ($ptree != '') { - $result .= ''; - } - - $result .= "
- -
-
-
"; - - $ptree = $tree_name; - } - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $index => $host) { - if (is_device_allowed($host['id'])) { - $host_ids[] = $host['id']; - } else { - unset($hosts[$index]); - } - } - - $class = get_request_var('size'); - - $result .= "
"; - - foreach ($hosts as $host) { - $result .= render_host($host, true, $maxlen); - } - - $result .= '
'; - } - } - } - - $result .= '
'; - } - - // begin others - lets get the monitor items that are not associated with any tree - if (get_request_var('tree') < 0) { - $hosts = get_host_non_tree_array(); - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $index => $host) { - if (is_device_allowed($host['id'])) { - $host_ids[] = $host['id']; - } else { - unset($hosts[$index]); - } - } - - // Determine the correct width of the cell - $maxlen = 10; - - if (get_request_var('view') == 'default') { - if (cacti_sizeof($host_ids)) { - $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) - FROM host AS h - WHERE id IN (' . implode(',', $host_ids) . ") - AND h.deleted = ''"); - } - } - $maxlen = get_monitor_trim_length($maxlen); - - $result .= "
- -
-
"; - - foreach ($hosts as $leaf) { - $result .= render_host($leaf, true, $maxlen); - } - - $result .= '
'; - } - } - - $function = 'render_footer_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_footer_ function - $result .= $function($hosts); - } - - return $result; -} - -function get_host_status($host, $real = false) { - global $thold_hosts, $iclasses; - - // If the host has been muted, show the muted Icon - if ($host['status'] != 1 && in_array($host['id'], $thold_hosts, true)) { - $host['status'] = 4; - } - - if (in_array($host['id'], $_SESSION['monitor_muted_hosts'], true) && $host['status'] == 1) { - $host['status'] = 5; - } elseif (in_array($host['id'], $_SESSION['monitor_muted_hosts'], true) && $host['status'] == 4) { - $host['status'] = 9; - } elseif ($host['status'] == 3) { - if ($host['cur_time'] > $host['monitor_alert'] && !empty($host['monitor_alert'])) { - $host['status'] = 8; - } elseif ($host['cur_time'] > $host['monitor_warn'] && !empty($host['monitor_warn'])) { - $host['status'] = 7; - } - } - - // If wanting the real status, or the status is already known - // return the real status, otherwise default to unknown - return ($real || array_key_exists($host['status'], $iclasses)) ? $host['status'] : 0; -} - -function get_host_status_description($status) { - global $icolorsdisplay; - - if (array_key_exists($status, $icolorsdisplay)) { - return $icolorsdisplay[$status]; - } else { - return __('Unknown', 'monitor') . " ($status)"; - } -} - -/** - * render_host - Renders a host using a sub-function - * @param mixed $host - * @param mixed $float - * @param mixed $maxlen - */ -function render_host($host, $float = true, $maxlen = 10) { - global $thold_hosts, $config, $icolorsdisplay, $iclasses, $classes, $maxchars, $mon_zoom_state; - - // throw out tree root items - if (array_key_exists('name', $host)) { - return; - } - - if ($host['id'] <= 0) { - return; - } - - $host['anchor'] = $config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']; - - if ($host['status'] == 3 && array_key_exists($host['id'], $thold_hosts)) { - $host['status'] = 4; - $host['anchor'] = $config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']; - } - - $host['real_status'] = get_host_status($host, true); - $host['status'] = get_host_status($host); - $host['iclass'] = $iclasses[$host['status']]; - - $function = 'render_host_' . get_request_var('view'); - - if (function_exists($function)) { - // Call the custom render_host_ function - $result = $function($host); - } else { - $iclass = get_status_icon($host['status'], $host['monitor_icon']); - $fclass = get_request_var('size'); - - $monitor_times = read_user_setting('monitor_uptime'); - $monitor_time_html = ''; - - if ($host['status'] <= 2 || $host['status'] == 5) { - if ($mon_zoom_state) { - $fclass = 'monitor_errorzoom'; - } - $tis = get_timeinstate($host); - - if ($monitor_times == 'on') { - $monitor_time_html = "
$tis"; - } - $result = "

" . title_trim(html_escape($host['description']), $maxlen) . "$monitor_time_html
"; - } else { - $tis = get_uptime($host); - - if ($monitor_times == 'on') { - $monitor_time_html = "
$tis
"; - } - - $result = "

" . title_trim(html_escape($host['description']), $maxlen) . "$monitor_time_html
"; - } - } - - return $result; -} - -function get_status_icon($status, $icon) { - global $fa_icons; - - if (($status == 1 || ($status == 4 && get_request_var('status') > 0)) && read_user_setting('monitor_sound') == 'First Orders Suite.mp3') { - return 'fab fa-first-order fa-spin mon_icon'; - } - - if ($icon != '' && array_key_exists($icon, $fa_icons)) { - if (isset($fa_icons[$icon]['class'])) { - return $fa_icons[$icon]['class'] . ' mon_icon'; - } else { - return "fa fa-$icon mon_icon"; - } - } else { - return 'fa fa-server' . ' mon_icon'; - } -} - -function monitor_print_host_time($status_time, $seconds = false) { - // If the host is down, make a downtime since message - $dt = ''; - - if (is_numeric($status_time)) { - $sfd = round($status_time / 100,0); - } else { - $sfd = time() - strtotime($status_time); - } - $dt_d = floor($sfd / 86400); - $dt_h = floor(($sfd - ($dt_d * 86400)) / 3600); - $dt_m = floor(($sfd - ($dt_d * 86400) - ($dt_h * 3600)) / 60); - $dt_s = $sfd - ($dt_d * 86400) - ($dt_h * 3600) - ($dt_m * 60); - - if ($dt_d > 0) { - $dt .= $dt_d . 'd:' . $dt_h . 'h:' . $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); - } elseif ($dt_h > 0) { - $dt .= $dt_h . 'h:' . $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); - } elseif ($dt_m > 0) { - $dt .= $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); - } else { - $dt .= ($seconds ? $dt_s . 's' : __('Just Up', 'monitor')); - } - - return $dt; -} - -function ajax_status() { - global $thold_hosts, $config, $icolorsdisplay, $iclasses, $criticalities; - - $tholds = 0; - - validate_request_vars(); - - if (isset_request_var('id') && get_filter_request_var('id')) { - $id = get_request_var('id'); - $size = get_request_var('size'); - - $host = db_fetch_row_prepared('SELECT * - FROM host - WHERE id = ?', - [$id]); - - if (!cacti_sizeof($host)) { - cacti_log('Attempted to retrieve status for missing Device ' . $id, false, 'MONITOR', POLLER_VERBOSITY_HIGH); - - return false; - } - - $host['anchor'] = $config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']; - - if ($host['status'] == 3 && array_key_exists($host['id'], $thold_hosts)) { - $host['status'] = 4; - $host['anchor'] = $config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']; - } - - if ($host['availability_method'] == 0) { - $host['status'] = 6; - } - - $host['real_status'] = get_host_status($host, true); - $host['status'] = get_host_status($host); - - if (cacti_sizeof($host)) { - if (api_plugin_user_realm_auth('host.php')) { - $host_link = html_escape($config['url_path'] . 'host.php?action=edit&id=' . $host['id']); - } - - // Get the number of graphs - $graphs = db_fetch_cell_prepared('SELECT COUNT(*) - FROM graph_local - WHERE host_id = ?', - [$host['id']]); - - if ($graphs > 0) { - $graph_link = html_escape($config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']); - } - - // Get the number of thresholds - if (api_plugin_is_enabled('thold')) { - $tholds = db_fetch_cell_prepared('SELECT count(*) - FROM thold_data - WHERE host_id = ?', - [$host['id']]); - - if ($tholds) { - $thold_link = html_escape($config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']); - } - } - - // Get the number of syslogs - if (api_plugin_is_enabled('syslog') && api_plugin_user_realm_auth('syslog.php')) { - include($config['base_path'] . '/plugins/syslog/config.php'); - include_once($config['base_path'] . '/plugins/syslog/functions.php'); - - $syslog_logs = syslog_db_fetch_cell_prepared('SELECT count(*) - FROM syslog_logs - WHERE host = ?', - [$host['hostname']]); - - $syslog_host = syslog_db_fetch_cell_prepared('SELECT host_id - FROM syslog_hosts - WHERE host = ?', - [$host['hostname']]); - - if ($syslog_logs && $syslog_host) { - $syslog_log_link = html_escape($config['url_path'] . 'plugins/syslog/syslog/syslog.php?reset=1&tab=alerts&host_id=' . $syslog_host); - } - - if ($syslog_host) { - $syslog_link = html_escape($config['url_path'] . 'plugins/syslog/syslog/syslog.php?reset=1&tab=syslog&host_id=' . $syslog_host); - } - } else { - $syslog_logs = 0; - $syslog_host = 0; - } - - $links = ''; - - if (isset($host_link)) { - $links .= '
'; - } - - if (isset($graph_link)) { - $links .= '
'; - } - - if (isset($thold_link)) { - $links .= '
'; - } - - if (isset($syslog_log_link)) { - $links .= '
'; - } - - if (isset($syslog_link)) { - $links .= '
'; - } - - if (strtotime($host['status_fail_date']) < 86400) { - $host['status_fail_date'] = __('Never', 'monitor'); - } - - $iclass = $iclasses[$host['status']]; - $sdisplay = get_host_status_description($host['real_status']); - $site = db_fetch_cell_prepared('SELECT name FROM sites WHERE id = ?', [$host['site_id']]); - - if ($host['location'] == '') { - $host['location'] = __('Unspecified', 'monitor'); - } - - if ($site == '') { - $site = __('None', 'monitor'); - } - - print " - - - - - - - - - - - - - - ' . (isset($host['monitor_criticality']) && $host['monitor_criticality'] > 0 ? ' - - - - ' : '') . ' - - - - " . ($host['status'] < 3 || $host['status'] == 5 ? ' - - - - ' : '') . ($host['availability_method'] > 0 ? ' - - - - ' : '') . ($host['availability_method'] > 0 ? " - - - - ' : '') . (isset($host['monitor_warn']) && ($host['monitor_warn'] > 0 || $host['monitor_alert'] > 0) ? " - - - - ' : '') . ' - - - - - - - - - - - - ' . ($host['snmp_version'] > 0 && ($host['status'] == 3 || $host['status'] == 2) ? ' - - - - - - - - - - - - - - - - ' : '') . ($host['notes'] != '' ? ' - - - - ' : '') . " - - -
" . __('Device Status Information', 'monitor') . '
' . __('Device:', 'monitor') . "" . html_escape($host['description']) . '
' . __('Site:', 'monitor') . '' . html_escape($site) . '
' . __('Location:', 'monitor') . '' . html_escape($host['location']) . '
' . __('Criticality:', 'monitor') . '' . html_escape($criticalities[$host['monitor_criticality']]) . '
' . __('Status:', 'monitor') . "$sdisplay
' . __('Admin Note:', 'monitor') . "" . html_escape($host['monitor_text']) . '
' . __('IP/Hostname:', 'monitor') . '' . html_escape($host['hostname']) . '
" . __('Curr/Avg:', 'monitor') . '' . __('%d ms', $host['cur_time'], 'monitor') . ' / ' . __('%d ms', $host['avg_time'], 'monitor') . '
" . __('Warn/Alert:', 'monitor') . '' . __('%0.2d ms', $host['monitor_warn'], 'monitor') . ' / ' . __('%0.2d ms', $host['monitor_alert'], 'monitor') . '
' . __('Last Fail:', 'monitor') . '' . html_escape($host['status_fail_date']) . '
' . __('Time In State:', 'monitor') . '' . get_timeinstate($host) . '
' . __('Availability:', 'monitor') . '' . round($host['availability'],2) . ' %
' . __('Agent Uptime:', 'monitor') . '' . ($host['status'] == 3 || $host['status'] == 5 ? monitor_print_host_time($host['snmp_sysUpTimeInstance']) : __('N/A', 'monitor')) . "
" . __('Sys Description:', 'monitor') . '' . html_escape(monitor_trim($host['snmp_sysDescr'])) . '
' . __('Location:', 'monitor') . '' . html_escape(monitor_trim($host['snmp_sysLocation'])) . '
' . __('Contact:', 'monitor') . '' . html_escape(monitor_trim($host['snmp_sysContact'])) . '
' . __('Notes:', 'monitor') . '' . html_escape($host['notes']) . '

$links
"; - } - } -} - -function monitor_trim($string) { - return trim($string, "\"'\\ \n\t\r"); -} - -function render_header_default($hosts) { - return "
"; -} - -function render_header_names($hosts) { - return ""; -} - -function render_header_tiles($hosts) { - return render_header_default($hosts); -} - -function render_header_tilesadt($hosts) { - return render_header_default($hosts); -} - -function render_header_list($hosts, $total_rows = 0, $rows = 0) { - $display_text = [ - 'hostname' => [ - 'display' => __('Hostname', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left', 'tip' => __('Hostname of device', 'monitor') - ], - 'id' => [ - 'display' => __('ID', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left' - ], - 'description' => [ - 'display' => __('Description', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left' - ], - 'site_name' => [ - 'display' => __('Site', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left' - ], - 'monitor_criticality' => [ - 'display' => __('Criticality', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left' - ], - 'status' => [ - 'display' => __('Status', 'monitor'), - 'sort' => 'DESC', - 'align' => 'center' - ], - 'instate' => [ - 'display' => __('Length in Status', 'monitor'), - 'sort' => 'ASC', - 'align' => 'center' - ], - 'avg_time' => [ - 'display' => __('Averages', 'monitor'), - 'sort' => 'DESC', - 'align' => 'left' - ], - 'monitor_warn' => [ - 'display' => __('Warning', 'monitor'), - 'sort' => 'DESC', - 'align' => 'left' - ], - 'monitor_text' => [ - 'display' => __('Admin', 'monitor'), - 'sort' => 'ASC', - 'tip' => __('Monitor Text Column represents \'Admin\'', 'monitor'), - 'align' => 'left' - ], - 'notes' => [ - 'display' => __('Notes', 'monitor'), - 'sort' => 'ASC', - 'align' => 'left' - ], - 'availability' => [ - 'display' => __('Availability', 'monitor'), - 'sort' => 'DESC', - 'align' => 'right' - ], - 'status_fail_date' => [ - 'display' => __('Last Fail', 'monitor'), - 'sort' => 'DESC', - 'align' => 'right' - ], - ]; - - ob_start(); - - $nav = html_nav_bar('monitor.php?rfilter=' . get_request_var('rfilter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 12, __('Devices'), 'page', 'main'); - - html_start_box(__('Monitored Devices', 'monitor'), '100%', false, '3', 'center', ''); - - print $nav; - - html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); - - $output = ob_get_contents(); - - ob_end_clean(); - - return $output; -} - -function render_suppressgroups_list($hosts) { - return true; -} - -function render_footer_default($hosts) { - return ''; -} - -function render_footer_names($hosts) { - $col = 7 - $_SESSION['names']; - - if ($col == 0) { - return '
'; - } else { - return ''; - } -} - -function render_footer_tiles($hosts) { - return render_footer_default($hosts); -} - -function render_footer_tilesadt($hosts) { - return render_footer_default($hosts); -} - -function render_footer_list($hosts, $total_rows, $rows) { - ob_start(); - - html_end_box(false); - - if ($total_rows > 0) { - $nav = html_nav_bar('monitor.php?rfilter=' . get_request_var('rfilter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 12, __('Devices'), 'page', 'main'); - - print $nav; - } - - $output = ob_get_contents(); - - ob_end_clean(); - - return $output; -} - -function render_host_list($host) { - global $criticalities, $iclasses; - - if ($host['status'] < 2 || $host['status'] == 5) { - $dt = get_timeinstate($host); - } elseif (strtotime($host['status_rec_date']) > 192800) { - $dt = get_timeinstate($host); - } else { - $dt = __('Never', 'monitor'); - } - - if ($host['status'] < 3 || $host['status'] == 5) { - $host_admin = $host['monitor_text']; - } else { - $host_admin = ''; - } - - if (isset($host['monitor_criticality']) && $host['monitor_criticality'] > 0) { - $host_crit = $criticalities[$host['monitor_criticality']]; - } else { - $host_crit = ''; - } - - if ($host['availability_method'] > 0) { - $host_address = $host['hostname']; - $host_avg = __('%d ms', $host['cur_time'], 'monitor') . ' / ' . __('%d ms', $host['avg_time'], 'monitor'); - } else { - $host_address = ''; - $host_avg = __('N/A', 'monitor'); - } - - if (isset($host['monitor_warn']) && ($host['monitor_warn'] > 0 || $host['monitor_alert'] > 0)) { - $host_warn = __('%0.2d ms', $host['monitor_warn'], 'monitor') . ' / ' . __('%0.2d ms', $host['monitor_alert'], 'monitor'); - } else { - $host_warn = ''; - } - - if (strtotime($host['status_fail_date']) < 86400) { - $host['status_fail_date'] = __('Never', 'monitor'); - } - - $host_datefail = $host['status_fail_date']; - - $iclass = $iclasses[$host['status']]; - $sdisplay = get_host_status_description($host['real_status']); - - $row_class = "{$iclass}Full"; - - ob_start(); - - print ""; - - $url = $host['anchor']; - - form_selectable_cell(filter_value($host['hostname'], '', $url), $host['id'], '', 'left'); - form_selectable_cell($host['id'], $host['id'], '', 'left'); - form_selectable_cell($host['description'], $host['id'], '', 'left'); - form_selectable_cell($host['site_name'], $host['id'], '', 'left'); - form_selectable_cell($host_crit, $host['id'], '', 'left'); - form_selectable_cell($sdisplay, $host['id'], '', 'center'); - form_selectable_cell($dt, $host['id'], '', 'center'); - form_selectable_cell($host_avg, $host['id'], '', 'left'); - form_selectable_cell($host_warn, $host['id'], '', 'left'); - form_selectable_cell($host_admin, $host['id'], '', 'white-space:pre-wrap;text-align:left'); - form_selectable_cell(str_replace(["\n", "\r"], [' ', ''], $host['notes']), $host['id'], '', 'white-space:pre-wrap;text-align:left'); - form_selectable_cell(round($host['availability'],2) . ' %', $host['id'], '', 'right'); - form_selectable_cell($host_datefail, $host['id'], '', 'right'); - - form_end_row(); - - $result = ob_get_contents(); - - ob_end_clean(); - - return $result; -} - -function render_host_names($host) { - $fclass = get_request_var('size'); - - $result = ''; - - $maxlen = get_monitor_trim_length(100); - $monitor_times = read_user_setting('monitor_uptime'); - $monitor_time_html = ''; - - if ($_SESSION['names'] == 0) { - $result .= ''; - } - - if ($host['status'] <= 2 || $host['status'] == 5) { - $result .= "" . title_trim(html_escape($host['description']), $maxlen) . ''; - } else { - $result .= "" . title_trim(html_escape($host['description']), $maxlen) . ''; - } - - $_SESSION['names']++; - - if ($_SESSION['names'] > 7) { - $result .= ''; - $_SESSION['names'] = 0; - } - - return $result; -} - -function render_host_tiles($host, $maxlen = 10) { - $class = get_status_icon($host['status'], $host['monitor_icon']); - $fclass = get_request_var('size'); - - $result = "
"; - - return $result; -} - -function render_host_tilesadt($host, $maxlen = 10) { - $tis = ''; - - $class = get_status_icon($host['status'], $host['monitor_icon']); - $fclass = get_request_var('size'); - - if ($host['status'] < 2 || $host['status'] == 5) { - $tis = get_timeinstate($host); - - $result = ""; - - return $result; - } else { - $tis = get_uptime($host); - - $result = ""; - - return $result; - } -} - -function get_hosts_down_or_triggered_by_permission($prescan) { - global $render_style; - $PreScanValue = 2; - - if ($prescan) { - $PreScanValue = 3; - } - - $result = []; - - if (get_request_var('crit') > 0) { - $sql_add_where = 'monitor_criticality >= ' . get_request_var('crit'); - } else { - $sql_add_where = ''; - } - - if (get_request_var('grouping') == 'tree') { - if (get_request_var('tree') > 0) { - $devices = db_fetch_cell_prepared('SELECT GROUP_CONCAT(DISTINCT host_id) AS hosts - FROM graph_tree_items AS gti - INNER JOIN host AS h - WHERE host_id > 0 - AND h.deleted = "" - AND graph_tree_id = ?', - [get_request_var('tree')]); - - render_group_concat($sql_add_where, ' OR ', 'h.id', $devices,'AND h.status < 2'); - } - } - - if (get_request_var('status') > 0) { - $triggered = db_fetch_cell('SELECT GROUP_CONCAT(DISTINCT host_id) AS hosts - FROM host AS h - INNER JOIN thold_data AS td - ON td.host_id = h.id - WHERE ' . get_thold_where() . ' - AND h.deleted = ""'); - - render_group_concat($sql_add_where, ' OR ', 'h.id', $triggered, 'AND h.status > 1'); - - $_SESSION['monitor_triggered'] = array_rekey( - db_fetch_assoc('SELECT td.host_id, COUNT(DISTINCT td.id) AS triggered - FROM thold_data AS td - INNER JOIN host AS h - ON td.host_id = h.id - WHERE ' . get_thold_where() . ' - AND h.deleted = "" - GROUP BY td.host_id'), - 'host_id', 'triggered' - ); - } - - $sql_where = "h.monitor = 'on' - AND h.disabled = '' - AND h.deleted = '' - AND ((h.status < " . $PreScanValue . ' AND (h.availability_method > 0 OR h.snmp_version > 0)) ' . - ($sql_add_where != '' ? ' OR (' . $sql_add_where . '))' : ')'); - - // do a quick loop through to pull the hosts that are down - $hosts = get_allowed_devices($sql_where); - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $host) { - $result[] = $host['id']; - sort($result); - } - } - - return $result; -} - -/* -// This function is not used and contains an undefined variable - -function get_host_tree_array() { - return $leafs; -} -*/ - -function get_host_non_tree_array() { - $leafs = []; - - $sql_where = ''; - $sql_join = ''; - - render_where_join($sql_where, $sql_join); - - $hierarchy = db_fetch_assoc("SELECT DISTINCT - h.*, gti.title, gti.host_id, gti.host_grouping_type, gti.graph_tree_id - FROM host AS h - LEFT JOIN graph_tree_items AS gti - ON h.id=gti.host_id - $sql_join - $sql_where - AND gti.graph_tree_id IS NULL - ORDER BY h.description"); - - if (cacti_sizeof($hierarchy) > 0) { - $leafs = []; - $branchleafs = 0; - - foreach ($hierarchy as $leaf) { - $leafs[$branchleafs] = $leaf; - $branchleafs++; - } - } - - return $leafs; -} - -function get_monitor_trim_length($fieldlen) { - global $maxchars; - - if (get_request_var('view') == 'default' || get_request_var('view') == 'names') { - $maxlen = $maxchars; - - if (get_request_var('trim') < 0) { - $maxlen = 4000; - } elseif (get_request_var('trim') > 0) { - $maxlen = get_request_var('trim'); - } - - if ($fieldlen > $maxlen) { - $fieldlen = $maxlen; - } - } - - return $fieldlen; -} +exit; diff --git a/monitor_controller.php b/monitor_controller.php new file mode 100644 index 0000000..d84cfcf --- /dev/null +++ b/monitor_controller.php @@ -0,0 +1,1247 @@ + 0) { + $db_settings = db_fetch_cell_prepared( + 'SELECT url + FROM plugin_monitor_dashboards + WHERE id = ?', + [$dashboard] + ); + + if ($db_settings != '') { + $db_settings = str_replace('monitor.php?', '', $db_settings); + $settings = explode('&', $db_settings); + + if (cacti_sizeof($settings)) { + foreach ($settings as $setting) { + [$name, $value] = explode('=', $setting); + + set_request_var($name, $value); + } + } + } + } +} + +/** + * Render monitor page including filters, host layout, legend, and audio. + * + * @return void + */ +function drawPage(): void +{ + global $config, $iclasses, $icolorsdisplay, $mon_zoom_state, $dozoomrefresh, $dozoombgndcolor, $font_sizes; + global $new_form, $new_title; + + $errored_list = getHostsDownOrTriggeredByPermission(true); + + if (cacti_sizeof($errored_list) && read_user_setting('monitor_error_zoom') == 'on') { + if ($_SESSION['monitor_zoom_state'] == 0) { + $mon_zoom_state = $_SESSION['monitor_zoom_state'] = 1; + $_SESSION['mon_zoom_hist_status'] = get_nfilter_request_var('status'); + $_SESSION['mon_zoom_hist_size'] = get_nfilter_request_var('size'); + $dozoomrefresh = true; + $dozoombgndcolor = true; + } + } elseif (isset($_SESSION['monitor_zoom_state']) && $_SESSION['monitor_zoom_state'] == 1) { + $_SESSION['monitor_zoom_state'] = 0; + $dozoomrefresh = true; + $dozoombgndcolor = false; + } + + $name = db_fetch_cell_prepared( + 'SELECT name + FROM plugin_monitor_dashboards + WHERE id = ?', + [get_request_var('dashboard')] + ); + + if ($name == '') { + $name = __('New Dashboard', 'monitor'); + } + + $new_form = "
" . __('Enter the Dashboard Name and then press \'Save\' to continue, else press \'Cancel\'', 'monitor') . '
' . __('Dashboard', 'monitor') . "
"; + + $new_title = __('Create New Dashboard', 'monitor'); + + findDownHosts(); + + general_header(); + + drawFilterAndStatus(); + + print ''; + + // Default with permissions = default_by_permission + // Tree = group_by_tree + $function = 'render' . ucfirst(get_request_var('grouping')); + + if (function_exists($function) && get_request_var('view') != 'list') { + if (get_request_var('grouping') == 'default' || get_request_var('grouping') == 'site') { + html_start_box(__('Monitored Devices', 'monitor'), '100%', true, '3', 'center', ''); + } else { + html_start_box('', '100%', true, '3', 'center', ''); + } + print $function(); + } else { + print renderDefault(); + } + + print ''; + + html_end_box(); + + if (read_user_setting('monitor_legend', read_config_option('monitor_legend'))) { + print "
"; + + foreach ($iclasses as $index => $class) { + print "
" . $icolorsdisplay[$index] . '
'; + } + + print '
'; + } + + // If the host is down, we need to insert the embedded wav file + $monitor_sound = getMonitorSound(); + + if (isMonitorAudible()) { + if (read_user_setting('monitor_sound_loop', read_config_option('monitor_sound_loop'))) { + print ""; + } else { + print ""; + } + } + + print '
' . getFilterText() . '
'; + + bottom_footer(); +} + +/** + * Determine if alert audio is available and should be considered playable. + * + * @return bool + */ +function isMonitorAudible(): bool +{ + return getMonitorSound() != ''; +} + +/** + * Resolve configured monitor sound file if it exists on disk. + * + * @return string + */ +function getMonitorSound(): string +{ + $sound = read_user_setting('monitor_sound', read_config_option('monitor_sound')); + clearstatcache(); + $file = __DIR__ . '/sounds/' . $sound; + $exists = file_exists($file); + + return $exists ? $sound : ''; +} + +/** + * Update down-host request flags and mute state based on current host status. + * + * @return void + */ +function findDownHosts(): void +{ + $dhosts = getHostsDownOrTriggeredByPermission(false); + + if (cacti_sizeof($dhosts)) { + set_request_var('downhosts', 'true'); + + if (isset($_SESSION['monitor_muted_hosts'])) { + unmute_up_non_triggered_hosts($dhosts); + + $unmuted_hosts = array_diff($dhosts, $_SESSION['monitor_muted_hosts']); + + if (cacti_sizeof($unmuted_hosts)) { + unmute_user(); + } + } else { + set_request_var('mute', 'false'); + } + } else { + unmuteAllHosts(); + set_request_var('downhosts', 'false'); + } +} + +/** + * Remove recovered hosts from muted-host session state. + * + * @param array $dhosts Current down/triggered host id list. + * + * @return void + */ +function unmuteUpNonTriggeredHosts(array $dhosts): void +{ + if (isset($_SESSION['monitor_muted_hosts'])) { + foreach ($_SESSION['monitor_muted_hosts'] as $index => $host_id) { + if (array_search($host_id, $dhosts, true) === false) { + unset($_SESSION['monitor_muted_hosts'][$index]); + } + } + } +} + +/** + * Mute all currently down/triggered hosts for the active user session. + * + * @return void + */ +function muteAllHosts(): void +{ + $_SESSION['monitor_muted_hosts'] = getHostsDownOrTriggeredByPermission(false); + muteUser(); +} + +/** + * Clear muted-host list and unmute monitor notifications for this user. + * + * @return void + */ +function unmuteAllHosts(): void +{ + $_SESSION['monitor_muted_hosts'] = []; + unmuteUser(); +} + +/** + * Persist user mute state as enabled. + * + * @return void + */ +function muteUser(): void +{ + set_request_var('mute', 'true'); + set_user_setting('monitor_mute', 'true'); +} + +/** + * Persist user mute state as disabled. + * + * @return void + */ +function unmuteUser(): void +{ + set_request_var('mute', 'false'); + set_user_setting('monitor_mute', 'false'); +} + + +/** + * Build footer text describing currently active monitor filters. + * + * @return string + */ +function getFilterText(): string +{ + $filter = '
'; + + switch (get_request_var('status')) { + case '-4': + $filter .= __('Devices without Thresholds', 'monitor'); + + break; + case '-3': + $filter .= __('Not Monitored Devices', 'monitor'); + + break; + case '-2': + $filter .= __('All Devices', 'monitor'); + + break; + case '-1': + $filter .= __('All Monitored Devices', 'monitor'); + + break; + case '0': + $filter .= __('Monitored Devices either Down or Recovering', 'monitor'); + + break; + case '1': + $filter .= __('Monitored Devices either Down, Recovering, or with Triggered Thresholds', 'monitor'); + + break; + case '2': + $filter .= __('Monitored Devices either Down, Recovering, or with Breached or Triggered Thresholds', 'monitor'); + + break; + default: + $filter .= __('Unknown monitoring status (%s)', get_request_var('status'), 'monitor'); + } + + switch (get_request_var('crit')) { + case '0': + $filter .= __(', and All Criticalities', 'monitor'); + + break; + case '1': + $filter .= __(', and of Low Criticality or Higher', 'monitor'); + + break; + case '2': + $filter .= __(', and of Medium Criticality or Higher', 'monitor'); + + break; + case '3': + $filter .= __(', and of High Criticality or Higher', 'monitor'); + + break; + case '4': + $filter .= __(', and of Mission Critical Status', 'monitor'); + + break; + } + + $filter .= __('
Remember to first select eligible Devices to be Monitored from the Devices page!
', 'monitor'); + + return $filter; +} + +/** + * Render one filter dropdown (or hidden fallback input) for the monitor form. + * + * @param string $id Filter field id/name. + * @param string $title Filter display title. + * @param array $settings Option map of value => label. + * @param string|int $value Selected value override. + * + * @return void + */ +function drawFilterDropdown(string $id, string $title, array $settings = [], mixed $value = null): void +{ + if ($value == null) { + $value = get_nfilter_request_var($id); + } + + if (cacti_sizeof($settings)) { + print '' . html_escape($title) . ''; + print '' . PHP_EOL; + } else { + print "" . PHP_EOL; + } +} + +/** + * Build dashboard dropdown option map for current user context. + * + * @return array + */ +function monitorGetDashboardOptions(): array +{ + $dashboards = [0 => __('Unsaved', 'monitor')]; + $dashboards += array_rekey( + db_fetch_assoc_prepared( + 'SELECT id, name + FROM plugin_monitor_dashboards + WHERE user_id = 0 OR user_id = ? + ORDER BY name', + [$_SESSION['sess_user_id']] + ), + 'id', + 'name' + ); + + return $dashboards; +} + +/** + * Resolve zoom override dropdown state from session values. + * + * @param bool $dozoombgndcolor Zoom background flag, updated in place. + * + * @return array{int|null, string|null} + */ +function monitorGetZoomDropdownState(bool &$dozoombgndcolor): array +{ + $mon_zoom_status = null; + $mon_zoom_size = null; + + if (isset($_SESSION['monitor_zoom_state'])) { + if ($_SESSION['monitor_zoom_state'] == 1) { + $mon_zoom_status = 2; + $mon_zoom_size = 'monitor_errorzoom'; + $dozoombgndcolor = true; + } else { + if (isset($_SESSION['mon_zoom_hist_status'])) { + $mon_zoom_status = $_SESSION['mon_zoom_hist_status']; + } + + if (isset($_SESSION['mon_zoom_hist_size'])) { + $currentddsize = get_nfilter_request_var('size'); + + if ($currentddsize != $_SESSION['mon_zoom_hist_size'] && $currentddsize != 'monitor_errorzoom') { + $_SESSION['mon_zoom_hist_size'] = $currentddsize; + } + + $mon_zoom_size = $_SESSION['mon_zoom_hist_size']; + } + } + } + + return [$mon_zoom_status, $mon_zoom_size]; +} + +/** + * Render the primary filter row (layout/status/view/grouping/actions). + * + * @param array $dashboards Dashboard option map. + * @param array $monitor_status Status filter options. + * @param array $monitor_view_type View mode options. + * @param array $monitor_grouping Grouping mode options. + * @param array $item_rows Device row count options. + * @param int|null $mon_zoom_status Zoom-driven status override. + * + * @return void + */ +function monitorRenderPrimaryFilterRow(array $dashboards, array $monitor_status, array $monitor_view_type, array $monitor_grouping, array $item_rows, int|null $mon_zoom_status): void +{ + drawFilterDropdown('dashboard', __('Layout', 'monitor'), $dashboards); + drawFilterDropdown('status', __('Status', 'monitor'), $monitor_status, $mon_zoom_status); + drawFilterDropdown('view', __('View', 'monitor'), $monitor_view_type); + drawFilterDropdown('grouping', __('Grouping', 'monitor'), $monitor_grouping); + drawFilterDropdown('rows', __('Devices', 'monitor'), $item_rows); + + print '' . PHP_EOL; + print '' . PHP_EOL; + print '' . PHP_EOL; + print '' . PHP_EOL; + print '' . PHP_EOL; + + if (get_request_var('dashboard') > 0) { + print '' . PHP_EOL; + print '' . PHP_EOL; + } + + print '' . PHP_EOL; + print '' . PHP_EOL; + print ''; +} + +/** + * Render secondary grouping/filter controls for monitor page. + * + * @param array $classes Size class options. + * @param array $criticalities Criticality options. + * @param array $monitor_trim Trim options. + * @param array $page_refresh_interval Refresh options. + * @param string|null $mon_zoom_size Zoom-driven size override. + * + * @return void + */ +function monitorRenderGroupingDropdowns(array $classes, array $criticalities, array $monitor_trim, array $page_refresh_interval, string|null $mon_zoom_size): void +{ + drawFilterDropdown('crit', __('Criticality', 'monitor'), $criticalities); + + if (get_request_var('view') != 'list') { + drawFilterDropdown('size', __('Size', 'monitor'), $classes, $mon_zoom_size); + } + + if (get_request_var('view') == 'default' || get_request_var('view') == 'names') { + drawFilterDropdown('trim', __('Trim', 'monitor'), $monitor_trim); + } + + if (get_nfilter_request_var('grouping') == 'tree') { + $trees = []; + + if (get_request_var('grouping') == 'tree') { + $trees_allowed = array_rekey(get_allowed_trees(), 'id', 'name'); + + if (cacti_sizeof($trees_allowed)) { + $trees_prefix = [-1 => __('All Trees', 'monitor')]; + $trees_suffix = [-2 => __('Non-Tree Devices', 'monitor')]; + $trees = $trees_prefix + $trees_allowed + $trees_suffix; + } + } + + drawFilterDropdown('tree', __('Tree', 'monitor'), $trees); + } + + if (get_nfilter_request_var('grouping') == 'site') { + $sites = []; + + if (get_request_var('grouping') == 'site') { + $sites = array_rekey( + db_fetch_assoc('SELECT id, name + FROM sites + ORDER BY name'), + 'id', + 'name' + ); + + if (cacti_sizeof($sites)) { + $sites_prefix = [-1 => __('All Sites', 'monitor')]; + $sites_suffix = [-2 => __('Non-Site Devices', 'monitor')]; + $sites = $sites_prefix + $sites + $sites_suffix; + } + } + + drawFilterDropdown('site', __('Sites', 'monitor'), $sites); + } + + if (get_request_var('grouping') == 'template') { + $templates = []; + $templates_allowed = array_rekey( + db_fetch_assoc('SELECT ht.id, ht.name, COUNT(gl.id) AS graphs + FROM host_template AS ht + INNER JOIN host AS h + ON h.host_template_id = ht.id + INNER JOIN graph_local AS gl + ON h.id = gl.host_id + GROUP BY ht.id + HAVING graphs > 0'), + 'id', + 'name' + ); + + if (cacti_sizeof($templates_allowed)) { + $templates_prefix = [-1 => __('All Templates', 'monitor')]; + $templates_suffix = [-2 => __('Non-Templated Devices', 'monitor')]; + $templates = $templates_prefix + $templates_allowed + $templates_suffix; + } + + drawFilterDropdown('template', __('Template', 'monitor'), $templates); + } + + drawFilterDropdown('refresh', __('Refresh', 'monitor'), $page_refresh_interval); +} + +/** + * Render hidden fallback input fields for inactive filters. + * + * @return void + */ +function monitorRenderHiddenFilterInputs(): void +{ + if (get_request_var('grouping') != 'tree') { + print '' . PHP_EOL; + } + + if (get_request_var('grouping') != 'site') { + print '' . PHP_EOL; + } + + if (get_request_var('grouping') != 'template') { + print '' . PHP_EOL; + } + + if (get_request_var('view') == 'list') { + print '' . PHP_EOL; + } + + if (get_request_var('view') != 'default') { + print '' . PHP_EOL; + } +} + +/** + * Resolve zoom background color/font style for page rendering. + * + * @param bool $dozoombgndcolor Whether zoom background styling is enabled. + * + * @return array{string, string} + */ +function monitorGetZoomBackgroundStyle(bool $dozoombgndcolor): array +{ + if ($dozoombgndcolor) { + $mbcolora = db_fetch_row_prepared( + 'SELECT * + FROM colors + WHERE id = ?', + [read_user_setting('monitor_error_background')] + ); + + $monitor_error_fontsize = read_user_setting('monitor_error_fontsize') . 'px'; + $mbcolor = cacti_sizeof($mbcolora) ? '#' . $mbcolora['hex'] : 'snow'; + } else { + $mbcolor = ''; + $monitor_error_fontsize = '10px'; + } + + return [$mbcolor, $monitor_error_fontsize]; +} + +/** + * Emit JavaScript bootstrap config and monitor JS include tag. + * + * @param array $config Global Cacti config. + * @param string $mbcolor Monitor background color. + * @param string $monitor_error_fontsize Zoom mode font size. + * @param bool $dozoomrefresh Auto-refresh flag for zoom mode. + * @param string $new_form New dashboard dialog markup. + * @param string $new_title New dashboard dialog title. + * + * @return void + */ +function monitorPrintJsBootstrap(array $config, string $mbcolor, string $monitor_error_fontsize, bool $dozoomrefresh, string $new_form, string $new_title): void +{ + $monitor_js_config = [ + 'mbColor' => $mbcolor, + 'monitorFont' => $monitor_error_fontsize, + 'doZoomRefresh' => $dozoomrefresh, + 'newForm' => $new_form, + 'newTitle' => $new_title, + 'messages' => [ + 'filterSaved' => __(' [ Filter Settings Saved ]', 'monitor'), + 'cancel' => __('Cancel', 'monitor'), + 'save' => __('Save', 'monitor') + ] + ]; + + print ''; + print ''; +} + +/** + * Render monitor filter area and inject page JS bootstrap config. + * + * @return void + */ +function drawFilterAndStatus(): void +{ + global $config, $criticalities, $page_refresh_interval, $classes, $monitor_grouping; + global $monitor_view_type, $monitor_status, $monitor_trim; + global $dozoombgndcolor, $dozoomrefresh, $zoom_hist_status, $zoom_hist_size, $mon_zoom_state; + global $new_form, $new_title, $item_rows; + + $header = __('Monitor Filter [ Last Refresh: %s ]', date('g:i:s a', time()), 'monitor') . (get_request_var('refresh') < 99999 ? __(' [ Refresh Again in %d Seconds ]', get_request_var('refresh'), 'monitor') : '') . (get_request_var('view') == 'list' ? __('[ Showing only first 30 Devices ]', 'monitor') : '') . ''; + + html_start_box($header, '100%', false, '3', 'center', ''); + + print '' . PHP_EOL; + print '
' . PHP_EOL; + + print '' . PHP_EOL; + print '' . PHP_EOL; + [$mon_zoom_status, $mon_zoom_size] = monitorGetZoomDropdownState($dozoombgndcolor); + monitorRenderPrimaryFilterRow( + monitorGetDashboardOptions(), + $monitor_status, + $monitor_view_type, + $monitor_grouping, + $item_rows, + $mon_zoom_status + ); + print ''; + print '
'; + + // Second line of filter + print '' . PHP_EOL; + print '' . PHP_EOL; + print ''; + print ''; + monitorRenderGroupingDropdowns($classes, $criticalities, $monitor_trim, $page_refresh_interval, $mon_zoom_size); + monitorRenderHiddenFilterInputs(); + + print ''; + print '
' . __('Search', 'monitor') . '
'; + print '
' . PHP_EOL; + + html_end_box(); + + [$mbcolor, $monitor_error_fontsize] = monitorGetZoomBackgroundStyle($dozoombgndcolor); + monitorPrintJsBootstrap($config, $mbcolor, $monitor_error_fontsize, $dozoomrefresh, $new_form, $new_title); +} + +/** + * Get action label for mute button based on current audio capability. + * + * @return string + */ +function getMuteText(): string +{ + if (isMonitorAudible()) { + return __('Mute', 'monitor'); + } else { + return __('Acknowledge', 'monitor'); + } +} + +/** + * Get action label for unmute/reset button based on audio capability. + * + * @return string + */ +function getUnmuteText(): string +{ + if (isMonitorAudible()) { + return __('Un-Mute', 'monitor'); + } else { + return __('Reset', 'monitor'); + } +} + +/** + * Delete selected dashboard when owned by current user. + * + * @return void + */ +function removeDashboard(): void +{ + $dashboard = get_filter_request_var('dashboard'); + + $name = db_fetch_cell_prepared( + 'SELECT name + FROM plugin_monitor_dashboards + WHERE id = ? + AND user_id = ?', + [$dashboard, $_SESSION['sess_user_id']] + ); + + if ($name != '') { + db_execute_prepared( + 'DELETE FROM plugin_monitor_dashboards + WHERE id = ?', + [$dashboard] + ); + + raise_message('removed', __('Dashboard \'%s\' Removed.', $name, 'monitor'), MESSAGE_LEVEL_INFO); + } else { + $name = db_fetch_cell_prepared( + 'SELECT name + FROM plugin_monitor_dashboards + WHERE id = ?', + [$dashboard] + ); + + raise_message('notremoved', __('Dashboard \'%s\' is not owned by you.', $name, 'monitor'), MESSAGE_LEVEL_ERROR); + } + + set_request_var('dashboard', '0'); +} + +/** + * Save monitor filter settings to user prefs or selected dashboard record. + * + * @return void + */ +function saveSettings(): void +{ + if (isset_request_var('dashboard') && get_filter_request_var('dashboard') != 0) { + $save_db = true; + } else { + $save_db = false; + } + + validateRequestVars(); + + if (!$save_db) { + if (cacti_sizeof($_REQUEST)) { + foreach ($_REQUEST as $var => $value) { + switch ($var) { + case 'dashboard': + set_user_setting('monitor_rfilter', get_request_var('dashboard')); + + break; + case 'rfilter': + set_user_setting('monitor_rfilter', get_request_var('rfilter')); + + break; + case 'refresh': + set_user_setting('monitor_refresh', get_request_var('refresh')); + + break; + case 'grouping': + set_user_setting('monitor_grouping', get_request_var('grouping')); + + break; + case 'view': + set_user_setting('monitor_view', get_request_var('view')); + + break; + case 'rows': + set_user_setting('monitor_rows', get_request_var('rows')); + + break; + case 'crit': + set_user_setting('monitor_crit', get_request_var('crit')); + + break; + case 'mute': + set_user_setting('monitor_mute', get_request_var('mute')); + + break; + case 'size': + set_user_setting('monitor_size', get_request_var('size')); + + break; + case 'trim': + set_user_setting('monitor_trim', get_request_var('trim')); + + break; + case 'status': + set_user_setting('monitor_status', get_request_var('status')); + + break; + case 'tree': + set_user_setting('monitor_tree', get_request_var('tree')); + + break; + case 'mute': + set_user_setting('monitor_mute', get_request_var('mute')); + + break; + case 'site': + set_user_setting('monitor_site', get_request_var('site')); + + break; + } + } + } + } else { + $url = 'monitor.php' . + '?refresh=' . get_request_var('refresh') . + '&grouping=' . get_request_var('grouping') . + '&view=' . get_request_var('view') . + '&rows=' . get_request_var('rows') . + '&crit=' . get_request_var('crit') . + '&size=' . get_request_var('size') . + '&trim=' . get_request_var('trim') . + '&status=' . get_request_var('status') . + '&tree=' . get_request_var('tree') . + '&site=' . get_request_var('site'); + + if (!isset_request_var('user')) { + $user = $_SESSION['sess_user_id']; + } else { + $user = get_request_var('user'); + } + + $id = get_request_var('dashboard'); + $name = get_nfilter_request_var('name'); + + $save = []; + $save['id'] = $id; + $save['name'] = $name; + $save['user_id'] = $user; + $save['url'] = $url; + + $id = sql_save($save, 'plugin_monitor_dashboards'); + + if (!empty($id)) { + raise_message('monitorsaved', __('Dashboard \'%s\' has been Saved!', $name, 'monitor'), MESSAGE_LEVEL_INFO); + set_request_var('dashboard', $id); + } else { + raise_message('monitornotsaved', __('Dashboard \'%s\' could not be Saved!', $name, 'monitor'), MESSAGE_LEVEL_INFO); + set_request_var('dashboard', '0'); + } + } + + validateRequestVars(true); +} + +/** + * Validate and persist monitor request/session filter variables. + * + * @param bool $force Force reload from saved defaults. + * + * @return void + */ +function validateRequestVars(bool $force = false): void +{ + // ================= input validation and session storage ================= + $filters = [ + 'refresh' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => read_user_setting('monitor_refresh', read_config_option('monitor_refresh'), $force) + ], + 'dashboard' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_dashboard', '0', $force) + ], + 'rfilter' => [ + 'filter' => FILTER_VALIDATE_IS_REGEX, + 'pageset' => true, + 'default' => read_user_setting('monitor_rfilter', '', $force) + ], + 'name' => [ + 'filter' => FILTER_CALLBACK, + 'options' => ['options' => 'sanitize_search_string'], + 'default' => '' + ], + 'mute' => [ + 'filter' => FILTER_CALLBACK, + 'options' => ['options' => 'sanitize_search_string'], + 'default' => read_user_setting('monitor_mute', 'false', $force) + ], + 'grouping' => [ + 'filter' => FILTER_CALLBACK, + 'options' => ['options' => 'sanitize_search_string'], + 'pageset' => true, + 'default' => read_user_setting('monitor_grouping', read_config_option('monitor_grouping'), $force) + ], + 'view' => [ + 'filter' => FILTER_CALLBACK, + 'options' => ['options' => 'sanitize_search_string'], + 'pageset' => true, + 'default' => read_user_setting('monitor_view', read_config_option('monitor_view'), $force) + ], + 'rows' => [ + 'filter' => FILTER_VALIDATE_INT, + 'options' => ['options' => 'sanitize_search_string'], + 'default' => read_user_setting('monitor_rows', read_config_option('num_rows_table'), $force) + ], + 'size' => [ + 'filter' => FILTER_CALLBACK, + 'options' => ['options' => 'sanitize_search_string'], + 'default' => read_user_setting('monitor_size', 'monitor_medium', $force) + ], + 'trim' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => read_user_setting('monitor_trim', read_config_option('monitor_trim'), $force) + ], + 'crit' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_crit', '-1', $force) + ], + 'status' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_status', '-1', $force) + ], + 'tree' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_tree', '-1', $force) + ], + 'site' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_site', '-1', $force) + ], + 'template' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => read_user_setting('monitor_template', '-1', $force) + ], + 'id' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => '-1' + ], + 'page' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => '1' + ], + 'sort_column' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'status', + 'options' => ['options' => 'sanitize_search_string'] + ], + 'sort_direction' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'ASC', + 'options' => ['options' => 'sanitize_search_string'] + ] + ]; + + validate_store_request_vars($filters, 'sess_monitor'); + // ================= input validation ================= +} + +/** + * Load host row and normalize status fields for AJAX tooltip rendering. + * + * @param int $id Host id. + * @param array $thold_hosts Threshold host map. + * @param array $config Global Cacti config. + * + * @return array + */ +function monitorLoadAjaxStatusHost(int|string $id, array $thold_hosts, array $config): array +{ + $host = db_fetch_row_prepared( + 'SELECT * + FROM host + WHERE id = ?', + [$id] + ); + + if (!cacti_sizeof($host)) { + return []; + } + + $host['anchor'] = $config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']; + + if ($host['status'] == 3 && array_key_exists($host['id'], $thold_hosts)) { + $host['status'] = 4; + $host['anchor'] = $config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']; + } + + if ($host['availability_method'] == 0) { + $host['status'] = 6; + } + + $host['real_status'] = getHostStatus($host, true); + $host['status'] = getHostStatus($host); + + return $host; +} + +/** + * Build quick-action link markup for AJAX tooltip panel. + * + * @param array $host Host row. + * @param array $config Global Cacti config. + * + * @return string + */ +function monitorGetAjaxStatusLinks(array $host, array $config): string +{ + $links = ''; + + if (api_plugin_user_realm_auth('host.php')) { + $host_link = html_escape($config['url_path'] . 'host.php?action=edit&id=' . $host['id']); + $links .= '
'; + } + + $graphs = db_fetch_cell_prepared( + 'SELECT COUNT(*) + FROM graph_local + WHERE host_id = ?', + [$host['id']] + ); + + if ($graphs > 0) { + $graph_link = html_escape($config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']); + $links .= '
'; + } + + if (api_plugin_is_enabled('thold')) { + $tholds = db_fetch_cell_prepared( + 'SELECT count(*) + FROM thold_data + WHERE host_id = ?', + [$host['id']] + ); + + if ($tholds) { + $thold_link = html_escape($config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']); + $links .= '
'; + } + } + + if (api_plugin_is_enabled('syslog') && api_plugin_user_realm_auth('syslog.php')) { + include($config['base_path'] . '/plugins/syslog/config.php'); + include_once($config['base_path'] . '/plugins/syslog/functions.php'); + + $syslog_logs = syslog_db_fetch_cell_prepared( + 'SELECT count(*) + FROM syslog_logs + WHERE host = ?', + [$host['hostname']] + ); + + $syslog_host = syslog_db_fetch_cell_prepared( + 'SELECT host_id + FROM syslog_hosts + WHERE host = ?', + [$host['hostname']] + ); + + if ($syslog_logs && $syslog_host) { + $syslog_log_link = html_escape($config['url_path'] . 'plugins/syslog/syslog/syslog.php?reset=1&tab=alerts&host_id=' . $syslog_host); + $links .= '
'; + } + + if ($syslog_host) { + $syslog_link = html_escape($config['url_path'] . 'plugins/syslog/syslog/syslog.php?reset=1&tab=syslog&host_id=' . $syslog_host); + $links .= '
'; + } + } + + return $links; +} + +/** + * Render full hover tooltip table HTML for one host. + * + * @param array $host Host row. + * @param string $size Tooltip CSS size key. + * @param string $links Action links HTML. + * @param string $site Site display value. + * @param string $sdisplay Status display string. + * @param string $iclass Status CSS class. + * @param array $criticalities Criticality label map. + * + * @return string + */ +function monitorRenderAjaxStatusTooltip(array $host, string $size, string $links, string $site, string $sdisplay, string $iclass, array $criticalities): string +{ + return " + + + + + + + + + + + + + + ' . (isset($host['monitor_criticality']) && $host['monitor_criticality'] > 0 ? ' + + + + ' : '') . ' + + + + " . ($host['status'] < 3 || $host['status'] == 5 ? ' + + + + ' : '') . ($host['availability_method'] > 0 ? ' + + + + ' : '') . ($host['availability_method'] > 0 ? " + + + + ' : '') . (isset($host['monitor_warn']) && ($host['monitor_warn'] > 0 || $host['monitor_alert'] > 0) ? " + + + + ' : '') . ' + + + + + + + + + + + + ' . ($host['snmp_version'] > 0 && ($host['status'] == 3 || $host['status'] == 2) ? ' + + + + + + + + + + + + + + + + ' : '') . ($host['notes'] != '' ? ' + + + + ' : '') . " + + +
" . __('Device Status Information', 'monitor') . '
' . __('Device:', 'monitor') . "" . html_escape($host['description']) . '
' . __('Site:', 'monitor') . '' . html_escape($site) . '
' . __('Location:', 'monitor') . '' . html_escape($host['location']) . '
' . __('Criticality:', 'monitor') . '' . html_escape($criticalities[$host['monitor_criticality']]) . '
' . __('Status:', 'monitor') . "$sdisplay
' . __('Admin Note:', 'monitor') . "" . html_escape($host['monitor_text']) . '
' . __('IP/Hostname:', 'monitor') . '' . html_escape($host['hostname']) . '
" . __('Curr/Avg:', 'monitor') . '' . __('%d ms', $host['cur_time'], 'monitor') . ' / ' . __('%d ms', $host['avg_time'], 'monitor') . '
" . __('Warn/Alert:', 'monitor') . '' . __('%0.2d ms', $host['monitor_warn'], 'monitor') . ' / ' . __('%0.2d ms', $host['monitor_alert'], 'monitor') . '
' . __('Last Fail:', 'monitor') . '' . html_escape($host['status_fail_date']) . '
' . __('Time In State:', 'monitor') . '' . get_timeinstate($host) . '
' . __('Availability:', 'monitor') . '' . round($host['availability'], 2) . ' %
' . __('Agent Uptime:', 'monitor') . '' . ($host['status'] == 3 || $host['status'] == 5 ? monitorPrintHostTime($host['snmp_sysUpTimeInstance']) : __('N/A', 'monitor')) . "
" . __('Sys Description:', 'monitor') . '' . html_escape(monitorTrim($host['snmp_sysDescr'])) . '
' . __('Location:', 'monitor') . '' . html_escape(monitorTrim($host['snmp_sysLocation'])) . '
' . __('Contact:', 'monitor') . '' . html_escape(monitorTrim($host['snmp_sysContact'])) . '
' . __('Notes:', 'monitor') . '' . html_escape($host['notes']) . '

$links
"; +} + + +/** + * Handle AJAX monitor status tooltip request and print response HTML. + * + * @return bool|null + */ +function ajaxStatus(): void +{ + global $thold_hosts, $config, $iclasses, $criticalities; + + validateRequestVars(); + + if (!isset_request_var('id') || !get_filter_request_var('id')) { + return; + } + + $id = get_request_var('id'); + $size = get_request_var('size'); + $host = monitorLoadAjaxStatusHost($id, $thold_hosts, $config); + + if (!cacti_sizeof($host)) { + cacti_log('Attempted to retrieve status for missing Device ' . $id, false, 'MONITOR', POLLER_VERBOSITY_HIGH); + + return; + } + + $links = monitorGetAjaxStatusLinks($host, $config); + + if (strtotime($host['status_fail_date']) < 86400) { + $host['status_fail_date'] = __('Never', 'monitor'); + } + + if ($host['location'] == '') { + $host['location'] = __('Unspecified', 'monitor'); + } + + $iclass = $iclasses[$host['status']]; + $sdisplay = getHostStatusDescription($host['real_status']); + $site = db_fetch_cell_prepared('SELECT name FROM sites WHERE id = ?', [$host['site_id']]); + + if ($site == '') { + $site = __('None', 'monitor'); + } + + print monitorRenderAjaxStatusTooltip($host, $size, $links, $site, $sdisplay, $iclass, $criticalities); +} diff --git a/monitor_render.php b/monitor_render.php new file mode 100644 index 0000000..5c4bfcd --- /dev/null +++ b/monitor_render.php @@ -0,0 +1,1266 @@ + 0 AND status IN (1, 2), status_event_count*$poller_interval, + IF(UNIX_TIMESTAMP(status_rec_date) < 943916400 AND status IN (0, 3), total_polls*$poller_interval, + IF(UNIX_TIMESTAMP(status_rec_date) > 943916400, UNIX_TIMESTAMP() - UNIX_TIMESTAMP(status_rec_date), + IF(snmp_sysUptimeInstance>0 AND snmp_version > 0, snmp_sysUptimeInstance/100, UNIX_TIMESTAMP() + ))))) AS unsigned) AS instate + FROM host AS h + LEFT JOIN sites AS s + ON h.site_id = s.id + $sql_join + $sql_where + $sql_order + $sql_limit"); + + $hosts = db_fetch_assoc($hosts_sql); + + $total_rows = db_fetch_cell("SELECT COUNT(DISTINCT h.id) + FROM host AS h + LEFT JOIN sites AS s + ON h.site_id = s.id + $sql_join + $sql_where"); + + if (cacti_sizeof($hosts)) { + // Determine the correct width of the cell + $maxlen = 10; + + if (get_request_var('view') == 'default') { + $maxlen = db_fetch_cell("SELECT MAX(LENGTH(description)) + FROM host AS h + $sql_join + $sql_where"); + } + + $maxlen = getMonitorTrimLength($maxlen); + + $function = 'renderHeader' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_header_ function + $result .= $function($total_rows, $rows); + } + + $count = 0; + + foreach ($hosts as $host) { + if (is_device_allowed($host['id'])) { + $result .= renderHost($host, true, $maxlen); + } + + $count++; + } + + $function = 'renderFooter' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_footer_ function + $result .= $function($total_rows, $rows); + } + } + + return $result; +} + +/** + * Render host output grouped by site. + * + * @return string + */ +function renderSite(): string +{ + global $maxchars; + + $result = ''; + + $sql_where = ''; + $sql_join = ''; + $sql_limit = ''; + + $rows = get_request_var('rows'); + + if ($rows == '-1') { + $rows = read_user_setting('monitor_rows'); + } + + if (!is_numeric($rows)) { + $rows = read_config_option('num_rows_table'); + } + + renderWhereJoin($sql_where, $sql_join); + + $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ',' . $rows; + + $hosts_sql = ("SELECT DISTINCT h.*, IFNULL(s.name,' " . __('Non-Site Devices', 'monitor') . " ') AS site_name + FROM host AS h + LEFT JOIN sites AS s + ON s.id = h.site_id + $sql_join + $sql_where + ORDER BY site_name, description + $sql_limit"); + + $hosts = db_fetch_assoc($hosts_sql); + + $ctemp = -1; + $ptemp = -1; + + if (cacti_sizeof($hosts)) { + $suppressGroups = false; + $function = 'renderSuppressgroups' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + $suppressGroups = $function(); + } + + $function = 'renderHeader' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_header_ function + $result .= $function(); + $suppressGroups = true; + } + + foreach ($hosts as $index => $host) { + if (is_device_allowed($host['id'])) { + $host_ids[] = $host['id']; + } else { + unset($hosts[$index]); + } + } + + // Determine the correct width of the cell + $maxlen = 10; + + if (get_request_var('view') == 'default') { + $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) + FROM host AS h + WHERE id IN (' . implode(',', $host_ids) . ')'); + } + $maxlen = getMonitorTrimLength($maxlen); + + $class = get_request_var('size'); + $csuffix = get_request_var('view'); + + if ($csuffix == 'default') { + $csuffix = ''; + } + + foreach ($hosts as $host) { + $ctemp = $host['site_id']; + + if (!$suppressGroups) { + if ($ctemp != $ptemp && $ptemp > 0) { + $result .= ''; + } + + if ($ctemp != $ptemp) { + $result .= "
+ +
+
"; + } + } + + $result .= renderHost($host, true, $maxlen); + + if ($ctemp != $ptemp) { + $ptemp = $ctemp; + } + } + + if ($ptemp == $ctemp && !$suppressGroups) { + $result .= '
'; + } + + $function = 'renderFooter' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_footer_ function + $result .= $function(); + } + } + + return $result; +} + +/** + * Render host output grouped by host template. + * + * @return string + */ +function renderTemplate(): string +{ + global $maxchars; + + $result = ''; + + $sql_where = ''; + $sql_join = ''; + $sql_limit = ''; + + $rows = get_request_var('rows'); + + if ($rows == '-1') { + $rows = read_user_setting('monitor_rows'); + } + + if (!is_numeric($rows)) { + $rows = read_config_option('num_rows_table'); + } + + renderWhereJoin($sql_where, $sql_join); + + $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ',' . $rows; + + if (get_request_var('template') > 0) { + $sql_where .= ($sql_where == '' ? '' : 'AND ') . 'ht.id = ' . get_request_var('template'); + } + + $sql_template = 'INNER JOIN host_template AS ht ON h.host_template_id=ht.id '; + + if (get_request_var('template') == -2) { + $sql_where .= ($sql_where == '' ? '' : 'AND ') . 'ht.id IS NULL'; + $sql_template = 'LEFT JOIN host_template AS ht ON h.host_template_id=ht.id '; + } + + $hosts = db_fetch_assoc("SELECT DISTINCT + h.*, ht.name AS host_template_name + FROM host AS h + $sql_template + $sql_join + $sql_where + ORDER BY ht.name, h.description + $sql_limit"); + + $ctemp = -1; + $ptemp = -1; + + if (cacti_sizeof($hosts)) { + $suppressGroups = false; + $function = 'renderSuppressgroups' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + $suppressGroups = $function(); + } + + $function = 'renderHeader' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_header_ function + $result .= $function(); + $suppressGroups = true; + } + + foreach ($hosts as $index => $host) { + if (is_device_allowed($host['id'])) { + $host_ids[] = $host['id']; + } else { + unset($hosts[$index]); + } + } + + // Determine the correct width of the cell + $maxlen = 10; + + if (get_request_var('view') == 'default') { + $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) + FROM host AS h + WHERE id IN (' . implode(',', $host_ids) . ')'); + } + $maxlen = getMonitorTrimLength($maxlen); + + $class = get_request_var('size'); + $csuffix = get_request_var('view'); + + if ($csuffix == 'default') { + $csuffix = ''; + } + + foreach ($hosts as $host) { + $ctemp = $host['host_template_id']; + + if (!$suppressGroups) { + if ($ctemp != $ptemp && $ptemp > 0) { + $result .= ''; + } + + if ($ctemp != $ptemp) { + $result .= "
+ +
+
"; + } + } + + $result .= renderHost($host, true, $maxlen); + + if ($ctemp != $ptemp) { + $ptemp = $ctemp; + } + } + + if ($ptemp == $ctemp && !$suppressGroups) { + $result .= '
'; + } + + $function = 'renderFooter' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_footer_ function + $result .= $function(); + } + } + + return $result; +} + +/** + * Filter out disallowed hosts and return normalized host/id lists. + * + * @param array $hosts Host rows. + * + * @return array{array, array} + */ +function monitorFilterAllowedHosts(array $hosts): array +{ + $host_ids = []; + + foreach ($hosts as $index => $host) { + if (is_device_allowed($host['id'])) { + $host_ids[] = $host['id']; + } else { + unset($hosts[$index]); + } + } + + return [array_values($hosts), $host_ids]; +} + +/** + * Determine max description trim length for tree rendering context. + * + * @return int + */ +function monitorGetTreeRenderMaxLength(): int +{ + $maxlen = 10; + + if (get_request_var('view') == 'default') { + $maxlen = db_fetch_cell("SELECT MAX(LENGTH(description)) + FROM host AS h + INNER JOIN graph_tree_items AS gti + ON gti.host_id = h.id + WHERE disabled = '' + AND deleted = ''"); + } + + return getMonitorTrimLength($maxlen); +} + +/** + * Build map of tree branch labels keyed by "tree_id:parent_id". + * + * @param array $branchWhost Tree branch/host rows. + * + * @return array + */ +function monitorBuildTreeTitles(array $branchWhost): array +{ + $titles = []; + $ptree = ''; + + foreach ($branchWhost as $b) { + if ($ptree != $b['graph_tree_id']) { + $titles[$b['graph_tree_id'] . ':0'] = __('Root Branch', 'monitor'); + $ptree = $b['graph_tree_id']; + } + + if ($b['parent'] > 0) { + $titles[$b['graph_tree_id'] . ':' . $b['parent']] = db_fetch_cell_prepared( + 'SELECT title + FROM graph_tree_items + WHERE id = ? + AND graph_tree_id = ? + ORDER BY position', + [$b['parent'], $b['graph_tree_id']] + ); + } + } + + return $titles; +} + +/** + * Render grouped tree title/branch sections for monitor view. + * + * @param array $titles Tree titles map keyed by "tree_id:parent_id". + * @param int $maxlen Trim length used for host title rendering. + * + * @return string + */ +function monitorRenderTreeTitleSections(array $titles, int $maxlen): string +{ + $result = ''; + $ptree = ''; + + foreach ($titles as $index => $title) { + [$graph_tree_id, $parent] = explode(':', $index); + $oid = $parent; + + $sql_where = ''; + $sql_join = ''; + renderWhereJoin($sql_where, $sql_join); + + $hosts_sql = "SELECT h.*, IFNULL(s.name,' " . __('Non-Site Device', 'monitor') . " ') AS site_name + FROM host AS h + LEFT JOIN sites AS s + ON h.site_id = s.id + INNER JOIN graph_tree_items AS gti + ON h.id = gti.host_id + $sql_join + $sql_where + AND parent = ? + AND graph_tree_id = ? + GROUP BY h.id + ORDER BY gti.position"; + + $hosts = db_fetch_assoc_prepared($hosts_sql, [$oid, $graph_tree_id]); + + $tree_name = db_fetch_cell_prepared( + 'SELECT name + FROM graph_tree + WHERE id = ?', + [$graph_tree_id] + ); + + if ($ptree != $tree_name) { + if ($ptree != '') { + $result .= ''; + } + + $result .= "
+ +
+
+
"; + + $ptree = $tree_name; + } + + if (!cacti_sizeof($hosts)) { + continue; + } + + [$hosts] = monitorFilterAllowedHosts($hosts); + + if (!cacti_sizeof($hosts)) { + continue; + } + + $result .= "
"; + + foreach ($hosts as $host) { + $result .= renderHost($host, true, $maxlen); + } + + $result .= '
'; + } + + return $result; +} + +/** + * Render section for monitored hosts that are not attached to any tree. + * + * @return string + */ +function monitorRenderNonTreeSection(): string +{ + $result = ''; + + if (get_request_var('tree') >= 0) { + return $result; + } + + $hosts = getHostNonTreeArray(); + + if (!cacti_sizeof($hosts)) { + return $result; + } + + [$hosts, $host_ids] = monitorFilterAllowedHosts($hosts); + + if (!cacti_sizeof($hosts)) { + return $result; + } + + $maxlen = 10; + + if (get_request_var('view') == 'default' && cacti_sizeof($host_ids)) { + $maxlen = db_fetch_cell('SELECT MAX(LENGTH(description)) + FROM host AS h + WHERE id IN (' . implode(',', $host_ids) . ") + AND h.deleted = ''"); + } + + $maxlen = getMonitorTrimLength($maxlen); + + $result .= "
+ +
+
"; + + foreach ($hosts as $leaf) { + $result .= renderHost($leaf, true, $maxlen); + } + + $result .= '
'; + + return $result; +} + +/** + * Render monitor tree grouping view, including tree and non-tree sections. + * + * @return string + */ +function renderTree(): string +{ + $result = ''; + + if (get_request_var('tree') > 0) { + $sql_where = 'gt.id=' . get_request_var('tree'); + } else { + $sql_where = ''; + } + + if (get_request_var('tree') != -2) { + $tree_list = get_allowed_trees(false, false, $sql_where, 'sequence'); + } else { + $tree_list = []; + } + + $function = 'renderHeader' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + $hosts = []; + + // Call the custom render_header_ function + $result .= $function(); + } + + if (cacti_sizeof($tree_list)) { + $tree_ids = []; + foreach ($tree_list as $tree) { + $tree_ids[$tree['id']] = $tree['id']; + } + + renderWhereJoin($sql_where, $sql_join); + + $branchWhost = db_fetch_assoc("SELECT DISTINCT gti.graph_tree_id, gti.parent + FROM graph_tree_items AS gti + INNER JOIN graph_tree AS gt + ON gt.id = gti.graph_tree_id + INNER JOIN host AS h + ON h.id = gti.host_id + $sql_join + $sql_where + AND gti.host_id > 0 + AND gti.graph_tree_id IN (" . implode(',', $tree_ids) . ') + ORDER BY gt.sequence, gti.position'); + + if (cacti_sizeof($branchWhost)) { + $titles = monitorBuildTreeTitles($branchWhost); + $result .= monitorRenderTreeTitleSections($titles, monitorGetTreeRenderMaxLength()); + } + + $result .= '
'; + } + + $result .= monitorRenderNonTreeSection(); + + $function = 'renderFooter' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_footer_ function + $result .= $function(); + } + + return $result; +} + +/** + * Resolve display status for a host with monitor/thold/mute overlays applied. + * + * @param array $host Host row data. + * @param bool $real Return raw computed status even if icon class is missing. + * + * @return int + */ +function getHostStatus(array $host, bool $real = false): int +{ + global $thold_hosts, $iclasses; + + // If the host has been muted, show the muted Icon + if ($host['status'] != 1 && in_array($host['id'], $thold_hosts, true)) { + $host['status'] = 4; + } + + if (in_array($host['id'], $_SESSION['monitor_muted_hosts'], true) && $host['status'] == 1) { + $host['status'] = 5; + } elseif (in_array($host['id'], $_SESSION['monitor_muted_hosts'], true) && $host['status'] == 4) { + $host['status'] = 9; + } elseif ($host['status'] == 3) { + if ($host['cur_time'] > $host['monitor_alert'] && !empty($host['monitor_alert'])) { + $host['status'] = 8; + } elseif ($host['cur_time'] > $host['monitor_warn'] && !empty($host['monitor_warn'])) { + $host['status'] = 7; + } + } + + // If wanting the real status, or the status is already known + // return the real status, otherwise default to unknown + return ($real || array_key_exists($host['status'], $iclasses)) ? $host['status'] : 0; +} + +/** + * Translate status code into localized display label. + * + * @param int $status Monitor status code. + * + * @return string + */ +function getHostStatusDescription(int|string $status): string +{ + global $icolorsdisplay; + + if (array_key_exists($status, $icolorsdisplay)) { + return $icolorsdisplay[$status]; + } else { + return __('Unknown', 'monitor') . " ($status)"; + } +} + +/** + * Render one host using view-specific renderer or default tile layout. + * + * @param array $host Host row data. + * @param bool $float Legacy compatibility flag (currently unused). + * @param int $maxlen Maximum host description trim length. + * + * @return string|null + */ +function renderHost(array $host, bool $float = true, int $maxlen = 10): ?string +{ + global $thold_hosts, $config, $icolorsdisplay, $iclasses, $classes, $maxchars, $mon_zoom_state; + + // throw out tree root items + if (array_key_exists('name', $host)) { + return null; + } + + if ($host['id'] <= 0) { + return null; + } + + $host['anchor'] = $config['url_path'] . 'graph_view.php?action=preview&reset=1&host_id=' . $host['id']; + + if ($host['status'] == 3 && array_key_exists($host['id'], $thold_hosts)) { + $host['status'] = 4; + $host['anchor'] = $config['url_path'] . 'plugins/thold/thold_graph.php?action=thold&reset=true&status=1&host_id=' . $host['id']; + } + + $host['real_status'] = getHostStatus($host, true); + $host['status'] = getHostStatus($host); + $host['iclass'] = $iclasses[$host['status']]; + + $function = 'renderHost' . ucfirst(get_request_var('view')); + + if (function_exists($function)) { + // Call the custom render_host_ function + $result = $function($host); + } else { + $iclass = getStatusIcon($host['status'], $host['monitor_icon']); + $fclass = get_request_var('size'); + + $monitor_times = read_user_setting('monitor_uptime'); + $monitor_time_html = ''; + + if ($host['status'] <= 2 || $host['status'] == 5) { + if ($mon_zoom_state) { + $fclass = 'monitor_errorzoom'; + } + $tis = get_timeinstate($host); + + if ($monitor_times == 'on') { + $monitor_time_html = "
$tis"; + } + $result = "

" . title_trim(html_escape($host['description']), $maxlen) . "$monitor_time_html
"; + } else { + $tis = get_uptime($host); + + if ($monitor_times == 'on') { + $monitor_time_html = "
$tis
"; + } + + $result = "

" . title_trim(html_escape($host['description']), $maxlen) . "$monitor_time_html
"; + } + } + + return $result; +} + +/** + * Resolve icon class for host status and configured monitor icon. + * + * @param int $status Current monitor status. + * @param string $icon Configured icon key. + * + * @return string + */ +function getStatusIcon(int $status, string $icon): string +{ + global $fa_icons; + + if (($status == 1 || ($status == 4 && get_request_var('status') > 0)) && read_user_setting('monitor_sound') == 'First Orders Suite.mp3') { + return 'fab fa-first-order fa-spin mon_icon'; + } + + if ($icon != '' && array_key_exists($icon, $fa_icons)) { + if (isset($fa_icons[$icon]['class'])) { + return $fa_icons[$icon]['class'] . ' mon_icon'; + } else { + return "fa fa-$icon mon_icon"; + } + } else { + return 'fa fa-server' . ' mon_icon'; + } +} + +/** + * Convert uptime/fail timestamp into compact human-readable duration text. + * + * @param string|int $status_time Timestamp string or SNMP uptime ticks. + * @param bool $seconds Include seconds in output string. + * + * @return string + */ +function monitorPrintHostTime(int|string $status_time, bool $seconds = false): string +{ + // If the host is down, make a downtime since message + $dt = ''; + + if (is_numeric($status_time)) { + $sfd = round($status_time / 100, 0); + } else { + $sfd = time() - strtotime($status_time); + } + $dt_d = floor($sfd / 86400); + $dt_h = floor(($sfd - ($dt_d * 86400)) / 3600); + $dt_m = floor(($sfd - ($dt_d * 86400) - ($dt_h * 3600)) / 60); + $dt_s = $sfd - ($dt_d * 86400) - ($dt_h * 3600) - ($dt_m * 60); + + if ($dt_d > 0) { + $dt .= $dt_d . 'd:' . $dt_h . 'h:' . $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); + } elseif ($dt_h > 0) { + $dt .= $dt_h . 'h:' . $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); + } elseif ($dt_m > 0) { + $dt .= $dt_m . 'm' . ($seconds ? ':' . $dt_s . 's' : ''); + } else { + $dt .= ($seconds ? $dt_s . 's' : __('Just Up', 'monitor')); + } + + return $dt; +} + + +/** + * Trim monitor text fields for quote and whitespace artifacts. + * + * @param string $string Input string. + * + * @return string + */ +function monitorTrim(string $string): string +{ + return trim($string, "\"'\\ \n\t\r"); +} + +/** + * Render wrapper header for default/tile monitor views. + * + * @return string + */ +function renderHeaderDefault(): string +{ + return "
"; +} + +/** + * Render wrapper header for names view table. + * + * @return string + */ +function renderHeaderNames(): string +{ + return ""; +} + +/** + * Render wrapper header for icon tile view. + * + * @return string + */ +function renderHeaderTiles(): string +{ + return renderHeaderDefault(); +} + +/** + * Render wrapper header for advanced tile view. + * + * @return string + */ +function renderHeaderTilesadt(): string +{ + return renderHeaderDefault(); +} + +/** + * Render header and sortable column bar for list view. + * + * @param int $total_rows Total rows matching active filters. + * @param int $rows Page row limit. + * + * @return string + */ +function renderHeaderList(int $total_rows = 0, int $rows = 0): string +{ + $display_text = [ + 'hostname' => [ + 'display' => __('Hostname', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left', 'tip' => __('Hostname of device', 'monitor') + ], + 'id' => [ + 'display' => __('ID', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left' + ], + 'description' => [ + 'display' => __('Description', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left' + ], + 'site_name' => [ + 'display' => __('Site', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left' + ], + 'monitor_criticality' => [ + 'display' => __('Criticality', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left' + ], + 'status' => [ + 'display' => __('Status', 'monitor'), + 'sort' => 'DESC', + 'align' => 'center' + ], + 'instate' => [ + 'display' => __('Length in Status', 'monitor'), + 'sort' => 'ASC', + 'align' => 'center' + ], + 'avg_time' => [ + 'display' => __('Averages', 'monitor'), + 'sort' => 'DESC', + 'align' => 'left' + ], + 'monitor_warn' => [ + 'display' => __('Warning', 'monitor'), + 'sort' => 'DESC', + 'align' => 'left' + ], + 'monitor_text' => [ + 'display' => __('Admin', 'monitor'), + 'sort' => 'ASC', + 'tip' => __('Monitor Text Column represents \'Admin\'', 'monitor'), + 'align' => 'left' + ], + 'notes' => [ + 'display' => __('Notes', 'monitor'), + 'sort' => 'ASC', + 'align' => 'left' + ], + 'availability' => [ + 'display' => __('Availability', 'monitor'), + 'sort' => 'DESC', + 'align' => 'right' + ], + 'status_fail_date' => [ + 'display' => __('Last Fail', 'monitor'), + 'sort' => 'DESC', + 'align' => 'right' + ], + ]; + + ob_start(); + + $nav = html_nav_bar('monitor.php?rfilter=' . get_request_var('rfilter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 12, __('Devices'), 'page', 'main'); + + html_start_box(__('Monitored Devices', 'monitor'), '100%', false, '3', 'center', ''); + + print $nav; + + html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); + + $output = ob_get_contents(); + + ob_end_clean(); + + return $output; +} + +/** + * Indicate whether grouped section headers are suppressed for list view. + * + * @return bool + */ +function renderSuppressgroupsList(): bool +{ + return true; +} + +/** + * Render wrapper footer for default/tile monitor views. + * + * @return string + */ +function renderFooterDefault(): string +{ + return ''; +} + +/** + * Render footer row for names view including trailing empty cells. + * + * @return string + */ +function renderFooterNames(): string +{ + $col = 7 - $_SESSION['names']; + + if ($col == 0) { + return '
'; + } else { + return ''; + } +} + +/** + * Render wrapper footer for icon tile view. + * + * @return string + */ +function renderFooterTiles(): string +{ + return renderFooterDefault(); +} + +/** + * Render wrapper footer for advanced tile view. + * + * @return string + */ +function renderFooterTilesadt(): string +{ + return renderFooterDefault(); +} + +/** + * Render list view footer and bottom pager. + * + * @param int $total_rows Total rows matching active filters. + * @param int $rows Page row limit. + * + * @return string + */ +function renderFooterList(int $total_rows, int $rows): string +{ + ob_start(); + + html_end_box(false); + + if ($total_rows > 0) { + $nav = html_nav_bar('monitor.php?rfilter=' . get_request_var('rfilter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 12, __('Devices'), 'page', 'main'); + + print $nav; + } + + $output = ob_get_contents(); + + ob_end_clean(); + + return $output; +} + +/** + * Render one host row in list view. + * + * @param array $host Host row data. + * + * @return string + */ +function renderHostList(array $host): string +{ + global $criticalities, $iclasses; + + if ($host['status'] < 2 || $host['status'] == 5) { + $dt = get_timeinstate($host); + } elseif (strtotime($host['status_rec_date']) > 192800) { + $dt = get_timeinstate($host); + } else { + $dt = __('Never', 'monitor'); + } + + if ($host['status'] < 3 || $host['status'] == 5) { + $host_admin = $host['monitor_text']; + } else { + $host_admin = ''; + } + + if (isset($host['monitor_criticality']) && $host['monitor_criticality'] > 0) { + $host_crit = $criticalities[$host['monitor_criticality']]; + } else { + $host_crit = ''; + } + + if ($host['availability_method'] > 0) { + $host_avg = __('%d ms', $host['cur_time'], 'monitor') . ' / ' . __('%d ms', $host['avg_time'], 'monitor'); + } else { + $host_avg = __('N/A', 'monitor'); + } + + if (isset($host['monitor_warn']) && ($host['monitor_warn'] > 0 || $host['monitor_alert'] > 0)) { + $host_warn = __('%0.2d ms', $host['monitor_warn'], 'monitor') . ' / ' . __('%0.2d ms', $host['monitor_alert'], 'monitor'); + } else { + $host_warn = ''; + } + + if (strtotime($host['status_fail_date']) < 86400) { + $host['status_fail_date'] = __('Never', 'monitor'); + } + + $host_datefail = $host['status_fail_date']; + + $iclass = $iclasses[$host['status']]; + $sdisplay = getHostStatusDescription($host['real_status']); + + $row_class = "{$iclass}Full"; + + ob_start(); + + print ""; + + $url = $host['anchor']; + + form_selectable_cell(filter_value($host['hostname'], '', $url), $host['id'], '', 'left'); + form_selectable_cell($host['id'], $host['id'], '', 'left'); + form_selectable_cell($host['description'], $host['id'], '', 'left'); + form_selectable_cell($host['site_name'], $host['id'], '', 'left'); + form_selectable_cell($host_crit, $host['id'], '', 'left'); + form_selectable_cell($sdisplay, $host['id'], '', 'center'); + form_selectable_cell($dt, $host['id'], '', 'center'); + form_selectable_cell($host_avg, $host['id'], '', 'left'); + form_selectable_cell($host_warn, $host['id'], '', 'left'); + form_selectable_cell($host_admin, $host['id'], '', 'white-space:pre-wrap;text-align:left'); + form_selectable_cell(str_replace(["\n", "\r"], [' ', ''], $host['notes']), $host['id'], '', 'white-space:pre-wrap;text-align:left'); + form_selectable_cell(round($host['availability'], 2) . ' %', $host['id'], '', 'right'); + form_selectable_cell($host_datefail, $host['id'], '', 'right'); + + form_end_row(); + + $result = ob_get_contents(); + + ob_end_clean(); + + return $result; +} + +/** + * Render one host cell in names view grid. + * + * @param array $host Host row data. + * + * @return string + */ +function renderHostNames(array $host): string +{ + $fclass = get_request_var('size'); + + $result = ''; + + $maxlen = getMonitorTrimLength(100); + + if ($_SESSION['names'] == 0) { + $result .= ''; + } + + if ($host['status'] <= 2 || $host['status'] == 5) { + $result .= "" . title_trim(html_escape($host['description']), $maxlen) . ''; + } else { + $result .= "" . title_trim(html_escape($host['description']), $maxlen) . ''; + } + + $_SESSION['names']++; + + if ($_SESSION['names'] > 7) { + $result .= ''; + $_SESSION['names'] = 0; + } + + return $result; +} + +/** + * Render one host icon tile. + * + * @param array $host Host row data. + * + * @return string + */ +function renderHostTiles(array $host): string +{ + $class = getStatusIcon($host['status'], $host['monitor_icon']); + $fclass = get_request_var('size'); + + return "
"; +} + +/** + * Render one advanced host tile including time-in-state/uptime text. + * + * @param array $host Host row data. + * + * @return string + */ +function renderHostTilesadt(array $host): string +{ + $tis = ''; + + $class = getStatusIcon($host['status'], $host['monitor_icon']); + $fclass = get_request_var('size'); + + if ($host['status'] < 2 || $host['status'] == 5) { + $tis = get_timeinstate($host); + + return ""; + } else { + $tis = get_uptime($host); + + return ""; + } +} + + +/** + * Apply monitor trim setting to a computed source field length. + * + * @param int $fieldlen Initial field length. + * + * @return int + */ +function getMonitorTrimLength(int $fieldlen): int +{ + global $maxchars; + + if (get_request_var('view') == 'default' || get_request_var('view') == 'names') { + $maxlen = $maxchars; + + if (get_request_var('trim') < 0) { + $maxlen = 4000; + } elseif (get_request_var('trim') > 0) { + $maxlen = get_request_var('trim'); + } + + if ($fieldlen > $maxlen) { + $fieldlen = $maxlen; + } + } + + return $fieldlen; +} diff --git a/poller_functions.php b/poller_functions.php new file mode 100644 index 0000000..b05c540 --- /dev/null +++ b/poller_functions.php @@ -0,0 +1,1111 @@ + 0 && isset($notification_lists[$notify_list])) { + $emails = explode(',', $notification_lists[$notify_list]); + monitorAddEmails($reboot_emails, $emails, $host_id); + } +} + +/** + * Fetch the configured global alert email list. + * + * @return array + */ +function getAlertEmails(): array +{ + $alert_email = read_config_option('alert_email'); + + return ($alert_email != '') ? explode(',', $alert_email) : []; +} + +/** + * Delete monitor table rows that reference missing hosts. + * + * @param string $table_name Monitor table to purge. + * + * @return void + */ +function purgeOrphanMonitorRows(string $table_name): void +{ + $removed_hosts = db_fetch_assoc("SELECT mu.host_id + FROM $table_name AS mu + LEFT JOIN host AS h + ON h.id = mu.host_id + WHERE h.id IS NULL"); + + if (cacti_sizeof($removed_hosts)) { + db_execute("DELETE mu + FROM $table_name AS mu + LEFT JOIN host AS h + ON h.id = mu.host_id + WHERE h.id IS NULL"); + } +} + +/** + * Get monitored hosts whose uptime indicates a reboot. + * + * @return array + */ +function getRebootedHosts(): array +{ + return db_fetch_assoc('SELECT h.id, h.description, + h.hostname, h.snmp_sysUpTimeInstance, mu.uptime + FROM host AS h + LEFT JOIN plugin_monitor_uptime AS mu + ON h.id = mu.host_id + WHERE h.snmp_version > 0 + AND status IN (2,3) + AND h.deleted = "" + AND h.monitor = "on" + AND (mu.uptime IS NULL OR mu.uptime > h.snmp_sysUpTimeInstance) + AND h.snmp_sysUpTimeInstance > 0'); +} + +/** + * Fetch notification lists and map id to email string. + * + * @return array + */ +function getNotificationListsMap(): array +{ + return array_rekey( + db_fetch_assoc('SELECT id, emails + FROM plugin_notification_lists + ORDER BY id'), + 'id', + 'emails' + ); +} + +/** + * Add reboot recipients based on host threshold notification settings. + * + * @param array $reboot_emails Recipient map keyed by email, then host id. + * @param int $host_id Host id being processed. + * @param array $alert_emails Global alert email list. + * @param array $notification_lists Map of notification list id to emails. + * + * @return void + */ +function addTholdRebootRecipients(array &$reboot_emails, int|string $host_id, array $alert_emails, array $notification_lists): void +{ + $notify = db_fetch_row_prepared( + 'SELECT thold_send_email, thold_host_email + FROM host + WHERE id = ?', + [$host_id] + ); + + if (!cacti_sizeof($notify)) { + return; + } + + switch ($notify['thold_send_email']) { + case '1': + monitorAddEmails($reboot_emails, $alert_emails, $host_id); + + break; + case '2': + monitorAddNotificationList($reboot_emails, $notify['thold_host_email'], $host_id, $notification_lists); + + break; + case '3': + monitorAddEmails($reboot_emails, $alert_emails, $host_id); + monitorAddNotificationList($reboot_emails, $notify['thold_host_email'], $host_id, $notification_lists); + + break; + default: + break; + } +} + +/** + * Build reboot recipient map and persist reboot history rows. + * + * @param array $rebooted_hosts Rebooted host rows. + * @param array $alert_emails Global alert email list. + * + * @return array + */ +function buildRebootEmailMap(array $rebooted_hosts, array $alert_emails): array +{ + $reboot_emails = []; + $notification_lists = getNotificationListsMap(); + $monitor_list = read_config_option('monitor_list'); + $monitor_thold = read_config_option('monitor_reboot_thold'); + + foreach ($rebooted_hosts as $host) { + db_execute_prepared( + 'INSERT INTO plugin_monitor_reboot_history + (host_id, reboot_time) + VALUES (?, ?)', + [$host['id'], date(MONITOR_DATE_TIME_FORMAT, time() - intval($host['snmp_sysUpTimeInstance']))] + ); + + monitorAddNotificationList($reboot_emails, $monitor_list, $host['id'], $notification_lists); + + if ($monitor_thold == 'on') { + addTholdRebootRecipients($reboot_emails, $host['id'], $alert_emails, $notification_lists); + } + } + + return $reboot_emails; +} + +/** + * Dispatch reboot notifications per-recipient or as a single batched email. + * + * @param array $reboot_emails Recipient map keyed by email, then host id. + * + * @return void + */ +function sendRebootNotifications(array $reboot_emails): void +{ + $monitor_send_one_email = read_config_option('monitor_send_one_email'); + + if (!cacti_sizeof($reboot_emails)) { + return; + } + + $all_hosts = []; + $to_email = ''; + + foreach ($reboot_emails as $email => $hosts) { + if ($email == '') { + monitorDebug('Unable to process reboot notification due to empty Email address.'); + + continue; + } + + $to_email .= ($to_email != '' ? ',' : '') . $email; + $all_hosts = array_unique(array_merge($all_hosts, array_values($hosts))); + + if ($monitor_send_one_email !== 'on') { + monitorDebug('Processing the Email address: ' . $email); + processRebootEmail($email, $hosts); + } + } + + if ($monitor_send_one_email == 'on' && $to_email !== '') { + monitorDebug('Processing the Email address: ' . $to_email); + processRebootEmail($to_email, $all_hosts); + } +} + +/** + * Process reboot detection, uptime refresh, and down-event history logging. + * + * @return array{int, int} Reboot count and recent down count. + */ +function monitorUptimeChecker(): array +{ + monitorDebug('Checking for Uptime of Devices'); + + $alert_emails = getAlertEmails(); + + purgeOrphanMonitorRows('plugin_monitor_uptime'); + purgeOrphanMonitorRows('plugin_monitor_reboot_history'); + + $rebooted_hosts = getRebootedHosts(); + + if (cacti_sizeof($rebooted_hosts)) { + $reboot_emails = buildRebootEmailMap($rebooted_hosts, $alert_emails); + sendRebootNotifications($reboot_emails); + } + + // Freshen the uptimes + db_execute('REPLACE INTO plugin_monitor_uptime + (host_id, uptime) + SELECT id, snmp_sysUpTimeInstance + FROM host + WHERE snmp_version > 0 + AND status IN(2,3) + AND deleted = "" + AND monitor = "on" + AND snmp_sysUpTimeInstance > 0'); + + // Log Recently Down + db_execute('INSERT IGNORE INTO plugin_monitor_notify_history + (host_id, notify_type, notification_time, notes) + SELECT h.id, "3" AS notify_type, status_fail_date AS notification_time, status_last_error AS notes + FROM host AS h + WHERE status = 1 + AND deleted = "" + AND monitor = "on" + AND status_event_count = 1'); + + $recent = db_affected_rows(); + + return [cacti_sizeof($rebooted_hosts), $recent]; +} + +/** + * Build reboot details table/text and capture the last resolved host row. + * + * @param array $hosts Host ids to include. + * + * @return array{string, string, array} + */ +function buildRebootDetails(array $hosts): array +{ + $body_txt = ''; + $last_host = []; + + $body = '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= + '' . + '' . PHP_EOL; + $body .= '' . PHP_EOL; + + foreach ($hosts as $host_id) { + $host = db_fetch_row_prepared( + 'SELECT description, hostname + FROM host + WHERE id = ?', + [$host_id] + ); + + if (!cacti_sizeof($host)) { + continue; + } + + $last_host = $host; + $body .= '' . + '' . + '' . + '' . PHP_EOL; + + $body_txt .= + __('Description: ', 'monitor') . $host['description'] . PHP_EOL . + __('Hostname: ', 'monitor') . $host['hostname'] . PHP_EOL . PHP_EOL; + } + + $body .= '
' . __('Description', 'monitor') . '' . __('Hostname', 'monitor') . '
' . $host['description'] . '' . $host['hostname'] . '
' . PHP_EOL; + + return [$body, $body_txt, $last_host]; +} + +/** + * Build reboot notification subject from host count and delivery mode. + * + * @param array $hosts Host id list. + * @param array $last_host Last host row seen while building details. + * + * @return string + */ +function buildRebootSubject(array $hosts, array $last_host): string +{ + $subject = read_config_option('monitor_subject'); + $monitor_send_one_email = read_config_option('monitor_send_one_email'); + + if ($monitor_send_one_email == 'on' && cacti_sizeof($last_host)) { + return $subject . ' ' . $last_host['description'] . ' (' . $last_host['hostname'] . ')'; + } + + if (cacti_sizeof($hosts) == 1 && cacti_sizeof($last_host)) { + return $subject . ' 1 device - ' . $last_host['description'] . ' (' . $last_host['hostname'] . ')'; + } + + return $subject . ' ' . cacti_sizeof($hosts) . ' devices'; +} + +/** + * Wrap body output with report format template and build mail headers. + * + * @param string $body HTML body content. + * @param string $body_txt Plain text body content. + * + * @return array{string, string, array} + */ +function prepareReportOutput(string $body, string $body_txt): array +{ + $output = ''; + + $report_tag = ''; + $theme = 'modern'; + + monitorDebug('Loading Format File'); + + $format_ok = reports_load_format_file(read_config_option('monitor_format_file'), $output, $report_tag, $theme); + + monitorDebug('Format File Loaded, Format is ' . ($format_ok ? 'Ok' : 'Not Ok') . ', Report Tag is ' . $report_tag); + + if ($format_ok) { + if ($report_tag) { + $output = str_replace('', $body, $output); + } else { + $output = $output . PHP_EOL . $body; + } + } else { + $output = $body; + } + + monitorDebug('HTML Processed'); + + if (defined('CACTI_VERSION')) { + $version = CACTI_VERSION; + } else { + $version = get_cacti_version(); + } + + $headers = ['User-Agent' => 'Cacti-Monitor-v' . $version]; + + return [$output, $body_txt, $headers]; +} + +/** + * Render and send one reboot notification email payload. + * + * @param string $email Recipient email address string. + * @param array $hosts Host ids to include in the message. + * + * @return void + */ +function processRebootEmail(string $email, array $hosts): void +{ + monitorDebug("Reboot Processing for $email starting"); + + [$body, $body_txt, $last_host] = buildRebootDetails($hosts); + $subject = buildRebootSubject($hosts, $last_host); + + $template_output = read_config_option('monitor_body'); + $template_output = str_replace('
', $body, $template_output) . PHP_EOL; + + if (strpos($template_output, '
') !== false) { + $toutput = str_replace('
', $body_txt, $template_output) . PHP_EOL; + } else { + $toutput = $body_txt; + } + + if (read_config_option('monitor_reboot_notify') != 'on') { + return; + } + + [$output, $toutput, $headers] = prepareReportOutput($body, $toutput); + + processSendEmail($email, $subject, $output, $toutput, $headers, 'Reboot Notifications'); +} + +/** + * Resolve alert/warn host ids for requested global/list subscription scopes. + * + * @param array $lists Requested scopes (`global` or list ids). + * @param array $global_list Flattened global notification host ids by severity. + * @param array $notify_list Flattened list notification host ids by severity. + * + * @return array{array, array} + */ +function collectNotificationHosts(array $lists, array $global_list, array $notify_list): array +{ + $alert_hosts = []; + $warn_hosts = []; + + foreach ($lists as $list) { + if ($list === 'global') { + if (isset($global_list['alert'])) { + $alert_hosts = array_merge($alert_hosts, explode(',', $global_list['alert'])); + } + + if (isset($global_list['warn'])) { + $warn_hosts = array_merge($warn_hosts, explode(',', $global_list['warn'])); + } + + continue; + } + + if (isset($notify_list[$list]['alert'])) { + $alert_hosts = array_merge($alert_hosts, explode(',', $notify_list[$list]['alert'])); + } + + if (isset($notify_list[$list]['warn'])) { + $warn_hosts = array_merge($warn_hosts, explode(',', $notify_list[$list]['warn'])); + } + } + + return [$alert_hosts, $warn_hosts]; +} + +/** + * De-duplicate alert/warn host ids and log notification history entries. + * + * @param array $alert_hosts Alert host ids; normalized in place. + * @param array $warn_hosts Warning host ids; normalized in place. + * + * @return void + */ +function normalizeAndLogNotificationHosts(array &$alert_hosts, array &$warn_hosts): void +{ + if (cacti_sizeof($alert_hosts)) { + $alert_hosts = array_unique($alert_hosts, SORT_NUMERIC); + logMessages('alert', $alert_hosts); + } + + if (cacti_sizeof($warn_hosts)) { + $warn_hosts = array_unique($warn_hosts, SORT_NUMERIC); + logMessages('warn', $warn_hosts); + } +} + +/** + * Build shared intro copy for ping threshold email and text output. + * + * @param int $freq Resend frequency in minutes. + * + * @return array{string, string} + */ +function buildPingNotificationIntro(int|string $freq): array +{ + $body = '

' . __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor') . '

' . PHP_EOL; + $body_txt = __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor') . PHP_EOL; + + $message = __('The following report will identify Devices that have eclipsed their ping latency thresholds. You are receiving this report since you are subscribed to a Device associated with the Cacti system located at the following URL below.'); + + $body .= '

' . $message . '

' . PHP_EOL; + $body_txt .= $message . PHP_EOL; + + $body .= '

Cacti Monitoring Site

' . PHP_EOL; + $body_txt .= __('Cacti Monitoring Site', 'monitor') . PHP_EOL; + + if ($freq > 0) { + $body .= '

' . __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . '

' . PHP_EOL; + $body_txt .= __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . PHP_EOL; + } else { + $body .= '

' . __('You will receive notifications every time the Device is above its threshold.', 'monitor') . '

' . PHP_EOL; + $body_txt .= __('You will receive notifications every time the Device is above its threshold.', 'monitor') . PHP_EOL; + } + + return [$body, $body_txt]; +} + +/** + * Append one severity section for breached host thresholds. + * + * @param string $body HTML body output buffer. + * @param string $body_txt Plain text output buffer. + * @param array $host_ids Host ids for the section. + * @param array $criticalities Criticality label map. + * @param string $section_text Section heading text. + * @param string $threshold_field Host threshold field name. + * + * @return void + */ +function appendThresholdSection(string &$body, string &$body_txt, array $host_ids, array $criticalities, string $section_text, string $threshold_field): void +{ + global $config; + + if (!cacti_sizeof($host_ids)) { + return; + } + + $body .= '

' . __($section_text, 'monitor') . '

' . PHP_EOL; + $body_txt .= __($section_text, 'monitor') . PHP_EOL; + + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= + '' . + '' . + '' . + '' . PHP_EOL; + $body .= '' . PHP_EOL; + + $body_txt .= + __('Hostname', 'monitor') . "\t" . + __('Criticality', 'monitor') . "\t" . + __(MONITOR_ALERT_PING_LABEL, 'monitor') . "\t" . + __(MONITOR_CURRENT_PING_LABEL, 'monitor') . PHP_EOL; + + $hosts = db_fetch_assoc('SELECT * + FROM host + WHERE id IN(' . implode(',', $host_ids) . ') + AND deleted = ""'); + + if (cacti_sizeof($hosts)) { + foreach ($hosts as $host) { + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + $body .= '' . PHP_EOL; + + $body_txt .= + $host['description'] . "\t" . + $criticalities[$host['monitor_criticality']] . "\t" . + number_format_i18n($host[$threshold_field], 2) . " ms\t" . + number_format_i18n($host['cur_time'], 2) . ' ms' . PHP_EOL; + } + } + + $body .= '
' . __('Hostname', 'monitor') . '' . __('Criticality', 'monitor') . '' . __(MONITOR_ALERT_PING_LABEL, 'monitor') . '' . __(MONITOR_CURRENT_PING_LABEL, 'monitor') . '
' . $host['description'] . '' . $criticalities[$host['monitor_criticality']] . '' . number_format_i18n($host[$threshold_field], 2) . ' ms' . number_format_i18n($host['cur_time'], 2) . ' ms
' . PHP_EOL; +} + +/** + * Build log-friendly summary text for alert/warn delivery counts. + * + * @param array $alert_hosts Alert host ids. + * @param array $warn_hosts Warning host ids. + * + * @return string + */ +function buildNotificationStatus(array $alert_hosts, array $warn_hosts): string +{ + $status = ''; + + if (cacti_sizeof($alert_hosts)) { + $status = sizeof($alert_hosts) . ' Alert Notifications'; + } + + if (cacti_sizeof($warn_hosts)) { + if ($status !== '') { + $status .= ', and '; + } + + $status .= sizeof($warn_hosts) . ' Warning Notifications'; + } + + return $status; +} + +/** + * Build and send a ping-threshold notification for one recipient. + * + * @param string $email Recipient email. + * @param array $lists Requested scopes (`global` or list ids). + * @param array $global_list Flattened global host ids by severity. + * @param array $notify_list Flattened list host ids by severity. + * + * @return void + */ +function processEmail(string $email, array $lists, array $global_list, array $notify_list): void +{ + monitorDebug('Into Processing'); + + $criticalities = [ + 0 => __('Disabled', 'monitor'), + 1 => __('Low', 'monitor'), + 2 => __('Medium', 'monitor'), + 3 => __('High', 'monitor'), + 4 => __('Mission Critical', 'monitor') + ]; + + [$alert_hosts, $warn_hosts] = collectNotificationHosts($lists, $global_list, $notify_list); + monitorDebug('Lists Processed'); + + normalizeAndLogNotificationHosts($alert_hosts, $warn_hosts); + monitorDebug('Found ' . sizeof($alert_hosts) . ' Alert Hosts, and ' . sizeof($warn_hosts) . ' Warn Hosts'); + + if (!cacti_sizeof($alert_hosts) && !cacti_sizeof($warn_hosts)) { + return; + } + + monitorDebug('Formatting Email'); + + $freq = read_config_option('monitor_resend_frequency'); + $subject = __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor'); + [$body, $body_txt] = buildPingNotificationIntro($freq); + + appendThresholdSection( + $body, + $body_txt, + $alert_hosts, + $criticalities, + 'The following Devices have breached their Alert Notification Threshold.', + 'monitor_alert' + ); + + appendThresholdSection( + $body, + $body_txt, + $warn_hosts, + $criticalities, + 'The following Devices have breached their Warning Notification Threshold.', + 'monitor_warn' + ); + + [$output, $toutput, $headers] = prepareReportOutput($body, $body_txt); + $status = buildNotificationStatus($alert_hosts, $warn_hosts); + + processSendEmail($email, $subject, $output, $toutput, $headers, $status); +} + +/** + * Send an email through Cacti mailer with configured sender fallbacks. + * + * @param string $email Recipient email string. + * @param string $subject Message subject. + * @param string $output HTML output body. + * @param string $toutput Plain text output body. + * @param array $headers Extra headers passed to mailer. + * @param string $status Status text used in logs. + * + * @return void + */ +function processSendEmail(string $email, string $subject, string $output, string $toutput, array $headers, string $status): void +{ + $from_email = read_config_option('monitor_fromemail'); + + if ($from_email == '') { + $from_email = read_config_option('settings_from_email'); + + if ($from_email == '') { + $from_email = 'Cacti@cacti.net'; + } + } + + $from_name = read_config_option('monitor_fromname'); + + if ($from_name == '') { + $from_name = read_config_option('settings_from_name'); + + if ($from_name == '') { + $from_name = 'Cacti Reporting'; + } + } + + $html = true; + + if (read_config_option('thold_send_text_only') == 'on') { + $output = monitorText($toutput); + $html = false; + } + + monitorDebug("Sending Email to '$email' for $status"); + + $error = mailer( + [$from_email, $from_name], + $email, + '', + '', + '', + $subject, + $output, + monitorText($toutput), + null, + $headers, + $html + ); + + monitorDebug("The return from the mailer was '$error'"); + + if (strlen($error)) { + cacti_log("WARNING: Monitor had problems sending to '$email' for $status. The error was '$error'", false, 'MONITOR'); + } else { + cacti_log("NOTICE: Email Notification Sent to '$email' for $status.", false, 'MONITOR'); + } +} + +/** + * Convert HTML-ish report content into plain text line output. + * + * @param string $output HTML or mixed output. + * + * @return string + */ +function monitorText(string $output): string +{ + $output = explode(PHP_EOL, $output); + + $new_output = ''; + + if (cacti_sizeof($output)) { + foreach ($output as $line) { + $line = str_replace('
', PHP_EOL, $line); + $line = str_replace('
', PHP_EOL, $line); + $line = trim(strip_tags($line)); + $new_output .= $line . PHP_EOL; + } + } + + return $new_output; +} + +/** + * Persist alert or warning notification history rows once per host. + * + * @param string $type Severity type (`alert` or `warn`). + * @param array $alert_hosts Host ids to log for the severity. + * + * @return void + */ +function logMessages(string $type, array $alert_hosts): void +{ + global $start_date; + + static $processed = []; + + if ($type == 'warn') { + $type = '0'; + $column = 'monitor_warn'; + } elseif ($type == 'alert') { + $type = '1'; + $column = 'monitor_alert'; + } + + foreach ($alert_hosts as $id) { + if (!isset($processed[$id])) { + db_execute_prepared( + "INSERT INTO plugin_monitor_notify_history + (host_id, notify_type, ping_time, ping_threshold, notification_time) + SELECT id, '$type' AS notify_type, cur_time, $column, '$start_date' AS notification_time + FROM host + WHERE deleted = '' + AND monitor = 'on' + AND id = ?", + [$id] + ); + } + + $processed[$id] = true; + } +} + +/** + * Add grouped host ids to global/list collections by host email mode. + * + * @param string $type Severity key (`alert` or `warn`). + * @param array $entry Grouped SQL row containing mode/list/ids. + * @param array $global_list Global grouped bucket, updated in place. + * @param array $notify_list Per-list grouped bucket, updated in place. + * @param array $lists Set of list ids used for recipient lookups. + * + * @return void + */ +function addGroupedNotificationEntry(string $type, array $entry, array &$global_list, array &$notify_list, array &$lists): void +{ + if ($entry['thold_send_email'] == '1' || $entry['thold_send_email'] == '3') { + $global_list[$type][] = $entry; + } + + if (($entry['thold_send_email'] == '2' || $entry['thold_send_email'] == '3') && $entry['thold_host_email'] > 0) { + $notify_list[$type][$entry['thold_host_email']][] = $entry; + $lists[$entry['thold_host_email']] = $entry['thold_host_email']; + } +} + +/** + * Query and group threshold-breached hosts for one severity type. + * + * @param string $type Severity key (`alert` or `warn`). + * @param int $criticality Minimum criticality threshold. + * @param array $global_list Global grouped bucket, updated in place. + * @param array $notify_list Per-list grouped bucket, updated in place. + * @param array $lists Set of notification list ids, updated in place. + * + * @return void + */ +function getHostsByListType(string $type, int|string $criticality, array &$global_list, array &$notify_list, array &$lists): void +{ + $last_time = date(MONITOR_DATE_TIME_FORMAT, time() - read_config_option('monitor_resend_frequency') * 60); + + $hosts = db_fetch_cell_prepared( + "SELECT COUNT(*) + FROM host + WHERE status = 3 + AND deleted = '' + AND monitor = 'on' + AND thold_send_email > 0 + AND monitor_criticality >= ? + AND cur_time > monitor_$type", + [$criticality] + ); + + if ($hosts <= 0) { + return; + } + + $htype = ($type == 'warn') ? 1 : 0; + + $groups = db_fetch_assoc_prepared( + "SELECT + thold_send_email, thold_host_email, GROUP_CONCAT(host.id) AS id + FROM host + LEFT JOIN ( + SELECT host_id, MAX(notification_time) AS notification_time + FROM plugin_monitor_notify_history + WHERE notify_type = ? + GROUP BY host_id + ) AS nh + ON host.id=nh.host_id + WHERE status = 3 + AND deleted = '' + AND monitor = 'on' + AND thold_send_email > 0 + AND monitor_criticality >= ? + AND cur_time > monitor_$type " . ($type == 'warn' ? ' AND cur_time < monitor_alert' : '') . ' + AND (notification_time < ? OR notification_time IS NULL) + AND host.total_polls > 1 + GROUP BY thold_host_email, thold_send_email + ORDER BY thold_host_email, thold_send_email', + [$htype, $criticality, $last_time] + ); + + if (!cacti_sizeof($groups)) { + return; + } + + foreach ($groups as $entry) { + addGroupedNotificationEntry($type, $entry, $global_list, $notify_list, $lists); + } +} + +/** + * Flatten grouped host id chunks for a single severity bucket. + * + * @param array $list Grouped rows, each row containing a CSV `id` field. + * + * @return string + */ +function flattenGroupSeverityList(array $list): string +{ + $flattened = ''; + + foreach ($list as $item) { + $flattened .= ($flattened !== '' ? ',' : '') . $item['id']; + } + + return $flattened; +} + +/** + * Flatten grouped host ids for each notification list id. + * + * @param array $lists Grouped entries keyed by notification list id. + * + * @return array + */ +function flattenNotifySeverityLists(array $lists): array +{ + $flattened = []; + + foreach ($lists as $id => $list) { + $flattened[$id] = flattenGroupSeverityList($list); + } + + return $flattened; +} + +/** + * Flatten grouped global and per-list structures into CSV host id strings. + * + * @param array $global_list Global grouped structure, updated in place. + * @param array $notify_list Per-list grouped structure, updated in place. + * + * @return void + */ +function flattenLists(array &$global_list, array &$notify_list): void +{ + if (cacti_sizeof($global_list)) { + $new_global = []; + + foreach ($global_list as $severity => $list) { + $new_global[$severity] = flattenGroupSeverityList($list); + } + + $global_list = $new_global; + } + + if (cacti_sizeof($notify_list)) { + $new_list = []; + + foreach ($notify_list as $severity => $lists) { + $new_list[$severity] = flattenNotifySeverityLists($lists); + } + + $notify_list = $new_list; + } +} + +/** + * Add email addresses into the recipient scope map. + * + * @param array $notification_emails Recipient map, updated in place. + * @param array $emails Raw email values to normalize. + * @param string|int $scope_key Scope key (`global` or list id). + * + * @return void + */ +function addEmailsToNotificationMap(array &$notification_emails, array $emails, string|int $scope_key): void +{ + foreach ($emails as $user) { + $user = trim($user); + + if ($user !== '') { + $notification_emails[$user][$scope_key] = true; + } + } +} + +/** + * Build recipient map for global and notification list subscriptions. + * + * @param array $lists Notification list ids to resolve. + * + * @return array + */ +function getEmailsAndLists(array $lists): array +{ + $notification_emails = []; + + $alert_email = read_config_option('alert_email'); + $global_emails = ($alert_email != '') ? explode(',', $alert_email) : []; + + if (cacti_sizeof($global_emails)) { + addEmailsToNotificationMap($notification_emails, $global_emails, 'global'); + } + + if (!cacti_sizeof($lists)) { + return $notification_emails; + } + + $list_emails = db_fetch_assoc('SELECT id, emails + FROM plugin_notification_lists + WHERE id IN (' . implode(',', $lists) . ')'); + + if (!cacti_sizeof($list_emails)) { + return $notification_emails; + } + + foreach ($list_emails as $email) { + addEmailsToNotificationMap($notification_emails, explode(',', $email['emails']), $email['id']); + } + + return $notification_emails; +} + +/** + * Purge notification and reboot history records older than configured retention. + * + * @return array{int, int} Purged notify count and purged reboot count. + */ +function purgeEventRecords(): array +{ + // Purge old records + $days = read_config_option('monitor_log_storage'); + + if (empty($days)) { + $days = 120; + } + + db_execute_prepared( + 'DELETE FROM plugin_monitor_notify_history + WHERE notification_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))', + [$days] + ); + + $purge_n = db_affected_rows(); + + db_execute_prepared( + 'DELETE FROM plugin_monitor_reboot_history + WHERE log_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))', + [$days] + ); + + $purge_r = db_affected_rows(); + + return [$purge_n, $purge_r]; +} + +/** + * Print a debug line when poller debug mode is enabled. + * + * @param string $message Debug message. + * + * @return void + */ +function monitorDebug(string $message): void +{ + global $debug; + + if ($debug) { + print trim($message) . PHP_EOL; + } +} + +/** + * Print monitor poller version information. + * + * @return void + */ +function displayVersion(): void +{ + global $config; + + if (!function_exists('pluginMonitorVersion')) { + include_once $config['base_path'] . '/plugins/monitor/setup.php'; + } + + $info = pluginMonitorVersion(); + print 'Cacti Monitor Poller, Version ' . $info['version'] . ', ' . COPYRIGHT_YEARS . PHP_EOL; +} + +/** + * Print CLI help output for this poller entrypoint. + * + * @return void + */ +function displayHelp(): void +{ + displayVersion(); + + print PHP_EOL; + print 'usage: poller_monitor.php [--debug]' . PHP_EOL . PHP_EOL; + print ' --debug - debug execution, e.g. for testing' . PHP_EOL . PHP_EOL; +} diff --git a/poller_monitor.php b/poller_monitor.php index 433da8a..756ef64 100644 --- a/poller_monitor.php +++ b/poller_monitor.php @@ -1,4 +1,7 @@ 0 || $alert_criticality > 0) { - monitor_debug('Monitor Notification Enabled for Devices'); + monitorDebug('Monitor Notification Enabled for Devices'); - // Get hosts that are above threshold. Start with Alert, and then Warning - if ($alert_criticality) { - get_hosts_by_list_type('alert', $alert_criticality, $global_list, $notify_list, $lists); - } + // Get hosts that are above threshold. Start with Alert, and then Warning. + if ($alert_criticality) { + getHostsByListType('alert', $alert_criticality, $global_list, $notify_list, $lists); + } - if ($warning_criticality) { - get_hosts_by_list_type('warn', $warning_criticality, $global_list, $notify_list, $lists); - } + if ($warning_criticality) { + getHostsByListType('warn', $warning_criticality, $global_list, $notify_list, $lists); + } - flatten_lists($global_list, $notify_list); + flattenLists($global_list, $notify_list); - monitor_debug('Lists Flattened there are ' . sizeof($global_list) . ' Global Notifications and ' . sizeof($notify_list) . ' Notification List Notifications.'); + monitorDebug('Lists Flattened there are ' . sizeof($global_list) . ' Global Notifications and ' . sizeof($notify_list) . ' Notification List Notifications.'); - if (strlen(read_config_option('alert_email')) == 0) { - monitor_debug('WARNING: No Global List Defined. Please set under Settings -> Thresholds'); - cacti_log('WARNING: No Global Notification List defined. Please set under Settings -> Thresholds', false, 'MONITOR'); - } + if (strlen(read_config_option('alert_email')) == 0) { + monitorDebug('WARNING: No Global List Defined. Please set under Settings -> Thresholds'); + cacti_log('WARNING: No Global Notification List defined. Please set under Settings -> Thresholds', false, 'MONITOR'); + } - if (cacti_sizeof($global_list) || sizeof($notify_list)) { - // array of email[list|'g'] = true; - $notification_emails = get_emails_and_lists($lists); + if (cacti_sizeof($global_list) || sizeof($notify_list)) { + // array of email[list|'g'] = true; + $notification_emails = getEmailsAndLists($lists); - // Send out emails to each emails address with all notifications in one - if (cacti_sizeof($notification_emails)) { - foreach ($notification_emails as $email => $lists) { - monitor_debug('Processing the email address: ' . $email); - process_email($email, $lists, $global_list, $notify_list); + // Send out emails to each emails address with all notifications in one + if (cacti_sizeof($notification_emails)) { + foreach ($notification_emails as $email => $lists) { + monitorDebug('Processing the email address: ' . $email); + processEmail($email, $lists, $global_list, $notify_list); - $notifications++; - } - } - } + $notifications++; + } + } + } } else { - monitor_debug('Both Warning and Alert Notification are Disabled.'); + monitorDebug('Both Warning and Alert Notification are Disabled.'); } -[$purge_n, $purge_r] = purge_event_records(); +[$purge_n, $purge_r] = purgeEventRecords(); $poller_end = microtime(true); $stats = - 'Time:' . round($poller_end - $poller_start, 2) . - ' Reboots:' . $reboots . - ' DownDevices:' . $recent_down . - ' Notifications:' . $notifications . - ' Purges:' . ($purge_n + $purge_r); + 'Time:' . round($poller_end - $poller_start, 2) . + ' Reboots:' . $reboots . + ' DownDevices:' . $recent_down . + ' Notifications:' . $notifications . + ' Purges:' . ($purge_n + $purge_r); cacti_log('MONITOR STATS: ' . $stats, false, 'SYSTEM'); set_config_option('stats_monitor', $stats); exit; - -function monitor_addemails(&$reboot_emails, $alert_emails, $host_id) { - if (cacti_sizeof($alert_emails)) { - foreach ($alert_emails as $email) { - $reboot_emails[trim(strtolower($email))][$host_id] = $host_id; - } - } -} - -function monitor_addnotificationlist(&$reboot_emails, $notify_list, $host_id, $notification_lists) { - if ($notify_list > 0) { - if (isset($notification_lists[$notify_list])) { - $emails = explode(',', $notification_lists[$notify_list]); - monitor_addemails($reboot_emails, $emails, $host_id); - } - } -} - -function monitor_uptime_checker() { - monitor_debug('Checking for Uptime of Devices'); - - $start = date('Y-m-d H:i:s'); - - $reboot_emails = []; - - $alert_email = read_config_option('alert_email'); - - if ($alert_email != '') { - $alert_emails = explode(',', $alert_email); - } else { - $alert_emails = []; - } - - // Remove unneeded device records in associated tables - $removed_hosts = db_fetch_assoc('SELECT mu.host_id - FROM plugin_monitor_uptime AS mu - LEFT JOIN host AS h - ON h.id = mu.host_id - WHERE h.id IS NULL'); - - if (cacti_sizeof($removed_hosts)) { - db_execute('DELETE mu - FROM plugin_monitor_uptime AS mu - LEFT JOIN host AS h - ON h.id = mu.host_id - WHERE h.id IS NULL'); - } - - $removed_hosts = db_fetch_assoc('SELECT mu.host_id - FROM plugin_monitor_reboot_history AS mu - LEFT JOIN host AS h - ON h.id = mu.host_id - WHERE h.id IS NULL'); - - if (cacti_sizeof($removed_hosts)) { - db_execute('DELETE mu - FROM plugin_monitor_reboot_history AS mu - LEFT JOIN host AS h - ON h.id = mu.host_id - WHERE h.id IS NULL'); - } - - // Get the rebooted devices - $rebooted_hosts = db_fetch_assoc('SELECT h.id, h.description, - h.hostname, h.snmp_sysUpTimeInstance, mu.uptime - FROM host AS h - LEFT JOIN plugin_monitor_uptime AS mu - ON h.id = mu.host_id - WHERE h.snmp_version > 0 - AND status IN (2,3) - AND h.deleted = "" - AND h.monitor = "on" - AND (mu.uptime IS NULL OR mu.uptime > h.snmp_sysUpTimeInstance) - AND h.snmp_sysUpTimeInstance > 0'); - - if (cacti_sizeof($rebooted_hosts)) { - $notification_lists = array_rekey( - db_fetch_assoc('SELECT id, emails - FROM plugin_notification_lists - ORDER BY id'), - 'id', 'emails' - ); - - $monitor_list = read_config_option('monitor_list'); - $monitor_thold = read_config_option('monitor_reboot_thold'); - - foreach ($rebooted_hosts as $host) { - db_execute_prepared('INSERT INTO plugin_monitor_reboot_history - (host_id, reboot_time) - VALUES (?, ?)', - [$host['id'], date('Y-m-d H:i:s', time() - intval($host['snmp_sysUpTimeInstance']))]); - - monitor_addnotificationlist($reboot_emails, $monitor_list, $host['id'], $notification_lists); - - if ($monitor_thold == 'on') { - $notify = db_fetch_row_prepared('SELECT thold_send_email, thold_host_email - FROM host - WHERE id = ?', - [$host['id']]); - - if (cacti_sizeof($notify)) { - switch($notify['thold_send_email']) { - case '0': // Disabled - break; - case '1': // Global List - monitor_addemails($reboot_emails, $alert_emails, $host['id']); - - break; - case '2': // Nofitication List - monitor_addnotificationlist($reboot_emails, $notify['thold_host_email'], - $host['id'], $notification_lists); - - break; - case '3': // Both Global and Nofication list - monitor_addemails($reboot_emails, $alert_emails, $host['id']); - monitor_addnotificationlist($reboot_emails, $notify['thold_host_email'], - $host['id'], $notification_lists); - - break; - } - } - } - } - - $monitor_send_one_email = read_config_option('monitor_send_one_email'); - $to_email = ''; - - if (cacti_sizeof($reboot_emails)) { - foreach ($reboot_emails as $email => $hosts) { - if ($email != '') { - $to_email .= ($to_email != '' ? ',' : '') . $email; - - if ($monitor_send_one_email !== 'on') { - monitor_debug('Processing the Email address: ' . $email); - process_reboot_email($email, $hosts); - } - } else { - monitor_debug('Unable to process reboot notification due to empty Email address.'); - } - } - - if ($monitor_send_one_email == 'on') { - monitor_debug('Processing the Email address: ' . $to_email); - process_reboot_email($to_email, $hosts); - } - } - } - - // Freshen the uptimes - db_execute('REPLACE INTO plugin_monitor_uptime - (host_id, uptime) - SELECT id, snmp_sysUpTimeInstance - FROM host - WHERE snmp_version > 0 - AND status IN(2,3) - AND deleted = "" - AND monitor = "on" - AND snmp_sysUpTimeInstance > 0'); - - // Log Recently Down - db_execute('INSERT IGNORE INTO plugin_monitor_notify_history - (host_id, notify_type, notification_time, notes) - SELECT h.id, "3" AS notify_type, status_fail_date AS notification_time, status_last_error AS notes - FROM host AS h - WHERE status = 1 - AND deleted = "" - AND monitor = "on" - AND status_event_count = 1'); - - $recent = db_affected_rows(); - - return [cacti_sizeof($rebooted_hosts), $recent]; -} - -function process_reboot_email($email, $hosts) { - monitor_debug("Reboot Processing for $email starting"); - - $body_txt = ''; - - $body = '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body .= - '' . - '' . PHP_EOL; - - $body .= '' . PHP_EOL; - - foreach ($hosts as $host) { - $host = db_fetch_row_prepared('SELECT description, hostname - FROM host - WHERE id = ?', - [$host]); - - if (cacti_sizeof($host)) { - $body .= '' . - '' . - '' . - '' . PHP_EOL; - - $body_txt .= - __('Description: ', 'monitor') . $host['description'] . PHP_EOL . - __('Hostname: ', 'monitor') . $host['hostname'] . PHP_EOL . PHP_EOL; - } - } - - $body .= '
' . __('Description', 'monitor') . '' . __('Hostname', 'monitor') . '
' . $host['description'] . '' . $host['hostname'] . '
' . PHP_EOL; - - $subject = read_config_option('monitor_subject'); - $monitor_send_one_email = read_config_option('monitor_send_one_email'); - - if ($monitor_send_one_email == 'on') { - $subject .= ' ' . $host['description'] . ' (' . $host['hostname'] . ')'; - } else { - if (cacti_sizeof($hosts) == 1) { - $subject .= ' 1 device - ' . $host['description'] . ' (' . $host['hostname'] . ')'; - } else { - $subject .= ' ' . cacti_sizeof($hosts) . ' devices'; - } - } - - $output = read_config_option('monitor_body'); - $output = str_replace('
', $body, $output) . PHP_EOL; - - if (strpos($output, '
') !== false) { - $toutput = str_replace('
', $body_txt, $output) . PHP_EOL; - } else { - $toutput = $body_txt; - } - - if (read_config_option('monitor_reboot_notify') == 'on') { - $report_tag = ''; - $theme = 'modern'; - - monitor_debug('Loading Format File'); - - $format_ok = reports_load_format_file(read_config_option('monitor_format_file'), $output, $report_tag, $theme); - - monitor_debug('Format File Loaded, Format is ' . ($format_ok ? 'Ok' : 'Not Ok') . ', Report Tag is ' . $report_tag); - - if ($format_ok) { - if ($report_tag) { - $output = str_replace('', $body, $output); - } else { - $output = $output . PHP_EOL . $body; - } - } else { - $output = $body; - } - - monitor_debug('HTML Processed'); - - if (defined('CACTI_VERSION')) { - $version = CACTI_VERSION; - } else { - $version = get_cacti_version(); - } - - $headers['User-Agent'] = 'Cacti-Monitor-v' . $version; - - $status = 'Reboot Notifications'; - - process_send_email($email, $subject, $output, $toutput, $headers, $status); - } -} - -function process_email($email, $lists, $global_list, $notify_list) { - global $config; - - monitor_debug('Into Processing'); - - $alert_hosts = []; - $warn_hosts = []; - - $criticalities = [ - 0 => __('Disabled', 'monnitor'), - 1 => __('Low', 'monnitor'), - 2 => __('Medium', 'monnitor'), - 3 => __('High', 'monnitor'), - 4 => __('Mission Critical', 'monnitor') - ]; - - foreach ($lists as $list) { - switch($list) { - case 'global': - $hosts = []; - - if (isset($global_list['alert'])) { - $alert_hosts += explode(',', $global_list['alert']); - } - - if (isset($global_list['warn'])) { - $warn_hosts += explode(',', $global_list['warn']); - } - - break; - default: - if (isset($notify_list[$list]['alert'])) { - $alert_hosts = explode(',', $notify_list[$list]['alert']); - } - - if (isset($notify_list[$list]['warn'])) { - $warn_hosts = explode(',', $notify_list[$list]['warn']); - } - - break; - } - } - - monitor_debug('Lists Processed'); - - if (cacti_sizeof($alert_hosts)) { - $alert_hosts = array_unique($alert_hosts, SORT_NUMERIC); - - log_messages('alert', $alert_hosts); - } - - if (cacti_sizeof($warn_hosts)) { - $warn_hosts = array_unique($warn_hosts, SORT_NUMERIC); - - log_messages('warn', $alert_hosts); - } - - monitor_debug('Found ' . sizeof($alert_hosts) . ' Alert Hosts, and ' . sizeof($warn_hosts) . ' Warn Hosts'); - - if (cacti_sizeof($alert_hosts) || sizeof($warn_hosts)) { - monitor_debug('Formatting Email'); - - $freq = read_config_option('monitor_resend_frequency'); - $subject = __('Cacti Monitor Plugin Ping Threshold Notification', 'monitor'); - - $body = '

' . __('Cacti Monitor Plugin Ping Threshold Notification', 'monitor') . '

' . PHP_EOL; - $body_txt = __('Cacti Monitor Plugin Ping Threshold Notification', 'monitor') . PHP_EOL; - - $body .= '

' . __('The following report will identify Devices that have eclipsed their ping latency thresholds. You are receiving this report since you are subscribed to a Device associated with the Cacti system located at the following URL below.') . '

' . PHP_EOL; - - $body_txt .= __('The following report will identify Devices that have eclipsed their ping latency thresholds. You are receiving this report since you are subscribed to a Device associated with the Cacti system located at the following URL below.') . PHP_EOL; - - $body .= '

Cacti Monitoring Site

' . PHP_EOL; - - $body_txt .= __('Cacti Monitoring Site', 'monitor') . PHP_EOL; - - if ($freq > 0) { - $body .= '

' . __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . '

' . PHP_EOL; - - $body_txt .= __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . PHP_EOL; - } else { - $body .= '

' . __('You will receive notifications every time the Device is above its threshold.', 'monitor') . '

' . PHP_EOL; - - $body_txt .= __('You will receive notifications every time the Device is above its threshold.', 'monitor') . PHP_EOL; - } - - if (cacti_sizeof($alert_hosts)) { - $body .= '

' . __('The following Devices have breached their Alert Notification Threshold.', 'monitor') . '

' . PHP_EOL; - - $body_txt .= __('The following Devices have breached their Alert Notification Threshold.', 'monitor') . PHP_EOL; - - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body .= - '' . - '' . - '' . - '' . PHP_EOL; - - $body_txt .= - __('Hostname', 'monitor') . "\t" . - __('Criticality', 'monitor') . "\t" . - __('Alert Ping', 'monitor') . "\t" . - __('Current Ping', 'monitor') . PHP_EOL; - - $body .= '' . PHP_EOL; - - $hosts = db_fetch_assoc('SELECT * - FROM host - WHERE id IN(' . implode(',', $alert_hosts) . ') - AND deleted = ""'); - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $host) { - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body_txt .= - $host['description'] . "\t" . - $criticalities[$host['monitor_criticality']] . "\t" . - number_format_i18n($host['monitor_alert'],2) . " ms\t" . - number_format_i18n($host['cur_time'],2) . ' ms' . PHP_EOL; - - $body .= '' . PHP_EOL; - } - } - - $body .= '
' . __('Hostname', 'monitor') . '' . __('Criticality', 'monitor') . '' . __('Alert Ping', 'monitor') . '' . __('Current Ping', 'monitor') . '
' . $host['description'] . '' . $criticalities[$host['monitor_criticality']] . '' . number_format_i18n($host['monitor_alert'],2) . ' ms' . number_format_i18n($host['cur_time'],2) . ' ms
' . PHP_EOL; - } - - if (cacti_sizeof($warn_hosts)) { - $body .= '

' . __('The following Devices have breached their Warning Notification Threshold.', 'monitor') . '

' . PHP_EOL; - - $body_txt .= __('The following Devices have breached their Warning Notification Threshold.', 'monitor') . PHP_EOL; - - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body .= - '' . - '' . - '' . - '' . PHP_EOL; - - $body_txt .= - __('Hostname', 'monitor') . "\t" . - __('Criticality', 'monitor') . "\t" . - __('Alert Ping', 'monitor') . "\t" . - __('Current Ping', 'monitor') . PHP_EOL; - - $body .= '' . PHP_EOL; - - $hosts = db_fetch_assoc('SELECT * - FROM host - WHERE id IN(' . implode(',', $warn_hosts) . ') - AND deleted = ""'); - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $host) { - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - $body .= '' . PHP_EOL; - - $body_txt .= - $host['description'] . "\t" . - $criticalities[$host['monitor_criticality']] . "\t" . - number_format_i18n($host['monitor_alert'],2) . " ms\t" . - number_format_i18n($host['cur_time'],2) . ' ms' . PHP_EOL; - - $body .= '' . PHP_EOL; - } - } - $body .= '
' . __('Hostname', 'monitor') . '' . __('Criticality', 'monitor') . '' . __('Alert Ping', 'monitor') . '' . __('Current Ping', 'monitor') . '
' . $host['description'] . '' . $criticalities[$host['monitor_criticality']] . '' . number_format_i18n($host['monitor_warn'],2) . ' ms' . number_format_i18n($host['cur_time'],2) . ' ms
' . PHP_EOL; - } - - $output = ''; - $toutput = $body_txt; - $report_tag = ''; - $theme = 'modern'; - - monitor_debug('Loading Format File'); - - $format_ok = reports_load_format_file(read_config_option('monitor_format_file'), $output, $report_tag, $theme); - - monitor_debug('Format File Loaded, Format is ' . ($format_ok ? 'Ok' : 'Not Ok') . ', Report Tag is ' . $report_tag); - - if ($format_ok) { - if ($report_tag) { - $output = str_replace('', $body, $output); - } else { - $output = $output . PHP_EOL . $body; - } - } else { - $output = $body; - } - - monitor_debug('HTML Processed'); - - if (defined('CACTI_VERSION')) { - $version = CACTI_VERSION; - } else { - $version = get_cacti_version(); - } - - $headers['User-Agent'] = 'Cacti-Monitor-v' . $version; - - $status = (cacti_sizeof($alert_hosts) ? sizeof($alert_hosts) . ' Alert Notifications' : '') . - (cacti_sizeof($warn_hosts) ? (cacti_sizeof($alert_hosts) ? ', and ' : '') . - sizeof($warn_hosts) . ' Warning Notifications' : ''); - - process_send_email($email, $subject, $output, $toutput, $headers, $status); - } -} - -function process_send_email($email, $subject, $output, $toutput, $headers, $status) { - $from_email = read_config_option('monitor_fromemail'); - - if ($from_email == '') { - $from_email = read_config_option('settings_from_email'); - - if ($from_email == '') { - $from_email = 'Cacti@cacti.net'; - } - } - - $from_name = read_config_option('monitor_fromname'); - - if ($from_name != '') { - $from_name = read_config_option('settings_from_name'); - - if ($from_name == '') { - $from_name = 'Cacti Reporting'; - } - } - - $html = true; - - if (read_config_option('thold_send_text_only') == 'on') { - $output = monitor_text($toutput); - $html = false; - } - - monitor_debug("Sending Email to '$email' for $status"); - - $error = mailer( - [$from_email, $from_name], - $email, - '', - '', - '', - $subject, - $output, - monitor_text($toutput), - null, - $headers, - $html - ); - - monitor_debug("The return from the mailer was '$error'"); - - if (strlen($error)) { - cacti_log("WARNING: Monitor had problems sending to '$email' for $status. The error was '$error'", false, 'MONITOR'); - } else { - cacti_log("NOTICE: Email Notification Sent to '$email' for $status.", false, 'MONITOR'); - } -} - -function monitor_text($output) { - $output = explode(PHP_EOL, $output); - - $new_output = ''; - - if (cacti_sizeof($output)) { - foreach ($output as $line) { - $line = str_replace('
', PHP_EOL, $line); - $line = str_replace('
', PHP_EOL, $line); - $line = trim(strip_tags($line)); - $new_output .= $line . PHP_EOL; - } - } - - return $new_output; -} - -function log_messages($type, $alert_hosts) { - global $start_date; - - static $processed = []; - - if ($type == 'warn') { - $type = '0'; - $column = 'monitor_warn'; - } elseif ($type == 'alert') { - $type = '1'; - $column = 'monitor_alert'; - } - - foreach ($alert_hosts as $id) { - if (!isset($processed[$id])) { - db_execute_prepared("INSERT INTO plugin_monitor_notify_history - (host_id, notify_type, ping_time, ping_threshold, notification_time) - SELECT id, '$type' AS notify_type, cur_time, $column, '$start_date' AS notification_time - FROM host - WHERE deleted = '' - AND monitor = 'on' - AND id = ?", - [$id]); - } - - $processed[$id] = true; - } -} - -function get_hosts_by_list_type($type, $criticality, &$global_list, &$notify_list, &$lists) { - global $force; - - $last_time = date('Y-m-d H:i:s', time() - read_config_option('monitor_resend_frequency') * 60); - - $hosts = db_fetch_cell_prepared("SELECT COUNT(*) - FROM host - WHERE status = 3 - AND deleted = '' - AND monitor = 'on' - AND thold_send_email > 0 - AND monitor_criticality >= ? - AND cur_time > monitor_$type", - [$criticality]); - - if ($type == 'warn') { - $htype = 1; - } else { - $htype = 0; - } - - if ($hosts > 0) { - $groups = db_fetch_assoc_prepared("SELECT - thold_send_email, thold_host_email, GROUP_CONCAT(host.id) AS id - FROM host - LEFT JOIN ( - SELECT host_id, MAX(notification_time) AS notification_time - FROM plugin_monitor_notify_history - WHERE notify_type = ? - GROUP BY host_id - ) AS nh - ON host.id=nh.host_id - WHERE status = 3 - AND deleted = '' - AND monitor = 'on' - AND thold_send_email > 0 - AND monitor_criticality >= ? - AND cur_time > monitor_$type " . ($type == 'warn' ? ' AND cur_time < monitor_alert' : '') . ' - AND (notification_time < ? OR notification_time IS NULL) - AND host.total_polls > 1 - GROUP BY thold_host_email, thold_send_email - ORDER BY thold_host_email, thold_send_email', - [$htype, $criticality, $last_time]); - - if (cacti_sizeof($groups)) { - foreach ($groups as $entry) { - switch($entry['thold_send_email']) { - case '1': // Global List - $global_list[$type][] = $entry; - - break; - case '2': // Notification List - if ($entry['thold_host_email'] > 0) { - $notify_list[$type][$entry['thold_host_email']][] = $entry; - $lists[$entry['thold_host_email']] = $entry['thold_host_email']; - } - - break; - case '3': // Both Notification and Global - $global_list[$type][] = $entry; - - if ($entry['thold_host_email'] > 0) { - $notify_list[$type][$entry['thold_host_email']][] = $entry; - $lists[$entry['thold_host_email']] = $entry['thold_host_email']; - } - - break; - } - } - } - } -} - -function flatten_lists(&$global_list, &$notify_list) { - if (cacti_sizeof($global_list)) { - foreach ($global_list as $severity => $list) { - foreach ($list as $item) { - $new_global[$severity] = (isset($new_global[$severity]) ? $new_global[$severity] . ',' : '') . $item['id']; - } - } - $global_list = $new_global; - } - - if (cacti_sizeof($notify_list)) { - foreach ($notify_list as $severity => $lists) { - foreach ($lists as $id => $list) { - foreach ($list as $item) { - $new_list[$severity][$id] = (isset($new_list[$severity][$id]) ? $new_list[$severity][$id] . ',' : '') . $item['id']; - } - } - } - $notify_list = $new_list; - } -} - -function get_emails_and_lists($lists) { - $notification_emails = []; - - $alert_email = read_config_option('alert_email'); - - if ($alert_email != '') { - $global_emails = explode(',', $alert_email); - } else { - $global_emails = []; - } - - if (cacti_sizeof($global_emails)) { - foreach ($global_emails as $index => $user) { - if (trim($user) != '') { - $notification_emails[trim($user)]['global'] = true; - } - } - } - - if (cacti_sizeof($lists)) { - $list_emails = db_fetch_assoc('SELECT id, emails - FROM plugin_notification_lists - WHERE id IN (' . implode(',', $lists) . ')'); - - if (cacti_sizeof($list_emails)) { - foreach ($list_emails as $email) { - $emails = explode(',', $email['emails']); - - foreach ($emails as $user) { - if (trim($user) != '') { - $notification_emails[trim($user)][$email['id']] = true; - } - } - } - } - } - - return $notification_emails; -} - -function purge_event_records() { - // Purge old records - $days = read_config_option('monitor_log_storage'); - - if (empty($days)) { - $days = 120; - } - - db_execute_prepared('DELETE FROM plugin_monitor_notify_history - WHERE notification_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))', - [$days]); - - $purge_n = db_affected_rows(); - - db_execute_prepared('DELETE FROM plugin_monitor_reboot_history - WHERE log_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))', - [$days]); - - $purge_r = db_affected_rows(); - - return [$purge_n, $purge_r]; -} - -function monitor_debug($message) { - global $debug; - - if ($debug) { - print trim($message) . PHP_EOL; - } -} - -function display_version() { - global $config; - - if (!function_exists('plugin_monitor_version')) { - include_once($config['base_path'] . '/plugins/monitor/setup.php'); - } - - $info = plugin_monitor_version(); - print 'Cacti Monitor Poller, Version ' . $info['version'] . ', ' . COPYRIGHT_YEARS . PHP_EOL; -} - -/* - * display_help - * displays the usage of the function - */ -function display_help() { - display_version(); - - print PHP_EOL; - print 'usage: poller_monitor.php [--force] [--debug]' . PHP_EOL . PHP_EOL; - print ' --force - force execution, e.g. for testing' . PHP_EOL; - print ' --debug - debug execution, e.g. for testing' . PHP_EOL . PHP_EOL; -} diff --git a/setup.php b/setup.php index 8304a92..f939999 100644 --- a/setup.php +++ b/setup.php @@ -1,4 +1,6 @@ __('Any', 'monitor'), - '0' => __('None', 'monitor'), - '1' => __('Low', 'monitor'), - '2' => __('Medium', 'monitor'), - '3' => __('High', 'monitor'), - '4' => __('Mission Critical', 'monitor') - ]; - - $filters['criticality'] = [ - 'friendly_name' => __('Criticality', 'monitor'), - 'method' => 'drop_array', - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1', - 'array' => $criticalities, - 'value' => '-1' - ]; - - return $filters; +/** + * Determine whether current runtime satisfies plugin PHP requirement. + * + * @return bool + */ +function monitorHasSupportedPhpVersion(): bool +{ + return version_compare(PHP_VERSION, monitorGetMinimumPhpVersion(), '>='); } -function monitor_device_sql_where($sql_where) { - if (get_request_var('criticality') >= 0) { - $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' monitor_criticality = ' . get_request_var('criticality'); - } +/** + * Enforce plugin minimum PHP version and optionally raise a UI message. + * + * @param bool $raise_message Whether to raise a Cacti UI error message. + * + * @return bool + */ +function monitorEnsureSupportedPhpVersion(bool $raise_message = true): bool +{ + if (monitorHasSupportedPhpVersion()) { + return true; + } + + $message = sprintf( + 'Monitor plugin requires PHP %s or newer. Current runtime: %s', + monitorGetMinimumPhpVersion(), + PHP_VERSION + ); + + cacti_log($message, false, 'MONITOR'); + + if ($raise_message) { + raise_message('monitor_php_version', $message, MESSAGE_LEVEL_ERROR); + } + + return false; +} - return $sql_where; +/** + * Register monitor plugin hooks, realm, defaults, and schema. + * + * @return void + */ +function pluginMonitorInstall(): void +{ + if (!monitorEnsureSupportedPhpVersion()) { + return; + } + + // core plugin functionality + api_plugin_register_hook('monitor', 'top_header_tabs', 'monitorShowTab', 'setup.php'); + api_plugin_register_hook('monitor', 'top_graph_header_tabs', 'monitorShowTab', 'setup.php'); + api_plugin_register_hook('monitor', 'top_graph_refresh', 'monitorTopGraphRefresh', 'setup.php'); + + api_plugin_register_hook('monitor', 'draw_navigation_text', 'monitorDrawNavigationText', 'setup.php'); + api_plugin_register_hook('monitor', 'config_form', 'monitorConfigForm', 'setup.php'); + api_plugin_register_hook('monitor', 'config_settings', 'monitorConfigSettings', 'setup.php'); + api_plugin_register_hook('monitor', 'config_arrays', 'monitorConfigArrays', 'setup.php'); + api_plugin_register_hook('monitor', 'poller_bottom', 'monitorPollerBottom', 'setup.php'); + api_plugin_register_hook('monitor', 'page_head', 'pluginMonitorPageHead', 'setup.php'); + + // device actions and interaction + api_plugin_register_hook('monitor', 'api_device_save', 'monitorApiDeviceSave', 'setup.php'); + api_plugin_register_hook('monitor', 'device_action_array', 'monitorDeviceActionArray', 'setup.php'); + api_plugin_register_hook('monitor', 'device_action_execute', 'monitorDeviceActionExecute', 'setup.php'); + api_plugin_register_hook('monitor', 'device_action_prepare', 'monitorDeviceActionPrepare', 'setup.php'); + api_plugin_register_hook('monitor', 'device_remove', 'monitorDeviceRemove', 'setup.php'); + + // add new filter for device + api_plugin_register_hook('monitor', 'device_filters', 'monitorDeviceFilters', 'setup.php'); + api_plugin_register_hook('monitor', 'device_sql_where', 'monitorDeviceSqlWhere', 'setup.php'); + api_plugin_register_hook('monitor', 'device_table_bottom', 'monitorDeviceTableBottom', 'setup.php'); + + api_plugin_register_realm('monitor', 'monitor.php', 'View Monitoring Dashboard', 1); + + set_config_option('monitor_view', 'default'); + set_config_option('monitor_grouping', 'default'); + set_config_option('monitor_trim', '4000'); + set_config_option('monitor_rows', 100); + + monitorSetupTable(); } -function monitor_device_table_bottom() { - $criticalities = [ - '-1' => __('Any', 'monitor'), - '0' => __('None', 'monitor'), - '1' => __('Low', 'monitor'), - '2' => __('Medium', 'monitor'), - '3' => __('High', 'monitor'), - '4' => __('Mission Critical', 'monitor') - ]; - - if (version_compare(CACTI_VERSION, '1.3.0', '<')) { - $select = '' . __('Criticality') . ''; +/** + * Add monitor criticality filter definition to device filter list. + * + * @param array $filters Existing device filters. + * + * @return array + */ +function monitorDeviceFilters(array $filters): array +{ + $criticalities = [ + '-1' => __('Any', 'monitor'), + '0' => __('None', 'monitor'), + '1' => __('Low', 'monitor'), + '2' => __('Medium', 'monitor'), + '3' => __('High', 'monitor'), + '4' => __('Mission Critical', 'monitor') + ]; + + $filters['criticality'] = [ + 'friendly_name' => __('Criticality', 'monitor'), + 'method' => 'drop_array', + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1', + 'array' => $criticalities, + 'value' => '-1' + ]; + + return $filters; +} + +/** + * Append monitor criticality SQL predicate to host filter query. + * + * @param string $sql_where Existing WHERE fragment. + * + * @return string + */ +function monitorDeviceSqlWhere(string $sql_where): string +{ + if (get_request_var('criticality') >= 0) { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' monitor_criticality = ' . get_request_var('criticality'); + } + + return $sql_where; +} - ?> +/** + * Render legacy criticality filter control for pre-1.3 device table UI. + * + * @return void + */ +function monitorDeviceTableBottom(): void +{ + $criticalities = [ + '-1' => __('Any', 'monitor'), + '0' => __('None', 'monitor'), + '1' => __('Low', 'monitor'), + '2' => __('Medium', 'monitor'), + '3' => __('High', 'monitor'), + '4' => __('Mission Critical', 'monitor') + ]; + + if (version_compare(CACTI_VERSION, '1.3.0', '<')) { + $select = '' . __('Criticality') . ''; + + ?> 300) { - set_config_option('monitor_refresh', '300'); - } + // Here we will check to ensure everything is configured + monitorCheckUpgrade(); - return true; -} + include_once($config['library_path'] . '/database.php'); + $r = read_config_option('monitor_refresh'); + + if ($r == '' || $r < 1 || $r > 300) { + set_config_option('monitor_refresh', '300'); + } -function plugin_monitor_upgrade() { - // Here we will upgrade to the newest version - monitor_check_upgrade(); + return true; +} - return false; +/** + * Execute plugin upgrade checks when plugin manager requests upgrade. + * + * @return bool + */ +function pluginMonitorUpgrade(): bool +{ + if (!monitorEnsureSupportedPhpVersion()) { + return false; + } + + // Here we will upgrade to the newest version + monitorCheckUpgrade(); + + return false; } -function monitor_check_upgrade() { - $files = ['plugins.php', 'monitor.php']; +/** + * Apply pending monitor plugin schema/metadata upgrades. + * + * @return void + */ +function monitorCheckUpgrade(): void +{ + $files = ['plugins.php', 'monitor.php']; - if (isset($_SERVER['PHP_SELF']) && !in_array(basename($_SERVER['PHP_SELF']), $files, true)) { - return; - } + if (isset($_SERVER['PHP_SELF']) && !in_array(basename($_SERVER['PHP_SELF']), $files, true)) { + return; + } - $info = plugin_monitor_version(); - $current = $info['version']; - $old = db_fetch_cell('SELECT version FROM plugin_config WHERE directory = "monitor"'); + $info = pluginMonitorVersion(); + $current = $info['version']; + $old = db_fetch_cell('SELECT version FROM plugin_config WHERE directory = "monitor"'); - if ($current != $old) { - monitor_setup_table(); + if ($current != $old) { + monitorSetupTable(); - api_plugin_register_hook('monitor', 'page_head', 'plugin_monitor_page_head', 'setup.php', 1); + api_plugin_register_hook('monitor', 'page_head', 'pluginMonitorPageHead', 'setup.php', 1); - db_execute('ALTER TABLE host MODIFY COLUMN monitor char(3) DEFAULT "on"'); + db_execute('ALTER TABLE host MODIFY COLUMN monitor char(3) DEFAULT "on"'); - db_execute('ALTER TABLE plugin_monitor_uptime + db_execute('ALTER TABLE plugin_monitor_uptime MODIFY COLUMN uptime BIGINT unsigned NOT NULL default "0"'); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_icon', 'type' => 'varchar(30)', 'NULL' => false, 'default' => '', 'after' => 'monitor_alert']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_icon', 'type' => 'varchar(30)', 'NULL' => false, 'default' => '', 'after' => 'monitor_alert']); - if (function_exists('api_plugin_upgrade_register')) { - api_plugin_upgrade_register('monitor'); - } else { - db_execute_prepared('UPDATE plugin_config + if (function_exists('api_plugin_upgrade_register')) { + api_plugin_upgrade_register('monitor'); + } else { + db_execute_prepared( + 'UPDATE plugin_config SET version = ?, name = ?, author = ?, webpage = ? WHERE directory = ?', - [ - $info['version'], - $info['longname'], - $info['author'], - $info['homepage'], - $info['name'] - ] - ); - } - } + [ + $info['version'], + $info['longname'], + $info['author'], + $info['homepage'], + $info['name'] + ] + ); + } + } } -function plugin_monitor_version() { - global $config; - $info = parse_ini_file($config['base_path'] . '/plugins/monitor/INFO', true); - - return $info['info']; +/** + * Read monitor plugin metadata from INFO file. + * + * @return array + */ +function pluginMonitorVersion(): array +{ + global $config; + $info = parse_ini_file($config['base_path'] . '/plugins/monitor/INFO', true); + + return $info['info']; } -function monitor_device_action_execute($action) { - global $config, $fields_host_edit; - - if ($action != 'monitor_enable' && $action != 'monitor_disable' && $action != 'monitor_settings') { - return $action; - } - - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - - if ($selected_items != false) { - if ($action == 'monitor_enable' || $action == 'monitor_disable') { - for ($i = 0; ($i < count($selected_items)); $i++) { - if ($action == 'monitor_enable') { - db_execute_prepared('UPDATE host +/** + * Execute custom monitor bulk actions for selected devices. + * + * @param string $action Requested device action key. + * + * @return string + */ +function monitorDeviceActionExecute(string $action): string +{ + global $config, $fields_host_edit; + + if ($action != 'monitor_enable' && $action != 'monitor_disable' && $action != 'monitor_settings') { + return $action; + } + + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if ($selected_items != false) { + if ($action == 'monitor_enable' || $action == 'monitor_disable') { + for ($i = 0; ($i < count($selected_items)); $i++) { + if ($action == 'monitor_enable') { + db_execute_prepared( + 'UPDATE host SET monitor = "on" WHERE deleted = "" AND id = ?', - [$selected_items[$i]]); - } elseif ($action == 'monitor_disable') { - db_execute_prepared('UPDATE host + [$selected_items[$i]] + ); + } elseif ($action == 'monitor_disable') { + db_execute_prepared( + 'UPDATE host SET monitor = "" WHERE deleted = "" AND id = ?', - [$selected_items[$i]]); - } - } - } else { - for ($i = 0; ($i < count($selected_items)); $i++) { - reset($fields_host_edit); - - foreach ($fields_host_edit as $field_name => $field_array) { - if (isset_request_var("t_$field_name")) { - if ($field_name == 'monitor_alert_baseline') { - $cur_time = db_fetch_cell_prepared('SELECT cur_time + [$selected_items[$i]] + ); + } + } + } else { + for ($i = 0; ($i < count($selected_items)); $i++) { + reset($fields_host_edit); + + foreach ($fields_host_edit as $field_name => $field_array) { + if (isset_request_var("t_$field_name")) { + if ($field_name == 'monitor_alert_baseline') { + $cur_time = db_fetch_cell_prepared( + 'SELECT cur_time FROM host WHERE deleted = "" AND id = ?', - [$selected_items[$i]]); + [$selected_items[$i]] + ); - if ($cur_time > 0) { - db_execute_prepared('UPDATE host + if ($cur_time > 0) { + db_execute_prepared( + 'UPDATE host SET monitor_alert = CEIL(avg_time*?) WHERE deleted = "" AND id = ?', - [get_nfilter_request_var($field_name), $selected_items[$i]]); - } - } elseif ($field_name == 'monitor_warn_baseline') { - $cur_time = db_fetch_cell_prepared('SELECT cur_time + [get_nfilter_request_var($field_name), $selected_items[$i]] + ); + } + } elseif ($field_name == 'monitor_warn_baseline') { + $cur_time = db_fetch_cell_prepared( + 'SELECT cur_time FROM host WHERE deleted = "" AND id = ?', - [$selected_items[$i]]); + [$selected_items[$i]] + ); - if ($cur_time > 0) { - db_execute_prepared('UPDATE host + if ($cur_time > 0) { + db_execute_prepared( + 'UPDATE host SET monitor_warn = CEIL(avg_time*?) WHERE deleted = "" AND id = ?', - [get_nfilter_request_var($field_name), $selected_items[$i]]); - } - } else { - db_execute_prepared("UPDATE host + [get_nfilter_request_var($field_name), $selected_items[$i]] + ); + } + } else { + db_execute_prepared( + "UPDATE host SET $field_name = ? WHERE deleted='' AND id = ?", - [get_nfilter_request_var($field_name), $selected_items[$i]]); - } - } - } - } - } - } - - return $action; + [get_nfilter_request_var($field_name), $selected_items[$i]] + ); + } + } + } + } + } + } + + return $action; } -function monitor_device_remove($devices) { - db_execute('DELETE FROM plugin_monitor_notify_history WHERE host_id IN(' . implode(',', $devices) . ')'); - db_execute('DELETE FROM plugin_monitor_reboot_history WHERE host_id IN(' . implode(',', $devices) . ')'); - db_execute('DELETE FROM plugin_monitor_uptime WHERE host_id IN(' . implode(',', $devices) . ')'); - - return $devices; +/** + * Remove monitor history/uptime rows for deleted devices. + * + * @param array $devices Device ids being removed. + * + * @return array + */ +function monitorDeviceRemove(array $devices): array +{ + db_execute('DELETE FROM plugin_monitor_notify_history WHERE host_id IN(' . implode(',', $devices) . ')'); + db_execute('DELETE FROM plugin_monitor_reboot_history WHERE host_id IN(' . implode(',', $devices) . ')'); + db_execute('DELETE FROM plugin_monitor_uptime WHERE host_id IN(' . implode(',', $devices) . ')'); + + return $devices; } -function monitor_device_action_prepare($save) { - global $host_list, $fields_host_edit; - - if (!isset($save['drp_action'])) { - return $save; - } else { - $action = $save['drp_action']; - - if ($action != 'monitor_enable' && $action != 'monitor_disable' && $action != 'monitor_settings') { - return $save; - } - - if ($action == 'monitor_enable' || $action == 'monitor_disable') { - if ($action == 'monitor_enable') { - $action_description = 'enable'; - } elseif ($action == 'monitor_disable') { - $action_description = 'disable'; - } - - print " +/** + * Render confirmation/edit UI for monitor-specific bulk device actions. + * + * @param array $save Bulk action context payload. + * + * @return array + */ +function monitorDeviceActionPrepare(array $save): array +{ + global $host_list, $fields_host_edit; + + if (!isset($save['drp_action'])) { + return $save; + } else { + $action = $save['drp_action']; + + if ($action != 'monitor_enable' && $action != 'monitor_disable' && $action != 'monitor_settings') { + return $save; + } + + if ($action == 'monitor_enable' || $action == 'monitor_disable') { + if ($action == 'monitor_enable') { + $action_description = 'enable'; + } elseif ($action == 'monitor_disable') { + $action_description = 'disable'; + } + + print "

" . __('Click \'Continue\' to %s monitoring on these Device(s)', $action_description, 'monitor') . "

    " . $save['host_list'] . '

'; - } else { - print " + } else { + print "

" . __('Click \'Continue\' to Change the Monitoring settings for the following Device(s). Remember to check \'Update this Field\' to indicate which columns to update.', 'monitor') . "

    " . $save['host_list'] . '

'; - $form_array = []; - $fields = [ - 'monitor', - 'monitor_text', - 'monitor_criticality', - 'monitor_warn', - 'monitor_alert', - 'monitor_warn_baseline', - 'monitor_alert_baseline', - 'monitor_icon' - ]; - - foreach ($fields as $field) { - $form_array += [$field => $fields_host_edit[$field]]; - - $form_array[$field]['value'] = ''; - $form_array[$field]['form_id'] = 0; - $form_array[$field]['sub_checkbox'] = [ - 'name' => 't_' . $field, - 'friendly_name' => __('Update this Field', 'monitor'), - 'value' => '' - ]; - } - - draw_edit_form( - [ - 'config' => ['no_form_tag' => true], - 'fields' => $form_array - ] - ); - } - - return $save; - } + $form_array = []; + $fields = [ + 'monitor', + 'monitor_text', + 'monitor_criticality', + 'monitor_warn', + 'monitor_alert', + 'monitor_warn_baseline', + 'monitor_alert_baseline', + 'monitor_icon' + ]; + + foreach ($fields as $field) { + $form_array += [$field => $fields_host_edit[$field]]; + + $form_array[$field]['value'] = ''; + $form_array[$field]['form_id'] = 0; + $form_array[$field]['sub_checkbox'] = [ + 'name' => 't_' . $field, + 'friendly_name' => __('Update this Field', 'monitor'), + 'value' => '' + ]; + } + + draw_edit_form( + [ + 'config' => ['no_form_tag' => true], + 'fields' => $form_array + ] + ); + } + + return $save; + } } -function monitor_device_action_array($device_action_array) { - $device_action_array['monitor_settings'] = __('Change Monitoring Options', 'monitor'); - $device_action_array['monitor_enable'] = __('Enable Monitoring', 'monitor'); - $device_action_array['monitor_disable'] = __('Disable Monitoring', 'monitor'); - - return $device_action_array; +/** + * Register monitor-specific entries in device action dropdown. + * + * @param array $device_action_array Existing action map. + * + * @return array + */ +function monitorDeviceActionArray(array $device_action_array): array +{ + $device_action_array['monitor_settings'] = __('Change Monitoring Options', 'monitor'); + $device_action_array['monitor_enable'] = __('Enable Monitoring', 'monitor'); + $device_action_array['monitor_disable'] = __('Disable Monitoring', 'monitor'); + + return $device_action_array; } -function monitor_scan_dir() { - global $config; - - $ext = ['.wav', '.mp3']; - $d = dir($config['base_path'] . '/plugins/monitor/sounds/'); - $files = []; - - while (false !== ($entry = $d->read())) { - if ($entry != '.' && $entry != '..' && in_array(strtolower(substr($entry,-4)),$ext, true)) { - $files[$entry] = $entry; - } - } - $d->close(); - asort($files); // sort the files - array_unshift($files, 'None'); // prepend the None option - - return $files; +/** + * Scan monitor sounds directory and return selectable alarm files. + * + * @return array + */ +function monitorScanDir(): array +{ + global $config; + + $ext = ['.wav', '.mp3']; + $d = dir($config['base_path'] . '/plugins/monitor/sounds/'); + $files = []; + + while (false !== ($entry = $d->read())) { + if ($entry != '.' && $entry != '..' && in_array(strtolower(substr($entry, -4)), $ext, true)) { + $files[$entry] = $entry; + } + } + $d->close(); + asort($files); // sort the files + array_unshift($files, 'None'); // prepend the None option + + return $files; } -function monitor_config_settings() { - global $tabs, $formats, $settings, $criticalities, $page_refresh_interval, $config, $settings_user, $tabs_graphs; - - include_once($config['base_path'] . '/lib/reports.php'); - - if (get_nfilter_request_var('tab') == 'monitor') { - $formats = reports_get_format_files(); - } elseif (empty($formats)) { - $formats = []; - } - - $criticalities = [ - 0 => __('Disabled', 'monitor'), - 1 => __('Low', 'monitor'), - 2 => __('Medium', 'monitor'), - 3 => __('High', 'monitor'), - 4 => __('Mission Critical', 'monitor') - ]; - - $log_retentions = [ - '-1' => __('Indefinitely', 'monitor'), - '31' => __('%d Month', 1, 'monitor'), - '62' => __('%d Months', 2, 'monitor'), - '93' => __('%d Months', 3, 'monitor'), - '124' => __('%d Months', 4, 'monitor'), - '186' => __('%d Months', 6, 'monitor'), - '365' => __('%d Year', 1, 'monitor') - ]; - - $font_sizes = [ - '20' => '20px', - '30' => '30px', - '40' => '40px', - '50' => '50px', - '60' => '60px', - '70' => '70px' - ]; - - if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('Normal User'), ['monitor.php']); - } - - $tabs_graphs += ['monitor' => __('Monitor Settings', 'monitor')]; - - $settings_user += [ - 'monitor' => [ - 'monitor_sound' => [ - 'friendly_name' => __('Alarm Sound', 'monitor'), - 'description' => __('This is the sound file that will be played when a Device goes down.', 'monitor'), - 'method' => 'drop_array', - 'array' => monitor_scan_dir(), - 'default' => 'attn-noc.wav', - ], - 'monitor_sound_loop' => [ - 'friendly_name' => __('Loop Alarm Sound', 'monitor'), - 'description' => __('Play the above sound on a loop when a Device goes down.', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_legend' => [ - 'friendly_name' => __('Show Icon Legend', 'monitor'), - 'description' => __('Check this to show an icon legend on the Monitor display', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_uptime' => [ - 'friendly_name' => __('Show Uptime', 'monitor'), - 'description' => __('Check this to show Uptime on the Monitor display', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_error_zoom' => [ - 'friendly_name' => __('Zoom to Errors', 'monitor'), - 'description' => __('Check this to zoom to errored items on the Monitor display', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_error_background' => [ - 'friendly_name' => __('Zoom Background', 'monitor'), - 'description' => __('Background Color for Zoomed Errors on the Monitor display', 'monitor'), - 'method' => 'drop_color', - ], - 'monitor_error_fontsize' => [ - 'friendly_name' => __('Zoom Fontsize', 'monitor'), - 'description' => __('Check this to zoom to errored items on the Monitor display', 'monitor'), - 'method' => 'drop_array', - 'default' => '50', - 'array' => $font_sizes - ] - ] - ]; - - if (get_current_page() != 'settings.php') { - return; - } - - $tabs['monitor'] = __('Monitor', 'monitor'); - - $temp = [ - 'monitor_header' => [ - 'friendly_name' => __('Monitor Settings', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ], - 'monitor_new_enabled' => [ - 'friendly_name' => __('Enable on new devices', 'monitor'), - 'description' => __('Check this to automatically enable monitoring when creating new devices', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_log_storage' => [ - 'friendly_name' => __('Notification/Reboot Log Retention', 'monitor'), - 'description' => __('Keep Notification and Reboot Logs for this number of days.', 'monitor'), - 'method' => 'drop_array', - 'default' => '31', - 'array' => $log_retentions - ], - 'monitor_sound' => [ - 'friendly_name' => __('Alarm Sound', 'monitor'), - 'description' => __('This is the sound file that will be played when a Device goes down.', 'monitor'), - 'method' => 'drop_array', - 'array' => monitor_scan_dir(), - 'default' => 'attn-noc.wav', - ], - 'monitor_sound_loop' => [ - 'friendly_name' => __('Loop Alarm Sound', 'monitor'), - 'description' => __('Play the above sound on a loop when a Device goes down.', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_refresh' => [ - 'friendly_name' => __('Refresh Interval', 'monitor'), - 'description' => __('This is the time in seconds before the page refreshes. (1 - 300)', 'monitor'), - 'method' => 'drop_array', - 'default' => '60', - 'array' => $page_refresh_interval - ], - 'monitor_legend' => [ - 'friendly_name' => __('Show Icon Legend', 'monitor'), - 'description' => __('Check this to show an icon legend on the Monitor display', 'monitor'), - 'method' => 'checkbox', - ], - 'monitor_grouping' => [ - 'friendly_name' => __('Grouping', 'monitor'), - 'description' => __('This is how monitor will Group Devices.', 'monitor'), - 'method' => 'drop_array', - 'default' => 'default', - 'array' => [ - 'default' => __('Default', 'monitor'), - 'default_by_permissions' => __('Default with permissions', 'monitor'), - 'group_by_tree' => __('Tree', 'monitor'), - 'group_by_device_template' => __('Device Template', 'monitor'), - ] - ], - 'monitor_view' => [ - 'friendly_name' => __('View', 'monitor'), - 'description' => __('This is how monitor will render Devices.', 'monitor'), - 'method' => 'drop_array', - 'default' => 'default', - 'array' => [ - 'default' => __('Default', 'monitor'), - 'list' => __('List', 'monitor'), - 'names' => __('Names only', 'monitor'), - 'tiles' => __('Tiles', 'monitor'), - 'tilesadt' => __('Tiles & Downtime', 'monitor') - ] - ], - 'monitor_format_header' => [ - 'friendly_name' => __('Notification Report Format', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ], - 'monitor_format_file' => [ - 'friendly_name' => __('Format File to Use', 'monitor'), - 'method' => 'drop_array', - 'default' => 'default.format', - 'description' => __('Choose the custom html wrapper and CSS file to use. This file contains both html and CSS to wrap around your report. If it contains more than simply CSS, you need to place a special tag inside of the file. This format tag will be replaced by the report content. These files are located in the \'formats\' directory.', 'monitor'), - 'array' => $formats - ], - 'monitor_threshold' => [ - 'friendly_name' => __('Ping Threshold Notifications', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ], - 'monitor_warn_criticality' => [ - 'friendly_name' => __('Warning Latency Notification', 'monitor'), - 'description' => __('If a Device has a Round Trip Ping Latency above the Warning Threshold and above the Criticality below, subscribing emails to the Device will receive an email notification. Select \'Disabled\' to Disable. The Thold Plugin is required to enable this feature.', 'monitor'), - 'method' => 'drop_array', - 'default' => '0', - 'array' => $criticalities - ], - 'monitor_alert_criticality' => [ - 'friendly_name' => __('Alert Latency Notification', 'monitor'), - 'description' => __('If a Device has a Round Trip Ping Latency above the Alert Threshold and above the Criticality below, subscribing emails to the Device will receive an email notification. Select \'Disabled\' to Disable. The Thold Plugin is required to enable this feature.', 'monitor'), - 'method' => 'drop_array', - 'default' => '0', - 'array' => $criticalities - ], - 'monitor_resend_frequency' => [ - 'friendly_name' => __('How Often to Resend Emails', 'monitor'), - 'description' => __('How often should emails notifications be sent to subscribers for these Devices if they are exceeding their latency thresholds', 'monitor'), - 'method' => 'drop_array', - 'default' => '0', - 'array' => [ - '0' => __('Every Occurrence', 'monitor'), - '20' => __('Every %d Minutes', 20, 'monitor'), - '30' => __('Every %d Minutes', 30, 'monitor'), - '60' => __('Every Hour', 'monitor'), - '120' => __('Every %d Hours', 2, 'monitor'), - '240' => __('Every %d Hours', 4, 'monitor') - ] - ], - 'monitor_reboot' => [ - 'friendly_name' => __('Reboot Notifications', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ], - 'monitor_reboot_notify' => [ - 'friendly_name' => __('Send Reboot Notifications', 'monitor'), - 'method' => 'checkbox', - 'description' => __('Should Device Reboot Notifications be sent to users?', 'monitor'), - 'default' => 'on', - ], - 'monitor_send_one_email' => [ - 'friendly_name' => __('Send one Email to all addresses', 'monitor'), - 'description' => __('If checked, the system will send one Email only to all addresses.', 'monitor'), - 'method' => 'checkbox', - 'default' => 'on' - ], - 'monitor_reboot_thold' => [ - 'friendly_name' => __('Include Threshold Alert Lists', 'monitor'), - 'method' => 'checkbox', - 'description' => __('Should Threshold Alert Lists also receive Notification', 'monitor'), - 'default' => 'on', - ], - 'monitor_subject' => [ - 'friendly_name' => __('Subject', 'monitor'), - 'description' => __('Enter a Reboot message subject for the Reboot Notification.', 'monitor'), - 'method' => 'textbox', - 'default' => __('Cacti Device Reboot Notification', 'monitor'), - 'size' => 60, - 'max_length' => 60 - ], - 'monitor_body' => [ - 'friendly_name' => __('Email Body', 'monitor'), - 'description' => __('Enter an Email body to include in the Reboot Notification message. Currently, the only supported replacement tag accepted is <DETAILS>', 'monitor'), - 'method' => 'textarea', - 'textarea_rows' => 4, - 'textarea_cols' => 80, - 'default' => __('

Monitor Reboot Notification

The following Device\'s were Rebooted. See details below for additional information.


', 'monitor') - ], - 'monitor_email_header' => [ - 'friendly_name' => __('Notification Email Addresses', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ], - 'monitor_fromname' => [ - 'friendly_name' => __('From Name', 'monitor'), - 'description' => __('Enter the Email Name to send the notifications from', 'monitor'), - 'method' => 'textbox', - 'size' => '60', - 'max_length' => '255' - ], - 'monitor_fromemail' => [ - 'friendly_name' => __('From Address', 'monitor'), - 'description' => __('Enter the Email Address to send the notification from', 'monitor'), - 'method' => 'textbox', - 'size' => '60', - 'max_length' => '255' - ], - 'monitor_list' => [ - 'friendly_name' => __('Notification List', 'thold'), - 'description' => __('Select a Notification List below. All Emails subscribed to the notification list will be notified.', 'thold'), - 'method' => 'drop_sql', - 'sql' => 'SELECT id, name FROM plugin_notification_lists ORDER BY name', - 'default' => '', - 'none_value' => __('None', 'monitor') - ], - 'monitor_emails' => [ - 'friendly_name' => __('Email Addresses', 'monitor'), - 'description' => __('Enter a comma delimited list of Email addresses to inform of a reboot event.', 'monitor'), - 'method' => 'textarea', - 'textarea_rows' => 2, - 'textarea_cols' => 80, - 'default' => '' - ] - ]; - - if (isset($settings['monitor'])) { - $settings['monitor'] = array_merge($settings['monitor'], $temp); - } else { - $settings['monitor'] = $temp; - } +/** + * Register monitor plugin settings and user preference definitions. + * + * @return void + */ +function monitorConfigSettings(): void +{ + global $tabs, $formats, $settings, $criticalities, $page_refresh_interval, $config, $settings_user, $tabs_graphs; + + include_once($config['base_path'] . '/lib/reports.php'); + + if (get_nfilter_request_var('tab') == 'monitor') { + $formats = reports_get_format_files(); + } elseif (empty($formats)) { + $formats = []; + } + + $criticalities = [ + 0 => __('Disabled', 'monitor'), + 1 => __('Low', 'monitor'), + 2 => __('Medium', 'monitor'), + 3 => __('High', 'monitor'), + 4 => __('Mission Critical', 'monitor') + ]; + + $log_retentions = [ + '-1' => __('Indefinitely', 'monitor'), + '31' => __('%d Month', 1, 'monitor'), + '62' => __('%d Months', 2, 'monitor'), + '93' => __('%d Months', 3, 'monitor'), + '124' => __('%d Months', 4, 'monitor'), + '186' => __('%d Months', 6, 'monitor'), + '365' => __('%d Year', 1, 'monitor') + ]; + + $font_sizes = [ + '20' => '20px', + '30' => '30px', + '40' => '40px', + '50' => '50px', + '60' => '60px', + '70' => '70px' + ]; + + if (function_exists('auth_augment_roles')) { + auth_augment_roles(__('Normal User'), ['monitor.php']); + } + + $tabs_graphs += ['monitor' => __('Monitor Settings', 'monitor')]; + + $settings_user += [ + 'monitor' => [ + 'monitor_sound' => [ + 'friendly_name' => __('Alarm Sound', 'monitor'), + 'description' => __('This is the sound file that will be played when a Device goes down.', 'monitor'), + 'method' => 'drop_array', + 'array' => monitorScanDir(), + 'default' => 'attn-noc.wav', + ], + 'monitor_sound_loop' => [ + 'friendly_name' => __('Loop Alarm Sound', 'monitor'), + 'description' => __('Play the above sound on a loop when a Device goes down.', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_legend' => [ + 'friendly_name' => __('Show Icon Legend', 'monitor'), + 'description' => __('Check this to show an icon legend on the Monitor display', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_uptime' => [ + 'friendly_name' => __('Show Uptime', 'monitor'), + 'description' => __('Check this to show Uptime on the Monitor display', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_error_zoom' => [ + 'friendly_name' => __('Zoom to Errors', 'monitor'), + 'description' => __('Check this to zoom to errored items on the Monitor display', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_error_background' => [ + 'friendly_name' => __('Zoom Background', 'monitor'), + 'description' => __('Background Color for Zoomed Errors on the Monitor display', 'monitor'), + 'method' => 'drop_color', + ], + 'monitor_error_fontsize' => [ + 'friendly_name' => __('Zoom Fontsize', 'monitor'), + 'description' => __('Check this to zoom to errored items on the Monitor display', 'monitor'), + 'method' => 'drop_array', + 'default' => '50', + 'array' => $font_sizes + ] + ] + ]; + + if (get_current_page() != 'settings.php') { + return; + } + + $tabs['monitor'] = __('Monitor', 'monitor'); + + $temp = [ + 'monitor_header' => [ + 'friendly_name' => __('Monitor Settings', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ], + 'monitor_new_enabled' => [ + 'friendly_name' => __('Enable on new devices', 'monitor'), + 'description' => __('Check this to automatically enable monitoring when creating new devices', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_log_storage' => [ + 'friendly_name' => __('Notification/Reboot Log Retention', 'monitor'), + 'description' => __('Keep Notification and Reboot Logs for this number of days.', 'monitor'), + 'method' => 'drop_array', + 'default' => '31', + 'array' => $log_retentions + ], + 'monitor_sound' => [ + 'friendly_name' => __('Alarm Sound', 'monitor'), + 'description' => __('This is the sound file that will be played when a Device goes down.', 'monitor'), + 'method' => 'drop_array', + 'array' => monitorScanDir(), + 'default' => 'attn-noc.wav', + ], + 'monitor_sound_loop' => [ + 'friendly_name' => __('Loop Alarm Sound', 'monitor'), + 'description' => __('Play the above sound on a loop when a Device goes down.', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_refresh' => [ + 'friendly_name' => __('Refresh Interval', 'monitor'), + 'description' => __('This is the time in seconds before the page refreshes. (1 - 300)', 'monitor'), + 'method' => 'drop_array', + 'default' => '60', + 'array' => $page_refresh_interval + ], + 'monitor_legend' => [ + 'friendly_name' => __('Show Icon Legend', 'monitor'), + 'description' => __('Check this to show an icon legend on the Monitor display', 'monitor'), + 'method' => 'checkbox', + ], + 'monitor_grouping' => [ + 'friendly_name' => __('Grouping', 'monitor'), + 'description' => __('This is how monitor will Group Devices.', 'monitor'), + 'method' => 'drop_array', + 'default' => 'default', + 'array' => [ + 'default' => __('Default', 'monitor'), + 'default_by_permissions' => __('Default with permissions', 'monitor'), + 'group_by_tree' => __('Tree', 'monitor'), + 'group_by_device_template' => __('Device Template', 'monitor'), + ] + ], + 'monitor_view' => [ + 'friendly_name' => __('View', 'monitor'), + 'description' => __('This is how monitor will render Devices.', 'monitor'), + 'method' => 'drop_array', + 'default' => 'default', + 'array' => [ + 'default' => __('Default', 'monitor'), + 'list' => __('List', 'monitor'), + 'names' => __('Names only', 'monitor'), + 'tiles' => __('Tiles', 'monitor'), + 'tilesadt' => __('Tiles & Downtime', 'monitor') + ] + ], + 'monitor_format_header' => [ + 'friendly_name' => __('Notification Report Format', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ], + 'monitor_format_file' => [ + 'friendly_name' => __('Format File to Use', 'monitor'), + 'method' => 'drop_array', + 'default' => 'default.format', + 'description' => __('Choose the custom html wrapper and CSS file to use. This file contains both html and CSS to wrap around your report. If it contains more than simply CSS, you need to place a special tag inside of the file. This format tag will be replaced by the report content. These files are located in the \'formats\' directory.', 'monitor'), + 'array' => $formats + ], + 'monitor_threshold' => [ + 'friendly_name' => __('Ping Threshold Notifications', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ], + 'monitor_warn_criticality' => [ + 'friendly_name' => __('Warning Latency Notification', 'monitor'), + 'description' => __('If a Device has a Round Trip Ping Latency above the Warning Threshold and above the Criticality below, subscribing emails to the Device will receive an email notification. Select \'Disabled\' to Disable. The Thold Plugin is required to enable this feature.', 'monitor'), + 'method' => 'drop_array', + 'default' => '0', + 'array' => $criticalities + ], + 'monitor_alert_criticality' => [ + 'friendly_name' => __('Alert Latency Notification', 'monitor'), + 'description' => __('If a Device has a Round Trip Ping Latency above the Alert Threshold and above the Criticality below, subscribing emails to the Device will receive an email notification. Select \'Disabled\' to Disable. The Thold Plugin is required to enable this feature.', 'monitor'), + 'method' => 'drop_array', + 'default' => '0', + 'array' => $criticalities + ], + 'monitor_resend_frequency' => [ + 'friendly_name' => __('How Often to Resend Emails', 'monitor'), + 'description' => __('How often should emails notifications be sent to subscribers for these Devices if they are exceeding their latency thresholds', 'monitor'), + 'method' => 'drop_array', + 'default' => '0', + 'array' => [ + '0' => __('Every Occurrence', 'monitor'), + '20' => __('Every %d Minutes', 20, 'monitor'), + '30' => __('Every %d Minutes', 30, 'monitor'), + '60' => __('Every Hour', 'monitor'), + '120' => __('Every %d Hours', 2, 'monitor'), + '240' => __('Every %d Hours', 4, 'monitor') + ] + ], + 'monitor_reboot' => [ + 'friendly_name' => __('Reboot Notifications', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ], + 'monitor_reboot_notify' => [ + 'friendly_name' => __('Send Reboot Notifications', 'monitor'), + 'method' => 'checkbox', + 'description' => __('Should Device Reboot Notifications be sent to users?', 'monitor'), + 'default' => 'on', + ], + 'monitor_send_one_email' => [ + 'friendly_name' => __('Send one Email to all addresses', 'monitor'), + 'description' => __('If checked, the system will send one Email only to all addresses.', 'monitor'), + 'method' => 'checkbox', + 'default' => 'on' + ], + 'monitor_reboot_thold' => [ + 'friendly_name' => __('Include Threshold Alert Lists', 'monitor'), + 'method' => 'checkbox', + 'description' => __('Should Threshold Alert Lists also receive Notification', 'monitor'), + 'default' => 'on', + ], + 'monitor_subject' => [ + 'friendly_name' => __('Subject', 'monitor'), + 'description' => __('Enter a Reboot message subject for the Reboot Notification.', 'monitor'), + 'method' => 'textbox', + 'default' => __('Cacti Device Reboot Notification', 'monitor'), + 'size' => 60, + 'max_length' => 60 + ], + 'monitor_body' => [ + 'friendly_name' => __('Email Body', 'monitor'), + 'description' => __('Enter an Email body to include in the Reboot Notification message. Currently, the only supported replacement tag accepted is <DETAILS>', 'monitor'), + 'method' => 'textarea', + 'textarea_rows' => 4, + 'textarea_cols' => 80, + 'default' => __('

Monitor Reboot Notification

The following Device\'s were Rebooted. See details below for additional information.


', 'monitor') + ], + 'monitor_email_header' => [ + 'friendly_name' => __('Notification Email Addresses', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ], + 'monitor_fromname' => [ + 'friendly_name' => __('From Name', 'monitor'), + 'description' => __('Enter the Email Name to send the notifications from', 'monitor'), + 'method' => 'textbox', + 'size' => '60', + 'max_length' => '255' + ], + 'monitor_fromemail' => [ + 'friendly_name' => __('From Address', 'monitor'), + 'description' => __('Enter the Email Address to send the notification from', 'monitor'), + 'method' => 'textbox', + 'size' => '60', + 'max_length' => '255' + ], + 'monitor_list' => [ + 'friendly_name' => __('Notification List', 'thold'), + 'description' => __('Select a Notification List below. All Emails subscribed to the notification list will be notified.', 'thold'), + 'method' => 'drop_sql', + 'sql' => 'SELECT id, name FROM plugin_notification_lists ORDER BY name', + 'default' => '', + 'none_value' => __('None', 'monitor') + ], + 'monitor_emails' => [ + 'friendly_name' => __('Email Addresses', 'monitor'), + 'description' => __('Enter a comma delimited list of Email addresses to inform of a reboot event.', 'monitor'), + 'method' => 'textarea', + 'textarea_rows' => 2, + 'textarea_cols' => 80, + 'default' => '' + ] + ]; + + if (isset($settings['monitor'])) { + $settings['monitor'] = array_merge($settings['monitor'], $temp); + } else { + $settings['monitor'] = $temp; + } } -function monitor_config_arrays() { - global $fa_icons; - - $fa_icons = [ - 'server' => [ - 'display' => __('Server', 'monitor'), - 'class' => 'fa fa-server deviceUp', - 'style' => '' - ], - 'print' => [ - 'display' => __('Printer', 'monitor'), - 'class' => 'fa fa-print deviceUp', - 'style' => '' - ], - 'desktop' => [ - 'display' => __('Desktop PC', 'monitor'), - 'class' => 'fa fa-desktop deviceUp', - 'style' => '' - ], - 'laptop' => [ - 'display' => __('Laptop/notebook', 'monitor'), - 'class' => 'fa fa-laptop deviceUp', - 'style' => '' - ], - 'wifi' => [ - 'display' => __('Wifi', 'monitor'), - 'class' => 'fa fa-wifi deviceUp', - 'style' => '' - ], - 'network-wired' => [ - 'display' => __('Wired network', 'monitor'), - 'class' => 'fa fa-network-wired deviceUp', - 'style' => '' - ], - 'database' => [ - 'display' => __('Database', 'monitor'), - 'class' => 'fa fa-database deviceUp', - 'style' => '' - ], - 'clock' => [ - 'display' => __('Clock', 'monitor'), - 'class' => 'fa fa-clock deviceUp', - 'style' => '' - ], - 'asterisk' => [ - 'display' => __('Asterisk', 'monitor'), - 'class' => 'fas fa-asterisk deviceUp', - 'style' => '' - ], - 'hdd' => [ - 'display' => __('Harddisk', 'monitor'), - 'class' => 'fa fa-hdd deviceUp', - 'style' => '' - ], - 'boxes' => [ - 'display' => __('Boxes', 'monitor'), - 'class' => 'fa fa-boxes deviceUp', - 'style' => '' - ], - 'phone' => [ - 'display' => __('Phone', 'monitor'), - 'class' => 'fa fa-phone deviceUp', - 'style' => '' - ], - 'cloud' => [ - 'display' => __('Cloud', 'monitor'), - 'class' => 'fa fa-cloud deviceUp', - 'style' => '' - ] - ]; - - if (!function_exists('form_dropicon')) { - foreach ($fa_icons as $key => $data) { - $nfa_icons[$key] = $data['display']; - } - - $fa_icons = $nfa_icons; - } - - monitor_check_upgrade(); +/** + * Build monitor icon option arrays and normalize legacy format fallback. + * + * @return void + */ +function monitorConfigArrays(): void +{ + global $fa_icons; + + $fa_icons = [ + 'server' => [ + 'display' => __('Server', 'monitor'), + 'class' => 'fa fa-server deviceUp', + 'style' => '' + ], + 'print' => [ + 'display' => __('Printer', 'monitor'), + 'class' => 'fa fa-print deviceUp', + 'style' => '' + ], + 'desktop' => [ + 'display' => __('Desktop PC', 'monitor'), + 'class' => 'fa fa-desktop deviceUp', + 'style' => '' + ], + 'laptop' => [ + 'display' => __('Laptop/notebook', 'monitor'), + 'class' => 'fa fa-laptop deviceUp', + 'style' => '' + ], + 'wifi' => [ + 'display' => __('Wifi', 'monitor'), + 'class' => 'fa fa-wifi deviceUp', + 'style' => '' + ], + 'network-wired' => [ + 'display' => __('Wired network', 'monitor'), + 'class' => 'fa fa-network-wired deviceUp', + 'style' => '' + ], + 'database' => [ + 'display' => __('Database', 'monitor'), + 'class' => 'fa fa-database deviceUp', + 'style' => '' + ], + 'clock' => [ + 'display' => __('Clock', 'monitor'), + 'class' => 'fa fa-clock deviceUp', + 'style' => '' + ], + 'asterisk' => [ + 'display' => __('Asterisk', 'monitor'), + 'class' => 'fas fa-asterisk deviceUp', + 'style' => '' + ], + 'hdd' => [ + 'display' => __('Harddisk', 'monitor'), + 'class' => 'fa fa-hdd deviceUp', + 'style' => '' + ], + 'boxes' => [ + 'display' => __('Boxes', 'monitor'), + 'class' => 'fa fa-boxes deviceUp', + 'style' => '' + ], + 'phone' => [ + 'display' => __('Phone', 'monitor'), + 'class' => 'fa fa-phone deviceUp', + 'style' => '' + ], + 'cloud' => [ + 'display' => __('Cloud', 'monitor'), + 'class' => 'fa fa-cloud deviceUp', + 'style' => '' + ] + ]; + + if (!function_exists('form_dropicon')) { + foreach ($fa_icons as $key => $data) { + $nfa_icons[$key] = $data['display']; + } + + $fa_icons = $nfa_icons; + } + + monitorCheckUpgrade(); } -function monitor_top_graph_refresh($refresh) { - if (get_current_page() != 'monitor.php') { - return $refresh; - } - - $r = read_config_option('monitor_refresh'); - - if ($r == '' || $r < 1) { - return $refresh; - } - - return $r; +/** + * Override top-graph refresh interval when on monitor page. + * + * @param int|string $refresh Existing refresh interval. + * + * @return int|string + */ +function monitorTopGraphRefresh(mixed $refresh): mixed +{ + if (get_current_page() != 'monitor.php') { + return $refresh; + } + + $r = read_config_option('monitor_refresh'); + + if ($r == '' || $r < 1) { + return $refresh; + } + + return $r; } -function monitor_show_tab() { - global $config; - - monitor_check_upgrade(); - - if (api_user_realm_auth('monitor.php')) { - if (substr_count($_SERVER['REQUEST_URI'], 'monitor.php')) { - print '' . __('Monitor', 'monitor') . ''; - } else { - print '' . __('Monitor', 'monitor') . ''; - } - } +/** + * Render monitor navigation tab icon for authorized users. + * + * @return void + */ +function monitorShowTab(): void +{ + global $config; + + monitorCheckUpgrade(); + + if (api_user_realm_auth('monitor.php')) { + if (substr_count($_SERVER['REQUEST_URI'], 'monitor.php')) { + print '' . __('Monitor', 'monitor') . ''; + } else { + print '' . __('Monitor', 'monitor') . ''; + } + } } -function monitor_config_form() { - global $config, $fields_host_edit, $criticalities, $fa_icons; - - $baselines = [ - '0' => __('Do not Change', 'monitor'), - '1.20' => __('%d Percent Above Average', 20, 'monitor'), - '1.30' => __('%d Percent Above Average', 30, 'monitor'), - '1.40' => __('%d Percent Above Average', 40, 'monitor'), - '1.50' => __('%d Percent Above Average', 50, 'monitor'), - '1.60' => __('%d Percent Above Average', 60, 'monitor'), - '1.70' => __('%d Percent Above Average', 70, 'monitor'), - '1.80' => __('%d Percent Above Average', 80, 'monitor'), - '1.90' => __('%d Percent Above Average', 90, 'monitor'), - '2.00' => __('%d Percent Above Average', 100, 'monitor'), - '2.20' => __('%d Percent Above Average', 120, 'monitor'), - '2.40' => __('%d Percent Above Average', 140, 'monitor'), - '2.50' => __('%d Percent Above Average', 150, 'monitor'), - '3.00' => __('%d Percent Above Average', 200, 'monitor'), - '4.00' => __('%d Percent Above Average', 300, 'monitor'), - '5.00' => __('%d Percent Above Average', 400, 'monitor'), - '6.00' => __('%d Percent Above Average', 500, 'monitor') - ]; - - $fields_host_edit2 = $fields_host_edit; - $fields_host_edit3 = []; - - if (array_key_exists('bulk_walk_size', $fields_host_edit2)) { - $insert_field = 'bulk_walk_size'; - } else { - $insert_field = 'disabled'; - } - - foreach ($fields_host_edit2 as $f => $a) { - $fields_host_edit3[$f] = $a; - - if ($f == $insert_field) { - $fields_host_edit3['monitor_header'] = [ - 'friendly_name' => __('Device Monitoring Settings', 'monitor'), - 'method' => 'spacer', - 'collapsible' => 'true' - ]; - - $fields_host_edit3['monitor'] = [ - 'method' => 'checkbox', - 'friendly_name' => __('Monitor Device', 'monitor'), - 'description' => __('Check this box to monitor this Device on the Monitor Tab.', 'monitor'), - 'value' => '|arg1:monitor|', - 'form_id' => false - ]; - - $host_id = get_nfilter_request_var('id'); - - if (empty($host_id) || !is_numeric($host_id)) { - $fields_host_edit3['monitor']['default'] = monitor_get_default($host_id); - } - - $fields_host_edit3['monitor_criticality'] = [ - 'friendly_name' => __('Device Criticality', 'monitor'), - 'description' => __('What is the Criticality of this Device.', 'monitor'), - 'method' => 'drop_array', - 'array' => $criticalities, - 'value' => '|arg1:monitor_criticality|', - 'default' => '0', - ]; - - $fields_host_edit3['monitor_warn'] = [ - 'friendly_name' => __('Ping Warning Threshold', 'monitor'), - 'description' => __('If the round-trip latency via any of the predefined Cacti ping methods raises above this threshold, log a warning or send email based upon the Devices Criticality and Monitor setting. The unit is in milliseconds. Setting to 0 disables. The Thold Plugin is required to leverage this functionality.', 'monitor'), - 'method' => 'textbox', - 'size' => '10', - 'max_length' => '5', - 'placeholder' => __('milliseconds', 'monitor'), - 'value' => '|arg1:monitor_warn|', - 'default' => '', - ]; - - $fields_host_edit3['monitor_alert'] = [ - 'friendly_name' => __('Ping Alert Threshold', 'monitor'), - 'description' => __('If the round-trip latency via any of the predefined Cacti ping methods raises above this threshold, log an alert or send an email based upon the Devices Criticality and Monitor setting. The unit is in milliseconds. Setting to 0 disables. The Thold Plugin is required to leverage this functionality.', 'monitor'), - 'method' => 'textbox', - 'size' => '10', - 'max_length' => '5', - 'placeholder' => __('milliseconds', 'monitor'), - 'value' => '|arg1:monitor_alert|', - 'default' => '', - ]; - - $fields_host_edit3['monitor_warn_baseline'] = [ - 'friendly_name' => __('Re-Baseline Warning', 'monitor'), - 'description' => __('The percentage above the current average ping time to consider a Warning Threshold. If updated, this will automatically adjust the Ping Warning Threshold.', 'monitor'), - 'method' => 'drop_array', - 'default' => '0', - 'value' => '0', - 'array' => $baselines - ]; - - $fields_host_edit3['monitor_alert_baseline'] = [ - 'friendly_name' => __('Re-Baseline Alert', 'monitor'), - 'description' => __('The percentage above the current average ping time to consider a Alert Threshold. If updated, this will automatically adjust the Ping Alert Threshold.', 'monitor'), - 'method' => 'drop_array', - 'default' => '0', - 'value' => '0', - 'array' => $baselines - ]; - - $fields_host_edit3['monitor_text'] = [ - 'friendly_name' => __('Down Device Message', 'monitor'), - 'description' => __('This is the message that will be displayed when this Device is reported as down.', 'monitor'), - 'method' => 'textarea', - 'max_length' => 1000, - 'textarea_rows' => 2, - 'textarea_cols' => 80, - 'value' => '|arg1:monitor_text|', - 'default' => '', - ]; - - if (function_exists('form_dropicon')) { - $method = 'drop_icon'; - } else { - $method = 'drop_array'; - } - - $fields_host_edit3['monitor_icon'] = [ - 'friendly_name' => __('Device icon', 'monitor'), - 'description' => __('You can select device icon.', 'monitor'), - 'method' => $method, - 'default' => '0', - 'value' => '|arg1:monitor_icon|', - 'array' => $fa_icons, - ]; - } - } - - $fields_host_edit = $fields_host_edit3; +/** + * Inject monitor fields into device edit form configuration. + * + * @return void + */ +function monitorConfigForm(): void +{ + global $config, $fields_host_edit, $criticalities, $fa_icons; + + $baselines = [ + '0' => __('Do not Change', 'monitor'), + '1.20' => __('%d Percent Above Average', 20, 'monitor'), + '1.30' => __('%d Percent Above Average', 30, 'monitor'), + '1.40' => __('%d Percent Above Average', 40, 'monitor'), + '1.50' => __('%d Percent Above Average', 50, 'monitor'), + '1.60' => __('%d Percent Above Average', 60, 'monitor'), + '1.70' => __('%d Percent Above Average', 70, 'monitor'), + '1.80' => __('%d Percent Above Average', 80, 'monitor'), + '1.90' => __('%d Percent Above Average', 90, 'monitor'), + '2.00' => __('%d Percent Above Average', 100, 'monitor'), + '2.20' => __('%d Percent Above Average', 120, 'monitor'), + '2.40' => __('%d Percent Above Average', 140, 'monitor'), + '2.50' => __('%d Percent Above Average', 150, 'monitor'), + '3.00' => __('%d Percent Above Average', 200, 'monitor'), + '4.00' => __('%d Percent Above Average', 300, 'monitor'), + '5.00' => __('%d Percent Above Average', 400, 'monitor'), + '6.00' => __('%d Percent Above Average', 500, 'monitor') + ]; + + $fields_host_edit2 = $fields_host_edit; + $fields_host_edit3 = []; + + if (array_key_exists('bulk_walk_size', $fields_host_edit2)) { + $insert_field = 'bulk_walk_size'; + } else { + $insert_field = 'disabled'; + } + + foreach ($fields_host_edit2 as $f => $a) { + $fields_host_edit3[$f] = $a; + + if ($f == $insert_field) { + $fields_host_edit3['monitor_header'] = [ + 'friendly_name' => __('Device Monitoring Settings', 'monitor'), + 'method' => 'spacer', + 'collapsible' => 'true' + ]; + + $fields_host_edit3['monitor'] = [ + 'method' => 'checkbox', + 'friendly_name' => __('Monitor Device', 'monitor'), + 'description' => __('Check this box to monitor this Device on the Monitor Tab.', 'monitor'), + 'value' => '|arg1:monitor|', + 'form_id' => false + ]; + + $host_id = get_nfilter_request_var('id'); + + if (empty($host_id) || !is_numeric($host_id)) { + $fields_host_edit3['monitor']['default'] = monitorGetDefault($host_id); + } + + $fields_host_edit3['monitor_criticality'] = [ + 'friendly_name' => __('Device Criticality', 'monitor'), + 'description' => __('What is the Criticality of this Device.', 'monitor'), + 'method' => 'drop_array', + 'array' => $criticalities, + 'value' => '|arg1:monitor_criticality|', + 'default' => '0', + ]; + + $fields_host_edit3['monitor_warn'] = [ + 'friendly_name' => __('Ping Warning Threshold', 'monitor'), + 'description' => __('If the round-trip latency via any of the predefined Cacti ping methods raises above this threshold, log a warning or send email based upon the Devices Criticality and Monitor setting. The unit is in milliseconds. Setting to 0 disables. The Thold Plugin is required to leverage this functionality.', 'monitor'), + 'method' => 'textbox', + 'size' => '10', + 'max_length' => '5', + 'placeholder' => __('milliseconds', 'monitor'), + 'value' => '|arg1:monitor_warn|', + 'default' => '', + ]; + + $fields_host_edit3['monitor_alert'] = [ + 'friendly_name' => __('Ping Alert Threshold', 'monitor'), + 'description' => __('If the round-trip latency via any of the predefined Cacti ping methods raises above this threshold, log an alert or send an email based upon the Devices Criticality and Monitor setting. The unit is in milliseconds. Setting to 0 disables. The Thold Plugin is required to leverage this functionality.', 'monitor'), + 'method' => 'textbox', + 'size' => '10', + 'max_length' => '5', + 'placeholder' => __('milliseconds', 'monitor'), + 'value' => '|arg1:monitor_alert|', + 'default' => '', + ]; + + $fields_host_edit3['monitor_warn_baseline'] = [ + 'friendly_name' => __('Re-Baseline Warning', 'monitor'), + 'description' => __('The percentage above the current average ping time to consider a Warning Threshold. If updated, this will automatically adjust the Ping Warning Threshold.', 'monitor'), + 'method' => 'drop_array', + 'default' => '0', + 'value' => '0', + 'array' => $baselines + ]; + + $fields_host_edit3['monitor_alert_baseline'] = [ + 'friendly_name' => __('Re-Baseline Alert', 'monitor'), + 'description' => __('The percentage above the current average ping time to consider a Alert Threshold. If updated, this will automatically adjust the Ping Alert Threshold.', 'monitor'), + 'method' => 'drop_array', + 'default' => '0', + 'value' => '0', + 'array' => $baselines + ]; + + $fields_host_edit3['monitor_text'] = [ + 'friendly_name' => __('Down Device Message', 'monitor'), + 'description' => __('This is the message that will be displayed when this Device is reported as down.', 'monitor'), + 'method' => 'textarea', + 'max_length' => 1000, + 'textarea_rows' => 2, + 'textarea_cols' => 80, + 'value' => '|arg1:monitor_text|', + 'default' => '', + ]; + + if (function_exists('form_dropicon')) { + $method = 'drop_icon'; + } else { + $method = 'drop_array'; + } + + $fields_host_edit3['monitor_icon'] = [ + 'friendly_name' => __('Device icon', 'monitor'), + 'description' => __('You can select device icon.', 'monitor'), + 'method' => $method, + 'default' => '0', + 'value' => '|arg1:monitor_icon|', + 'array' => $fa_icons, + ]; + } + } + + $fields_host_edit = $fields_host_edit3; } -function monitor_get_default($host_id) { - $monitor_new_device = ''; - - if ($host_id <= 0) { - $monitor_new_device = read_config_option('monitor_new_enabled'); - } - - return $monitor_new_device; +/** + * Resolve default monitor enabled state for new devices. + * + * @param int|string $host_id Host identifier. + * + * @return string + */ +function monitorGetDefault(int|string $host_id): string +{ + $monitor_new_device = ''; + + if ($host_id <= 0) { + $monitor_new_device = read_config_option('monitor_new_enabled'); + } + + return $monitor_new_device; } -function monitor_api_device_save($save) { - global $fa_icons; - - $monitor_default = monitor_get_default($save['id']); - - if (isset_request_var('monitor')) { - $save['monitor'] = form_input_validate(get_nfilter_request_var('monitor'), 'monitor', $monitor_default, true, 3); - } else { - $save['monitor'] = form_input_validate($monitor_default, 'monitor', '', true, 3); - } - - if (isset_request_var('monitor_text')) { - $save['monitor_text'] = form_input_validate(get_nfilter_request_var('monitor_text'), 'monitor_text', '', true, 3); - } else { - $save['monitor_text'] = form_input_validate('', 'monitor_text', '', true, 3); - } - - if (isset_request_var('monitor_criticality')) { - $save['monitor_criticality'] = form_input_validate(get_nfilter_request_var('monitor_criticality'), 'monitor_criticality', '^[0-9]+$', true, 3); - } else { - $save['monitor_criticality'] = form_input_validate('', 'monitor_criticality', '', true, 3); - } - - if (isset_request_var('monitor_warn')) { - $save['monitor_warn'] = form_input_validate(get_nfilter_request_var('monitor_warn'), 'monitor_warn', '^[0-9]+$', true, 3); - } else { - $save['monitor_warn'] = form_input_validate('', 'monitor_warn', '', true, 3); - } - - if (isset_request_var('monitor_alert')) { - $save['monitor_alert'] = form_input_validate(get_nfilter_request_var('monitor_alert'), 'monitor_alert', '^[0-9]+$', true, 3); - } else { - $save['monitor_alert'] = form_input_validate('', 'monitor_alert', '', true, 3); - } - - if (isset_request_var('monitor_icon') && array_key_exists(get_nfilter_request_var('monitor_icon'), $fa_icons)) { - $save['monitor_icon'] = get_nfilter_request_var('monitor_icon'); - } else { - $save['monitor_icon'] = ''; - } - - if (!isempty_request_var('monitor_alert_baseline') && !empty($save['id'])) { - $cur_time = db_fetch_cell_prepared('SELECT cur_time +/** + * Validate and map monitor-specific host fields during device save. + * + * @param array $save Device save payload. + * + * @return array + */ +function monitorApiDeviceSave(array $save): array +{ + global $fa_icons; + + $monitor_default = monitorGetDefault($save['id']); + + if (isset_request_var('monitor')) { + $save['monitor'] = form_input_validate(get_nfilter_request_var('monitor'), 'monitor', $monitor_default, true, 3); + } else { + $save['monitor'] = form_input_validate($monitor_default, 'monitor', '', true, 3); + } + + if (isset_request_var('monitor_text')) { + $save['monitor_text'] = form_input_validate(get_nfilter_request_var('monitor_text'), 'monitor_text', '', true, 3); + } else { + $save['monitor_text'] = form_input_validate('', 'monitor_text', '', true, 3); + } + + if (isset_request_var('monitor_criticality')) { + $save['monitor_criticality'] = form_input_validate(get_nfilter_request_var('monitor_criticality'), 'monitor_criticality', '^[0-9]+$', true, 3); + } else { + $save['monitor_criticality'] = form_input_validate('', 'monitor_criticality', '', true, 3); + } + + if (isset_request_var('monitor_warn')) { + $save['monitor_warn'] = form_input_validate(get_nfilter_request_var('monitor_warn'), 'monitor_warn', '^[0-9]+$', true, 3); + } else { + $save['monitor_warn'] = form_input_validate('', 'monitor_warn', '', true, 3); + } + + if (isset_request_var('monitor_alert')) { + $save['monitor_alert'] = form_input_validate(get_nfilter_request_var('monitor_alert'), 'monitor_alert', '^[0-9]+$', true, 3); + } else { + $save['monitor_alert'] = form_input_validate('', 'monitor_alert', '', true, 3); + } + + if (isset_request_var('monitor_icon') && array_key_exists(get_nfilter_request_var('monitor_icon'), $fa_icons)) { + $save['monitor_icon'] = get_nfilter_request_var('monitor_icon'); + } else { + $save['monitor_icon'] = ''; + } + + if (!isempty_request_var('monitor_alert_baseline') && !empty($save['id'])) { + $cur_time = db_fetch_cell_prepared( + 'SELECT cur_time FROM host WHERE id = ?', - [$save['id']]); + [$save['id']] + ); - if ($cur_time > 0) { - $save['monitor_alert'] = ceil($cur_time * get_nfilter_request_var('monitor_alert_baseline')); - } - } + if ($cur_time > 0) { + $save['monitor_alert'] = ceil($cur_time * get_nfilter_request_var('monitor_alert_baseline')); + } + } - if (!isempty_request_var('monitor_warn_baseline') && !empty($save['id'])) { - $cur_time = db_fetch_cell_prepared('SELECT cur_time + if (!isempty_request_var('monitor_warn_baseline') && !empty($save['id'])) { + $cur_time = db_fetch_cell_prepared( + 'SELECT cur_time FROM host WHERE id = ?', - [$save['id']]); + [$save['id']] + ); - if ($cur_time > 0) { - $save['monitor_warn'] = ceil($cur_time * get_nfilter_request_var('monitor_alert_baseline')); - } - } + if ($cur_time > 0) { + $save['monitor_warn'] = ceil($cur_time * get_nfilter_request_var('monitor_alert_baseline')); + } + } - return $save; + return $save; } -function monitor_draw_navigation_text($nav) { - $nav['monitor.php:'] = ['title' => __('Monitoring', 'monitor'), 'mapping' => '', 'url' => 'monitor.php', 'level' => '0']; - - return $nav; +/** + * Add monitor page breadcrumb/nav metadata. + * + * @param array $nav Existing navigation mapping. + * + * @return array + */ +function monitorDrawNavigationText(array $nav): array +{ + $nav['monitor.php:'] = ['title' => __('Monitoring', 'monitor'), 'mapping' => '', 'url' => 'monitor.php', 'level' => '0']; + + return $nav; } -function monitor_setup_table() { - if (!db_table_exists('plugin_monitor_notify_history')) { - db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_notify_history ( +/** + * Create/upgrade monitor plugin tables and host columns. + * + * @return void + */ +function monitorSetupTable(): void +{ + if (!db_table_exists('plugin_monitor_notify_history')) { + db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_notify_history ( id int(10) unsigned NOT NULL AUTO_INCREMENT, host_id int(10) unsigned DEFAULT NULL, notify_type tinyint(3) unsigned DEFAULT NULL, @@ -1053,10 +1299,10 @@ function monitor_setup_table() { UNIQUE KEY unique_key (host_id,notify_type,notification_time)) ENGINE=InnoDB COMMENT='Stores Notification Event History'"); - } + } - if (!db_table_exists('plugin_monitor_reboot_history')) { - db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_reboot_history ( + if (!db_table_exists('plugin_monitor_reboot_history')) { + db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_reboot_history ( id int(10) unsigned NOT NULL AUTO_INCREMENT, host_id int(10) unsigned DEFAULT NULL, reboot_time timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', @@ -1067,20 +1313,20 @@ function monitor_setup_table() { KEY reboot_time (reboot_time)) ENGINE=InnoDB COMMENT='Keeps Track of Device Reboot Times'"); - } + } - if (!db_table_exists('plugin_monitor_uptime')) { - db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_uptime ( + if (!db_table_exists('plugin_monitor_uptime')) { + db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_uptime ( host_id int(10) unsigned DEFAULT '0', uptime bigint(20) unsigned DEFAULT '0', PRIMARY KEY (host_id), KEY uptime (uptime)) ENGINE=InnoDB COMMENT='Keeps Track of the Devices last uptime to track agent restarts and reboots'"); - } + } - if (!db_table_exists('plugin_monitor_dashboards')) { - db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_dashboards ( + if (!db_table_exists('plugin_monitor_dashboards')) { + db_execute("CREATE TABLE IF NOT EXISTS plugin_monitor_dashboards ( id int(10) unsigned auto_increment, user_id int(10) unsigned DEFAULT '0', name varchar(128) DEFAULT '', @@ -1089,30 +1335,36 @@ function monitor_setup_table() { KEY user_id (user_id)) ENGINE=InnoDB COMMENT='Stores predefined dashboard information for a user or users'"); - } - - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor', 'type' => 'char(3)', 'NULL' => true, 'default' => 'on', 'after' => 'disabled']); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_text', 'type' => 'varchar(1024)', 'default' => '', 'NULL' => false, 'after' => 'monitor']); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_criticality', 'type' => 'tinyint', 'unsigned' => true, 'NULL' => false, 'default' => '0', 'after' => 'monitor_text']); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_warn', 'type' => 'double', 'NULL' => false, 'default' => '0', 'after' => 'monitor_criticality']); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_alert', 'type' => 'double', 'NULL' => false, 'default' => '0', 'after' => 'monitor_warn']); - api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_icon', 'type' => 'varchar(30)', 'NULL' => false, 'default' => '', 'after' => 'monitor_alert']); + } + + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor', 'type' => 'char(3)', 'NULL' => true, 'default' => 'on', 'after' => 'disabled']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_text', 'type' => 'varchar(1024)', 'default' => '', 'NULL' => false, 'after' => 'monitor']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_criticality', 'type' => 'tinyint', 'unsigned' => true, 'NULL' => false, 'default' => '0', 'after' => 'monitor_text']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_warn', 'type' => 'double', 'NULL' => false, 'default' => '0', 'after' => 'monitor_criticality']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_alert', 'type' => 'double', 'NULL' => false, 'default' => '0', 'after' => 'monitor_warn']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_icon', 'type' => 'varchar(30)', 'NULL' => false, 'default' => '', 'after' => 'monitor_alert']); } -function monitor_poller_bottom() { - global $config; +/** + * Trigger monitor poller script from primary poller process. + * + * @return void + */ +function monitorPollerBottom(): void +{ + global $config; - if ($config['poller_id'] == 1) { - include_once($config['library_path'] . '/poller.php'); + if ($config['poller_id'] == 1) { + include_once($config['library_path'] . '/poller.php'); - $command_string = trim(read_config_option('path_php_binary')); + $command_string = trim(read_config_option('path_php_binary')); - if (trim($command_string) == '') { - $command_string = 'php'; - } + if (trim($command_string) == '') { + $command_string = 'php'; + } - $extra_args = ' -q ' . $config['base_path'] . '/plugins/monitor/poller_monitor.php'; + $extra_args = ' -q ' . $config['base_path'] . '/plugins/monitor/poller_monitor.php'; - exec_background($command_string, $extra_args); - } + exec_background($command_string, $extra_args); + } } diff --git a/sounds/index.php b/sounds/index.php index 828ecbe..362957c 100644 --- a/sounds/index.php +++ b/sounds/index.php @@ -1,4 +1,5 @@