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__).'/graphs.inc.php';
23require_once dirname(__FILE__).'/screens.inc.php';
24require_once dirname(__FILE__).'/maps.inc.php';
25require_once dirname(__FILE__).'/users.inc.php';
26
27/**
28 * @param array  $filter
29 * @param array  $filter['groupids']           (optional)
30 * @param array  $filter['exclude_groupids']   (optional)
31 * @param array  $filter['hostids']            (optional)
32 * @param string $filter['problem']            (optional)
33 * @param array  $filter['severities']         (optional)
34 * @param int    $filter['show_suppressed']    (optional)
35 * @param int    $filter['hide_empty_groups']  (optional)
36 * @param int    $filter['ext_ack']            (optional)
37 * @param int    $filter['show_opdata']        (optional)
38 *
39 * @return array
40 */
41function getSystemStatusData(array $filter) {
42	$filter_groupids = (array_key_exists('groupids', $filter) && $filter['groupids']) ? $filter['groupids'] : null;
43	$filter_hostids = (array_key_exists('hostids', $filter) && $filter['hostids']) ? $filter['hostids'] : null;
44	$filter_severities = (array_key_exists('severities', $filter) && $filter['severities'])
45		? $filter['severities']
46		: range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1);
47	$filter_ext_ack = array_key_exists('ext_ack', $filter)
48		? $filter['ext_ack']
49		: EXTACK_OPTION_ALL;
50	$filter_evaltype = array_key_exists('evaltype', $filter) ? $filter['evaltype'] : TAG_EVAL_TYPE_AND_OR;
51	$filter_tags = array_key_exists('tags', $filter) ? $filter['tags'] : [];
52
53	if (array_key_exists('exclude_groupids', $filter) && $filter['exclude_groupids']) {
54		if ($filter_hostids === null) {
55			// Get all groups if no selected groups defined.
56			if ($filter_groupids === null) {
57				$filter_groupids = array_keys(API::HostGroup()->get([
58					'output' => [],
59					'real_hosts' => true,
60					'preservekeys' => true
61				]));
62			}
63
64			$filter_groupids = array_diff($filter_groupids, $filter['exclude_groupids']);
65
66			// Get available hosts.
67			$filter_hostids = array_keys(API::Host()->get([
68				'output' => [],
69				'groupids' => $filter_groupids,
70				'preservekeys' => true
71			]));
72		}
73
74		$exclude_hostids = array_keys(API::Host()->get([
75			'output' => [],
76			'groupids' => $filter['exclude_groupids'],
77			'preservekeys' => true
78		]));
79
80		$filter_hostids = array_diff($filter_hostids, $exclude_hostids);
81	}
82
83	$data = [
84		'groups' => API::HostGroup()->get([
85			'output' => ['groupid', 'name'],
86			'groupids' => $filter_groupids,
87			'hostids' => $filter_hostids,
88			'monitored_hosts' => true,
89			'preservekeys' => true
90		]),
91		'triggers' => [],
92		'actions' => [],
93		'stats' => []
94	];
95
96	CArrayHelper::sort($data['groups'], [['field' => 'name', 'order' => ZBX_SORT_UP]]);
97
98	$default_stats = [];
99
100	for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) {
101		if (in_array($severity, $filter_severities)) {
102			$default_stats[$severity] = ['count' => 0, 'problems' => [], 'count_unack' => 0, 'problems_unack' => []];
103		}
104	}
105
106	$data['stats'] = $default_stats;
107
108	foreach ($data['groups'] as &$group) {
109		$group['stats'] = $default_stats;
110		$group['has_problems'] = false;
111	}
112	unset($group);
113
114	$options = [
115		'output' => ['eventid', 'objectid', 'clock', 'ns', 'name', 'acknowledged', 'severity'],
116		'groupids' => array_keys($data['groups']),
117		'hostids' => $filter_hostids,
118		'evaltype' => $filter_evaltype,
119		'tags' => $filter_tags,
120		'source' => EVENT_SOURCE_TRIGGERS,
121		'object' => EVENT_OBJECT_TRIGGER,
122		'suppressed' => false,
123		'sortfield' => ['eventid'],
124		'sortorder' => ZBX_SORT_DOWN,
125		'preservekeys' => true
126	];
127
128	if (array_key_exists('severities', $filter)) {
129		$filter_severities = implode(',', $filter['severities']);
130		$all_severities = implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1));
131
132		if ($filter_severities !== '' && $filter_severities !== $all_severities) {
133			$options['severities'] = $filter['severities'];
134		}
135	}
136
137	if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) {
138		unset($options['suppressed']);
139		$options['selectSuppressionData'] = ['maintenanceid', 'suppress_until'];
140	}
141
142	if ($filter_ext_ack == EXTACK_OPTION_UNACK) {
143		$options['acknowledged'] = false;
144	}
145
146	if (array_key_exists('problem', $filter) && $filter['problem'] !== '') {
147		$options['search'] = ['name' => $filter['problem']];
148	}
149
150	$problems = API::Problem()->get($options);
151	if ($problems) {
152		$triggerids = [];
153
154		foreach ($problems as $problem) {
155			$triggerids[$problem['objectid']] = true;
156		}
157
158		$options = [
159			'output' => ['priority'],
160			'selectGroups' => ['groupid'],
161			'selectHosts' => ['name'],
162			'selectItems' => ['itemid', 'hostid', 'name', 'key_', 'value_type', 'units', 'valuemapid'],
163			'triggerids' => array_keys($triggerids),
164			'monitored' => true,
165			'skipDependent' => true,
166			'preservekeys' => true
167		];
168
169		if (array_key_exists('show_opdata', $filter) && $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) {
170			$options['output'] = array_merge(
171				$options['output'],
172				['url', 'expression', 'recovery_mode', 'recovery_expression', 'opdata']
173			);
174		}
175
176		$data['triggers'] = API::Trigger()->get($options);
177
178		foreach ($data['triggers'] as &$trigger) {
179			CArrayHelper::sort($trigger['hosts'], [['field' => 'name', 'order' => ZBX_SORT_UP]]);
180		}
181		unset($trigger);
182
183		foreach ($problems as $eventid => $problem) {
184			if (!array_key_exists($problem['objectid'], $data['triggers'])) {
185				unset($problems[$eventid]);
186			}
187		}
188
189		$visible_problems = [];
190
191		foreach ($problems as $eventid => $problem) {
192			$trigger = $data['triggers'][$problem['objectid']];
193
194			$data['stats'][$problem['severity']]['count']++;
195			if ($problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
196				$data['stats'][$problem['severity']]['count_unack']++;
197			}
198
199			// groups
200			foreach ($trigger['groups'] as $trigger_group) {
201				if (!array_key_exists($trigger_group['groupid'], $data['groups'])) {
202					continue;
203				}
204
205				$group = &$data['groups'][$trigger_group['groupid']];
206
207				if (in_array($filter_ext_ack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH])) {
208					if ($group['stats'][$problem['severity']]['count'] < ZBX_WIDGET_ROWS) {
209						$group['stats'][$problem['severity']]['problems'][] = $problem;
210						$visible_problems[$eventid] = ['eventid' => $eventid];
211					}
212
213					$group['stats'][$problem['severity']]['count']++;
214				}
215
216				if (in_array($filter_ext_ack, [EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH])
217						&& $problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
218					if ($group['stats'][$problem['severity']]['count_unack'] < ZBX_WIDGET_ROWS) {
219						$group['stats'][$problem['severity']]['problems_unack'][] = $problem;
220						$visible_problems[$eventid] = ['eventid' => $eventid];
221					}
222
223					$group['stats'][$problem['severity']]['count_unack']++;
224				}
225
226				$group['has_problems'] = true;
227			}
228			unset($group);
229		}
230
231		// actions & tags
232		$problems_data = API::Problem()->get([
233			'output' => ['eventid', 'r_eventid', 'clock', 'objectid', 'severity'],
234			'eventids' => array_keys($visible_problems),
235			'selectAcknowledges' => ['userid', 'clock', 'message', 'action', 'old_severity', 'new_severity'],
236			'selectTags' => ['tag', 'value'],
237			'preservekeys' => true
238		]);
239
240		// Remove problems that were resolved between requests or set tags.
241		foreach ($data['groups'] as $groupid => &$group) {
242			foreach ($group['stats'] as $severity => &$stat) {
243				foreach (['problems', 'problems_unack'] as $key) {
244					foreach ($stat[$key] as $event_no => &$problem) {
245						if (array_key_exists($problem['eventid'], $problems_data)) {
246							$problem['tags'] = $problems_data[$problem['eventid']]['tags'];
247						}
248						else {
249							if ($key === 'problems') {
250								$data['groups'][$groupid]['stats'][$severity]['count']--;
251							}
252							else {
253								$data['groups'][$groupid]['stats'][$severity]['count_unack']--;
254							}
255							unset($data['groups'][$groupid]['stats'][$severity][$key][$event_no]);
256						}
257					}
258					unset($problem);
259				}
260			}
261			unset($stat);
262		}
263		unset($group);
264
265		// actions
266		// Possible performance improvement: one API call may be saved, if r_clock for problem will be used.
267		$actions = getEventsActionsIconsData($problems_data, $data['triggers']);
268		$data['actions'] = [
269			'all_actions' => $actions['data'],
270			'users' => API::User()->get([
271				'output' => ['alias', 'name', 'surname'],
272				'userids' => array_keys($actions['userids']),
273				'preservekeys' => true
274			])
275		];
276
277		if (array_key_exists('show_opdata', $filter) && $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) {
278			$maked_data = CScreenProblem::makeData(
279				['problems' => $problems_data, 'triggers' => $data['triggers']],
280				['show' => 0, 'details' => 0, 'show_opdata' => $filter['show_opdata']]
281			);
282			$data['triggers'] = $maked_data['triggers'];
283		}
284	}
285
286	return $data;
287}
288
289/**
290 * @param array  $filter
291 * @param array  $filter['hostids']            (optional)
292 * @param string $filter['problem']            (optional)
293 * @param array  $filter['severities']         (optional)
294 * @param int    $filter['show_suppressed']    (optional)
295 * @param int    $filter['hide_empty_groups']  (optional)
296 * @param int    $filter['ext_ack']            (optional)
297 * @param int    $filter['show_timeline']      (optional)
298 * @param int    $filter['show_opdata']        (optional)
299 * @param array  $data
300 * @param array  $data['groups']
301 * @param string $data['groups'][]['groupid']
302 * @param string $data['groups'][]['name']
303 * @param bool   $data['groups'][]['has_problems']
304 * @param array  $data['groups'][]['stats']
305 * @param int    $data['groups'][]['stats']['count']
306 * @param array  $data['groups'][]['stats']['problems']
307 * @param string $data['groups'][]['stats']['problems'][]['eventid']
308 * @param string $data['groups'][]['stats']['problems'][]['objectid']
309 * @param int    $data['groups'][]['stats']['problems'][]['clock']
310 * @param int    $data['groups'][]['stats']['problems'][]['ns']
311 * @param int    $data['groups'][]['stats']['problems'][]['acknowledged']
312 * @param array  $data['groups'][]['stats']['problems'][]['tags']
313 * @param string $data['groups'][]['stats']['problems'][]['tags'][]['tag']
314 * @param string $data['groups'][]['stats']['problems'][]['tags'][]['value']
315 * @param int    $data['groups'][]['stats']['count_unack']
316 * @param array  $data['groups'][]['stats']['problems_unack']
317 * @param array  $data['triggers']
318 * @param string $data['triggers'][<triggerid>]['expression']
319 * @param string $data['triggers'][<triggerid>]['description']
320 * @param array  $data['triggers'][<triggerid>]['hosts']
321 * @param string $data['triggers'][<triggerid>]['hosts'][]['name']
322 * @param array  $data['triggers'][<triggerid>]['opdata']
323 * @param array  $config
324 * @param string $config['severity_name_*']
325 *
326 * @return CDiv
327 */
328function makeSystemStatus(array $filter, array $data, array $config) {
329	$filter_severities = (array_key_exists('severities', $filter) && $filter['severities'])
330		? $filter['severities']
331		: range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1);
332	$filter_hide_empty_groups = array_key_exists('hide_empty_groups', $filter) ? $filter['hide_empty_groups'] : 0;
333	$filter_ext_ack = array_key_exists('ext_ack', $filter)
334		? $filter['ext_ack']
335		: EXTACK_OPTION_ALL;
336
337	// indicator of sort field
338	$sort_div = (new CSpan())->addClass(ZBX_STYLE_ARROW_UP);
339
340	// Set trigger severities as table header starting from highest severity.
341	$header = [[_('Host group'), $sort_div]];
342
343	for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) {
344		if (in_array($severity, $filter_severities)) {
345			$header[] = getSeverityName($severity, $config);
346		}
347	}
348
349	$table = (new CTableInfo())
350		->setHeader($header)
351		->setHeadingColumn(0);
352
353	$url_group = (new CUrl('zabbix.php'))
354		->setArgument('action', 'problem.view')
355		->setArgument('filter_set', 1)
356		->setArgument('filter_show', TRIGGERS_OPTION_RECENT_PROBLEM)
357		->setArgument('filter_groupids', null)
358		->setArgument('filter_hostids', array_key_exists('hostids', $filter) ? $filter['hostids'] : null)
359		->setArgument('filter_name', array_key_exists('problem', $filter) ? $filter['problem'] : null)
360		->setArgument('filter_show_suppressed',
361			(array_key_exists('show_suppressed', $filter) && $filter['show_suppressed'] == 1)
362				? 1
363				: null
364		);
365
366	foreach ($data['groups'] as $group) {
367		if ($filter_hide_empty_groups && !$group['has_problems']) {
368			continue;
369		}
370
371		$url_group->setArgument('filter_groupids', [$group['groupid']]);
372		$row = [new CLink($group['name'], $url_group->getUrl())];
373
374		foreach ($group['stats'] as $severity => $stat) {
375			if ($stat['count'] == 0 && $stat['count_unack'] == 0) {
376				$row[] = '';
377				continue;
378			}
379
380			$allTriggersNum = $stat['count'];
381			if ($allTriggersNum) {
382				$allTriggersNum = (new CLinkAction($allTriggersNum))
383					->setHint(makeProblemsPopup($stat['problems'], $data['triggers'], $data['actions'], $config,
384						$filter
385					));
386			}
387
388			$unackTriggersNum = $stat['count_unack'];
389			if ($unackTriggersNum) {
390				$unackTriggersNum = (new CLinkAction($unackTriggersNum))
391					->setHint(makeProblemsPopup($stat['problems_unack'], $data['triggers'], $data['actions'], $config,
392						$filter
393					));
394			}
395
396			switch ($filter_ext_ack) {
397				case EXTACK_OPTION_ALL:
398					$row[] = getSeverityCell($severity, null, $allTriggersNum);
399					break;
400
401				case EXTACK_OPTION_UNACK:
402					$row[] = getSeverityCell($severity, null, $unackTriggersNum);
403					break;
404
405				case EXTACK_OPTION_BOTH:
406					if ($stat['count_unack'] != 0) {
407						$row[] = getSeverityCell($severity, $config, [
408							$unackTriggersNum, ' '._('of').' ', $allTriggersNum
409						]);
410					}
411					else {
412						$row[] = getSeverityCell($severity, $config, $allTriggersNum);
413					}
414					break;
415			}
416		}
417
418		$table->addRow($row);
419	}
420
421	return $table;
422}
423
424/**
425 * @param array  $data
426 * @param array  $data['groups']
427 * @param string $data['groups'][]['groupid']
428 * @param string $data['groups'][]['name']
429 * @param bool   $data['groups'][]['has_problems']
430 * @param array  $data['groups'][]['stats']
431 * @param int    $data['groups'][]['stats'][]['count']
432 * @param array  $data['groups'][]['stats'][]['problems']
433 * @param int    $data['groups'][]['stats'][]['count_unack']
434 * @param array  $data['groups'][]['stats'][]['problems_unack']
435 *
436 * @return array
437 */
438function getSystemStatusTotals(array $data) {
439	$groups_totals = [
440		0 => [
441			'groupid' => 0,
442			'stats' => []
443		]
444	];
445
446	foreach ($data['stats'] as $severity => $value) {
447		$groups_totals[0]['stats'][$severity] = [
448			'count' => $value['count'],
449			'problems' => [],
450			'count_unack' => $value['count_unack'],
451			'problems_unack' => []
452		];
453	}
454
455	foreach ($data['groups'] as $group) {
456		foreach ($group['stats'] as $severity => $stat) {
457			foreach ($stat['problems'] as $problem) {
458				$groups_totals[0]['stats'][$severity]['problems'][$problem['eventid']] = $problem;
459			}
460			foreach ($stat['problems_unack'] as $problem) {
461				$groups_totals[0]['stats'][$severity]['problems_unack'][$problem['eventid']] = $problem;
462			}
463		}
464	}
465
466	return $groups_totals;
467}
468
469/**
470 * @param array      $data
471 * @param array      $data['data']
472 * @param array      $data['data']['groups']
473 * @param array      $data['data']['groups'][]['stats']
474 * @param array      $data['filter']
475 * @param array      $data['filter']['severities']
476 * @param boolean    $hide_empty_groups
477 * @param CUrl       $groupurl
478 *
479 * @return CTableInfo
480 */
481function makeSeverityTable(array $data, $hide_empty_groups = false, CUrl $groupurl = null) {
482	$table = new CTableInfo();
483
484	foreach ($data['data']['groups'] as $group) {
485		if ($hide_empty_groups && !$group['has_problems']) {
486			// Skip row.
487			continue;
488		}
489
490		$groupurl->setArgument('filter_groupids', [$group['groupid']]);
491		$row = [new CLink($group['name'], $groupurl->getUrl())];
492
493		foreach ($group['stats'] as $severity => $stat) {
494			if ($data['filter']['severities'] && !in_array($severity, $data['filter']['severities'])) {
495				// Skip cell.
496				continue;
497			}
498
499			$row[] = getSeverityTableCell($severity, $data, $stat);
500		}
501
502		$table->addRow($row);
503	}
504
505	return $table;
506}
507
508/**
509 * @param array      $data
510 * @param array      $data['data']
511 * @param array      $data['data']['groups']
512 * @param array      $data['data']['groups'][]['stats']
513 * @param array      $data['filter']
514 * @param array      $data['filter']['severities']
515 *
516 * @return CDiv
517 */
518function makeSeverityTotals(array $data) {
519	$table = new CDiv();
520
521	foreach ($data['data']['groups'] as $group) {
522		foreach ($group['stats'] as $severity => $stat) {
523			if ($data['filter']['severities'] && !in_array($severity, $data['filter']['severities'])) {
524				// Skip cell.
525				continue;
526			}
527			$table->addItem(getSeverityTableCell($severity, $data, $stat, true));
528		}
529	}
530
531	return $table;
532}
533
534/**
535 * @param int     $severity
536 * @param array   $data
537 * @param array   $data['data']
538 * @param array   $data['data']['triggers']
539 * @param array   $data['data']['actions']
540 * @param array   $data['filter']
541 * @param array   $data['filter']['ext_ack']
542 * @param array   $data['severity_names']
543 * @param array   $stat
544 * @param int     $stats['count']
545 * @param array   $stats['problems']
546 * @param int     $stats['count_unack']
547 * @param array   $stats['problems_unack']
548 * @param boolean $is_total
549 *
550 * @return CCol|string
551 */
552function getSeverityTableCell($severity, array $data, array $stat, $is_total = false) {
553	if (!$is_total && $stat['count'] == 0 && $stat['count_unack'] == 0) {
554		return '';
555	}
556
557	$severity_name = $is_total ? ' '.getSeverityName($severity, $data['severity_names']) : '';
558	$ext_ack = array_key_exists('ext_ack', $data['filter']) ? $data['filter']['ext_ack'] : EXTACK_OPTION_ALL;
559
560	$allTriggersNum = $stat['count'];
561	if ($allTriggersNum) {
562		$allTriggersNum = (new CLinkAction($allTriggersNum))
563			->setHint(makeProblemsPopup($stat['problems'], $data['data']['triggers'], $data['data']['actions'],
564				$data['severity_names'], $data['filter']
565			));
566	}
567
568	$unackTriggersNum = $stat['count_unack'];
569	if ($unackTriggersNum) {
570		$unackTriggersNum = (new CLinkAction($unackTriggersNum))
571			->setHint(makeProblemsPopup($stat['problems_unack'], $data['data']['triggers'], $data['data']['actions'],
572				$data['severity_names'], $data['filter']
573			));
574	}
575
576	switch ($ext_ack) {
577		case EXTACK_OPTION_ALL:
578			return getSeverityCell($severity, null, [
579				(new CSpan($allTriggersNum))->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
580				$severity_name
581			], false, $is_total);
582
583		case EXTACK_OPTION_UNACK:
584			return getSeverityCell($severity, null, [
585				(new CSpan($unackTriggersNum))->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
586				$severity_name
587			], false, $is_total);
588
589		case EXTACK_OPTION_BOTH:
590			return getSeverityCell($severity, $data['severity_names'], [
591				(new CSpan([$unackTriggersNum, ' '._('of').' ', $allTriggersNum]))
592					->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
593				$severity_name
594			], false, $is_total);
595
596		default:
597			return '';
598	}
599}
600
601function make_status_of_zbx() {
602	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
603		global $ZBX_SERVER, $ZBX_SERVER_PORT;
604
605		$server_details = $ZBX_SERVER.':'.$ZBX_SERVER_PORT;
606	}
607	else {
608		$server_details = '';
609	}
610
611	$table = (new CTableInfo())
612		->setHeader([_('Parameter'), _('Value'), _('Details')])
613		->setHeadingColumn(0);
614
615	$status = get_status();
616
617	$table
618		->addRow([_('Zabbix server is running'),
619			(new CSpan($status['is_running'] ? _('Yes') : _('No')))
620				->addClass($status['is_running'] ? ZBX_STYLE_GREEN : ZBX_STYLE_RED),
621			$server_details
622		])
623		->addRow([_('Number of hosts (enabled/disabled)'),
624			$status['has_status'] ? $status['hosts_count'] : '',
625			$status['has_status']
626				? [
627					(new CSpan($status['hosts_count_monitored']))->addClass(ZBX_STYLE_GREEN), ' / ',
628					(new CSpan($status['hosts_count_not_monitored']))->addClass(ZBX_STYLE_RED)
629				]
630				: ''
631		])
632		->addRow([_('Number of templates'),
633			$status['has_status'] ? $status['hosts_count_template'] : '', ''
634		]);
635
636	$title = (new CSpan(_('Number of items (enabled/disabled/not supported)')))
637		->setTitle(_('Only items assigned to enabled hosts are counted'));
638	$table->addRow([$title, $status['has_status'] ? $status['items_count'] : '',
639		$status['has_status']
640			? [
641				(new CSpan($status['items_count_monitored']))->addClass(ZBX_STYLE_GREEN), ' / ',
642				(new CSpan($status['items_count_disabled']))->addClass(ZBX_STYLE_RED), ' / ',
643				(new CSpan($status['items_count_not_supported']))->addClass(ZBX_STYLE_GREY)
644			]
645			: ''
646	]);
647	$title = (new CSpan(_('Number of triggers (enabled/disabled [problem/ok])')))
648		->setTitle(_('Only triggers assigned to enabled hosts and depending on enabled items are counted'));
649	$table->addRow([$title, $status['has_status'] ? $status['triggers_count'] : '',
650		$status['has_status']
651			? [
652				$status['triggers_count_enabled'], ' / ',
653				$status['triggers_count_disabled'], ' [',
654				(new CSpan($status['triggers_count_on']))->addClass(ZBX_STYLE_RED), ' / ',
655				(new CSpan($status['triggers_count_off']))->addClass(ZBX_STYLE_GREEN), ']'
656			]
657			: ''
658	]);
659	$table->addRow([_('Number of users (online)'), $status['has_status'] ? $status['users_count'] : '',
660		$status['has_status'] ? (new CSpan($status['users_online']))->addClass(ZBX_STYLE_GREEN) : ''
661	]);
662	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
663		$table->addRow([_('Required server performance, new values per second'),
664			($status['has_status'] && array_key_exists('vps_total', $status)) ? round($status['vps_total'], 2) : '', ''
665		]);
666	}
667
668	// Check requirements.
669	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
670		$setup = new CFrontendSetup();
671		$reqs = $setup->checkRequirements();
672		$reqs[] = $setup->checkSslFiles();
673
674		foreach ($reqs as $req) {
675			if ($req['result'] == CFrontendSetup::CHECK_FATAL) {
676				$table->addRow(
677					(new CRow([$req['name'], $req['current'], $req['error']]))->addClass(ZBX_STYLE_RED)
678				);
679			}
680		}
681
682		$db = DB::getDbBackend();
683
684		if (!$db->checkEncoding()) {
685			$table->addRow(
686				(new CRow((new CCol($db->getWarning()))->setAttribute('colspan', 3)))->addClass(ZBX_STYLE_RED)
687			);
688		}
689	}
690
691	// Warn if database history tables have not been upgraded.
692	global $DB;
693
694	if (!$DB['DOUBLE_IEEE754']) {
695		$table->addRow([
696			_('Database history tables upgraded'),
697			(new CSpan(_('No')))->addClass(ZBX_STYLE_RED),
698			''
699		]);
700	}
701
702	return $table;
703}
704
705/**
706 * Generate table for dashboard triggers popup.
707 *
708 * @see makeSystemStatus
709 *
710 * @param array  $problems
711 * @param string $problems[]['objectid']
712 * @param int    $problems[]['clock']
713 * @param int    $problems[]['ns']
714 * @param array  $problems[]['acknowledged']
715 * @param array  $problems[]['severity']
716 * @param array  $problems[]['suppression_data']
717 * @param array  $problems[]['tags']
718 * @param string $problems[]['tags'][]['tag']
719 * @param string $problems[]['tags'][]['value']
720 * @param array  $triggers
721 * @param string $triggers[<triggerid>]['expression']
722 * @param string $triggers[<triggerid>]['description']
723 * @param array  $triggers[<triggerid>]['hosts']
724 * @param string $triggers[<triggerid>]['hosts'][]['name']
725 * @param string $triggers[<triggerid>]['opdata']
726 * @param array  $actions
727 * @param array  $config
728 * @param array  $filter
729 * @param array  $filter['show_suppressed']  (optional)
730 * @param array  $filter['show_timeline']    (optional)
731 * @param array  $filter['show_opdata']      (optional)
732 *
733 * @return CTableInfo
734 */
735function makeProblemsPopup(array $problems, array $triggers, array $actions, array $config, array $filter) {
736	$url_details = (new CUrl('tr_events.php'))
737		->setArgument('triggerid', '')
738		->setArgument('eventid', '');
739
740	$header_time = new CColHeader([_('Time'), (new CSpan())->addClass(ZBX_STYLE_ARROW_DOWN)]);
741
742	$show_timeline = (array_key_exists('show_timeline', $filter) && $filter['show_timeline']);
743	$show_opdata = (array_key_exists('show_opdata', $filter)) ? $filter['show_opdata'] : OPERATIONAL_DATA_SHOW_NONE;
744
745	if ($show_timeline) {
746		$header = [
747			$header_time->addClass(ZBX_STYLE_RIGHT),
748			(new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH),
749			(new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH)
750		];
751	}
752	else {
753		$header = [$header_time];
754	}
755
756	$table = (new CTableInfo())
757		->setHeader(array_merge($header, [
758			_('Info'),
759			_('Host'),
760			_('Problem'),
761			($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? _('Operational data') : null,
762			_('Duration'),
763			_('Ack'),
764			_('Actions'),
765			_('Tags')
766		]));
767
768	$today = strtotime('today');
769	$last_clock = 0;
770
771	// Unset triggers, which missing in problems array.
772	if ($problems) {
773		$objectids = [];
774
775		foreach ($problems as $problem) {
776			$objectids[$problem['objectid']] = true;
777		}
778
779		$triggers = array_intersect_key($triggers, $objectids);
780	}
781
782	$triggers_hosts = getTriggersHostsList($triggers);
783	$triggers_hosts = makeTriggersHostsList($triggers_hosts);
784
785	$tags = makeTags($problems);
786
787	if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) {
788		CScreenProblem::addMaintenanceNames($problems);
789	}
790
791	foreach ($problems as $problem) {
792		$trigger = $triggers[$problem['objectid']];
793
794		$url_details
795			->setArgument('triggerid', $problem['objectid'])
796			->setArgument('eventid', $problem['eventid']);
797
798		$cell_clock = ($problem['clock'] >= $today)
799			? zbx_date2str(TIME_FORMAT_SECONDS, $problem['clock'])
800			: zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']);
801		$cell_clock = new CCol(new CLink($cell_clock, $url_details));
802
803		if ($show_timeline) {
804			if ($last_clock != 0) {
805				CScreenProblem::addTimelineBreakpoint($table, $last_clock, $problem['clock'], ZBX_SORT_DOWN);
806			}
807			$last_clock = $problem['clock'];
808
809			$row = [
810				$cell_clock->addClass(ZBX_STYLE_TIMELINE_DATE),
811				(new CCol())
812					->addClass(ZBX_STYLE_TIMELINE_AXIS)
813					->addClass(ZBX_STYLE_TIMELINE_DOT),
814				(new CCol())->addClass(ZBX_STYLE_TIMELINE_TD)
815			];
816		}
817		else {
818			$row = [
819				$cell_clock
820					->addClass(ZBX_STYLE_NOWRAP)
821					->addClass(ZBX_STYLE_RIGHT)
822			];
823		}
824
825		$info_icons = [];
826		if (array_key_exists('suppression_data', $problem) && $problem['suppression_data']) {
827			$info_icons[] = makeSuppressedProblemIcon($problem['suppression_data']);
828		}
829
830		// operational data
831		$opdata = null;
832		if ($show_opdata != OPERATIONAL_DATA_SHOW_NONE) {
833
834			if ($trigger['opdata'] === '') {
835				if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) {
836					$opdata = (new CCol(CScreenProblem::getLatestValues($trigger['items'])))->addClass('latest-values');
837				}
838			}
839			else {
840				$opdata = CMacrosResolverHelper::resolveTriggerOpdata(
841					[
842						'triggerid' => $trigger['triggerid'],
843						'expression' => $trigger['expression'],
844						'opdata' => $trigger['opdata'],
845						'clock' => $problem['clock'],
846						'ns' => $problem['ns']
847					],
848					[
849						'events' => true,
850						'html' => true
851					]
852				);
853
854				if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) {
855					$opdata = (new CCol($opdata))
856						->addClass('opdata')
857						->addClass(ZBX_STYLE_WORDWRAP);
858				}
859			}
860		}
861
862		// Create acknowledge link.
863		$is_acknowledged = ($problem['acknowledged'] == EVENT_ACKNOWLEDGED);
864		$problem_update_link = (new CLink($is_acknowledged ? _('Yes') : _('No')))
865			->addClass($is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED)
866			->addClass(ZBX_STYLE_LINK_ALT)
867			->onClick('acknowledgePopUp('.json_encode(['eventids' => [$problem['eventid']]]).', this);');
868
869		$table->addRow(array_merge($row, [
870			makeInformationList($info_icons),
871			$triggers_hosts[$trigger['triggerid']],
872			getSeverityCell($problem['severity'], null,
873				(($show_opdata == OPERATIONAL_DATA_SHOW_WITH_PROBLEM && $opdata)
874					? [$problem['name'], ' (', $opdata, ')']
875					: $problem['name']
876				)
877			),
878			($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? $opdata : null,
879			zbx_date2age($problem['clock']),
880			$problem_update_link,
881			makeEventActionsIcons($problem['eventid'], $actions['all_actions'], $actions['users'], $config),
882			$tags[$problem['eventid']]
883		]));
884	}
885
886	return $table;
887}
888