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';
24require_once dirname(__FILE__).'/include/items.inc.php';
25
26$page['title'] = _('Latest data');
27$page['file'] = 'latest.php';
28$page['type'] = detect_page_type(PAGE_TYPE_HTML);
29$page['scripts'] = ['multiselect.js'];
30
31if (PAGE_TYPE_HTML == $page['type']) {
32	define('ZBX_PAGE_DO_REFRESH', 1);
33}
34
35require_once dirname(__FILE__).'/include/page_header.php';
36
37//	VAR						TYPE	OPTIONAL	FLAGS	VALIDATION	EXCEPTION
38$fields = [
39	'groupids' =>			[T_ZBX_INT, O_OPT, P_SYS,	DB_ID,		null],
40	'hostids' =>			[T_ZBX_INT, O_OPT, P_SYS,	DB_ID,		null],
41	'fullscreen' =>			[T_ZBX_INT, O_OPT, P_SYS,	IN('0,1'),	null],
42	'select' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
43	'show_without_data' =>	[T_ZBX_INT, O_OPT, null,	IN('0,1'),	null],
44	'show_details' =>		[T_ZBX_INT, O_OPT, null,	IN('0,1'),	null],
45	'application' =>		[T_ZBX_STR, O_OPT, null,	null,		null],
46	'filter_rst' =>			[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
47	'filter_set' =>			[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
48	// sort and sortorder
49	'sort' =>				[T_ZBX_STR, O_OPT, P_SYS, IN('"host","lastclock","name"'),				null],
50	'sortorder' =>			[T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
51];
52check_fields($fields);
53
54/*
55 * Permissions
56 */
57if (getRequest('groupids') && !API::HostGroup()->isReadable(getRequest('groupids'))) {
58	access_deny();
59}
60if (getRequest('hostids') && !API::Host()->isReadable(getRequest('hostids'))) {
61	access_deny();
62}
63
64if (PAGE_TYPE_JS == $page['type'] || PAGE_TYPE_HTML_BLOCK == $page['type']){
65	require_once dirname(__FILE__).'/include/page_footer.php';
66	exit;
67}
68
69require_once dirname(__FILE__).'/include/views/js/monitoring.latest.js.php';
70
71/*
72 * Filter
73 */
74if (hasRequest('filter_set')) {
75	CProfile::update('web.latest.filter.select', getRequest('select', ''), PROFILE_TYPE_STR);
76	CProfile::update('web.latest.filter.show_without_data', getRequest('show_without_data', 0), PROFILE_TYPE_INT);
77	CProfile::update('web.latest.filter.show_details', getRequest('show_details', 0), PROFILE_TYPE_INT);
78	CProfile::update('web.latest.filter.application', getRequest('application', ''), PROFILE_TYPE_STR);
79	CProfile::updateArray('web.latest.filter.groupids', getRequest('groupids', []), PROFILE_TYPE_STR);
80	CProfile::updateArray('web.latest.filter.hostids', getRequest('hostids', []), PROFILE_TYPE_STR);
81}
82elseif (hasRequest('filter_rst')) {
83	DBStart();
84	CProfile::delete('web.latest.filter.select');
85	CProfile::delete('web.latest.filter.show_without_data');
86	CProfile::delete('web.latest.filter.show_details');
87	CProfile::delete('web.latest.filter.application');
88	CProfile::deleteIdx('web.latest.filter.groupids');
89	CProfile::deleteIdx('web.latest.filter.hostids');
90	DBend();
91}
92
93$filter = [
94	'select' => CProfile::get('web.latest.filter.select', ''),
95	'showWithoutData' => CProfile::get('web.latest.filter.show_without_data', 1),
96	'showDetails' => CProfile::get('web.latest.filter.show_details'),
97	'application' => CProfile::get('web.latest.filter.application', ''),
98	'groupids' => CProfile::getArray('web.latest.filter.groupids'),
99	'hostids' => CProfile::getArray('web.latest.filter.hostids')
100];
101
102// we'll need to hide the host column if only one host is selected
103$singleHostSelected = (count($filter['hostids']) == 1);
104
105$sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name'));
106$sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP));
107
108CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR);
109CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR);
110
111$applications = $items = $hostScripts = [];
112
113// we'll only display the values if the filter is set
114$filterSet = ($filter['select'] !== '' || $filter['application'] !== '' || $filter['groupids'] || $filter['hostids']);
115if ($filterSet) {
116	$hosts = API::Host()->get([
117		'output' => ['name', 'hostid', 'status'],
118		'hostids' => $filter['hostids'],
119		'groupids' => $filter['groupids'],
120		'selectGraphs' => API_OUTPUT_COUNT,
121		'with_monitored_items' => true,
122		'preservekeys' => true
123	]);
124}
125else {
126	$hosts = [];
127}
128
129if ($hosts) {
130
131	foreach ($hosts as &$host) {
132		$host['item_cnt'] = 0;
133	}
134	unset($host);
135
136	if (!$singleHostSelected) {
137		$sortFields = ($sortField === 'host') ? [['field' => 'name', 'order' => $sortOrder]] : ['name'];
138		CArrayHelper::sort($hosts, $sortFields);
139	}
140
141	$hostIds = array_keys($hosts);
142
143	$applications = null;
144
145	// if an application filter is set, fetch the applications and then use them to filter items
146	if ($filter['application'] !== '') {
147		$applications = API::Application()->get([
148			'output' => API_OUTPUT_EXTEND,
149			'hostids' => $hostIds,
150			'search' => ['name' => $filter['application']],
151			'preservekeys' => true
152		]);
153	}
154
155	$items = API::Item()->get([
156		'hostids' => array_keys($hosts),
157		'output' => ['itemid', 'name', 'type', 'value_type', 'units', 'hostid', 'state', 'valuemapid', 'status',
158			'error', 'trends', 'history', 'delay', 'key_', 'flags'],
159		'selectApplications' => ['applicationid'],
160		'selectItemDiscovery' => ['ts_delete'],
161		'applicationids' => ($applications !== null) ? zbx_objectValues($applications, 'applicationid') : null,
162		'webitems' => true,
163		'filter' => [
164			'status' => [ITEM_STATUS_ACTIVE]
165		],
166		'preservekeys' => true
167	]);
168
169	// if the applications haven't been loaded when filtering, load them based on the retrieved items to avoid
170	// fetching applications from hosts that may not be displayed
171	if ($applications === null) {
172		$applications = API::Application()->get([
173			'output' => API_OUTPUT_EXTEND,
174			'hostids' => array_keys(array_flip(zbx_objectValues($items, 'hostid'))),
175			'search' => ['name' => $filter['application']],
176			'preservekeys' => true
177		]);
178	}
179}
180
181if ($items) {
182	// macros
183	$items = CMacrosResolverHelper::resolveItemKeys($items);
184	$items = CMacrosResolverHelper::resolveItemNames($items);
185
186	// filter items by name
187	foreach ($items as $key => $item) {
188		if (($filter['select'] !== '')) {
189			$haystack = mb_strtolower($item['name_expanded']);
190			$needle = mb_strtolower($filter['select']);
191
192			if (mb_strpos($haystack, $needle) === false) {
193				unset($items[$key]);
194			}
195		}
196	}
197
198	if ($items) {
199		// get history
200		$history = Manager::History()->getLast($items, 2, ZBX_HISTORY_PERIOD);
201
202		// filter items without history
203		if (!$filter['showWithoutData']) {
204			foreach ($items as $key => $item) {
205				if (!isset($history[$item['itemid']])) {
206					unset($items[$key]);
207				}
208			}
209		}
210	}
211
212	if ($items) {
213		// add item last update date for sorting
214		foreach ($items as &$item) {
215			if (isset($history[$item['itemid']])) {
216				$item['lastclock'] = $history[$item['itemid']][0]['clock'];
217			}
218		}
219		unset($item);
220
221		// sort
222		if ($sortField === 'name') {
223			$sortFields = [['field' => 'name_expanded', 'order' => $sortOrder], 'itemid'];
224		}
225		elseif ($sortField === 'lastclock') {
226			$sortFields = [['field' => 'lastclock', 'order' => $sortOrder], 'name_expanded', 'itemid'];
227		}
228		else {
229			$sortFields = ['name_expanded', 'itemid'];
230		}
231		CArrayHelper::sort($items, $sortFields);
232
233		if ($applications) {
234			foreach ($applications as &$application) {
235				$application['hostname'] = $hosts[$application['hostid']]['name'];
236				$application['item_cnt'] = 0;
237			}
238			unset($application);
239
240			// by default order by application name and application id
241			$sortFields = ($sortField === 'host') ? [['field' => 'hostname', 'order' => $sortOrder]] : [];
242			array_push($sortFields, 'name', 'applicationid');
243			CArrayHelper::sort($applications, $sortFields);
244		}
245
246		if (!$singleHostSelected) {
247			// get host scripts
248			$hostScripts = API::Script()->getScriptsByHosts($hostIds);
249
250			// get templates screen count
251			$screens = API::TemplateScreen()->get([
252				'hostids' => $hostIds,
253				'countOutput' => true,
254				'groupCount' => true
255			]);
256			$screens = zbx_toHash($screens, 'hostid');
257			foreach ($hosts as &$host) {
258				$host['screens'] = isset($screens[$host['hostid']]);
259			}
260			unset($host);
261		}
262	}
263}
264
265// multiselect hosts
266$multiSelectHostData = [];
267if ($filter['hostids']) {
268	$filterHosts = API::Host()->get([
269		'output' => ['hostid', 'name'],
270		'hostids' => $filter['hostids']
271	]);
272
273	foreach ($filterHosts as $host) {
274		$multiSelectHostData[] = [
275			'id' => $host['hostid'],
276			'name' => $host['name']
277		];
278	}
279}
280
281// multiselect host groups
282$multiSelectHostGroupData = [];
283if ($filter['groupids'] !== null) {
284	$filterGroups = API::HostGroup()->get([
285		'output' => ['groupid', 'name'],
286		'groupids' => $filter['groupids']
287	]);
288
289	foreach ($filterGroups as $group) {
290		$multiSelectHostGroupData[] = [
291			'id' => $group['groupid'],
292			'name' => $group['name']
293		];
294	}
295}
296
297/*
298 * Display
299 */
300$widget = (new CWidget())
301	->setTitle(_('Latest data'))
302	->setControls((new CList())
303		->addItem(get_icon('fullscreen', ['fullscreen' => getRequest('fullscreen')]))
304	);
305
306// Filter
307$filterForm = (new CFilter('web.latest.filter.state'))
308	->addVar('fullscreen', getRequest('fullscreen'));
309
310$filterColumn1 = new CFormList();
311$filterColumn1->addRow(
312	_('Host groups'),
313	(new CMultiSelect(
314		[
315			'name' => 'groupids[]',
316			'objectName' => 'hostGroup',
317			'data' => $multiSelectHostGroupData,
318			'popup' => [
319				'parameters' => 'srctbl=host_groups&dstfrm=zbx_filter&dstfld1=groupids_'.
320					'&srcfld1=groupid&multiselect=1'
321			]
322	]))->setWidth(ZBX_TEXTAREA_FILTER_STANDARD_WIDTH)
323);
324$filterColumn1->addRow(
325		_('Hosts'),
326		(new CMultiSelect(
327			[
328				'name' => 'hostids[]',
329				'objectName' => 'hosts',
330				'data' => $multiSelectHostData,
331				'popup' => [
332					'parameters' => 'srctbl=hosts&dstfrm=zbx_filter&dstfld1=hostids_&srcfld1=hostid'.
333						'&real_hosts=1&multiselect=1'
334				]
335			]
336		))->setWidth(ZBX_TEXTAREA_FILTER_STANDARD_WIDTH)
337);
338$filterColumn1->addRow(
339	_('Application'),
340	[
341		(new CTextBox('application', $filter['application']))->setWidth(ZBX_TEXTAREA_FILTER_STANDARD_WIDTH),
342		(new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN),
343		(new CButton('application_name', _('Select')))
344			->addClass(ZBX_STYLE_BTN_GREY)
345			->onClick('return PopUp("popup.php?srctbl=applications&srcfld1=name&real_hosts=1&dstfld1=application'.
346				'&with_applications=1&dstfrm=zbx_filter");'
347			)
348	]
349);
350
351$filterColumn2 = new CFormList();
352$filterColumn2->addRow(
353	_('Name'),
354	(new CTextBox('select', $filter['select']))->setWidth(ZBX_TEXTAREA_FILTER_STANDARD_WIDTH)
355);
356$filterColumn2->addRow(
357	_('Show items without data'),
358	(new CCheckBox('show_without_data'))->setChecked($filter['showWithoutData'] == 1)
359);
360$filterColumn2->addRow(
361	_('Show details'),
362	(new CCheckBox('show_details'))->setChecked($filter['showDetails'] == 1)
363);
364
365$filterForm->addColumn($filterColumn1);
366$filterForm->addColumn($filterColumn2);
367
368$widget->addItem($filterForm);
369// End of Filter
370
371$form = (new CForm('GET', 'history.php'))
372	->setName('items')
373	->addItem(new CVar('action', HISTORY_BATCH_GRAPH));
374// table
375$table = (new CTableInfo())->addClass(ZBX_STYLE_OVERFLOW_ELLIPSIS);
376if (!$filterSet) {
377	$table->setNoDataMessage(_('Specify some filter condition to see the values.'));
378}
379
380$toggle_all = (new CColHeader(
381	(new CSimpleButton())
382		->addClass(ZBX_STYLE_TREEVIEW)
383		->addClass('app-list-toggle-all')
384		->addItem(new CSpan())
385))->addStyle('width: 18px');
386
387$check_all = (new CColHeader(
388	(new CCheckBox('all_items'))->onClick("checkAll('".$form->getName()."', 'all_items', 'itemids');")
389))->addStyle('width: 15px');
390
391if ($filter['showDetails']) {
392	$table->setHeader([
393		$toggle_all,
394		$check_all,
395		$singleHostSelected
396			? null
397			: make_sorting_header(_('Host'), 'host', $sortField, $sortOrder)->addStyle('width: 13%'),
398		make_sorting_header(_('Name'), 'name', $sortField, $sortOrder)
399			->addStyle('width: '.($singleHostSelected ? 34 : 21).'%'),
400		(new CColHeader(_('Interval')))->addStyle('width: 5%'),
401		(new CColHeader(_('History')))->addStyle('width: 5%'),
402		(new CColHeader(_('Trends')))->addStyle('width: 5%'),
403		(new CColHeader(_('Type')))->addStyle('width: 8%'),
404		make_sorting_header(_('Last check'), 'lastclock', $sortField, $sortOrder)->addStyle('width: 14%'),
405		(new CColHeader(_('Last value')))->addStyle('width: 14%'),
406		(new CColHeader(_x('Change', 'noun in latest data')))->addStyle('width: 10%'),
407		(new CColHeader())->addStyle('width: 5%'),
408		(new CColHeader(_('Info')))->addStyle('width: 35px')
409	]);
410}
411else {
412	$table->setHeader([
413		$toggle_all,
414		$check_all,
415		$singleHostSelected
416			? null
417			: make_sorting_header(_('Host'), 'host', $sortField, $sortOrder)->addStyle('width: 17%'),
418		make_sorting_header(_('Name'), 'name', $sortField, $sortOrder)
419			->addStyle('width: '.($singleHostSelected ? 57 : 40).'%'),
420		make_sorting_header(_('Last check'), 'lastclock', $sortField, $sortOrder)->addStyle('width: 14%'),
421		(new CColHeader(_('Last value')))->addStyle('width: 14%'),
422		(new CColHeader(_x('Change', 'noun in latest data')))->addStyle('width: 10%'),
423		(new CColHeader())->addStyle('width: 5%')
424	]);
425}
426
427$hostColumn = $singleHostSelected ? null : '';
428$tab_rows = [];
429
430$config = select_config();
431
432foreach ($items as $key => $item){
433	if (!$item['applications']) {
434		continue;
435	}
436
437	$lastHistory = isset($history[$item['itemid']][0]) ? $history[$item['itemid']][0] : null;
438	$prevHistory = isset($history[$item['itemid']][1]) ? $history[$item['itemid']][1] : null;
439
440	if (strpos($item['units'], ',') !== false) {
441		list($item['units'], $item['unitsLong']) = explode(',', $item['units']);
442	}
443	else {
444		$item['unitsLong'] = '';
445	}
446
447	// last check time and last value
448	if ($lastHistory) {
449		$lastClock = zbx_date2str(DATE_TIME_FORMAT_SECONDS, $lastHistory['clock']);
450		$lastValue = formatHistoryValue($lastHistory['value'], $item, false);
451	}
452	else {
453		$lastClock = UNKNOWN_VALUE;
454		$lastValue = UNKNOWN_VALUE;
455	}
456
457	// change
458	$digits = ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT) ? 2 : 0;
459	if ($lastHistory && $prevHistory
460			&& ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64)
461			&& (bcsub($lastHistory['value'], $prevHistory['value'], $digits) != 0)) {
462
463		$change = '';
464		if (($lastHistory['value'] - $prevHistory['value']) > 0) {
465			$change = '+';
466		}
467
468		// for 'unixtime' change should be calculated as uptime
469		$change .= convert_units([
470			'value' => bcsub($lastHistory['value'], $prevHistory['value'], $digits),
471			'units' => $item['units'] == 'unixtime' ? 'uptime' : $item['units']
472		]);
473		$change = nbsp($change);
474	}
475	else {
476		$change = UNKNOWN_VALUE;
477	}
478
479	$showLink = ((($config['hk_history_global'] && $config['hk_history'] == 0) || $item['history'] == 0)
480			&& (($config['hk_trends_global'] && $config['hk_trends'] == 0) || $item['trends'] == 0)
481	);
482
483	$checkbox = (new CCheckBox('itemids['.$item['itemid'].']', $item['itemid']))
484		->removeAttribute('id');
485
486	if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
487		$actions = $showLink
488			? UNKNOWN_VALUE
489			: new CLink(_('Graph'), 'history.php?action='.HISTORY_GRAPH.'&itemids[]='.$item['itemid']);
490	}
491	else {
492		$actions = $showLink
493			? UNKNOWN_VALUE
494			: new CLink(_('History'), 'history.php?action='.HISTORY_VALUES.'&itemids[]='.$item['itemid']);
495		$checkbox->setEnabled(false);
496	}
497
498	$state_css = ($item['state'] == ITEM_STATE_NOTSUPPORTED) ? ZBX_STYLE_GREY : null;
499
500	if ($filter['showDetails']) {
501		// item key
502		$itemKey = ($item['type'] == ITEM_TYPE_HTTPTEST || $item['flags'] == ZBX_FLAG_DISCOVERY_CREATED)
503			? (new CSpan($item['key_expanded']))->addClass(ZBX_STYLE_GREEN)
504			: (new CLink($item['key_expanded'], 'items.php?form=update&itemid='.$item['itemid']))
505				->addClass(ZBX_STYLE_LINK_ALT)
506				->addClass(ZBX_STYLE_GREEN);
507
508		// info
509		if ($item['status'] == ITEM_STATUS_ACTIVE && $item['error'] !== '') {
510			$info = makeErrorIcon($item['error']);
511		}
512		else {
513			$info = '';
514		}
515
516		// trend value
517		if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
518			$trendValue = $config['hk_trends_global'] ? $config['hk_trends'] : $item['trends'];
519		}
520		else {
521			$trendValue = UNKNOWN_VALUE;
522		}
523
524		$row = new CRow([
525			'',
526			$checkbox,
527			$hostColumn,
528			(new CCol([$item['name_expanded'], BR(), $itemKey]))->addClass($state_css),
529			(new CCol(
530				($item['type'] == ITEM_TYPE_SNMPTRAP || $item['type'] == ITEM_TYPE_TRAPPER)
531					? UNKNOWN_VALUE
532					: $item['delay']
533			))->addClass($state_css),
534			(new CCol($config['hk_history_global'] ? $config['hk_history'] : $item['history']))->addClass($state_css),
535			(new CCol($trendValue))->addClass($state_css),
536			(new CCol(item_type2str($item['type'])))->addClass($state_css),
537			(new CCol($lastClock))->addClass($state_css),
538			(new CCol($lastValue))->addClass($state_css),
539			(new CCol($change))->addClass($state_css),
540			$actions,
541			$info
542		]);
543	}
544	else {
545		$row = new CRow([
546			'',
547			$checkbox,
548			$hostColumn,
549			(new CCol($item['name_expanded']))->addClass($state_css),
550			(new CCol($lastClock))->addClass($state_css),
551			(new CCol($lastValue))->addClass($state_css),
552			(new CCol($change))->addClass($state_css),
553			$actions
554		]);
555	}
556
557	// add the item row to each application tab
558	foreach ($item['applications'] as $itemApplication) {
559		$applicationId = $itemApplication['applicationid'];
560
561		if (isset($applications[$applicationId])) {
562			$applications[$applicationId]['item_cnt']++;
563			// objects may have different properties, so it's better to use a copy of it
564			$tab_rows[$applicationId][] = clone $row;
565		}
566	}
567
568	// remove items with applications from the collection
569	unset($items[$key]);
570}
571
572foreach ($applications as $appid => $dbApp) {
573	$host = $hosts[$dbApp['hostid']];
574
575	if(!isset($tab_rows[$appid])) continue;
576
577	$appRows = $tab_rows[$appid];
578
579	$open_state = CProfile::get('web.latest.toggle', null, $dbApp['applicationid']);
580
581	$hostName = null;
582
583	if (!$singleHostSelected) {
584		$hostName = (new CSpan($host['name']))
585			->addClass(ZBX_STYLE_LINK_ACTION)
586			->setMenuPopup(CMenuPopupHelper::getHost($host, $hostScripts[$host['hostid']]));
587		if ($host['status'] == HOST_STATUS_NOT_MONITORED) {
588			$hostName->addClass(ZBX_STYLE_RED);
589		}
590	}
591
592	// add toggle row
593	$table->addRow([
594		(new CSimpleButton())
595			->addClass(ZBX_STYLE_TREEVIEW)
596			->addClass('app-list-toggle')
597			->setAttribute('data-app-id', $dbApp['applicationid'])
598			->setAttribute('data-open-state', $open_state)
599			->addItem(new CSpan()),
600		'',
601		$hostName,
602		(new CCol([bold($dbApp['name']), ' ('._n('%1$s Item', '%1$s Items', $dbApp['item_cnt']).')']))
603			->setColSpan($filter['showDetails'] ? 10 : 5)
604	]);
605
606	// add toggle sub rows
607	foreach ($appRows as $row) {
608		$row->setAttribute('parent_app_id', $dbApp['applicationid']);
609		$table->addRow($row);
610	}
611}
612
613/**
614 * Display OTHER ITEMS (which are not linked to application)
615 */
616$tab_rows = [];
617foreach ($items as $item) {
618	$lastHistory = isset($history[$item['itemid']][0]) ? $history[$item['itemid']][0] : null;
619	$prevHistory = isset($history[$item['itemid']][1]) ? $history[$item['itemid']][1] : null;
620
621	if (strpos($item['units'], ',') !== false)
622		list($item['units'], $item['unitsLong']) = explode(',', $item['units']);
623	else
624		$item['unitsLong'] = '';
625
626	// last check time and last value
627	if ($lastHistory) {
628		$lastClock = zbx_date2str(DATE_TIME_FORMAT_SECONDS, $lastHistory['clock']);
629		$lastValue = formatHistoryValue($lastHistory['value'], $item, false);
630	}
631	else {
632		$lastClock = UNKNOWN_VALUE;
633		$lastValue = UNKNOWN_VALUE;
634	}
635
636	// column "change"
637	$digits = ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT) ? 2 : 0;
638	if (isset($lastHistory['value']) && isset($prevHistory['value'])
639			&& ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64)
640			&& (bcsub($lastHistory['value'], $prevHistory['value'], $digits) != 0)) {
641
642		$change = '';
643		if (($lastHistory['value'] - $prevHistory['value']) > 0) {
644			$change = '+';
645		}
646
647		// for 'unixtime' change should be calculated as uptime
648		$change .= convert_units([
649			'value' => bcsub($lastHistory['value'], $prevHistory['value'], $digits),
650			'units' => $item['units'] == 'unixtime' ? 'uptime' : $item['units']
651		]);
652		$change = nbsp($change);
653	}
654	else {
655		$change = UNKNOWN_VALUE;
656	}
657
658	// column "action"
659	$showLink = ((($config['hk_history_global'] && $config['hk_history'] == 0) || $item['history'] == 0)
660			&& (($config['hk_trends_global'] && $config['hk_trends'] == 0) || $item['trends'] == 0)
661	);
662
663	$checkbox = (new CCheckBox('itemids['.$item['itemid'].']', $item['itemid']))
664		->removeAttribute('id');
665
666	if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
667		$actions = $showLink
668			? UNKNOWN_VALUE
669			: new CLink(_('Graph'), 'history.php?action='.HISTORY_GRAPH.'&itemids[]='.$item['itemid']);
670	}
671	else {
672		$actions = $showLink
673			? UNKNOWN_VALUE
674			: new CLink(_('History'), 'history.php?action='.HISTORY_VALUES.'&itemids[]='.$item['itemid']);
675		$checkbox->setEnabled(false);
676	}
677
678	$state_css = ($item['state'] == ITEM_STATE_NOTSUPPORTED) ? ZBX_STYLE_GREY : null;
679
680	$host = $hosts[$item['hostid']];
681	if ($filter['showDetails']) {
682		// item key
683		$itemKey = ($item['type'] == ITEM_TYPE_HTTPTEST || $item['flags'] == ZBX_FLAG_DISCOVERY_CREATED)
684			? (new CSpan($item['key_expanded']))->addClass(ZBX_STYLE_GREEN)
685			: (new CLink($item['key_expanded'], 'items.php?form=update&itemid='.$item['itemid']))
686				->addClass(ZBX_STYLE_LINK_ALT)
687				->addClass(ZBX_STYLE_GREEN);
688
689		// info
690		if ($item['status'] == ITEM_STATUS_ACTIVE && $item['error'] !== '') {
691			$info = makeErrorIcon($item['error']);
692		}
693		else {
694			$info = '';
695		}
696
697		// trend value
698		if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
699			$trendValue = $config['hk_trends_global'] ? $config['hk_trends'] : $item['trends'];
700		}
701		else {
702			$trendValue = UNKNOWN_VALUE;
703		}
704
705		$row = new CRow([
706			'',
707			$checkbox,
708			$hostColumn,
709			(new CCol([$item['name_expanded'], BR(), $itemKey]))->addClass($state_css),
710			(new CCol(
711				($item['type'] == ITEM_TYPE_SNMPTRAP || $item['type'] == ITEM_TYPE_TRAPPER)
712					? UNKNOWN_VALUE
713					: $item['delay']
714			))->addClass($state_css),
715			(new CCol($config['hk_history_global'] ? $config['hk_history'] : $item['history']))->addClass($state_css),
716			(new CCol($trendValue))->addClass($state_css),
717			(new CCol(item_type2str($item['type'])))->addClass($state_css),
718			(new CCol($lastClock))->addClass($state_css),
719			(new CCol($lastValue))->addClass($state_css),
720			(new CCol($change))->addClass($state_css),
721			$actions,
722			$info
723		]);
724	}
725	else {
726		$row = new CRow([
727			'',
728			$checkbox,
729			$hostColumn,
730			(new CCol($item['name_expanded']))->addClass($state_css),
731			(new CCol($lastClock))->addClass($state_css),
732			(new CCol($lastValue))->addClass($state_css),
733			(new CCol($change))->addClass($state_css),
734			$actions
735		]);
736	}
737
738	$hosts[$item['hostid']]['item_cnt']++;
739	$tab_rows[$item['hostid']][] = $row;
740}
741
742foreach ($hosts as $hostId => $dbHost) {
743	$host = $hosts[$dbHost['hostid']];
744
745	if(!isset($tab_rows[$hostId])) {
746		continue;
747	}
748	$appRows = $tab_rows[$hostId];
749
750	$open_state = CProfile::get('web.latest.toggle_other', null, $host['hostid']);
751
752	$hostName = null;
753
754	if (!$singleHostSelected) {
755		$hostName = (new CSpan($host['name']))
756			->addClass(ZBX_STYLE_LINK_ACTION)
757			->setMenuPopup(CMenuPopupHelper::getHost($host, $hostScripts[$host['hostid']]));
758		if ($host['status'] == HOST_STATUS_NOT_MONITORED) {
759			$hostName->addClass(ZBX_STYLE_RED);
760		}
761	}
762
763	// add toggle row
764	$table->addRow([
765		(new CSimpleButton())
766			->addClass(ZBX_STYLE_TREEVIEW)
767			->addClass('app-list-toggle')
768			->setAttribute('data-host-id', $host['hostid'])
769			->setAttribute('data-open-state', $open_state)
770			->addItem(new CSpan()),
771		'',
772		$hostName,
773		(new CCol([bold('- '.('other').' -'), ' ('._n('%1$s Item', '%1$s Items', $dbHost['item_cnt']).')']))
774			->setColSpan($filter['showDetails'] ? 10 : 5)
775	]);
776
777	// add toggle sub rows
778	foreach($appRows as $row) {
779		$row->setAttribute('parent_host_id', $host['hostid']);
780		$table->addRow($row);
781	}
782}
783
784$form->addItem([
785	$table,
786	new CActionButtonList('graphtype', 'itemids', [
787		GRAPH_TYPE_STACKED => ['name' => _('Display stacked graph')],
788		GRAPH_TYPE_NORMAL => ['name' => _('Display graph')]
789	])
790]);
791
792$widget->addItem($form)->show();
793
794require_once dirname(__FILE__).'/include/page_footer.php';
795