You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

616 lines
17 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
class CScreenHistory extends CScreenBase {
/**
* Type of graph to display.
*
* Supported values:
* - GRAPH_TYPE_NORMAL
* - GRAPH_TYPE_STACKED
*
* @var int
*/
protected $graphType;
/**
* Search string
*
* @var string
*/
public $filter;
/**
* Filter show/hide
*
* @var int
*/
public $filterTask;
/**
* Filter highlight color
*
* @var string
*/
public $markColor;
/**
* Is plain text displayed
*
* @var boolean
*/
public $plaintext;
/**
* Items ids.
*
* @var array
*/
public $itemids;
/**
* Graph id.
*
* @var int
*/
public $graphid = 0;
/**
* String containing base URL for pager.
*
* @var string
*/
public $page_file;
/**
* Init screen data.
*
* @param array $options
* @param string $options['filter']
* @param int $options['filterTask']
* @param int $options['markColor']
* @param boolean $options['plaintext']
* @param array $options['itemids']
* @param array $options['graphid'] When set defines graph id where item.
* @param string $options['pageFile'] Current page file, is used for pagination links.
*/
public function __construct(array $options = []) {
parent::__construct($options);
$this->resourcetype = SCREEN_RESOURCE_HISTORY;
// mandatory
$this->filter = isset($options['filter']) ? $options['filter'] : '';
$this->filterTask = isset($options['filter_task']) ? $options['filter_task'] : null;
$this->markColor = isset($options['mark_color']) ? $options['mark_color'] : MARK_COLOR_RED;
$this->graphType = isset($options['graphtype']) ? $options['graphtype'] : GRAPH_TYPE_NORMAL;
// optional
$this->itemids = array_key_exists('itemids', $options) ? $options['itemids'] : [];
$this->plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
$this->page_file = array_key_exists('pageFile', $options) ? $options['pageFile'] : null;
if (!$this->itemids && array_key_exists('graphid', $options)) {
$itemids = API::Item()->get([
'output' => ['itemid'],
'graphids' => [$options['graphid']]
]);
$this->itemids = zbx_objectValues($itemids, 'itemid');
$this->graphid = $options['graphid'];
}
}
/**
* Process screen.
*
* @return CDiv (screen inside container)
*/
public function get() {
$output = [];
$items = API::Item()->get([
'output' => ['itemid', 'name', 'key_', 'value_type'],
'selectHosts' => ['name'],
'selectValueMap' => ['mappings'],
'itemids' => $this->itemids,
'webitems' => true,
'preservekeys' => true
]);
if (!$items) {
show_error_message(_('No permissions to referred object or it does not exist!'));
return;
}
$iv_string = [
ITEM_VALUE_TYPE_LOG => 1,
ITEM_VALUE_TYPE_TEXT => 1
];
if ($this->action == HISTORY_VALUES || $this->action == HISTORY_LATEST) {
$options = [
'output' => API_OUTPUT_EXTEND,
'sortfield' => ['clock'],
'sortorder' => ZBX_SORT_DOWN
];
if ($this->action == HISTORY_LATEST) {
$options['limit'] = 500;
}
else {
$options += [
'time_from' => $this->timeline['from_ts'],
'time_till' => $this->timeline['to_ts'],
'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT)
];
}
$is_many_items = (count($items) > 1);
$numeric_items = true;
foreach ($items as $item) {
$numeric_items = ($numeric_items && !array_key_exists($item['value_type'], $iv_string));
if (!$numeric_items) {
break;
}
}
/**
* View type: As plain text.
* Item type: numeric (unsigned, char), float, text, log.
*/
if ($this->plaintext) {
if (!$numeric_items && $this->filter !== ''
&& in_array($this->filterTask, [FILTER_TASK_SHOW, FILTER_TASK_HIDE])) {
$options['search'] = ['value' => $this->filter];
if ($this->filterTask == FILTER_TASK_HIDE) {
$options['excludeSearch'] = true;
}
}
$history_data = [];
$items_by_type = [];
foreach ($items as $item) {
$items_by_type[$item['value_type']][] = $item['itemid'];
}
foreach ($items_by_type as $value_type => $itemids) {
$options['history'] = $value_type;
$options['itemids'] = $itemids;
$item_data = API::History()->get($options);
if ($item_data) {
$history_data = array_merge($history_data, $item_data);
}
}
CArrayHelper::sort($history_data, [
['field' => 'clock', 'order' => ZBX_SORT_DOWN],
['field' => 'ns', 'order' => ZBX_SORT_DOWN]
]);
$history_data = array_slice($history_data, 0, $options['limit']);
foreach ($history_data as $history_row) {
$value = $history_row['value'];
if (in_array($items[$history_row['itemid']]['value_type'],
[ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])) {
$value = '"'.$value.'"';
}
elseif ($items[$history_row['itemid']]['value_type'] == ITEM_VALUE_TYPE_FLOAT) {
$value = formatFloat($value, ['decimals' => ZBX_UNITS_ROUNDOFF_UNSUFFIXED]);
}
$row = zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_row['clock']).' '.$history_row['clock'].
' '.$value;
if ($is_many_items) {
$row .= ' "'.$items[$history_row['itemid']]['hosts'][0]['name'].NAME_DELIMITER.
$items[$history_row['itemid']]['name'].'"';
}
$output[] = $row;
}
// Return values as array of formatted strings.
return $output;
}
/**
* View type: Values, 500 latest values
* Item type: text, log
*/
elseif (!$numeric_items) {
$use_log_item = false;
$use_eventlog_item = false;
$items_by_type = [];
$history_data = [];
foreach ($items as $item) {
$items_by_type[$item['value_type']][] = $item['itemid'];
if ($item['value_type'] == ITEM_VALUE_TYPE_LOG) {
$use_log_item = true;
}
if (strpos($item['key_'], 'eventlog[') === 0) {
$use_eventlog_item = true;
}
}
$history_table = (new CTableInfo())
->setHeader([
(new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH),
$is_many_items ? _('Item') : null,
$use_log_item ? (new CColHeader(_('Local time')))->addClass(ZBX_STYLE_CELL_WIDTH) : null,
($use_eventlog_item && $use_log_item)
? (new CColHeader(_('Source')))->addClass(ZBX_STYLE_CELL_WIDTH)
: null,
($use_eventlog_item && $use_log_item)
? (new CColHeader(_('Severity')))->addClass(ZBX_STYLE_CELL_WIDTH)
: null,
($use_eventlog_item && $use_log_item)
? (new CColHeader(_('Event ID')))->addClass(ZBX_STYLE_CELL_WIDTH)
: null,
_('Value')
]);
if ($this->filter !== '' && in_array($this->filterTask, [FILTER_TASK_SHOW, FILTER_TASK_HIDE])) {
$options['search'] = ['value' => $this->filter];
if ($this->filterTask == FILTER_TASK_HIDE) {
$options['excludeSearch'] = true;
}
}
foreach ($items_by_type as $value_type => $itemids) {
$options['history'] = $value_type;
$options['itemids'] = $itemids;
$item_data = API::History()->get($options);
if ($item_data) {
$history_data = array_merge($history_data, $item_data);
}
}
CArrayHelper::sort($history_data, [
['field' => 'clock', 'order' => ZBX_SORT_DOWN],
['field' => 'ns', 'order' => ZBX_SORT_DOWN]
]);
// Array $history_data will be modified according page and rows on page.
$pagination = CPagerHelper::paginate($this->page, $history_data, ZBX_SORT_UP,
new CUrl($this->page_file)
);
foreach ($history_data as $data) {
if ($value_type == ITEM_VALUE_TYPE_BINARY) {
$data['value'] = italic(_('binary value'))->addClass(ZBX_STYLE_GREY);
}
else {
$data['value'] = rtrim($data['value'], " \t\r\n");
$data['value'] = zbx_nl2br($data['value']);
}
$item = $items[$data['itemid']];
$host = reset($item['hosts']);
$color = null;
if ($this->filter !== '' && $value_type != ITEM_VALUE_TYPE_BINARY) {
$haystack = mb_strtolower($data['value']);
$needle = mb_strtolower($this->filter);
$pos = mb_strpos($haystack, $needle);
if ($pos !== false && $this->filterTask == FILTER_TASK_MARK) {
$color = $this->markColor;
}
elseif ($pos === false && $this->filterTask == FILTER_TASK_INVERT_MARK) {
$color = $this->markColor;
}
switch ($color) {
case MARK_COLOR_RED:
$color = ZBX_STYLE_RED;
break;
case MARK_COLOR_GREEN:
$color = ZBX_STYLE_GREEN;
break;
case MARK_COLOR_BLUE:
$color = ZBX_STYLE_BLUE;
break;
}
}
$row = [];
$row[] = (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $data['clock'])))
->addClass(ZBX_STYLE_NOWRAP)
->addClass($color);
if ($is_many_items) {
$row[] = (new CCol($host['name'].NAME_DELIMITER.$item['name']))
->addClass($color);
}
if ($use_log_item) {
$row[] = (array_key_exists('timestamp', $data) && $data['timestamp'] != 0)
? (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $data['timestamp'])))
->addClass(ZBX_STYLE_NOWRAP)
->addClass($color)
: '';
// If this is a eventLog item, showing additional info.
if ($use_eventlog_item) {
$row[] = array_key_exists('source', $data)
? (new CCol($data['source']))
->addClass(ZBX_STYLE_NOWRAP)
->addClass($color)
: '';
$row[] = (array_key_exists('severity', $data) && $data['severity'] != 0)
? (new CCol(get_item_logtype_description($data['severity'])))
->addClass(ZBX_STYLE_NOWRAP)
->addClass(get_item_logtype_style($data['severity']))
: '';
$row[] = array_key_exists('severity', $data)
? (new CCol($data['logeventid']))
->addClass(ZBX_STYLE_NOWRAP)
->addClass($color)
: '';
}
}
$row[] = (new CCol(new CPre($data['value'])))->addClass($color);
$history_table->addRow($row);
}
$output[] = [$history_table, $pagination];
}
/**
* View type: 500 latest values.
* Item type: numeric (unsigned, char), float.
*/
elseif ($this->action === HISTORY_LATEST) {
$history_table = (new CTableInfo())
->makeVerticalRotation()
->setHeader([(new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH), _('Value')]);
$items_by_type = [];
$history_data = [];
foreach ($items as $item) {
$items_by_type[$item['value_type']][] = $item['itemid'];
}
foreach ($items_by_type as $value_type => $itemids) {
$options['history'] = $value_type;
$options['itemids'] = $itemids;
$item_data = API::History()->get($options);
if ($item_data) {
$history_data = array_merge($history_data, $item_data);
}
}
CArrayHelper::sort($history_data, [
['field' => 'clock', 'order' => ZBX_SORT_DOWN],
['field' => 'ns', 'order' => ZBX_SORT_DOWN]
]);
$history_data = array_slice($history_data, 0, $options['limit']);
foreach ($history_data as $history_row) {
$item = $items[$history_row['itemid']];
$value = $history_row['value'];
if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT) {
$value = formatFloat($value, ['decimals' => ZBX_UNITS_ROUNDOFF_UNSUFFIXED]);
}
$value = $item['value_type'] == ITEM_VALUE_TYPE_BINARY
? italic(_('binary value'))->addClass(ZBX_STYLE_GREY)
: zbx_nl2br(CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']));
$history_table->addRow([
(new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_row['clock'])))
->addClass(ZBX_STYLE_NOWRAP),
new CPre($value)
]);
}
$output[] = $history_table;
}
/**
* View type: Values.
* Item type: numeric (unsigned, char), float.
*/
else {
CArrayHelper::sort($items, [
['field' => 'name', 'order' => ZBX_SORT_UP]
]);
$table_header = [(new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH)];
$history_data = [];
foreach ($items as $item) {
$options['itemids'] = [$item['itemid']];
$options['history'] = $item['value_type'];
$item_data = API::History()->get($options);
CArrayHelper::sort($item_data, [
['field' => 'clock', 'order' => ZBX_SORT_DOWN],
['field' => 'ns', 'order' => ZBX_SORT_DOWN]
]);
$table_header[] = (new CColHeader($item['name']))
->addClass('vertical_rotation')
->setTitle($item['name']);
$history_data_index = 0;
foreach ($item_data as $item_data_row) {
// Searching for starting 'insert before' index in results array.
while (array_key_exists($history_data_index, $history_data)) {
$history_row = $history_data[$history_data_index];
if ($history_row['clock'] <= $item_data_row['clock']
&& !array_key_exists($item['itemid'], $history_row['values'])) {
break;
}
++$history_data_index;
}
if (array_key_exists($history_data_index, $history_data)
&& !array_key_exists($item['itemid'], $history_row['values'])
&& $history_data[$history_data_index]['clock'] === $item_data_row['clock']) {
$history_data[$history_data_index]['values'][$item['itemid']] = $item_data_row['value'];
}
else {
array_splice($history_data, $history_data_index, 0, [[
'clock' => $item_data_row['clock'],
'values' => [$item['itemid'] => $item_data_row['value']]
]]);
}
}
}
// Array $history_data will be modified according page and rows on page.
$pagination = CPagerHelper::paginate($this->page, $history_data, ZBX_SORT_UP,
new CUrl($this->page_file)
);
$history_table = (new CTableInfo())->makeVerticalRotation()->setHeader($table_header);
foreach ($history_data as $history_data_row) {
$row = [(new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_data_row['clock'])))
->addClass(ZBX_STYLE_NOWRAP)
];
$values = $history_data_row['values'];
foreach ($items as $item) {
$value = array_key_exists($item['itemid'], $values) ? $values[$item['itemid']] : '';
if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT && $value !== '') {
$value = formatFloat($value, ['decimals' => ZBX_UNITS_ROUNDOFF_UNSUFFIXED]);
}
$value = $item['value_type'] == ITEM_VALUE_TYPE_BINARY
? italic(_('binary value'))->addClass(ZBX_STYLE_GREY)
: CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']);
$row[] = ($value === '') ? '' : new CPre($value);
}
$history_table->addRow($row);
}
$output[] = [$history_table, $pagination];
}
}
// time control
if (str_in_array($this->action, [HISTORY_VALUES, HISTORY_GRAPH, HISTORY_BATCH_GRAPH])) {
$graphDims = getGraphDims();
$this->dataId = 'historyGraph';
$timeControlData = [];
if ($this->action == HISTORY_GRAPH || $this->action == HISTORY_BATCH_GRAPH) {
$containerId = 'graph_cont1';
$output[] = (new CDiv())
->addClass('center')
->setId($containerId);
$timeControlData['id'] = $this->getDataId();
$timeControlData['containerid'] = $containerId;
$timeControlData['src'] = $this->getGraphUrl($this->itemids);
$timeControlData['objDims'] = $graphDims;
$timeControlData['loadSBox'] = 1;
$timeControlData['loadImage'] = 1;
$timeControlData['dynamic'] = 1;
}
else {
$timeControlData['id'] = $this->getDataId();
}
if ($this->mode == SCREEN_MODE_JS) {
$timeControlData['dynamic'] = 0;
return 'timeControl.addObject("'.$this->getDataId().'", '.json_encode($this->timeline).', '.
json_encode($timeControlData).');';
}
zbx_add_post_js('timeControl.addObject("'.$this->getDataId().'", '.json_encode($this->timeline).', '.
json_encode($timeControlData).');'
);
}
if ($this->mode != SCREEN_MODE_JS) {
$flickerfreeData = [
'itemids' => $this->itemids,
'action' => ($this->action == HISTORY_BATCH_GRAPH) ? HISTORY_GRAPH : $this->action,
'filter' => $this->filter,
'filterTask' => $this->filterTask,
'markColor' => $this->markColor
];
if ($this->action == HISTORY_VALUES) {
$flickerfreeData['page'] = $this->page;
}
if ($this->graphid != 0) {
unset($flickerfreeData['itemids']);
$flickerfreeData['graphid'] = $this->graphid;
}
return $this->getOutput($output, true, $flickerfreeData);
}
return $output;
}
/**
* Return the URL for the graph.
*
* @param array $itemIds
*
* @return string
*/
protected function getGraphUrl(array $itemIds) {
$url = (new CUrl('chart.php'))
->setArgument('from', $this->timeline['from'])
->setArgument('to', $this->timeline['to'])
->setArgument('itemids', $itemIds)
->setArgument('type', $this->graphType)
->setArgument('profileIdx', $this->profileIdx)
->setArgument('profileIdx2', $this->profileIdx2);
if ($this->action == HISTORY_BATCH_GRAPH) {
$url->setArgument('batch', 1);
}
return $url->getUrl();
}
}