├── .gitignore ├── CHANGELOG.md ├── Module.php ├── README.md ├── actions ├── CControllerBGHost.php ├── CControllerBGHostView.php └── CControllerBGHostViewRefresh.php ├── manifest.json ├── partials ├── js │ └── monitoring.host.view.refresh.js.php └── module.monitoring.host.view.html.php ├── screenshots └── zabbix-module-hosts-tree-1.png └── views ├── css └── bghost.css ├── js └── monitoring.host.view.js.php ├── module.monitoring.bghost.view.php └── module.monitoring.bghost.view.refresh.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [2.0.1] - 2023-06-24 10 | ### Changed 11 | - Fixed bug with showing   in browser for Zabbix >= 6.0.18 12 | 13 | ## [4.1.1] - 2023-06-11 14 | ### Changed 15 | - Fixed bug with hiding childeren groups at all levels. 16 | 17 | ## [4.1.0] - 2023-06-08 18 | ### Changed 19 | - Show group tree expanded at first load 20 | - Fixed bug with showing   in browser for Zabbix >= 6.4.3 21 | 22 | ## [4.0.0] - 2023-04-08 23 | ### Changed 24 | - Updated to work with Zabbix 6.4 25 | 26 | ## [3.0.0] - 2022-07-24 27 | ### Changed 28 | - Updated to work with Zabbix 6.2.0 29 | 30 | ## [2.0.0] - 2022-02-16 31 | ### Changed 32 | - Updated to work with Zabbix 6.0.0 33 | 34 | ## [1.3.0] - 2021-11-12 35 | ### Changed 36 | - Lots of code re-written 37 | - All calculations moved from View to Controller 38 | - Performance improvements (should be noticable when there is a lot of groups) 39 | - Fix number of hosts and number of problems calculation per group 40 | - Change the way of presenting Groups with paging (too many hosts belong to one group and to different groups) 41 | 42 | ## [1.2.0] - 2021-08-20 43 | ### Changed 44 | - Alphabetically arranged groups in the tree branches 45 | - Add space between the group name and the host number 46 | 47 | ## [1.1.0] - 2021-08-15 48 | ### Added 49 | - show number of hosts in groups and subgroups 50 | - show active triggers for groups 51 | 52 | ## [1.0.3] - 2021-08-11 53 | ### Changed 54 | - fixed pagination 55 | 56 | ## [1.0.2] - 2021-08-11 57 | ### Changed 58 | - fixed issue with "fake" group IDs duplication leading to hiding/showing multiple groups simultaneously 59 | 60 | ## [1.0.1] - 2021-08-10 61 | ### Changed 62 | - show all parent groups that do not exist in Zabbix DB or do not have any hosts 63 | 64 | ## [1.0.0] - 2021-08-08 65 | ### Added 66 | - the first version released 67 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | get('menu.main') 15 | ->findOrAdd(_('Monitoring')) 16 | ->getSubmenu() 17 | ->insertAfter('Hosts', (new \CMenuItem(_('Hosts tree'))) 18 | ->setAction('bghost.view') 19 | ); 20 | } 21 | 22 | /** 23 | * Event handler, triggered before executing the action. 24 | * 25 | * @param CAction $action Action instance responsible for current request. 26 | */ 27 | public function onBeforeAction(CAction $action): void { 28 | } 29 | 30 | /** 31 | * Event handler, triggered on application exit. 32 | * 33 | * @param CAction $action Action instance responsible for current request. 34 | */ 35 | public function onTerminate(CAction $action): void { 36 | } 37 | } 38 | ?> 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zabbix-module-hosts-tree 2 | Written according to Zabbix official documentation [Modules](https://www.zabbix.com/documentation/current/en/devel/modules/file_structure) 3 | 4 | A Zabbix module to show groups/hosts as a tree under Monitoring -> Hosts Tree menu item in Zabbix. 5 | ![screenshot](screenshots/zabbix-module-hosts-tree-1.png) 6 | 7 | IMPORTANT: pick module version according to Zabbix version: 8 | | Module version | Zabbix version | 9 | |:--------------:|:--------------:| 10 | | v1.3.0 | 5.4 | 11 | | v2.0.1 | 6.0 | 12 | | v3.0.0 | 6.2 | 13 | | v4.1.1 | 6.4 | 14 | | v6.0.1 | 7.0 | 15 | | v6.1.1 | 7.2 | 16 | 17 | # How to use 18 | 1) Create a folder in your Zabbix server modules folder (by default /usr/share/zabbix/) and copy contents of this repository into folder `zabbix-module-hosts-tree`. 19 | 2) Go to Administration -> General -> Modules click Scan directory and enable the module. You should get new 'Hosts tree' menu item under Monitoring. 20 | 21 | ## Authors 22 | See [Contributors](https://github.com/BGmot/zabbix-module-hosts-tree/graphs/contributors) 23 | -------------------------------------------------------------------------------- /actions/CControllerBGHost.php: -------------------------------------------------------------------------------- 1 | '', 39 | 'groupids' => [], 40 | 'ip' => '', 41 | 'dns' => '', 42 | 'port' => '', 43 | 'status' => -1, 44 | 'evaltype' => TAG_EVAL_TYPE_AND_OR, 45 | 'tags' => [], 46 | 'severities' => [], 47 | 'show_suppressed' => ZBX_PROBLEM_SUPPRESSED_FALSE, 48 | 'maintenance_status' => HOST_MAINTENANCE_STATUS_ON, 49 | 'page' => null, 50 | 'sort' => 'name', 51 | 'sortorder' => ZBX_SORT_UP 52 | ]; 53 | 54 | /** 55 | * Get host list results count for passed filter. 56 | * 57 | * @param array $filter Filter options. 58 | * @param string $filter['name'] Filter hosts by name. 59 | * @param array $filter['groupids'] Filter hosts by host groups. 60 | * @param string $filter['ip'] Filter hosts by IP. 61 | * @param string $filter['dns'] Filter hosts by DNS. 62 | * @param string $filter['port'] Filter hosts by port. 63 | * @param string $filter['status'] Filter hosts by status. 64 | * @param string $filter['evaltype'] Filter hosts by tags. 65 | * @param string $filter['tags'] Filter hosts by tag names and values. 66 | * @param string $filter['severities'] Filter problems on hosts by severities. 67 | * @param string $filter['show_suppressed'] Filter suppressed problems. 68 | * @param int $filter['maintenance_status'] Filter hosts by maintenance. 69 | * 70 | * @return int 71 | */ 72 | protected function getCount(array $filter): int { 73 | $groupids = $filter['groupids'] ? getSubGroups($filter['groupids']) : null; 74 | 75 | return (int) API::Host()->get([ 76 | 'countOutput' => true, 77 | 'evaltype' => $filter['evaltype'], 78 | 'tags' => $filter['tags'], 79 | 'inheritedTags' => true, 80 | 'groupids' => $groupids, 81 | 'severities' => $filter['severities'] ? $filter['severities'] : null, 82 | 'withProblemsSuppressed' => $filter['severities'] 83 | ? (($filter['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_TRUE) ? null : false) 84 | : null, 85 | 'search' => [ 86 | 'name' => ($filter['name'] === '') ? null : $filter['name'], 87 | 'ip' => ($filter['ip'] === '') ? null : $filter['ip'], 88 | 'dns' => ($filter['dns'] === '') ? null : $filter['dns'] 89 | ], 90 | 'filter' => [ 91 | 'status' => ($filter['status'] == -1) ? null : $filter['status'], 92 | 'port' => ($filter['port'] === '') ? null : $filter['port'], 93 | 'maintenance_status' => ($filter['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) 94 | ? null 95 | : HOST_MAINTENANCE_STATUS_OFF 96 | ], 97 | 'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1 98 | ]); 99 | } 100 | 101 | /** 102 | * Prepares the host list based on the given filter and sorting options. 103 | * 104 | * @param array $filter Filter options. 105 | * @param string $filter['name'] Filter hosts by name. 106 | * @param array $filter['groupids'] Filter hosts by host groups. 107 | * @param string $filter['ip'] Filter hosts by IP. 108 | * @param string $filter['dns'] Filter hosts by DNS. 109 | * @param string $filter['port'] Filter hosts by port. 110 | * @param string $filter['status'] Filter hosts by status. 111 | * @param string $filter['evaltype'] Filter hosts by tags. 112 | * @param string $filter['tags'] Filter hosts by tag names and values. 113 | * @param string $filter['severities'] Filter problems on hosts by severities. 114 | * @param string $filter['show_suppressed'] Filter suppressed problems. 115 | * @param int $filter['maintenance_status'] Filter hosts by maintenance. 116 | * @param int $filter['page'] Page number. 117 | * @param string $filter['sort'] Sorting field. 118 | * @param string $filter['sortorder'] Sorting order. 119 | * 120 | * @return array 121 | */ 122 | protected function getData(array $filter): array { 123 | $limit = CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1; 124 | $groupids = $filter['groupids'] ? getSubGroups($filter['groupids']) : null; 125 | $hosts = API::Host()->get([ 126 | 'output' => ['hostid', 'name', 'status'], 127 | 'evaltype' => $filter['evaltype'], 128 | 'tags' => $filter['tags'], 129 | 'inheritedTags' => true, 130 | 'groupids' => $groupids, 131 | 'severities' => $filter['severities'] ? $filter['severities'] : null, 132 | 'withProblemsSuppressed' => $filter['severities'] 133 | ? (($filter['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_TRUE) ? null : false) 134 | : null, 135 | 'search' => [ 136 | 'name' => ($filter['name'] === '') ? null : $filter['name'], 137 | 'ip' => ($filter['ip'] === '') ? null : $filter['ip'], 138 | 'dns' => ($filter['dns'] === '') ? null : $filter['dns'] 139 | ], 140 | 'filter' => [ 141 | 'status' => ($filter['status'] == -1) ? null : $filter['status'], 142 | 'port' => ($filter['port'] === '') ? null : $filter['port'], 143 | 'maintenance_status' => ($filter['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) 144 | ? null 145 | : HOST_MAINTENANCE_STATUS_OFF 146 | ], 147 | 'selectHostGroups' => ['groupid', 'name'], 148 | 'sortfield' => 'name', 149 | 'limit' => $limit, 150 | 'preservekeys' => true 151 | ]); 152 | 153 | $host_groups = []; // Information about all groups to build a tree 154 | $fake_group_id = 100000; 155 | 156 | foreach ($hosts as &$host) { 157 | foreach ($host['hostgroups'] as $group) { 158 | $groupid = $group['groupid']; 159 | $groupname_full = $group['name']; 160 | if (!array_key_exists($groupname_full, $host_groups)) { 161 | $host_groups[$groupname_full] = [ 162 | 'groupid' => $groupid, 163 | 'hosts' => [ 164 | $host['hostid'] 165 | ], 166 | 'children' => [], 167 | 'parent_group_name' => '', 168 | 'num_of_hosts' => 1, 169 | 'problem_count' => [], 170 | 'is_collapsed' => true 171 | ]; 172 | for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) { 173 | $host_groups[$groupname_full]['problem_count'][$severity] = 0; 174 | } 175 | } else { 176 | $host_groups[$groupname_full]['hosts'][] = $host['hostid']; 177 | $host_groups[$groupname_full]['num_of_hosts']++; 178 | } 179 | 180 | $grp_arr = explode('/', $groupname_full); 181 | if (count($grp_arr) > 1) { 182 | // Find all parent groups and create respective array elements in $host_groups 183 | $this->add_parent($host_groups, $fake_group_id, $groupname_full, $filter); 184 | } 185 | } 186 | } 187 | unset($host); 188 | 189 | $filter['sortorder'] == 'ASC' ? ksort($host_groups) : krsort($host_groups); 190 | 191 | $hosts_sorted_by_group = []; 192 | foreach ($host_groups as $host_group_name => $host_group) { 193 | $this->add_hosts_of_child_group($hosts_sorted_by_group, $hosts, $host_groups, $host_group_name, $filter); 194 | } 195 | 196 | $view_curl = (new CUrl())->setArgument('action', 'bghost.view'); 197 | 198 | // Split result array and create paging. 199 | $paging = CPagerHelper::paginate($filter['page'], $hosts_sorted_by_group, $filter['sortorder'], $view_curl); 200 | 201 | // Get additional data to limited host amount. 202 | $hosts = API::Host()->get([ 203 | 'output' => ['hostid', 'name', 'status', 'maintenance_status', 'maintenanceid', 'maintenance_type'], 204 | 'selectInterfaces' => ['ip', 'dns', 'port', 'main', 'type', 'useip', 'available', 'error', 'details'], 205 | 'selectGraphs' => API_OUTPUT_COUNT, 206 | 'selectHttpTests' => API_OUTPUT_COUNT, 207 | 'selectTags' => ['tag', 'value'], 208 | 'selectInheritedTags' => ['tag', 'value'], 209 | 'hostids' => array_keys($hosts_sorted_by_group), 210 | 'preservekeys' => true 211 | ]); 212 | 213 | // Get only those groups that need to be shown 214 | $host_groups_to_show = []; 215 | foreach ($hosts_sorted_by_group as $host) { 216 | foreach ($host['hostgroups'] as $group) { 217 | if (!array_key_exists($group['name'], $host_groups_to_show)) { 218 | $host_groups_to_show[$group['name']] = $host_groups[$group['name']]; 219 | $host_groups_to_show[$group['name']]['hosts'] = [ $host['hostid'] ]; 220 | // Make sure parent group exists as well 221 | $grp_arr = explode('/', $group['name']); 222 | for ($i = 1, $g_name = $grp_arr[0]; $i < count($grp_arr); $i++) { 223 | if (!array_key_exists($g_name, $host_groups_to_show)) { 224 | $host_groups_to_show[$g_name] = $host_groups[$g_name]; 225 | $host_groups_to_show[$g_name]['hosts'] = []; 226 | } 227 | $g_name = $g_name.'/'.$grp_arr[$i]; 228 | } 229 | } else { 230 | $host_groups_to_show[$group['name']]['hosts'][] = $host['hostid']; 231 | } 232 | } 233 | } 234 | // Remove groups that are not to be shown from 'children' groups list 235 | foreach ($host_groups_to_show as $group_name => &$group) { 236 | $groups_to_delete = []; 237 | foreach ($group['children'] as $child_group_name) { 238 | if (!array_key_exists($child_group_name, $host_groups_to_show)) { 239 | $groups_to_delete[] = $child_group_name; 240 | } 241 | } 242 | foreach ($groups_to_delete as $group_name) { 243 | if (($key = array_search($group_name, $group['children'])) !== false) { 244 | unset($group['children'][$key]); 245 | } 246 | } 247 | } 248 | unset($group); 249 | 250 | $filter['sortorder'] == 'ASC' ? ksort($host_groups_to_show) : krsort($host_groups_to_show); 251 | 252 | // Some hosts for shown groups can be on other pages thus not in $hosts_sorted_by_group 253 | // as we already applied paging. To calculate number of problems we need all hosts belonging to shown groups 254 | $all_hosts_in_groups_to_show = []; 255 | foreach ($host_groups_to_show as $group_name => $group) { 256 | foreach ($host_groups[$group_name]['hosts'] as $host) { 257 | $all_hosts_in_groups_to_show[] = $host; 258 | } 259 | } 260 | 261 | $maintenanceids = []; 262 | 263 | // Select triggers and problems to calculate number of problems for each host. 264 | $triggers = API::Trigger()->get([ 265 | 'output' => [], 266 | 'selectHosts' => ['hostid'], 267 | 'hostids' => $all_hosts_in_groups_to_show, 268 | 'skipDependent' => true, 269 | 'monitored' => true, 270 | 'preservekeys' => true 271 | ]); 272 | 273 | $problems = API::Problem()->get([ 274 | 'output' => ['eventid', 'objectid', 'severity'], 275 | 'objectids' => array_keys($triggers), 276 | 'source' => EVENT_SOURCE_TRIGGERS, 277 | 'object' => EVENT_OBJECT_TRIGGER, 278 | 'suppressed' => ($filter['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_TRUE) ? null : false 279 | ]); 280 | 281 | // Group all problems per host per severity. 282 | $host_problems = []; 283 | foreach ($problems as $problem) { 284 | foreach ($triggers[$problem['objectid']]['hosts'] as $trigger_host) { 285 | $host_problems[$trigger_host['hostid']][$problem['severity']][$problem['eventid']] = true; 286 | } 287 | } 288 | 289 | // Count problems for each shown group - take into account only hosts belonging to each group (no parents/children) 290 | foreach ($host_groups_to_show as $group_name => &$group) { 291 | foreach($host_groups[$group_name]['hosts'] as $hostid) { 292 | // Count the number of problems (as value) per severity (as key). 293 | for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) { 294 | // Fill empty arrays for hosts without problems. 295 | if (array_key_exists($hostid, $host_problems)) { 296 | if (array_key_exists($severity, $host_problems[$hostid])) { 297 | $group['problem_count'][$severity] += count($host_problems[$hostid][$severity]); 298 | 299 | // Increment problems count in parent groups 300 | $grp_arr = explode('/', $group_name); 301 | for ($i = count($grp_arr)-1, $g_name_child = $group_name; $i > 0; $i--) { 302 | array_pop($grp_arr); 303 | $g_name_parent = implode('/', $grp_arr); 304 | $host_groups_to_show[$g_name_parent]['problem_count'][$severity] += 305 | count($host_problems[$hostid][$severity]); 306 | $g_name_child = $g_name_parent; 307 | } 308 | } 309 | } 310 | } 311 | } 312 | } 313 | unset($group); 314 | 315 | foreach ($hosts as &$host) { 316 | // Count number of dashboards for each host. 317 | $host['dashboards'] = count(getHostDashboards($host['hostid'])); 318 | 319 | CArrayHelper::sort($host['interfaces'], [['field' => 'main', 'order' => ZBX_SORT_DOWN]]); 320 | 321 | if ($host['status'] == HOST_STATUS_MONITORED && $host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) { 322 | $maintenanceids[$host['maintenanceid']] = true; 323 | } 324 | 325 | // Fill empty arrays for hosts without problems. 326 | if (!array_key_exists($host['hostid'], $host_problems)) { 327 | $host_problems[$host['hostid']] = []; 328 | } 329 | 330 | // Count the number of problems (as value) per severity (as key). 331 | for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) { 332 | $host['problem_count'][$severity] = array_key_exists($severity, $host_problems[$host['hostid']]) 333 | ? count($host_problems[$host['hostid']][$severity]) 334 | : 0; 335 | } 336 | 337 | // Merge host tags with template tags, and skip duplicate tags and values. 338 | if (!$host['inheritedTags']) { 339 | $tags = $host['tags']; 340 | } 341 | elseif (!$host['tags']) { 342 | $tags = $host['inheritedTags']; 343 | } 344 | else { 345 | $tags = $host['tags']; 346 | 347 | foreach ($host['inheritedTags'] as $template_tag) { 348 | foreach ($tags as $host_tag) { 349 | // Skip tags with same name and value. 350 | if ($host_tag['tag'] === $template_tag['tag'] 351 | && $host_tag['value'] === $template_tag['value']) { 352 | continue 2; 353 | } 354 | } 355 | $tags[] = $template_tag; 356 | } 357 | } 358 | 359 | $host['tags'] = $tags; 360 | 361 | } 362 | unset($host); 363 | 364 | $maintenances = []; 365 | 366 | if ($maintenanceids) { 367 | $maintenances = API::Maintenance()->get([ 368 | 'output' => ['name', 'description'], 369 | 'maintenanceids' => array_keys($maintenanceids), 370 | 'preservekeys' => true 371 | ]); 372 | } 373 | 374 | $tags = makeTags($hosts, true, 'hostid', ZBX_TAG_COUNT_DEFAULT, $filter['tags']); 375 | 376 | foreach ($hosts as &$host) { 377 | $host['tags'] = $tags[$host['hostid']]; 378 | } 379 | unset($host); 380 | 381 | return [ 382 | 'paging' => $paging, 383 | 'hosts' => $hosts, 384 | 'host_groups' => $host_groups_to_show, 385 | 'maintenances' => $maintenances 386 | ]; 387 | } 388 | 389 | // Adds all hosts belonging to $host_group_name to global array $hosts_sorted_by_group 390 | protected function add_hosts_of_child_group(&$hosts_sorted_by_group, $hosts, $host_groups, $host_group_name, $filter) { 391 | // First add all the hosts belonging to this group 392 | $hosts_to_add = []; 393 | foreach($host_groups[$host_group_name]['hosts'] as $hostid) { 394 | $hosts_to_add[$hostid] = $hosts[$hostid]; 395 | } 396 | $hosts_to_add = $this->array_sort($hosts_to_add, 'name', $filter['sortorder']); 397 | foreach($hosts_to_add as $hostid => $host){ 398 | $hosts_sorted_by_group[$hostid] = $host; 399 | } 400 | // Add all hosts of children groups 401 | if (count($host_groups[$host_group_name]['children']) > 0) { 402 | foreach($host_groups[$host_group_name]['children'] as $child_group_name){ 403 | $this->add_hosts_of_child_group($hosts_sorted_by_group, $hosts, $host_groups, $child_group_name, $filter); 404 | } 405 | } 406 | } 407 | 408 | protected function array_sort($array, $on, $order='ASC') 409 | { 410 | $new_array = array(); 411 | $sortable_array = array(); 412 | 413 | if (count($array) > 0) { 414 | foreach ($array as $k => $v) { 415 | if (is_array($v)) { 416 | foreach ($v as $k2 => $v2) { 417 | if ($k2 == $on) { 418 | $sortable_array[$k] = $v2; 419 | } 420 | } 421 | } else { 422 | $sortable_array[$k] = $v; 423 | } 424 | } 425 | 426 | switch ($order) { 427 | case 'ASC': 428 | asort($sortable_array, SORT_STRING); 429 | break; 430 | case 'DESC': 431 | arsort($sortable_array, SORT_STRING); 432 | break; 433 | } 434 | 435 | foreach ($sortable_array as $k => $v) { 436 | $new_array[$k] = $array[$k]; 437 | } 438 | } 439 | 440 | return $new_array; 441 | } 442 | 443 | /** 444 | * Adds parent group 445 | * 446 | * @param array $host_groups All the groups to be shown in hierarchy 447 | * @param int $fake_group_id ID for groups that do not exist in Zabbix DB (autoincremented) 448 | * @param string $groupname_full Group name parent group of which needs to be added 449 | * @param array $filter Filter options. 450 | * 451 | * @return array $host_groups modified in-place 452 | */ 453 | protected function add_parent(&$host_groups, &$fake_group_id, $groupname_full, $filter) { 454 | // There is a '/' in group name 455 | $grp_arr = explode('/', $groupname_full); 456 | unset($grp_arr[count($grp_arr)-1]); // Remove last element 457 | $parent_group_name = implode('/', $grp_arr); 458 | // In Zabbix it is possible to have parent name that does not exist 459 | // e.g.: group '/level0/level1/level2' exists but '/level0/level1' does not 460 | if (array_key_exists($parent_group_name, $host_groups)) { 461 | // Parent group exists 462 | if (!in_array($groupname_full, $host_groups[$parent_group_name]['children'])) { 463 | $host_groups[$parent_group_name]['children'][] = $groupname_full; 464 | } 465 | $host_groups[$parent_group_name]['num_of_hosts']++; 466 | } else { 467 | // Parent group does not exist or does not have any hosts to show 468 | $host_groups[$parent_group_name] = [ 469 | 'groupid' => $fake_group_id++, 470 | 'hosts' => [], 471 | 'children' => [$groupname_full], 472 | 'parent_group_name' => '', 473 | 'num_of_hosts' => 1, 474 | 'problem_count' => [], 475 | 'is_collapsed' => true 476 | ]; 477 | for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) { 478 | $host_groups[$parent_group_name]['problem_count'][$severity] = 0; 479 | } 480 | } 481 | $host_groups[$groupname_full]['parent_group_name'] = $parent_group_name; 482 | $parent_group_name_arr = explode('/', $parent_group_name); 483 | if (count($parent_group_name_arr) > 1) { 484 | // Parent group also has parent 485 | $this->add_parent($host_groups, $fake_group_id, $parent_group_name, $filter); 486 | } 487 | // Sort group names 488 | $filter['sortorder'] == 'ASC' ? sort($host_groups[$parent_group_name]['children']) : rsort($host_groups[$parent_group_name]['children']); 489 | } 490 | 491 | /** 492 | * Get additional data for filters. Selected groups for multiselect, etc. 493 | * 494 | * @param array $filter Filter fields values array. 495 | * 496 | * @return array 497 | */ 498 | protected function getAdditionalData($filter): array { 499 | $data = []; 500 | 501 | if ($filter['groupids']) { 502 | $groups = API::HostGroup()->get([ 503 | 'output' => ['groupid', 'name'], 504 | 'groupids' => $filter['groupids'] 505 | ]); 506 | $data['groups_multiselect'] = CArrayHelper::renameObjectsKeys(array_values($groups), ['groupid' => 'id']); 507 | } 508 | 509 | return $data; 510 | } 511 | 512 | /** 513 | * Clean passed filter fields in input from default values required for HTML presentation. Convert field 514 | * 515 | * @param array $input Filter fields values. 516 | * 517 | * @return array 518 | */ 519 | protected function cleanInput(array $input): array { 520 | if (array_key_exists('tags', $input) && $input['tags']) { 521 | $input['tags'] = array_filter($input['tags'], function($tag) { 522 | return !($tag['tag'] === '' && $tag['value'] === ''); 523 | }); 524 | $input['tags'] = array_values($input['tags']); 525 | } 526 | 527 | return $input; 528 | } 529 | 530 | /** 531 | * Clean the filter from non-existing host group IDs. 532 | * 533 | * @param array $filter 534 | * 535 | * $filter = [ 536 | * 'groupids' => (array) Group IDs from filter to check. 537 | * ] 538 | * 539 | * @return array 540 | */ 541 | protected static function sanitizeFilter(array $filter): array { 542 | if ($filter['groupids']) { 543 | $groups = API::HostGroup()->get([ 544 | 'output' => [], 545 | 'groupids' => $filter['groupids'], 546 | 'preservekeys' => true 547 | ]); 548 | 549 | $filter['groupids'] = array_filter($filter['groupids'], static fn($groupid) => 550 | array_key_exists($groupid, $groups) 551 | ); 552 | } 553 | 554 | return $filter; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /actions/CControllerBGHostView.php: -------------------------------------------------------------------------------- 1 | disableCsrfValidation(); 36 | } 37 | 38 | protected function checkInput(): bool { 39 | $fields = [ 40 | 'name' => 'string', 41 | 'groupids' => 'array_id', 42 | 'ip' => 'string', 43 | 'dns' => 'string', 44 | 'port' => 'string', 45 | 'status' => 'in -1,'.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED, 46 | 'evaltype' => 'in '.TAG_EVAL_TYPE_AND_OR.','.TAG_EVAL_TYPE_OR, 47 | 'tags' => 'array', 48 | 'severities' => 'array', 49 | 'show_suppressed' => 'in '.ZBX_PROBLEM_SUPPRESSED_FALSE.','.ZBX_PROBLEM_SUPPRESSED_TRUE, 50 | 'maintenance_status' => 'in '.HOST_MAINTENANCE_STATUS_OFF.','.HOST_MAINTENANCE_STATUS_ON, 51 | 'sort' => 'in name,status', 52 | 'sortorder' => 'in '.ZBX_SORT_UP.','.ZBX_SORT_DOWN, 53 | 'page' => 'ge 1', 54 | 'filter_name' => 'string', 55 | 'filter_custom_time' => 'in 1,0', 56 | 'filter_show_counter' => 'in 1,0', 57 | 'filter_counters' => 'in 1', 58 | 'filter_reset' => 'in 1', 59 | 'counter_index' => 'ge 0' 60 | ]; 61 | 62 | $ret = $this->validateInput($fields); 63 | 64 | // Validate tags filter. 65 | if ($ret && $this->hasInput('tags')) { 66 | foreach ($this->getInput('tags') as $filter_tag) { 67 | if (count($filter_tag) != 3 68 | || !array_key_exists('tag', $filter_tag) || !is_string($filter_tag['tag']) 69 | || !array_key_exists('value', $filter_tag) || !is_string($filter_tag['value']) 70 | || !array_key_exists('operator', $filter_tag) || !is_string($filter_tag['operator'])) { 71 | $ret = false; 72 | break; 73 | } 74 | } 75 | } 76 | 77 | // Validate severity checkbox filter. 78 | if ($ret && $this->hasInput('severities')) { 79 | $ret = !array_diff($this->getInput('severities'), 80 | range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1) 81 | ); 82 | } 83 | 84 | if (!$ret) { 85 | $this->setResponse(new CControllerResponseFatal()); 86 | } 87 | 88 | return $ret; 89 | } 90 | 91 | protected function checkPermissions(): bool { 92 | return $this->checkAccess(CRoleHelper::UI_MONITORING_HOSTS); 93 | } 94 | 95 | protected function doAction(): void { 96 | $filter_tabs = []; 97 | $profile = (new CTabFilterProfile(static::FILTER_IDX, static::FILTER_FIELDS_DEFAULT))->read(); 98 | 99 | if ($this->hasInput('filter_reset')) { 100 | $profile->reset(); 101 | } 102 | else { 103 | $profile->setInput($this->cleanInput($this->getInputAll())); 104 | } 105 | 106 | foreach ($profile->getTabsWithDefaults() as $index => $filter_tab) { 107 | if ($index == $profile->selected) { 108 | // Initialize multiselect data for filter_scr to allow tabfilter correctly handle unsaved state. 109 | $filter_tab['filter_src']['filter_view_data'] = $this->getAdditionalData($filter_tab['filter_src']); 110 | } 111 | 112 | $filter_tabs[] = $filter_tab + ['filter_view_data' => $this->getAdditionalData($filter_tab)]; 113 | } 114 | 115 | $filter = $filter_tabs[$profile->selected]; 116 | $filter = self::sanitizeFilter($filter); 117 | 118 | $refresh_curl = new CUrl('zabbix.php'); 119 | $filter['action'] = 'bghost.view.refresh'; 120 | array_map([$refresh_curl, 'setArgument'], array_keys($filter), $filter); 121 | 122 | $data = [ 123 | 'refresh_url' => $refresh_curl->getUrl(), 124 | 'refresh_interval' => 3600000, 125 | 'filter_view' => 'monitoring.host.filter', 126 | 'filter_defaults' => $profile->filter_defaults, 127 | 'filter_groupids' => $this->getInput('groupids', []), 128 | 'filter_tabs' => $filter_tabs, 129 | 'can_create_hosts' => $this->checkAccess(CRoleHelper::UI_CONFIGURATION_HOSTS), 130 | 'tabfilter_options' => [ 131 | 'idx' => static::FILTER_IDX, 132 | 'selected' => $profile->selected, 133 | 'support_custom_time' => 0, 134 | 'expanded' => $profile->expanded, 135 | 'page' => $filter['page'], 136 | 'csrf_token' => CCsrfTokenHelper::get('tabfilter') 137 | ] 138 | ]; 139 | 140 | $response = new CControllerResponseData($data); 141 | $response->setTitle(_('Hosts')); 142 | $this->setResponse($response); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /actions/CControllerBGHostViewRefresh.php: -------------------------------------------------------------------------------- 1 | Monitoring" asynchronous refresh page. 31 | */ 32 | class CControllerBGHostViewRefresh extends CControllerBGHostView { 33 | 34 | protected function init(): void { 35 | $this->disableCsrfValidation(); 36 | } 37 | 38 | protected function doAction(): void { 39 | $filter = static::FILTER_FIELDS_DEFAULT; 40 | 41 | if ($this->getInput('filter_counters', 0)) { 42 | $profile = (new CTabFilterProfile(static::FILTER_IDX, static::FILTER_FIELDS_DEFAULT))->read(); 43 | $filters = $this->hasInput('counter_index') 44 | ? [$profile->getTabFilter($this->getInput('counter_index'))] 45 | : $profile->getTabsWithDefaults(); 46 | $filter_counters = []; 47 | 48 | foreach ($filters as $index => $tabfilter) { 49 | $tabfilter = self::sanitizeFilter($tabfilter); 50 | 51 | $filter_counters[$index] = $tabfilter['filter_show_counter'] ? $this->getCount($tabfilter) : 0; 52 | } 53 | 54 | $this->setResponse( 55 | (new CControllerResponseData([ 56 | 'main_block' => json_encode(['filter_counters' => $filter_counters]) 57 | ]))->disableView() 58 | ); 59 | } 60 | else { 61 | $this->getInputs($filter, array_keys($filter)); 62 | $filter = $this->cleanInput($filter); 63 | $filter = self::sanitizeFilter($filter); 64 | 65 | $view_url = (new CUrl()) 66 | ->setArgument('action', 'bghost.view') 67 | ->removeArgument('page'); 68 | 69 | $data = [ 70 | 'filter' => $filter, 71 | 'view_curl' => $view_url, 72 | 'sort' => $filter['sort'], 73 | 'sortorder' => $filter['sortorder'], 74 | 'allowed_ui_latest_data' => $this->checkAccess(CRoleHelper::UI_MONITORING_LATEST_DATA), 75 | 'allowed_ui_problems' => $this->checkAccess(CRoleHelper::UI_MONITORING_PROBLEMS) 76 | ] + $this->getData($filter); 77 | 78 | $response = new CControllerResponseData($data); 79 | $this->setResponse($response); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2.0, 3 | "id": "bghosts", 4 | "name": "Groups/Hosts tree", 5 | "version": "6.1.1", 6 | "namespace": "BGmotHosts", 7 | "author": "Evgeny Yurchenko", 8 | "url": "https://bgmot.com", 9 | "description": "Show groups/hosts as a tree under Monitoring -> Hosts Tree menu item", 10 | "actions": { 11 | "bghost.view": { 12 | "class": "CControllerBGHostView", 13 | "view": "module.monitoring.bghost.view" 14 | }, 15 | "bghost.view.refresh": { 16 | "class": "CControllerBGHostViewRefresh", 17 | "view": "module.monitoring.bghost.view.refresh", 18 | "layout": "layout.json" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /partials/js/monitoring.host.view.refresh.js.php: -------------------------------------------------------------------------------- 1 | 27 | 28 | 105 | -------------------------------------------------------------------------------- /partials/module.monitoring.host.view.html.php: -------------------------------------------------------------------------------- 1 | includeJsFile('monitoring.host.view.refresh.js.php'); 22 | 23 | $form = (new CForm()) 24 | ->setName('host_view'); 25 | 26 | $view_url = $data['view_curl']->getUrl(); 27 | 28 | $table = (new CTableInfo()) 29 | ->setHeader([ 30 | make_sorting_header(_('Name'), 'name', $data['sort'], $data['sortorder'], $view_url), 31 | (new CColHeader(_('Interface'))), 32 | (new CColHeader(_('Availability'))), 33 | (new CColHeader(_('Tags'))), 34 | // Fix: problems renamed to triggers to distinguish from the problems counter column 35 | (new CColHeader(_('Problems'))), 36 | make_sorting_header(_('Status'), 'status', $data['sort'], $data['sortorder'], $view_url), 37 | (new CColHeader(_('Latest data'))), 38 | (new CColHeader(_('Problems'))), 39 | (new CColHeader(_('Graphs'))), 40 | (new CColHeader(_('Dashboards'))), 41 | (new CColHeader(_('Web'))) 42 | ]) 43 | ->setPageNavigation($data['paging']); 44 | 45 | foreach ($data['host_groups'] as $group_name => $group) { 46 | if ($group['parent_group_name'] == '') { 47 | // Add only top level groups, children will be added recursively in addGroupRow() 48 | $rows = []; 49 | addGroupRow($data, $rows, $group_name, '', 0, $child_stat); 50 | 51 | foreach ($rows as $row) { 52 | $table->addRow($row); 53 | } 54 | } 55 | } 56 | 57 | $form->addItem($table); 58 | 59 | echo $form; 60 | 61 | // Adds one Group to the table (recursively calls itself for all sub-groups) 62 | function addGroupRow($data, &$rows, $group_name, $parent_group_name, $level, &$child_stat) { 63 | $interface_types = [INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI]; 64 | 65 | $group = $data['host_groups'][$group_name]; 66 | 67 | $host_rows = []; 68 | foreach ($group['hosts'] as $hostid) { 69 | $host = $data['hosts'][$hostid]; 70 | $host_name = (new CLinkAction($host['name']))->setMenuPopup(CMenuPopupHelper::getHost($hostid)); 71 | 72 | $interface = null; 73 | if ($host['interfaces']) { 74 | foreach ($host['interfaces'] as $index => $value) { 75 | $host['interfaces'][$index]['has_enabled_items'] = true; 76 | } 77 | foreach ($interface_types as $interface_type) { 78 | $host_interfaces = array_filter($host['interfaces'], function(array $host_interface) use ($interface_type) { 79 | return ($host_interface['type'] == $interface_type); 80 | }); 81 | if ($host_interfaces) { 82 | $interface = reset($host_interfaces); 83 | break; 84 | } 85 | } 86 | } 87 | 88 | $problems_link = new CLink('', (new CUrl('zabbix.php')) 89 | ->setArgument('action', 'problem.view') 90 | ->setArgument('filter_name', '') 91 | ->setArgument('severities', $data['filter']['severities']) 92 | ->setArgument('hostids', [$host['hostid']])); 93 | 94 | $total_problem_count = 0; 95 | 96 | // Fill the severity icons by problem count and style, and calculate the total number of problems. 97 | // Need this to have cosntant order of triggers from disater to information. 98 | krsort($host['problem_count'],true); 99 | 100 | foreach ($host['problem_count'] as $severity => $count) { 101 | if (($count > 0 && $data['filter']['severities'] && in_array($severity, $data['filter']['severities'])) 102 | || (!$data['filter']['severities'] && $count > 0)) { 103 | $total_problem_count += $count; 104 | 105 | $problems_link->addItem((new CSpan($count)) 106 | ->addClass(ZBX_STYLE_PROBLEM_ICON_LIST_ITEM) 107 | ->addClass(CSeverityHelper::getStatusStyle($severity)) 108 | ->setAttribute('title', CSeverityHelper::getName($severity)) 109 | ); 110 | } 111 | } 112 | 113 | if ($total_problem_count == 0) { 114 | $problems_link->addItem('Problems'); 115 | } 116 | else { 117 | $problems_link->addClass(ZBX_STYLE_PROBLEM_ICON_LINK); 118 | } 119 | 120 | $maintenance_icon = ''; 121 | 122 | if ($host['status'] == HOST_STATUS_MONITORED && $host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) { 123 | if (array_key_exists($host['maintenanceid'], $data['maintenances'])) { 124 | $maintenance = $data['maintenances'][$host['maintenanceid']]; 125 | $maintenance_icon = makeMaintenanceIcon($host['maintenance_type'], $maintenance['name'], 126 | $maintenance['description'] 127 | ); 128 | } 129 | else { 130 | $maintenance_icon = makeMaintenanceIcon($host['maintenance_type'], 131 | _('Inaccessible maintenance'), '' 132 | ); 133 | } 134 | } 135 | 136 | $col1 = new CCol(); 137 | for($i = 0; $i <= (6 + $level*5); $i++) { 138 | $col1 -> addItem(NBSP_BG()); 139 | } 140 | $col1 -> addItem($host_name) -> addItem($maintenance_icon); 141 | $table_row_host = new CRow([ 142 | $col1, 143 | (new CCol(getHostInterface($interface)))->addClass(ZBX_STYLE_NOWRAP), 144 | getHostAvailabilityTable($host['interfaces']), 145 | $host['tags'], 146 | $problems_link, 147 | ($host['status'] == HOST_STATUS_MONITORED) 148 | ? (new CSpan(_('Enabled')))->addClass(ZBX_STYLE_GREEN) 149 | : (new CSpan(_('Disabled')))->addClass(ZBX_STYLE_RED), 150 | [ 151 | $data['allowed_ui_latest_data'] 152 | ? new CLink(_('Latest data'), 153 | (new CUrl('zabbix.php')) 154 | ->setArgument('action', 'latest.view') 155 | ->setArgument('filter_set', '1') 156 | ->setArgument('filter_hostids', [$host['hostid']]) 157 | ) 158 | : _('Latest data') 159 | ], 160 | [ 161 | $data['allowed_ui_problems'] 162 | ? new CLink(_('Problems'), 163 | (new CUrl('zabbix.php')) 164 | ->setArgument('action', 'problem.view') 165 | ->setArgument('filter_name', '') 166 | ->setArgument('severities', $data['filter']['severities']) 167 | ->setArgument('hostids', [$host['hostid']]) 168 | ) 169 | : _('Problems'), 170 | CViewHelper::showNum($total_problem_count) 171 | ], 172 | $host['graphs'] 173 | ? [ 174 | new CLink(_('Graphs'), 175 | (new CUrl('zabbix.php')) 176 | ->setArgument('action', 'charts.view') 177 | ->setArgument('filter_set', '1') 178 | ->setArgument('filter_hostids', (array) $host['hostid']) 179 | ), 180 | CViewHelper::showNum($host['graphs']) 181 | ] 182 | : (new CSpan(_('Graphs')))->addClass(ZBX_STYLE_DISABLED), 183 | $host['dashboards'] 184 | ? [ 185 | new CLink(_('Dashboards'), 186 | (new CUrl('zabbix.php')) 187 | ->setArgument('action', 'host.dashboard.view') 188 | ->setArgument('hostid', $host['hostid']) 189 | ), 190 | CViewHelper::showNum($host['dashboards']) 191 | ] 192 | : (new CSpan(_('Dashboards')))->addClass(ZBX_STYLE_DISABLED), 193 | $host['httpTests'] 194 | ? [ 195 | new CLink(_('Web'), 196 | (new CUrl('zabbix.php')) 197 | ->setArgument('action', 'web.view') 198 | ->setArgument('filter_set', '1') 199 | ->setArgument('filter_hostids', (array) $host['hostid']) 200 | ), 201 | CViewHelper::showNum($host['httpTests']) 202 | ] 203 | : (new CSpan(_('Web')))->addClass(ZBX_STYLE_DISABLED) 204 | ]); 205 | 206 | if ($data['host_groups'][$group_name]['is_collapsed'] ) 207 | $table_row_host->addClass(ZBX_STYLE_DISPLAY_NONE); 208 | 209 | addParentGroupClass($data, $table_row_host, $group_name); 210 | $host_rows[] = $table_row_host; 211 | } 212 | 213 | $subgroup_rows=[]; 214 | 215 | foreach ($data['host_groups'][$group_name]['children'] as $child_group_name) { 216 | addGroupRow($data, $subgroup_rows, $child_group_name, $group_name, $level + 1, $my_stat); 217 | } 218 | 219 | 220 | $is_collapsed = $data['host_groups'][$group_name]['is_collapsed']; 221 | $toggle_tag = (new CSimpleButton()) 222 | ->addClass(ZBX_STYLE_TREEVIEW) 223 | ->addClass('js-toggle') 224 | ->addItem( 225 | (new CSpan())->addClass($is_collapsed ? ZBX_STYLE_ARROW_RIGHT : ZBX_STYLE_ARROW_DOWN) 226 | ); 227 | $toggle_tag->setAttribute( 228 | 'data-group_id_'.$data['host_groups'][$group_name]['groupid'], 229 | $data['host_groups'][$group_name]['groupid'] 230 | ); 231 | 232 | $group_name_arr = explode('/', $group_name); 233 | 234 | $group_problems_div = (new CDiv())->addClass(ZBX_STYLE_PROBLEM_ICON_LIST); 235 | 236 | foreach ($data['host_groups'][$group_name]['problem_count'] as $severity => $count) { 237 | if (($count > 0 && $data['filter']['severities'] && in_array($severity, $data['filter']['severities'])) 238 | || (!$data['filter']['severities'] && $count > 0)) { 239 | $group_problems_div->addItem((new CSpan($count)) 240 | ->addClass(ZBX_STYLE_PROBLEM_ICON_LIST_ITEM) 241 | ->addClass(CSeverityHelper::getStatusStyle($severity)) 242 | ->setAttribute('title', CSeverityHelper::getName($severity)) 243 | ); 244 | } 245 | } 246 | 247 | $col2 = (new CCol()) 248 | -> setColSpan(4); 249 | for ($i = 0; $i < $level*5; $i++) { 250 | $col2 -> addItem(NBSP_BG()); 251 | } 252 | $col2 -> addItem($toggle_tag); 253 | $col2 -> addItem(bold(end($group_name_arr))); 254 | $col2 -> addItem(NBSP_BG()); 255 | $col2 -> addItem(bold('(' . $data['host_groups'][$group_name]['num_of_hosts']. ')')); 256 | $table_row = new CRow([ 257 | $col2, 258 | $group_problems_div, 259 | (new CCol()) 260 | -> setColSpan(6) 261 | ]); 262 | 263 | if ($data['host_groups'][$group_name]['is_collapsed'] && $parent_group_name != '') 264 | $table_row->addClass(ZBX_STYLE_DISPLAY_NONE); 265 | 266 | // We don't render here, but just add rows to the array 267 | addParentGroupClass($data, $table_row, $parent_group_name); 268 | 269 | $rows[] = $table_row; 270 | 271 | // Now all subgroup rows 272 | foreach ($subgroup_rows as $idx=>$row) { 273 | $rows[] = $row; 274 | } 275 | 276 | // And finally, the hosts rows 277 | foreach ($host_rows as $idx=>$row) { 278 | $rows[] = $row; 279 | } 280 | } 281 | 282 | // Adds class 'data-group_id_=' to $element 283 | function addParentGroupClass($data, &$element, $parent_group_name) { 284 | if ($parent_group_name != '') { 285 | $element->setAttribute( 286 | 'data-group_id_'.$data['host_groups'][$parent_group_name]['groupid'], 287 | $data['host_groups'][$parent_group_name]['groupid'] 288 | ); 289 | } 290 | } 291 | 292 | function NBSP_BG() { 293 | return new CHtmlEntityBG(' '); 294 | } 295 | 296 | class CHtmlEntityBG { 297 | private $entity = ''; 298 | public function __construct(string $entity) { 299 | $this->entity = $entity; 300 | } 301 | public function toString(): string { 302 | return $this->entity; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /screenshots/zabbix-module-hosts-tree-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BGmot/zabbix-module-hosts-tree/e51637de0fdf61d8d30088bbf0649ed4f85154d5/screenshots/zabbix-module-hosts-tree-1.png -------------------------------------------------------------------------------- /views/css/bghost.css: -------------------------------------------------------------------------------- 1 | .treeview { 2 | display: inline-block; 3 | width: 14px; 4 | height: 16px; 5 | min-height: auto; 6 | line-height: 16px; 7 | padding: 0; 8 | margin: 0 2px 0 0; 9 | cursor: auto; 10 | text-align: center; 11 | border: 0; 12 | background-color: transparent; } 13 | .treeview .arrow-right { 14 | border-left-color: #768d99; } 15 | .treeview .arrow-down { 16 | margin: 0 0 2px; 17 | border-top-color: #768d99; } 18 | .treeview:hover, .treeview:focus { 19 | background-color: transparent; } 20 | .treeview:hover .arrow-right, .treeview:focus .arrow-right { 21 | border-left-color: #0275b8; } 22 | .treeview:hover .arrow-down, .treeview:focus .arrow-down { 23 | border-top-color: #0275b8; } 24 | -------------------------------------------------------------------------------- /views/js/monitoring.host.view.js.php: -------------------------------------------------------------------------------- 1 | 27 | 349 | -------------------------------------------------------------------------------- /views/module.monitoring.bghost.view.php: -------------------------------------------------------------------------------- 1 | addJsFile('layout.mode.js'); 27 | $this->addJsFile('gtlc.js'); 28 | $this->addJsFile('class.tabfilter.js'); 29 | $this->addJsFile('class.tabfilteritem.js'); 30 | 31 | $this->includeJsFile('monitoring.host.view.js.php', $data); 32 | 33 | $this->enableLayoutModes(); 34 | $web_layout_mode = $this->getLayoutMode(); 35 | $nav_items = new CList(); 36 | 37 | if ($data['can_create_hosts']) { 38 | $nav_items->addItem( 39 | (new CSimpleButton(_('Create host'))) 40 | ->addClass('js-create-host') 41 | ); 42 | } 43 | 44 | $nav_items->addItem(get_icon('kioskmode', ['mode' => $web_layout_mode])); 45 | 46 | $html_page = (new CHtmlPage()) 47 | ->setTitle(_('Hosts')) 48 | ->setWebLayoutMode($web_layout_mode) 49 | ->setDocUrl(CDocHelper::getUrl(CDocHelper::MONITORING_HOST_VIEW)) 50 | ->setControls((new CTag('nav', true, $nav_items)) 51 | ->setAttribute('aria-label', _('Content controls')) 52 | ); 53 | 54 | if ($web_layout_mode == ZBX_LAYOUT_NORMAL) { 55 | $filter = (new CTabFilter()) 56 | ->setId('monitoring_hosts_filter') 57 | ->setOptions($data['tabfilter_options']) 58 | ->addTemplate(new CPartial($data['filter_view'], $data['filter_defaults'])); 59 | 60 | foreach ($data['filter_tabs'] as $tab) { 61 | $tab['tab_view'] = $data['filter_view']; 62 | $filter->addTemplatedTab($tab['filter_name'], $tab); 63 | } 64 | 65 | // Set javascript options for tab filter initialization in monitoring.host.view.js.php file. 66 | $data['filter_options'] = $filter->options; 67 | $html_page->addItem($filter); 68 | } 69 | else { 70 | $data['filter_options'] = null; 71 | } 72 | 73 | $html_page 74 | ->addItem( 75 | (new CForm()) 76 | ->setName('host_view') 77 | ->addClass('is-loading') 78 | )->show(); 79 | $this->addCssFile('modules/zabbix-module-hosts-tree/views/css/bghost.css'); 80 | 81 | (new CScriptTag(' 82 | view.init('.json_encode([ 83 | 'filter_options' => $data['filter_options'], 84 | 'refresh_url' => $data['refresh_url'], 85 | 'refresh_interval' => $data['refresh_interval'], 86 | 'applied_filter_groupids' => $data['filter_groupids'] 87 | ]).'); 88 | ')) 89 | ->setOnDocumentReady() 90 | ->show(); 91 | -------------------------------------------------------------------------------- /views/module.monitoring.bghost.view.refresh.php: -------------------------------------------------------------------------------- 1 | (new CPartial('module.monitoring.host.view.html', $data))->getOutput(), 24 | 'groupids' => $data['filter']['groupids'] 25 | ]; 26 | 27 | if (($messages = getMessages()) !== null) { 28 | $output['messages'] = $messages->toString(); 29 | } 30 | 31 | echo json_encode($output); 32 | --------------------------------------------------------------------------------