diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ffda458..19f20c9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,153 +4,112 @@ 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 +- 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. -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. diff --git a/db_functions.php b/db_functions.php new file mode 100644 index 0000000..dac37e2 --- /dev/null +++ b/db_functions.php @@ -0,0 +1,314 @@ + 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 h.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..b8c7e48 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: 'left: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..96be144 100644 --- a/locales/LC_MESSAGES/index.php +++ b/locales/LC_MESSAGES/index.php @@ -1,4 +1,7 @@ 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); -} - -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 ================= -} - -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), ','); - - if (!empty($sql_data)) { - $sql_where .= ($sql_where != '' ? $sql_join : '') . "($sql_field IN($sql_data) $sql_suffix)"; - } - } -} - -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'; - } - } - - 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')]); - - 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 = ""'); - - render_group_concat($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 ' . 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'; - - $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; - } -} - -// 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; -} diff --git a/monitor_controller.php b/monitor_controller.php new file mode 100644 index 0000000..ff1e7c8 --- /dev/null +++ b/monitor_controller.php @@ -0,0 +1,1216 @@ + 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 = (string) 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'])) { + unmuteUpNonTriggeredHosts($dhosts); + + $unmuted_hosts = array_diff($dhosts, $_SESSION['monitor_muted_hosts']); + + if (cacti_sizeof($unmuted_hosts)) { + unmuteUser(); + } + } 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((float) $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..b2bc208 --- /dev/null +++ b/monitor_render.php @@ -0,0 +1,1236 @@ + 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']; + } + + $sql_where = ''; + $sql_join = ''; + 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..ddb8568 --- /dev/null +++ b/poller_functions.php @@ -0,0 +1,1078 @@ + 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('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; +} + +/** + * 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..0a7ba3c 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 + // 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); + 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); + 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'); + 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); + $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); + monitorDebug('Processing the email address: ' . $email); + processEmail($email, $lists, $global_list, $notify_list); $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); @@ -151,779 +153,3 @@ 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..283d533 100644 --- a/setup.php +++ b/setup.php @@ -215,7 +215,7 @@ function monitor_check_upgrade() { 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' => '']); if (function_exists('api_plugin_upgrade_register')) { api_plugin_upgrade_register('monitor'); @@ -1091,12 +1091,27 @@ function monitor_setup_table() { 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']); + if (db_table_exists('host')) { + $row_format = db_fetch_cell("SELECT ROW_FORMAT + FROM information_schema.tables + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'host'"); + + if (strtoupper((string) $row_format) !== 'DYNAMIC') { + db_execute('ALTER TABLE host ROW_FORMAT=DYNAMIC'); + } + } + + db_execute('SET SESSION innodb_strict_mode=0'); + + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor', 'type' => 'char(3)', 'NULL' => true, 'default' => 'on']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_text', 'type' => 'text', 'NULL' => false]); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_criticality', 'type' => 'tinyint', 'unsigned' => true, 'NULL' => false, 'default' => '0']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_warn', 'type' => 'double', 'NULL' => false, 'default' => '0']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_alert', 'type' => 'double', 'NULL' => false, 'default' => '0']); + api_plugin_db_add_column('monitor', 'host', ['name' => 'monitor_icon', 'type' => 'varchar(30)', 'NULL' => false, 'default' => '']); + + db_execute('SET SESSION innodb_strict_mode=1'); } function monitor_poller_bottom() { diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..5ed8db9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.projectKey=plugin_monitor +sonar.sources=. +sonar.exclusions=vendor/** + +# php:S4833 - "Use namespaces instead of include/require" +# This is a Cacti plugin with a required procedural architecture. +# Namespaces cannot be used; include_once is the correct and only mechanism. +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=php:S4833 +sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.php 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 @@