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 = (int) 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; } /** * Whether the current view uses the device-tile card layout (as opposed to the * name grid or paginated list), and so supports grouped card panels. * * @return bool */ function monitorUseCardLayout(): bool { return in_array(get_request_var('view'), ['default', 'tiles', 'tilesadt'], true); } /** * Resolve the worst (most severe) device status CSS class across a group so the * group's card title can be coloured to match (down/breached/triggered/etc.). * * @param array $hosts Host rows in the group. * * @return string Status CSS class (e.g. deviceDown), defaulting to deviceUp. */ function monitorGroupWorstStatusClass(array $hosts): string { global $iclasses; $rank = [1 => 100, 8 => 90, 4 => 80, 10 => 75, 9 => 70, 5 => 65, 7 => 60, 2 => 50, 0 => 40, 6 => 30, 3 => 10]; $worst = 3; $worst_val = -1; foreach ($hosts as $host) { $status = getHostStatus($host); $value = $rank[$status] ?? 0; if ($value > $worst_val) { $worst_val = $value; $worst = $status; } } return $iclasses[$worst] ?? 'deviceUp'; } /** * Load the saved card ordering for a grouping from the active dashboard's * properties, falling back to the per-user setting. * * @param string $grouping Grouping key (site/template). * * @return array Ordered list of group ids, or empty when none saved. */ function monitorGetCardOrder(string $grouping): array { $dashboard = get_request_var('dashboard'); if ($dashboard > 0 && monitorDashboardOwnedByUser($dashboard)) { $props = monitorGetDashboardProperties($dashboard); if (isset($props['cardorder'][$grouping]) && is_array($props['cardorder'][$grouping])) { return $props['cardorder'][$grouping]; } } $json = read_user_setting('monitor_cardorder'); if ($json != '') { $order = json_decode($json, true); if (isset($order[$grouping]) && is_array($order[$grouping])) { return $order[$grouping]; } } return []; } /** * Reorder a group map to honour a saved id ordering, appending any groups not * present in the saved order. * * @param array $groups Map of group id => group data. * @param array $order Saved list of group ids. * * @return array Reordered group map. */ function monitorApplyCardOrder(array $groups, array $order): array { if (!cacti_sizeof($order)) { return $groups; } $ordered = []; foreach ($order as $gid) { $gid = (string) $gid; if (isset($groups[$gid])) { $ordered[$gid] = $groups[$gid]; unset($groups[$gid]); } } foreach ($groups as $gid => $group) { $ordered[$gid] = $group; } return $ordered; } /** * Render a set of grouped device cards (one card/panel per group) in a * responsive grid, with saved ordering applied and each title coloured by the * group's worst device status. * * @param string $grouping Grouping key (site/template). * @param array $groups Map of group id => ['label' => string, 'hosts' => array]. * @param int $maxlen Trim length for host titles. * * @return string */ function monitorRenderGroupCards(string $grouping, array $groups, int $maxlen): string { $groups = monitorApplyCardOrder($groups, monitorGetCardOrder($grouping)); $out = "
"; foreach ($groups as $gid => $group) { $worst = monitorGroupWorstStatusClass($group['hosts']); // Let groups with many devices take a wider card so their tiles flow // across the page instead of stacking into one tall, narrow column. The // span maps to flex sizing in css (see .monitorGroupCard[data-span]). $span = (int) min(4, max(1, (int) ceil(cacti_sizeof($group['hosts']) / 6))); // Size every tile to the longest (trimmed) description in view so long // hostnames never overlap their neighbours. css widens the titles from // this custom property. $title_ch = max(8, min(40, $maxlen)); $body = "
"; foreach ($group['hosts'] as $host) { $body .= renderHost($host, true, $maxlen); } $body .= '
'; $out .= "
" . html_escape($group['label']) . "
$body
"; } $out .= '
'; return $out; } /** * 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); // Only the List view paginates; all other views show every matching device. if (get_request_var('view') == 'list') { $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); 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 = (int) db_fetch_cell('SELECT MAX(LENGTH(description)) FROM host AS h WHERE id IN (' . implode(',', $host_ids) . ')'); } $maxlen = getMonitorTrimLength($maxlen); if (monitorUseCardLayout()) { $groups = []; foreach ($hosts as $host) { $gid = (string) $host['site_id']; if (!isset($groups[$gid])) { $groups[$gid] = ['label' => $host['site_name'], 'hosts' => []]; } $groups[$gid]['hosts'][] = $host; } return monitorRenderGroupCards('site', $groups, $maxlen); } $function = 'renderHeader' . ucfirst(get_request_var('view')); if (function_exists($function)) { $result .= $function(); } foreach ($hosts as $host) { $result .= renderHost($host, true, $maxlen); } $function = 'renderFooter' . ucfirst(get_request_var('view')); if (function_exists($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); // Only the List view paginates; all other views show every matching device. if (get_request_var('view') == 'list') { $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"); 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 = (int) db_fetch_cell('SELECT MAX(LENGTH(description)) FROM host AS h WHERE id IN (' . implode(',', $host_ids) . ')'); } $maxlen = getMonitorTrimLength($maxlen); if (monitorUseCardLayout()) { $groups = []; foreach ($hosts as $host) { $gid = (string) $host['host_template_id']; $label = $host['host_template_name'] != '' ? $host['host_template_name'] : __('Non-Templated Devices', 'monitor'); if (!isset($groups[$gid])) { $groups[$gid] = ['label' => $label, 'hosts' => []]; } $groups[$gid]['hosts'][] = $host; } return monitorRenderGroupCards('template', $groups, $maxlen); } $function = 'renderHeader' . ucfirst(get_request_var('view')); if (function_exists($function)) { $result .= $function(); } foreach ($hosts as $host) { $result .= renderHost($host, true, $maxlen); } $function = 'renderFooter' . ucfirst(get_request_var('view')); if (function_exists($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; } /** * Translatable label for the card/section holding devices not on any tree. * * @return string */ function monitorNonTreeLabel(): string { return __('Non-Tree Devices', 'monitor'); } /** * 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 tree grouping as device cards (one card per tree, plus a Non-Tree * card), mirroring the Site and Device Template card layouts. * * @param array $tree_list Allowed trees for the current filter. * * @return string */ function monitorRenderTreeCards(array $tree_list): string { $groups = []; $host_ids = []; 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); $hosts = db_fetch_assoc('SELECT DISTINCT h.*, gt.id AS graph_tree_id, gt.name AS graph_tree_name FROM host AS h INNER JOIN graph_tree_items AS gti ON h.id = gti.host_id INNER JOIN graph_tree AS gt ON gt.id = gti.graph_tree_id ' . $sql_join . ' ' . $sql_where . ' AND gti.host_id > 0 AND gti.graph_tree_id IN (' . implode(',', $tree_ids) . ') ORDER BY gt.sequence, h.description'); if (cacti_sizeof($hosts)) { [$hosts, $ids] = monitorFilterAllowedHosts($hosts); $host_ids = array_merge($host_ids, $ids); foreach ($hosts as $host) { $gid = 'tree_' . $host['graph_tree_id']; if (!isset($groups[$gid])) { $groups[$gid] = ['label' => $host['graph_tree_name'], 'hosts' => []]; } $groups[$gid]['hosts'][] = $host; } } } // Devices not attached to any tree get their own card, unless a specific // tree is selected in the filter. if (get_request_var('tree') < 0) { $nontree = getHostNonTreeArray(); if (cacti_sizeof($nontree)) { [$nontree, $ids] = monitorFilterAllowedHosts($nontree); if (cacti_sizeof($nontree)) { $host_ids = array_merge($host_ids, $ids); $groups['nontree'] = ['label' => monitorNonTreeLabel(), 'hosts' => $nontree]; } } } if (!cacti_sizeof($groups)) { return ''; } // Size the trim length from the hosts actually rendered here (including the // Non-Tree card) so labels aren't trimmed against unrelated tree devices. $maxlen = 10; if (get_request_var('view') == 'default' && cacti_sizeof($host_ids)) { $maxlen = (int) db_fetch_cell('SELECT MAX(LENGTH(description)) FROM host AS h WHERE id IN (' . implode(',', $host_ids) . ')'); } $maxlen = getMonitorTrimLength($maxlen); // Default-order the tree cards by device count (busiest first); any saved // drag order the user set still wins in monitorRenderGroupCards(). uasort($groups, static function (array $a, array $b): int { return cacti_sizeof($b['hosts']) <=> cacti_sizeof($a['hosts']); }); return monitorRenderGroupCards('tree', $groups, $maxlen); } /** * 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 = []; } // Card-based views group each tree (and non-tree devices) into panels, the // same as the Site and Device Template groupings. if (monitorUseCardLayout()) { return monitorRenderTreeCards($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, $servcheck_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; } elseif (isset($servcheck_hosts) && array_key_exists($host['id'], $servcheck_hosts)) { $host['status'] = 10; } } // 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|string $status Monitor status code. * * @return string */ function getHostStatusDescription(mixed $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']; // Preserve the raw availability status (0-3) before it is collapsed into the // single display status, so the Events column can detect every active event. $host['raw_status'] = (int) $host['status']; 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 int|string $status_time Timestamp string or SNMP uptime ticks. * @param bool $seconds Include seconds in output string. * * @return string */ function monitorPrintHostTime(mixed $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(); } /** * Event key => short label and status CSS class for the Events column pills. * * @return array */ function monitorEventMeta(): array { return [ 'down' => ['label' => __('Down', 'monitor'), 'class' => 'deviceDown'], 'recovering' => ['label' => __('Recovering', 'monitor'), 'class' => 'deviceRecovering'], 'triggered' => ['label' => __('Triggered', 'monitor'), 'class' => 'deviceThreshold'], 'down_ma' => ['label' => __('Down (M/A)', 'monitor'), 'class' => 'deviceDownMuted'], 'triggered_ma' => ['label' => __('Triggered (M/A)', 'monitor'), 'class' => 'deviceThresholdMuted'], 'warn_ping' => ['label' => __('Warn Ping', 'monitor'), 'class' => 'deviceWarning'], 'alert_ping' => ['label' => __('Alert Ping', 'monitor'), 'class' => 'deviceAlert'], 'failed_svc' => ['label' => __('Failed Svc', 'monitor'), 'class' => 'deviceServiceCheck'], ]; } /** * Resolve every active event on a host for the Events column (a device can have * several, e.g. Down and Triggered). Uses the preserved raw availability status * plus the triggered-thold, failing-servcheck and session mute sets. * * @param array $host Host row data. * * @return array List of active event keys. */ function monitorHostEvents(array $host): array { global $thold_hosts, $servcheck_hosts; $raw = isset($host['raw_status']) ? (int) $host['raw_status'] : (int) $host['status']; $muted = isset($_SESSION['monitor_muted_hosts']) && in_array($host['id'], $_SESSION['monitor_muted_hosts'], true); $events = []; if ($raw == 1) { $events[] = $muted ? 'down_ma' : 'down'; } elseif ($raw == 2) { $events[] = 'recovering'; } if (isset($thold_hosts) && array_key_exists($host['id'], $thold_hosts)) { $events[] = $muted ? 'triggered_ma' : 'triggered'; } if ($raw == 3) { if (!empty($host['monitor_alert']) && $host['cur_time'] > $host['monitor_alert']) { $events[] = 'alert_ping'; } elseif (!empty($host['monitor_warn']) && $host['cur_time'] > $host['monitor_warn']) { $events[] = 'warn_ping'; } } // A failing service check is additive and independent of ping availability: // monitorEventFilterSql('failed_svc') matches the host regardless of its Up/ // Down state, so a Down+Failed-Svc host must still get the Failed Svc pill. if (isset($servcheck_hosts) && array_key_exists($host['id'], $servcheck_hosts)) { $events[] = 'failed_svc'; } return $events; } /** * 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 = [ 'nosort_actions' => [ 'display' => __('Actions', 'monitor'), 'align' => 'left', 'tip' => __('Open the device detail panels', 'monitor') ], 'description' => [ 'display' => __('Description', 'monitor'), 'sort' => 'ASC', 'align' => 'left', 'tip' => __('Device description as set on the Devices page', 'monitor') ], 'location' => [ 'display' => __('Location', 'monitor'), 'sort' => 'ASC', 'align' => 'left', 'tip' => __('Physical location recorded for the device', 'monitor') ], 'site_name' => [ 'display' => __('Site', 'monitor'), 'sort' => 'ASC', 'align' => 'left', 'tip' => __('Site the device is assigned to', 'monitor') ], 'id' => [ 'display' => __('ID', 'monitor'), 'sort' => 'ASC', 'align' => 'left', 'tip' => __('Cacti internal device id', 'monitor') ], 'monitor_criticality' => [ 'display' => __('Criticality', 'monitor'), 'sort' => 'ASC', 'align' => 'left', 'tip' => __('Monitoring criticality assigned to the device', 'monitor') ], 'nosort_events' => [ 'display' => __('Events', 'monitor'), 'align' => 'center', 'tip' => __('Active events on the device (Down, Triggered, Warn/Alert Ping, Failed Svc, ...); click a pill to add or remove that event from the Events filter', 'monitor') ], 'nosort_tholds' => [ 'display' => __('Thresholds', 'monitor'), 'align' => 'center', 'tip' => __('Breached / Triggered / Total thresholds (thold plugin) on the device; click to toggle the Triggered event filter', 'monitor') ], 'nosort_servchecks' => [ 'display' => __('Service Checks', 'monitor'), 'align' => 'center', 'tip' => __('Failed / Total service checks (servcheck plugin) for the device; click to toggle the Failed Svc event filter', 'monitor') ], 'monitor_warn' => [ 'display' => __('Warning', 'monitor'), 'sort' => 'DESC', 'align' => 'left', 'tip' => __('Ping latency Warning / Alert thresholds (ms) configured for this device; a notification is raised when the response time crosses them', 'monitor') ], 'monitor_text' => [ 'display' => __('Admin', 'monitor'), 'sort' => 'ASC', 'tip' => __('Administrative monitor message shown while the device is Down or Recovering', 'monitor'), 'align' => 'left' ], 'availability' => [ 'display' => __('Availability', 'monitor'), 'sort' => 'DESC', 'align' => 'right', 'tip' => __('Percentage of successful availability checks over the retained history', 'monitor') ], 'avg_time' => [ 'display' => __('Averages', 'monitor'), 'sort' => 'DESC', 'align' => 'right', 'tip' => __('Current / average ping response time in milliseconds', 'monitor') ], 'instate' => [ 'display' => __('Length in Status', 'monitor'), 'sort' => 'ASC', 'align' => 'right', 'tip' => __('How long the device has been in its current status', 'monitor') ], 'status_fail_date' => [ 'display' => __('Last Fail', 'monitor'), 'sort' => 'DESC', 'align' => 'right', 'tip' => __('Date and time the device last failed its availability check', 'monitor') ], ]; // Hide the Thresholds / Service Checks columns when their plugin is absent. if (!api_plugin_is_enabled('thold')) { unset($display_text['nosort_tholds']); } if (!api_plugin_is_enabled('servcheck') || !api_plugin_user_realm_auth('servcheck_test.php')) { unset($display_text['nosort_servchecks']); } ob_start(); $nav = html_nav_bar('monitor.php?rfilter=' . rawurlencode(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=' . rawurlencode(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, $thold_counts, $servcheck_counts, $config; 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 = ''; } $crit_level = isset($host['monitor_criticality']) ? (int) $host['monitor_criticality'] : 0; if ($crit_level > 0) { $host_crit = "" . html_escape($criticalities[$crit_level]) . ''; } else { $host_crit = "" . __esc('Not Set', 'monitor') . ''; } // Events column: a short pill for every active event (a device can have // several, e.g. Down + Triggered); each pill toggles that event in the // multi-select filter. A muted 'None' pill is shown when the device is clean. $event_keys = monitorHostEvents($host); if (cacti_sizeof($event_keys)) { $meta = monitorEventMeta(); $pills = []; foreach ($event_keys as $ekey) { if (isset($meta[$ekey])) { $pills[] = "" . html_escape($meta[$ekey]['label']) . ''; } } $events_cell = implode(' ', $pills); } else { $events_cell = "" . __esc('None', 'monitor') . ''; } // Thresholds pill: breached / triggered / total (thold plugin); a muted // 'Not Set' pill is shown when the device has no thresholds. Clicking toggles // the Triggered event. if (isset($thold_counts[$host['id']]) && $thold_counts[$host['id']]['total'] > 0) { $tc = $thold_counts[$host['id']]; $tcls = ($tc['triggered'] > 0 || $tc['breached'] > 0) ? 'deviceThreshold' : 'monitorPillMuted'; $thold_cell = "" . $tc['breached'] . ' / ' . $tc['triggered'] . ' / ' . $tc['total'] . ''; } else { $thold_cell = "" . __esc('Not Set', 'monitor') . ''; } // Service checks pill: failed / total (servcheck plugin); a muted 'Not Set' // pill is shown when the device has no service checks. Clicking toggles the // Failed Svc event. if (isset($servcheck_counts[$host['id']]) && $servcheck_counts[$host['id']]['total'] > 0) { $sc = $servcheck_counts[$host['id']]; $scls = ($sc['failed'] > 0) ? 'deviceServiceCheck' : 'monitorPillMuted'; $servcheck_cell = "" . $sc['failed'] . ' / ' . $sc['total'] . ''; } else { $servcheck_cell = "" . __esc('Not Set', 'monitor') . ''; } 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']; ob_start(); print ""; $actions = ""; $actions .= ""; if (api_plugin_user_realm_auth('host.php')) { $actions .= ""; } form_selectable_cell($actions, $host['id'], '1%', 'left'); form_selectable_cell($host['description'], $host['id'], '', 'left'); form_selectable_cell($host['location'], $host['id'], '', 'left'); form_selectable_cell($host['site_name'], $host['id'], '', 'left'); form_selectable_cell($host['id'], $host['id'], '', 'left'); form_selectable_cell($host_crit, $host['id'], '', 'left'); form_selectable_cell($events_cell, $host['id'], '', 'center'); if (api_plugin_is_enabled('thold')) { form_selectable_cell($thold_cell, $host['id'], '', 'center'); } if (api_plugin_is_enabled('servcheck') && api_plugin_user_realm_auth('servcheck_test.php')) { form_selectable_cell($servcheck_cell, $host['id'], '', 'center'); } 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(round($host['availability'], 2) . ' %', $host['id'], '', 'right'); form_selectable_cell($host_avg, $host['id'], '', 'right'); form_selectable_cell($dt, $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'); $name = title_trim(html_escape($host['description']), 40); 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; }