1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22require_once dirname(__FILE__).'/include/config.inc.php'; 23require_once dirname(__FILE__).'/include/hosts.inc.php'; 24 25$page['title'] = _('Availability report'); 26$page['file'] = 'report2.php'; 27$page['scripts'] = ['class.calendar.js', 'gtlc.js', 'multiselect.js', 'report2.js']; 28$page['type'] = detect_page_type(PAGE_TYPE_HTML); 29 30require_once dirname(__FILE__).'/include/page_header.php'; 31 32// VAR TYPE OPTIONAL FLAGS VALIDATION EXCEPTION 33$fields = [ 34 'mode' => [T_ZBX_INT, O_OPT, P_SYS, IN(implode(',', [AVAILABILITY_REPORT_BY_HOST, AVAILABILITY_REPORT_BY_TEMPLATE])), null], 35 'hostgroupid' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 36 'tpl_triggerid' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 37 'triggerid' => [T_ZBX_INT, O_OPT, P_SYS|P_NZERO, DB_ID, null], 38 // filter 39 'filter_groups' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 40 'filter_hostids' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 41 'filter_templateid' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 42 'filter_rst'=> [T_ZBX_STR, O_OPT, P_SYS, null, null], 43 'filter_set' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 44 'from' => [T_ZBX_RANGE_TIME, O_OPT, P_SYS, null, null], 45 'to' => [T_ZBX_RANGE_TIME, O_OPT, P_SYS, null, null] 46]; 47check_fields($fields); 48validateTimeSelectorPeriod(getRequest('from'), getRequest('to')); 49 50$report_mode = getRequest('mode', CProfile::get('web.avail_report.mode', AVAILABILITY_REPORT_BY_HOST)); 51CProfile::update('web.avail_report.mode', $report_mode, PROFILE_TYPE_INT); 52 53/* 54 * Permissions 55 */ 56if ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) { 57 if (getRequest('hostgroupid') && !isReadableHostGroups([getRequest('hostgroupid')])) { 58 access_deny(); 59 } 60 if (getRequest('filter_groups') && !isReadableHostGroups([getRequest('filter_groups')])) { 61 access_deny(); 62 } 63 if (getRequest('filter_templateid') && !isReadableTemplates([getRequest('filter_templateid')])) { 64 access_deny(); 65 } 66 if (getRequest('tpl_triggerid')) { 67 $trigger = API::Trigger()->get([ 68 'triggerids' => $_REQUEST['tpl_triggerid'], 69 'output' => ['triggerid'], 70 'filter' => ['flags' => null] 71 ]); 72 if (!$trigger) { 73 access_deny(); 74 } 75 } 76} 77else { 78 if (getRequest('filter_groupid') && !isReadableHostGroups([getRequest('filter_groupid')])) { 79 access_deny(); 80 } 81 if (getRequest('filter_hostid') && !isReadableHosts([getRequest('filter_hostid')])) { 82 access_deny(); 83 } 84} 85if (getRequest('triggerid') && !isReadableTriggers([getRequest('triggerid')])) { 86 access_deny(); 87} 88 89/* 90 * Filter 91 */ 92$key_prefix = 'web.avail_report.'.$report_mode; 93 94if (hasRequest('filter_set')) { 95 if ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) { 96 CProfile::update($key_prefix.'.groupid', getRequest('filter_groups', 0), PROFILE_TYPE_ID); 97 CProfile::update($key_prefix.'.hostid', getRequest('filter_templateid', 0), PROFILE_TYPE_ID); 98 CProfile::update($key_prefix.'.tpl_triggerid', getRequest('tpl_triggerid', 0), PROFILE_TYPE_ID); 99 CProfile::update($key_prefix.'.hostgroupid', getRequest('hostgroupid', 0), PROFILE_TYPE_ID); 100 } 101 else { 102 CProfile::updateArray($key_prefix.'.groupids', getRequest('filter_groups', []), PROFILE_TYPE_ID); 103 CProfile::updateArray($key_prefix.'.hostids', getRequest('filter_hostids', []), PROFILE_TYPE_ID); 104 } 105} 106elseif (hasRequest('filter_rst')) { 107 if ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) { 108 CProfile::delete($key_prefix.'.groupid'); 109 CProfile::delete($key_prefix.'.hostid'); 110 CProfile::delete($key_prefix.'.tpl_triggerid'); 111 CProfile::delete($key_prefix.'.hostgroupid'); 112 } 113 else { 114 CProfile::deleteIdx($key_prefix.'.groupids'); 115 CProfile::deleteIdx($key_prefix.'.hostids'); 116 } 117} 118 119// Get filter values. 120$data['filter'] = ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) 121 ? [ 122 // 'Template group' field. 123 'groups' => getRequest('filter_groups', CProfile::get($key_prefix.'.groupid', 0)), 124 // 'Template' field. 125 'hostids' => getRequest('filter_templateid', CProfile::get($key_prefix.'.hostid', 0)), 126 // 'Template trigger' field. 127 'tpl_triggerid' => getRequest('tpl_triggerid', CProfile::get($key_prefix.'.tpl_triggerid', 0)), 128 // 'Host group' field. 129 'hostgroupid' => getRequest('hostgroupid', CProfile::get($key_prefix.'.hostgroupid', 0)) 130 ] 131 : [ 132 // 'Host groups' field. 133 'groups' => CProfile::getArray($key_prefix.'.groupids', getRequest('filter_groups', [])), 134 // 'Hosts' field. 135 'hostids' => CProfile::getArray($key_prefix.'.hostids', getRequest('filter_hostids', [])) 136 ]; 137 138// Get time selector filter values. 139$timeselector_options = [ 140 'profileIdx' => 'web.avail_report.filter', 141 'profileIdx2' => 0, 142 'from' => getRequest('from'), 143 'to' => getRequest('to') 144]; 145updateTimeSelectorPeriod($timeselector_options); 146 147$config = select_config(); 148 149/* 150 * Header 151 */ 152$triggerData = isset($_REQUEST['triggerid']) 153 ? API::Trigger()->get([ 154 'triggerids' => $_REQUEST['triggerid'], 155 'output' => API_OUTPUT_EXTEND, 156 'selectHosts' => API_OUTPUT_EXTEND, 157 'expandDescription' => true 158 ]) 159 : null; 160 161$reportWidget = (new CWidget())->setTitle(_('Availability report')); 162 163if ($triggerData) { 164 $triggerData = reset($triggerData); 165 $host = reset($triggerData['hosts']); 166 167 $triggerData['hostid'] = $host['hostid']; 168 $triggerData['hostname'] = $host['name']; 169 170 $reportWidget->setControls((new CTag('nav', true, 171 (new CList()) 172 ->addItem(new CLink($triggerData['hostname'], (new CUrl('report2.php')) 173 ->setArgument('page', CPagerHelper::loadPage('report2.php', null)) 174 ->getUrl() 175 )) 176 ->addItem($triggerData['description']) 177 ))->setAttribute('aria-label', _('Content controls')) 178 ); 179 180 $table = (new CTableInfo()) 181 ->addRow(new CImg('chart4.php?triggerid='.$_REQUEST['triggerid'])); 182 183 $reportWidget->addItem(BR()) 184 ->addItem($table) 185 ->show(); 186} 187else { 188 /** 189 * Report list view (both data presentation modes). 190 */ 191 192 $select_mode = (new CSelect('mode')) 193 ->setValue($report_mode) 194 ->setFocusableElementId('mode') 195 ->addOption(new CSelectOption(AVAILABILITY_REPORT_BY_HOST, _('By host'))) 196 ->addOption(new CSelectOption(AVAILABILITY_REPORT_BY_TEMPLATE, _('By trigger template'))); 197 198 $reportWidget->setControls((new CForm('get')) 199 ->cleanItems() 200 ->setAttribute('aria-label', _('Main filter')) 201 ->addItem((new CList()) 202 ->addItem([ 203 new CLabel(_('Mode'), $select_mode->getFocusableElementId()), 204 (new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN), 205 $select_mode 206 ]) 207 ) 208 ->setName('report2') 209 ); 210 211 /* 212 * Filter 213 */ 214 $data['filter'] += [ 215 'timeline' => getTimeSelectorPeriod($timeselector_options), 216 'active_tab' => CProfile::get('web.avail_report.filter.active', 1) 217 ]; 218 219 $filter_column = new CFormList(); 220 221 // Make filter fields. 222 if ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) { 223 // Sanitize $data['filter']['groups'] and prepare "Template group" select options. 224 $groups = API::HostGroup()->get([ 225 'output' => ['name'], 226 'templated_hosts' => true, 227 'with_triggers' => true, 228 'preservekeys' => true 229 ]); 230 $groups = enrichParentGroups($groups); 231 CArrayHelper::sort($groups, ['name']); 232 233 if (!array_key_exists($data['filter']['groups'], $groups)) { 234 $data['filter']['groups'] = 0; 235 } 236 237 // Sanitize $data['filter']['hostids'] and prepare "Template" select options. 238 $templates = API::Template()->get([ 239 'output' => ['name'], 240 'groupids' => $data['filter']['groups'] ? [$data['filter']['groups']] : null, 241 'with_triggers' => true, 242 'preservekeys' => true 243 ]); 244 CArrayHelper::sort($templates, ['name']); 245 246 if (!array_key_exists($data['filter']['hostids'], $templates)) { 247 $data['filter']['hostids'] = 0; 248 } 249 250 $select_filter_hostid = (new CSelect('filter_templateid')) 251 ->setValue($data['filter']['hostids']) 252 ->setFocusableElementId('filter-templateid') 253 ->addOption(new CSelectOption(0, _('all'))); 254 255 foreach ($templates as $templateid => $template) { 256 $select_filter_hostid->addOption(new CSelectOption($templateid, $template['name'])); 257 } 258 259 // Sanitize $data['filter']['tpl_triggerid'] and prepare "Template Trigger" select options. 260 $triggers = API::Trigger()->get([ 261 'output' => ['description'], 262 'selectHosts' => ['name'], 263 'selectItems' => ['status'], 264 'templateids' => $data['filter']['hostids'] 265 ? [$data['filter']['hostids']] 266 : null, 267 'groupids' => $data['filter']['groups'] 268 ? [$data['filter']['groups']] 269 : null, 270 'templated' => true, 271 'filter' => [ 272 'status' => TRIGGER_STATUS_ENABLED, 273 'flags' => [ZBX_FLAG_DISCOVERY_NORMAL] 274 ], 275 'sortfield' => 'description', 276 'preservekeys' => true 277 ]); 278 279 foreach ($triggers as $triggerid => $trigger) { 280 foreach ($trigger['items'] as $item) { 281 if ($item['status'] != ITEM_STATUS_ACTIVE) { 282 unset($triggers[$triggerid]); 283 284 break; 285 } 286 } 287 } 288 289 if (!array_key_exists($data['filter']['tpl_triggerid'], $triggers)) { 290 $data['filter']['tpl_triggerid'] = 0; 291 } 292 293 $select_tpl_triggerid = (new CSelect('tpl_triggerid')) 294 ->setValue($data['filter']['tpl_triggerid']) 295 ->setFocusableElementId('tpl-triggerid') 296 ->addOption(new CSelectOption(0, _('all'))); 297 298 $tpl_triggerids = []; 299 300 foreach ($triggers as $triggerid => $trigger) { 301 $label = (($data['filter']['hostids'] == 0) ? $trigger['hosts'][0]['name'].NAME_DELIMITER : '').$trigger['description']; 302 $select_tpl_triggerid->addOption(new CSelectOption($triggerid, $label)); 303 304 $tpl_triggerids[$triggerid] = true; 305 } 306 307 // Sanitize $data['filter']['hostgroupid'] and prepare "Host Group" select options. 308 $host_groups = API::HostGroup()->get([ 309 'output' => ['name'], 310 'monitored_hosts' => true, 311 'preservekeys' => true 312 ]); 313 $host_groups = enrichParentGroups($host_groups); 314 CArrayHelper::sort($host_groups, ['name']); 315 316 if (!array_key_exists($data['filter']['hostgroupid'], $host_groups)) { 317 $data['filter']['hostgroupid'] = 0; 318 } 319 320 $select_hostgroupid = (new CSelect('hostgroupid')) 321 ->setValue($data['filter']['hostgroupid']) 322 ->setFocusableElementId('hostgroupid') 323 ->addOption(new CSelectOption(0, _('all'))); 324 325 foreach ($host_groups as $groupid => $group) { 326 $select_hostgroupid->addOption(new CSelectOption($groupid, $group['name'])); 327 } 328 329 $hostgroupids = []; 330 if ($data['filter']['hostgroupid'] != 0) { 331 $hostgroupids[$data['filter']['hostgroupid']] = true; 332 $parent = $host_groups[$data['filter']['hostgroupid']]['name'].'/'; 333 334 foreach ($host_groups as $groupid => $group) { 335 if (strpos($group['name'], $parent) === 0) { 336 $hostgroupids[$groupid] = true; 337 } 338 } 339 } 340 341 // Gather all templated triggers, originating from host templates, which belong to requested template groups. 342 $templated_triggers_all = ($data['filter']['tpl_triggerid'] == 0) 343 ? $tpl_triggerids 344 : [$data['filter']['tpl_triggerid'] => true]; 345 $templated_triggers_new = $templated_triggers_all; 346 347 while ($templated_triggers_new) { 348 $templated_triggers_new = API::Trigger()->get([ 349 'output' => ['triggerid'], 350 'templated' => true, 351 'filter' => ['templateid' => array_keys($templated_triggers_new)], 352 'preservekeys' => true 353 ]); 354 $templated_triggers_new = array_diff_key($templated_triggers_new, $templated_triggers_all); 355 $templated_triggers_all += $templated_triggers_new; 356 } 357 358 if ($templated_triggers_all) { 359 // Select monitored host triggers, derived from templates and belonging to the requested groups. 360 $triggers = API::Trigger()->get([ 361 'output' => ['triggerid', 'description', 'expression', 'value'], 362 'selectHosts' => ['name'], 363 'expandDescription' => true, 364 'monitored' => true, 365 'groupids' => ($data['filter']['hostgroupid'] == 0) ? null : array_keys($hostgroupids), 366 'filter' => ['templateid' => array_keys($templated_triggers_all)], 367 'limit' => $config['search_limit'] + 1 368 ]); 369 } 370 else { 371 // No trigger templates means there are no derived triggers. 372 $triggers = []; 373 } 374 375 $select_filter_groupid = (new CSelect('filter_groups')) 376 ->setAttribute('autofocus', 'autofocus') 377 ->setValue($data['filter']['groups']) 378 ->setFocusableElementId('filter-groups') 379 ->addOption(new CSelectOption(0, _('all'))); 380 381 foreach ($groups as $groupid => $group) { 382 $select_filter_groupid->addOption(new CSelectOption($groupid, $group['name'])); 383 } 384 385 $filter_column 386 ->addRow( 387 new CLabel(_('Template group'), $select_filter_groupid->getFocusableElementId()), 388 $select_filter_groupid 389 ) 390 ->addRow( 391 new CLabel(_('Template'), $select_filter_hostid->getFocusableElementId()), 392 $select_filter_hostid 393 ) 394 ->addRow( 395 new CLabel(_('Template trigger'), $select_tpl_triggerid->getFocusableElementId()), 396 $select_tpl_triggerid 397 ) 398 ->addRow( 399 new CLabel(_('Host group'), $select_hostgroupid->getFocusableElementId()), 400 $select_hostgroupid 401 ) 402 ->addVar('filter_set', '1'); 403 } 404 // Report by host. 405 else { 406 // Sanitize $data['filter']['groups'] and prepare "Host groups" filter field. 407 $data['filter']['groups'] = $data['filter']['groups'] 408 ? CArrayHelper::renameObjectsKeys(API::HostGroup()->get([ 409 'output' => ['groupid', 'name'], 410 'groupids' => $data['filter']['groups'], 411 'monitored_hosts' => true, 412 'preservekeys' => true 413 ]), ['groupid' => 'id']) 414 : []; 415 416 CArrayHelper::sort($data['filter']['groups'], ['name']); 417 418 // Sanitize $data['filter']['hostids'] and prepare "Hosts" filter field. 419 $data['filter']['hostids'] = $data['filter']['hostids'] 420 ? CArrayHelper::renameObjectsKeys(API::Host()->get([ 421 'output' => ['hostid', 'name'], 422 'hostids' => $data['filter']['hostids'], 423 'monitored_hosts' => true, 424 'with_triggers' => true, 425 'preservekeys' => true 426 ]), ['hostid' => 'id']) 427 : []; 428 429 CArrayHelper::sort($data['filter']['hostids'], ['name']); 430 431 // Select monitored host triggers, derived from templates and belonging to the requested groups. 432 $groups = enrichParentGroups($data['filter']['groups']); 433 434 $triggers = API::Trigger()->get([ 435 'output' => ['triggerid', 'description', 'expression', 'value'], 436 'selectHosts' => ['name'], 437 'groupids' => $groups ? array_keys($groups) : null, 438 'hostids' => $data['filter']['hostids'] ? array_keys($data['filter']['hostids']) : null, 439 'expandDescription' => true, 440 'monitored' => true, 441 'limit' => $config['search_limit'] + 1 442 ]); 443 444 $filter_column 445 ->addRow( 446 (new CLabel(_('Host groups'), 'filter_groups__ms')), 447 (new CMultiSelect([ 448 'name' => 'filter_groups[]', 449 'object_name' => 'hostGroup', 450 'data' => $data['filter']['groups'], 451 'popup' => [ 452 'parameters' => [ 453 'srctbl' => 'host_groups', 454 'srcfld1' => 'groupid', 455 'dstfrm' => 'zbx_filter', 456 'dstfld1' => 'filter_groups_', 457 'with_triggers' => true, 458 'real_hosts' => 1, 459 'enrich_parent_groups' => true 460 ] 461 ] 462 ]))->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH) 463 ) 464 ->addRow( 465 (new CLabel(_('Hosts'), 'filter_hostid__ms')), 466 (new CMultiSelect([ 467 'name' => 'filter_hostids[]', 468 'object_name' => 'hosts', 469 'data' => $data['filter']['hostids'], 470 'popup' => [ 471 'filter_preselect_fields' => [ 472 'hostgroups' => 'filter_groups_' 473 ], 474 'parameters' => [ 475 'srctbl' => 'hosts', 476 'srcfld1' => 'hostid', 477 'dstfrm' => 'zbx_filter', 478 'dstfld1' => 'filter_hostids_', 479 'with_triggers' => true, 480 'real_hosts' => 1 481 ] 482 ] 483 ]))->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH) 484 ); 485 } 486 487 // Now just prepare needed data. 488 foreach ($triggers as &$trigger) { 489 $trigger['host_name'] = $trigger['hosts'][0]['name']; 490 } 491 unset($trigger); 492 493 $reportWidget->addItem( 494 (new CFilter(new CUrl('report2.php'))) 495 ->setProfile($data['filter']['timeline']['profileIdx']) 496 ->setActiveTab($data['filter']['active_tab']) 497 ->addFormItem((new CVar('mode', $report_mode))->removeId()) 498 ->addTimeSelector($data['filter']['timeline']['from'], $data['filter']['timeline']['to'], true, 499 ZBX_DATE_TIME) 500 ->addFilterTab(_('Filter'), [$filter_column]) 501 ); 502 503 /* 504 * Triggers 505 */ 506 $triggerTable = (new CTableInfo())->setHeader([_('Host'), _('Name'), _('Problems'), _('Ok'), _('Graph')]); 507 508 CArrayHelper::sort($triggers, ['host_name', 'description']); 509 510 // pager 511 $page_num = getRequest('page', 1); 512 CPagerHelper::savePage($page['file'], $page_num); 513 $paging = CPagerHelper::paginate($page_num, $triggers, ZBX_SORT_UP, new CUrl('report2.php')); 514 515 foreach ($triggers as $trigger) { 516 $availability = calculateAvailability($trigger['triggerid'], $data['filter']['timeline']['from_ts'], 517 $data['filter']['timeline']['to_ts'] 518 ); 519 520 $url = (new CUrl('report2.php'))->setArgument('triggerid', $trigger['triggerid']); 521 if ($report_mode == AVAILABILITY_REPORT_BY_TEMPLATE) { 522 $url->setArgument('filter_templateid', $data['filter']['hostids']); 523 } 524 else { 525 $url->setArgument('filter_hostids', $trigger['hosts'][0]['hostid']); 526 } 527 528 $triggerTable->addRow([ 529 $trigger['host_name'], 530 new CLink($trigger['description'], 531 (new CUrl('zabbix.php')) 532 ->setArgument('action', 'problem.view') 533 ->setArgument('filter_triggerids', [$trigger['triggerid']]) 534 ->setArgument('filter_set', '1') 535 ), 536 ($availability['true'] < 0.00005) 537 ? '' 538 : (new CSpan(sprintf('%.4f%%', $availability['true'])))->addClass(ZBX_STYLE_RED), 539 ($availability['false'] < 0.00005) 540 ? '' 541 : (new CSpan(sprintf('%.4f%%', $availability['false'])))->addClass(ZBX_STYLE_GREEN), 542 new CLink(_('Show'), $url) 543 ]); 544 } 545 546 $obj_data = [ 547 'id' => 'timeline_1', 548 'domid' => 'avail_report', 549 'loadSBox' => 0, 550 'loadImage' => 0, 551 'dynamic' => 0, 552 'mainObject' => 1 553 ]; 554 zbx_add_post_js( 555 'timeControl.addObject("avail_report", '.zbx_jsvalue($data['filter']).', '.zbx_jsvalue($obj_data).');' 556 ); 557 zbx_add_post_js('timeControl.processObjects();'); 558 559 $reportWidget 560 ->addItem([$triggerTable, $paging]) 561 ->show(); 562} 563 564require_once dirname(__FILE__).'/include/page_footer.php'; 565