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'];
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('0,1'),	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_groupid' =>	[T_ZBX_INT,			O_OPT,	P_SYS,			DB_ID,		null],
40	'filter_hostid' =>	[T_ZBX_INT,			O_OPT,	P_SYS,			DB_ID,		null],
41	'filter_rst'=>		[T_ZBX_STR,			O_OPT,	P_SYS,			null,		null],
42	'filter_set' =>		[T_ZBX_STR,			O_OPT,	P_SYS,			null,		null],
43	'from' =>			[T_ZBX_RANGE_TIME,	O_OPT,	P_SYS,			null,		null],
44	'to' =>				[T_ZBX_RANGE_TIME,	O_OPT,	P_SYS,			null,		null]
45];
46check_fields($fields);
47validateTimeSelectorPeriod(getRequest('from'), getRequest('to'));
48
49$availabilityReportMode = getRequest('mode', CProfile::get('web.avail_report.mode', AVAILABILITY_REPORT_BY_HOST));
50CProfile::update('web.avail_report.mode', $availabilityReportMode, PROFILE_TYPE_INT);
51
52/*
53 * Permissions
54 */
55if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
56	if (getRequest('hostgroupid') && !isReadableHostGroups([getRequest('hostgroupid')])) {
57		access_deny();
58	}
59	if (getRequest('filter_groupid') && !isReadableHostGroups([getRequest('filter_groupid')])) {
60		access_deny();
61	}
62	if (getRequest('filter_hostid') && !isReadableTemplates([getRequest('filter_hostid')])) {
63		access_deny();
64	}
65	if (getRequest('tpl_triggerid')) {
66		$trigger = API::Trigger()->get([
67			'triggerids' => $_REQUEST['tpl_triggerid'],
68			'output' => ['triggerid'],
69			'filter' => ['flags' => null]
70		]);
71		if (!$trigger) {
72			access_deny();
73		}
74	}
75}
76else {
77	if (getRequest('filter_groupid') && !isReadableHostGroups([getRequest('filter_groupid')])) {
78		access_deny();
79	}
80	if (getRequest('filter_hostid') && !isReadableHosts([getRequest('filter_hostid')])) {
81		access_deny();
82	}
83}
84if (getRequest('triggerid') && !isReadableTriggers([getRequest('triggerid')])) {
85	access_deny();
86}
87
88/*
89 * Filter
90 */
91if (hasRequest('filter_rst')) {
92	$_REQUEST['filter_groupid'] = 0;
93	$_REQUEST['filter_hostid'] = 0;
94
95	if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
96		$_REQUEST['tpl_triggerid'] = 0;
97		$_REQUEST['hostgroupid'] = 0;
98	}
99}
100
101if (!hasRequest('filter_rst')) {
102	$_REQUEST['filter_groupid'] = getRequest('filter_groupid',
103		CProfile::get('web.avail_report.'.$availabilityReportMode.'.groupid', 0)
104	);
105	$_REQUEST['filter_hostid'] = getRequest('filter_hostid',
106		CProfile::get('web.avail_report.'.$availabilityReportMode.'.hostid', 0)
107	);
108
109	if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
110		$_REQUEST['tpl_triggerid'] = getRequest('tpl_triggerid',
111			CProfile::get('web.avail_report.'.$availabilityReportMode.'.tpl_triggerid', 0)
112		);
113
114		$_REQUEST['hostgroupid'] = getRequest('hostgroupid',
115			CProfile::get('web.avail_report.'.$availabilityReportMode.'.hostgroupid', 0)
116		);
117	}
118}
119
120CProfile::update('web.avail_report.'.$availabilityReportMode.'.groupid', getRequest('filter_groupid', 0),
121	PROFILE_TYPE_ID
122);
123CProfile::update('web.avail_report.'.$availabilityReportMode.'.hostid', getRequest('filter_hostid', 0),
124	PROFILE_TYPE_ID
125);
126
127if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
128	CProfile::update('web.avail_report.'.$availabilityReportMode.'.tpl_triggerid', getRequest('tpl_triggerid', 0),
129		PROFILE_TYPE_ID
130	);
131
132	CProfile::update('web.avail_report.'.$availabilityReportMode.'.hostgroupid', getRequest('hostgroupid', 0),
133		PROFILE_TYPE_ID
134	);
135}
136
137$timeselector_options = [
138	'profileIdx' => 'web.avail_report.filter',
139	'profileIdx2' => 0,
140	'from' => getRequest('from'),
141	'to' => getRequest('to')
142];
143updateTimeSelectorPeriod($timeselector_options);
144
145$config = select_config();
146
147/*
148 * Header
149 */
150$triggerData = isset($_REQUEST['triggerid'])
151	? API::Trigger()->get([
152		'triggerids' => $_REQUEST['triggerid'],
153		'output' => API_OUTPUT_EXTEND,
154		'selectHosts' => API_OUTPUT_EXTEND,
155		'expandDescription' => true
156	])
157	: null;
158
159$reportWidget = (new CWidget())->setTitle(_('Availability report'));
160
161if ($triggerData) {
162	$triggerData = reset($triggerData);
163	$host = reset($triggerData['hosts']);
164
165	$triggerData['hostid'] = $host['hostid'];
166	$triggerData['hostname'] = $host['name'];
167
168	$reportWidget->setControls((new CTag('nav', true,
169		(new CList())
170			->addItem(new CLink($triggerData['hostname'], '?filter_groupid='.$_REQUEST['filter_groupid']))
171			->addItem($triggerData['description'])
172		))
173			->setAttribute('aria-label', _('Content controls'))
174	);
175
176	$table = (new CTableInfo())
177		->addRow(new CImg('chart4.php?triggerid='.$_REQUEST['triggerid']));
178
179	$reportWidget->addItem(BR())
180		->addItem($table)
181		->show();
182}
183elseif (hasRequest('filter_hostid')) {
184	$reportWidget->setControls((new CForm('get'))
185		->setAttribute('aria-label', _('Main filter'))
186		->addItem((new CList())
187			->addItem([
188				new CLabel(_('Mode'), 'mode'),
189				(new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN),
190				new CComboBox('mode', $availabilityReportMode, 'submit()', [
191					AVAILABILITY_REPORT_BY_HOST => _('By host'),
192					AVAILABILITY_REPORT_BY_TEMPLATE => _('By trigger template')
193				])
194			])
195	));
196
197	/*
198	 * Filter
199	 */
200	$data = [
201		'filter' => [
202			'timeline' => getTimeSelectorPeriod($timeselector_options),
203			'active_tab' => CProfile::get('web.avail_report.filter.active', 1)
204		]
205	];
206
207	$filter_column = new CFormList();
208
209	$filter_hostid = getRequest('filter_hostid');
210	$filter_groupid = getRequest('filter_groupid');
211	$tpl_triggerid = getRequest('tpl_triggerid');
212	$hostgroupid = getRequest('hostgroupid');
213
214	// Sanitize $filter_groupid and prepare "Template group" or "Host group" combo box (for both view modes).
215
216	$options = [
217		'output' => ['name'],
218		'with_triggers' => true,
219		'preservekeys' => true
220	];
221
222	if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
223		$options['templated_hosts'] = true;
224	}
225	elseif ($availabilityReportMode == AVAILABILITY_REPORT_BY_HOST) {
226		$options['monitored_hosts'] = true;
227	}
228
229	$groups = API::HostGroup()->get($options);
230	$groups = enrichParentGroups($groups);
231	CArrayHelper::sort($groups, ['name']);
232
233	if (!array_key_exists($filter_groupid, $groups)) {
234		$filter_groupid = 0;
235	}
236
237	$filter_groupid_combobox = (new CComboBox('filter_groupid', $filter_groupid, 'javascript: submit();'))
238		->setAttribute('autofocus', 'autofocus')
239		->addItem(0, _('all'));
240
241	foreach ($groups as $groupid => $group) {
242		$filter_groupid_combobox->addItem($groupid, $group['name']);
243	}
244
245	if ($filter_groupid == 0) {
246		$filter_groupids = null;
247	}
248	else {
249		$filter_groupids = [$filter_groupid];
250		$parent = $groups[$filter_groupid]['name'].'/';
251
252		foreach ($groups as $groupid => $group) {
253			if (strpos($group['name'], $parent) === 0) {
254				$filter_groupids[] = $groupid;
255			}
256		}
257	}
258
259	if ($availabilityReportMode == AVAILABILITY_REPORT_BY_TEMPLATE) {
260		// Sanitize $filter_hostid and prepare "Template" combo box.
261
262		$templates = API::Template()->get([
263			'output' => ['name'],
264			'groupids' => $filter_groupids,
265			'with_triggers' => true,
266			'preservekeys' => true
267		]);
268		CArrayHelper::sort($templates, ['name']);
269
270		if (!array_key_exists($filter_hostid, $templates)) {
271			$filter_hostid = 0;
272		}
273
274		$filter_hostid_combobox = (new CComboBox('filter_hostid', $filter_hostid, 'javascript: submit();'))
275			->addItem(0, _('all'));
276
277		foreach ($templates as $templateid => $template) {
278			$filter_hostid_combobox->addItem($templateid, $template['name']);
279		}
280
281		// Sanitize $tpl_triggerid and prepare "Template Trigger" combo box.
282
283		$triggers = API::Trigger()->get([
284			'output' => ['description'],
285			'selectHosts' => ['name'],
286			'selectItems' => ['status'],
287			'templateids' => ($filter_hostid == 0) ? null : $filter_hostid,
288			'groupids' => $filter_groupids,
289			'templated' => true,
290			'filter' => ['status' => TRIGGER_STATUS_ENABLED, 'flags' => [ZBX_FLAG_DISCOVERY_NORMAL]],
291			'sortfield' => 'description',
292			'preservekeys' => true
293		]);
294
295		foreach ($triggers as $triggerid => $trigger) {
296			foreach ($trigger['items'] as $item) {
297				if ($item['status'] != ITEM_STATUS_ACTIVE) {
298					unset($triggers[$triggerid]);
299
300					break;
301				}
302			}
303		}
304
305		if (!array_key_exists($tpl_triggerid, $triggers)) {
306			$tpl_triggerid = 0;
307		}
308
309		$tpl_triggerid_combobox = (new CComboBox('tpl_triggerid', $tpl_triggerid, 'javascript: submit()'))
310			->addItem(0, _('all'));
311
312		$tpl_triggerids = [];
313
314		foreach ($triggers as $triggerid => $trigger) {
315			$tpl_triggerid_combobox->addItem($triggerid,
316				(($filter_hostid == 0) ? $trigger['hosts'][0]['name'].NAME_DELIMITER : '').$trigger['description']
317			);
318
319			$tpl_triggerids[$triggerid] = true;
320		}
321
322		// Sanitize $hostgroupid and prepare "Host Group" combo box.
323
324		$host_groups = API::HostGroup()->get([
325			'output' => ['name'],
326			'monitored_hosts' => true,
327			'preservekeys' => true
328		]);
329		$host_groups = enrichParentGroups($host_groups);
330		CArrayHelper::sort($host_groups, ['name']);
331
332		if (!array_key_exists($hostgroupid, $host_groups)) {
333			$hostgroupid = 0;
334		}
335
336		$hostgroupid_combobox = (new CComboBox('hostgroupid', $hostgroupid, 'javascript: submit()'))
337			->addItem(0, _('all'));
338
339		foreach ($host_groups as $groupid => $group) {
340			$hostgroupid_combobox->addItem($groupid, $group['name']);
341		}
342
343		$hostgroupids = [];
344
345		if ($hostgroupid != 0) {
346			$hostgroupids[$hostgroupid] = true;
347			$parent = $host_groups[$hostgroupid]['name'].'/';
348
349			foreach ($host_groups as $groupid => $group) {
350				if (strpos($group['name'], $parent) === 0) {
351					$hostgroupids[$groupid] = true;
352				}
353			}
354		}
355
356		// Gather all templated triggers, originating from host templates, which belong to requested template groups.
357
358		$templated_triggers_all = ($tpl_triggerid == 0) ? $tpl_triggerids : [$tpl_triggerid => true];
359		$templated_triggers_new = $templated_triggers_all;
360
361		while ($templated_triggers_new) {
362			$templated_triggers_new = API::Trigger()->get([
363				'output' => ['triggerid'],
364				'templated' => true,
365				'filter' => ['templateid' => array_keys($templated_triggers_new)],
366				'preservekeys' => true
367			]);
368			$templated_triggers_new = array_diff_key($templated_triggers_new, $templated_triggers_all);
369			$templated_triggers_all += $templated_triggers_new;
370		}
371
372		if ($templated_triggers_all) {
373			// Select monitored host triggers, derived from templates and belonging to the requested groups.
374			$triggers = API::Trigger()->get([
375				'output' => ['triggerid', 'description', 'expression', 'value'],
376				'selectHosts' => ['name'],
377				'expandDescription' => true,
378				'monitored' => true,
379				'groupids' => ($hostgroupid == 0) ? null : array_keys($hostgroupids),
380				'filter' => ['templateid' => array_keys($templated_triggers_all)],
381				'limit' => $config['search_limit'] + 1
382			]);
383		}
384		else {
385			// No trigger templates means there are no derived triggers.
386			$triggers = [];
387		}
388
389		$filter_column
390			->addRow(_('Template group'), $filter_groupid_combobox)
391			->addRow(_('Template'), $filter_hostid_combobox)
392			->addRow(_('Template trigger'), $tpl_triggerid_combobox)
393			->addRow(_('Host group'), $hostgroupid_combobox);
394	}
395	// Report by host.
396	elseif ($availabilityReportMode == AVAILABILITY_REPORT_BY_HOST) {
397		// Sanitize $filter_hostid and prepare "Host" combo box.
398
399		$hosts = API::Host()->get([
400			'output' => ['name'],
401			'groupids' => $filter_groupids,
402			'monitored_hosts' => true,
403			'with_triggers' => true,
404			'preservekeys' => true
405		]);
406		CArrayHelper::sort($hosts, ['name']);
407
408		if (!array_key_exists($filter_hostid, $hosts)) {
409			$filter_hostid = 0;
410		}
411
412		// Select monitored host triggers, derived from templates and belonging to the requested groups.
413		$triggers = API::Trigger()->get([
414			'output' => ['triggerid', 'description', 'expression', 'value'],
415			'selectHosts' => ['name'],
416			'expandDescription' => true,
417			'monitored' => true,
418			'groupids' => $filter_groupids,
419			'hostids' => ($filter_hostid == 0) ? null : [$filter_hostid],
420			'limit' => $config['search_limit'] + 1
421		]);
422
423		$hosts_combobox = (new CComboBox('filter_hostid', $filter_hostid, 'javascript: submit();'))
424			->addItem(0, _('all'));
425
426		foreach ($hosts as $hostid => $host) {
427			$hosts_combobox->addItem($hostid, $host['name']);
428		}
429
430		$filter_column
431			->addRow(_('Host group'), $filter_groupid_combobox)
432			->addRow(_('Host'), $hosts_combobox);
433	}
434
435	// Now just prepare needed data.
436
437	foreach ($triggers as &$trigger) {
438		$trigger['host_name'] = $trigger['hosts'][0]['name'];
439	}
440	unset($trigger);
441
442	$reportWidget->addItem(
443		(new CFilter(new CUrl('report2.php')))
444			->setProfile($data['filter']['timeline']['profileIdx'])
445			->setActiveTab($data['filter']['active_tab'])
446			->addFormItem((new CVar('mode', $availabilityReportMode))->removeId())
447			->addTimeSelector($data['filter']['timeline']['from'], $data['filter']['timeline']['to'], true,
448				ZBX_DATE_TIME)
449			->addFilterTab(_('Filter'), [$filter_column])
450	);
451
452	/*
453	 * Triggers
454	 */
455	$triggerTable = (new CTableInfo())->setHeader([_('Host'), _('Name'), _('Problems'), _('Ok'), _('Graph')]);
456
457	CArrayHelper::sort($triggers, ['host_name', 'description']);
458
459	$paging = getPagingLine($triggers, ZBX_SORT_UP, new CUrl('report2.php'));
460
461	foreach ($triggers as $trigger) {
462		$availability = calculateAvailability($trigger['triggerid'], $data['filter']['timeline']['from_ts'],
463			$data['filter']['timeline']['to_ts']
464		);
465
466		$triggerTable->addRow([
467			$trigger['host_name'],
468			new CLink($trigger['description'],
469				(new CUrl('zabbix.php'))
470					->setArgument('action', 'problem.view')
471					->setArgument('filter_triggerids[]', $trigger['triggerid'])
472					->setArgument('filter_set', '1')
473			),
474			($availability['true'] < 0.00005)
475				? ''
476				: (new CSpan(sprintf('%.4f%%', $availability['true'])))->addClass(ZBX_STYLE_RED),
477			($availability['false'] < 0.00005)
478				? ''
479				: (new CSpan(sprintf('%.4f%%', $availability['false'])))->addClass(ZBX_STYLE_GREEN),
480			new CLink(_('Show'), 'report2.php?filter_groupid='.$_REQUEST['filter_groupid'].
481				'&filter_hostid='.$_REQUEST['filter_hostid'].'&triggerid='.$trigger['triggerid']
482			)
483		]);
484	}
485
486	$obj_data = [
487		'id' => 'timeline_1',
488		'domid' => 'avail_report',
489		'loadSBox' => 0,
490		'loadImage' => 0,
491		'dynamic' => 0,
492		'mainObject' => 1
493	];
494	zbx_add_post_js(
495		'timeControl.addObject("avail_report", '.zbx_jsvalue($data['filter']).', '.zbx_jsvalue($obj_data).');'
496	);
497	zbx_add_post_js('timeControl.processObjects();');
498
499	$reportWidget
500		->addItem([$triggerTable, $paging])
501		->show();
502}
503
504require_once dirname(__FILE__).'/include/page_footer.php';
505