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 22class CScreenHistory extends CScreenBase { 23 24 /** 25 * Type of graph to display. 26 * 27 * Supported values: 28 * - GRAPH_TYPE_NORMAL 29 * - GRAPH_TYPE_STACKED 30 * 31 * @var int 32 */ 33 protected $graphType; 34 35 /** 36 * Search string 37 * 38 * @var string 39 */ 40 public $filter; 41 42 /** 43 * Filter show/hide 44 * 45 * @var int 46 */ 47 public $filterTask; 48 49 /** 50 * Filter highlight color 51 * 52 * @var string 53 */ 54 public $markColor; 55 56 /** 57 * Is plain text displayed 58 * 59 * @var boolean 60 */ 61 public $plaintext; 62 63 /** 64 * Items ids. 65 * 66 * @var array 67 */ 68 public $itemids; 69 70 /** 71 * Graph id. 72 * 73 * @var int 74 */ 75 public $graphid = 0; 76 77 /** 78 * String containing base URL for pager. 79 * 80 * @var string 81 */ 82 public $page_file; 83 84 /** 85 * Init screen data. 86 * 87 * @param array $options 88 * @param string $options['filter'] 89 * @param int $options['filterTask'] 90 * @param int $options['markColor'] 91 * @param boolean $options['plaintext'] 92 * @param array $options['itemids'] 93 * @param array $options['graphid'] When set defines graph id where item. 94 * @param string $options['pageFile'] Current page file, is used for pagination links. 95 */ 96 public function __construct(array $options = []) { 97 parent::__construct($options); 98 99 $this->resourcetype = SCREEN_RESOURCE_HISTORY; 100 101 // mandatory 102 $this->filter = isset($options['filter']) ? $options['filter'] : ''; 103 $this->filterTask = isset($options['filter_task']) ? $options['filter_task'] : null; 104 $this->markColor = isset($options['mark_color']) ? $options['mark_color'] : MARK_COLOR_RED; 105 $this->graphType = isset($options['graphtype']) ? $options['graphtype'] : GRAPH_TYPE_NORMAL; 106 107 // optional 108 $this->itemids = array_key_exists('itemids', $options) ? $options['itemids'] : []; 109 $this->plaintext = isset($options['plaintext']) ? $options['plaintext'] : false; 110 $this->page_file = array_key_exists('pageFile', $options) ? $options['pageFile'] : null; 111 112 if (!$this->itemids && array_key_exists('graphid', $options)) { 113 $itemids = API::Item()->get([ 114 'output' => ['itemid'], 115 'graphids' => [$options['graphid']] 116 ]); 117 $this->itemids = zbx_objectValues($itemids, 'itemid'); 118 $this->graphid = $options['graphid']; 119 } 120 } 121 122 /** 123 * Process screen. 124 * 125 * @return CDiv (screen inside container) 126 */ 127 public function get() { 128 $output = []; 129 130 $items = API::Item()->get([ 131 'output' => ['itemid', 'hostid', 'name', 'key_', 'value_type', 'history', 'trends'], 132 'selectHosts' => ['name'], 133 'selectValueMap' => ['mappings'], 134 'itemids' => $this->itemids, 135 'webitems' => true, 136 'preservekeys' => true 137 ]); 138 139 if (!$items) { 140 show_error_message(_('No permissions to referred object or it does not exist!')); 141 142 return; 143 } 144 145 $items = CMacrosResolverHelper::resolveItemNames($items); 146 147 $iv_string = [ 148 ITEM_VALUE_TYPE_LOG => 1, 149 ITEM_VALUE_TYPE_TEXT => 1 150 ]; 151 152 if ($this->action == HISTORY_VALUES || $this->action == HISTORY_LATEST) { 153 $options = [ 154 'output' => API_OUTPUT_EXTEND, 155 'sortfield' => ['clock'], 156 'sortorder' => ZBX_SORT_DOWN 157 ]; 158 159 if ($this->action == HISTORY_LATEST) { 160 $options['limit'] = 500; 161 } 162 else { 163 $options += [ 164 'time_from' => $this->timeline['from_ts'], 165 'time_till' => $this->timeline['to_ts'], 166 'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) 167 ]; 168 } 169 170 $is_many_items = (count($items) > 1); 171 $numeric_items = true; 172 173 foreach ($items as $item) { 174 $numeric_items = ($numeric_items && !array_key_exists($item['value_type'], $iv_string)); 175 if (!$numeric_items) { 176 break; 177 } 178 } 179 180 /** 181 * View type: As plain text. 182 * Item type: numeric (unsigned, char), float, text, log. 183 */ 184 if ($this->plaintext) { 185 if (!$numeric_items && $this->filter !== '' 186 && in_array($this->filterTask, [FILTER_TASK_SHOW, FILTER_TASK_HIDE])) { 187 $options['search'] = ['value' => $this->filter]; 188 189 if ($this->filterTask == FILTER_TASK_HIDE) { 190 $options['excludeSearch'] = true; 191 } 192 } 193 194 $history_data = []; 195 $items_by_type = []; 196 197 foreach ($items as $item) { 198 $items_by_type[$item['value_type']][] = $item['itemid']; 199 } 200 201 foreach ($items_by_type as $value_type => $itemids) { 202 $options['history'] = $value_type; 203 $options['itemids'] = $itemids; 204 205 $item_data = API::History()->get($options); 206 207 if ($item_data) { 208 $history_data = array_merge($history_data, $item_data); 209 } 210 } 211 212 CArrayHelper::sort($history_data, [ 213 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 214 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 215 ]); 216 217 $history_data = array_slice($history_data, 0, $options['limit']); 218 219 foreach ($history_data as $history_row) { 220 $value = $history_row['value']; 221 222 if (in_array($items[$history_row['itemid']]['value_type'], 223 [ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])) { 224 $value = '"'.$value.'"'; 225 } 226 elseif ($items[$history_row['itemid']]['value_type'] == ITEM_VALUE_TYPE_FLOAT) { 227 $value = formatFloat($value, null, ZBX_UNITS_ROUNDOFF_UNSUFFIXED); 228 } 229 230 $row = zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_row['clock']).' '.$history_row['clock']. 231 ' '.$value; 232 233 if ($is_many_items) { 234 $row .= ' "'.$items[$history_row['itemid']]['hosts'][0]['name'].NAME_DELIMITER. 235 $items[$history_row['itemid']]['name_expanded'].'"'; 236 } 237 $output[] = $row; 238 } 239 240 // Return values as array of formatted strings. 241 return $output; 242 } 243 /** 244 * View type: Values, 500 latest values 245 * Item type: text, log 246 */ 247 elseif (!$numeric_items) { 248 $use_log_item = false; 249 $use_eventlog_item = false; 250 $items_by_type = []; 251 $history_data = []; 252 253 foreach ($items as $item) { 254 $items_by_type[$item['value_type']][] = $item['itemid']; 255 256 if ($item['value_type'] == ITEM_VALUE_TYPE_LOG) { 257 $use_log_item = true; 258 } 259 260 if (strpos($item['key_'], 'eventlog[') === 0) { 261 $use_eventlog_item = true; 262 } 263 } 264 265 $history_table = (new CTableInfo()) 266 ->setHeader([ 267 (new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH), 268 $is_many_items ? _('Item') : null, 269 $use_log_item ? (new CColHeader(_('Local time')))->addClass(ZBX_STYLE_CELL_WIDTH) : null, 270 ($use_eventlog_item && $use_log_item) 271 ? (new CColHeader(_('Source')))->addClass(ZBX_STYLE_CELL_WIDTH) 272 : null, 273 ($use_eventlog_item && $use_log_item) 274 ? (new CColHeader(_('Severity')))->addClass(ZBX_STYLE_CELL_WIDTH) 275 : null, 276 ($use_eventlog_item && $use_log_item) 277 ? (new CColHeader(_('Event ID')))->addClass(ZBX_STYLE_CELL_WIDTH) 278 : null, 279 _('Value') 280 ]); 281 282 if ($this->filter !== '' && in_array($this->filterTask, [FILTER_TASK_SHOW, FILTER_TASK_HIDE])) { 283 $options['search'] = ['value' => $this->filter]; 284 if ($this->filterTask == FILTER_TASK_HIDE) { 285 $options['excludeSearch'] = true; 286 } 287 } 288 289 foreach ($items_by_type as $value_type => $itemids) { 290 $options['history'] = $value_type; 291 $options['itemids'] = $itemids; 292 $item_data = API::History()->get($options); 293 294 if ($item_data) { 295 $history_data = array_merge($history_data, $item_data); 296 } 297 } 298 299 CArrayHelper::sort($history_data, [ 300 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 301 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 302 ]); 303 304 // Array $history_data will be modified according page and rows on page. 305 $pagination = CPagerHelper::paginate($this->page, $history_data, ZBX_SORT_UP, 306 new CUrl($this->page_file) 307 ); 308 309 foreach ($history_data as $data) { 310 $data['value'] = rtrim($data['value'], " \t\r\n"); 311 312 $item = $items[$data['itemid']]; 313 $host = reset($item['hosts']); 314 $color = null; 315 316 if ($this->filter !== '') { 317 $haystack = mb_strtolower($data['value']); 318 $needle = mb_strtolower($this->filter); 319 $pos = mb_strpos($haystack, $needle); 320 321 if ($pos !== false && $this->filterTask == FILTER_TASK_MARK) { 322 $color = $this->markColor; 323 } 324 elseif ($pos === false && $this->filterTask == FILTER_TASK_INVERT_MARK) { 325 $color = $this->markColor; 326 } 327 328 switch ($color) { 329 case MARK_COLOR_RED: 330 $color = ZBX_STYLE_RED; 331 break; 332 case MARK_COLOR_GREEN: 333 $color = ZBX_STYLE_GREEN; 334 break; 335 case MARK_COLOR_BLUE: 336 $color = ZBX_STYLE_BLUE; 337 break; 338 } 339 } 340 341 $row = []; 342 343 $row[] = (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $data['clock']))) 344 ->addClass(ZBX_STYLE_NOWRAP) 345 ->addClass($color); 346 347 if ($is_many_items) { 348 $row[] = (new CCol($host['name'].NAME_DELIMITER.$item['name_expanded'])) 349 ->addClass($color); 350 } 351 352 if ($use_log_item) { 353 $row[] = (array_key_exists('timestamp', $data) && $data['timestamp'] != 0) 354 ? (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $data['timestamp']))) 355 ->addClass(ZBX_STYLE_NOWRAP) 356 ->addClass($color) 357 : ''; 358 359 // If this is a eventLog item, showing additional info. 360 if ($use_eventlog_item) { 361 $row[] = array_key_exists('source', $data) 362 ? (new CCol($data['source'])) 363 ->addClass(ZBX_STYLE_NOWRAP) 364 ->addClass($color) 365 : ''; 366 $row[] = (array_key_exists('severity', $data) && $data['severity'] != 0) 367 ? (new CCol(get_item_logtype_description($data['severity']))) 368 ->addClass(ZBX_STYLE_NOWRAP) 369 ->addClass(get_item_logtype_style($data['severity'])) 370 : ''; 371 $row[] = array_key_exists('severity', $data) 372 ? (new CCol($data['logeventid'])) 373 ->addClass(ZBX_STYLE_NOWRAP) 374 ->addClass($color) 375 : ''; 376 } 377 } 378 379 $row[] = (new CCol(new CPre(zbx_nl2br($data['value']))))->addClass($color); 380 381 $history_table->addRow($row); 382 } 383 384 $output[] = [$history_table, $pagination]; 385 } 386 /** 387 * View type: 500 latest values. 388 * Item type: numeric (unsigned, char), float. 389 */ 390 elseif ($this->action === HISTORY_LATEST) { 391 $history_table = (new CTableInfo()) 392 ->makeVerticalRotation() 393 ->setHeader([(new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH), _('Value')]); 394 395 $items_by_type = []; 396 $history_data = []; 397 398 foreach ($items as $item) { 399 $items_by_type[$item['value_type']][] = $item['itemid']; 400 } 401 402 foreach ($items_by_type as $value_type => $itemids) { 403 $options['history'] = $value_type; 404 $options['itemids'] = $itemids; 405 $item_data = API::History()->get($options); 406 407 if ($item_data) { 408 $history_data = array_merge($history_data, $item_data); 409 } 410 } 411 412 CArrayHelper::sort($history_data, [ 413 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 414 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 415 ]); 416 417 $history_data = array_slice($history_data, 0, $options['limit']); 418 419 foreach ($history_data as $history_row) { 420 $item = $items[$history_row['itemid']]; 421 $value = $history_row['value']; 422 423 if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT) { 424 $value = formatFloat($value, null, ZBX_UNITS_ROUNDOFF_UNSUFFIXED); 425 } 426 427 $value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']); 428 429 $history_table->addRow([ 430 (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_row['clock']))) 431 ->addClass(ZBX_STYLE_NOWRAP), 432 new CPre(zbx_nl2br($value)) 433 ]); 434 } 435 436 $output[] = $history_table; 437 } 438 /** 439 * View type: Values. 440 * Item type: numeric (unsigned, char), float. 441 */ 442 else { 443 CArrayHelper::sort($items, [ 444 ['field' => 'name_expanded', 'order' => ZBX_SORT_UP] 445 ]); 446 $table_header = [(new CColHeader(_('Timestamp')))->addClass(ZBX_STYLE_CELL_WIDTH)]; 447 $history_data = []; 448 449 foreach ($items as $item) { 450 $options['itemids'] = [$item['itemid']]; 451 $options['history'] = $item['value_type']; 452 $item_data = API::History()->get($options); 453 454 CArrayHelper::sort($item_data, [ 455 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 456 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 457 ]); 458 459 $table_header[] = (new CColHeader($item['name_expanded'])) 460 ->addClass('vertical_rotation') 461 ->setTitle($item['name_expanded']); 462 $history_data_index = 0; 463 464 foreach ($item_data as $item_data_row) { 465 // Searching for starting 'insert before' index in results array. 466 while (array_key_exists($history_data_index, $history_data)) { 467 $history_row = $history_data[$history_data_index]; 468 469 if ($history_row['clock'] <= $item_data_row['clock'] 470 && !array_key_exists($item['itemid'], $history_row['values'])) { 471 break; 472 } 473 474 ++$history_data_index; 475 } 476 477 if (array_key_exists($history_data_index, $history_data) 478 && !array_key_exists($item['itemid'], $history_row['values']) 479 && $history_data[$history_data_index]['clock'] === $item_data_row['clock']) { 480 $history_data[$history_data_index]['values'][$item['itemid']] = $item_data_row['value']; 481 } 482 else { 483 array_splice($history_data, $history_data_index, 0, [[ 484 'clock' => $item_data_row['clock'], 485 'values' => [$item['itemid'] => $item_data_row['value']] 486 ]]); 487 } 488 } 489 } 490 491 // Array $history_data will be modified according page and rows on page. 492 $pagination = CPagerHelper::paginate($this->page, $history_data, ZBX_SORT_UP, 493 new CUrl($this->page_file) 494 ); 495 496 $history_table = (new CTableInfo())->makeVerticalRotation()->setHeader($table_header); 497 498 foreach ($history_data as $history_data_row) { 499 $row = [(new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_data_row['clock']))) 500 ->addClass(ZBX_STYLE_NOWRAP) 501 ]; 502 $values = $history_data_row['values']; 503 504 foreach ($items as $item) { 505 $value = array_key_exists($item['itemid'], $values) ? $values[$item['itemid']] : ''; 506 507 if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT && $value !== '') { 508 $value = formatFloat($value, null, ZBX_UNITS_ROUNDOFF_UNSUFFIXED); 509 } 510 511 $value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']); 512 513 $row[] = ($value === '') ? '' : new CPre($value); 514 } 515 516 $history_table->addRow($row); 517 } 518 519 $output[] = [$history_table, $pagination]; 520 } 521 } 522 523 // time control 524 if (str_in_array($this->action, [HISTORY_VALUES, HISTORY_GRAPH, HISTORY_BATCH_GRAPH])) { 525 $graphDims = getGraphDims(); 526 527 $this->dataId = 'historyGraph'; 528 529 $timeControlData = []; 530 531 if ($this->action == HISTORY_GRAPH || $this->action == HISTORY_BATCH_GRAPH) { 532 $containerId = 'graph_cont1'; 533 $output[] = (new CDiv()) 534 ->addClass('center') 535 ->setId($containerId); 536 537 $timeControlData['id'] = $this->getDataId(); 538 $timeControlData['containerid'] = $containerId; 539 $timeControlData['src'] = $this->getGraphUrl($this->itemids); 540 $timeControlData['objDims'] = $graphDims; 541 $timeControlData['loadSBox'] = 1; 542 $timeControlData['loadImage'] = 1; 543 $timeControlData['dynamic'] = 1; 544 } 545 else { 546 $timeControlData['id'] = $this->getDataId(); 547 } 548 549 if ($this->mode == SCREEN_MODE_JS) { 550 $timeControlData['dynamic'] = 0; 551 552 return 'timeControl.addObject("'.$this->getDataId().'", '.json_encode($this->timeline).', '. 553 json_encode($timeControlData).');'; 554 } 555 556 zbx_add_post_js('timeControl.addObject("'.$this->getDataId().'", '.json_encode($this->timeline).', '. 557 json_encode($timeControlData).');' 558 ); 559 } 560 561 if ($this->mode != SCREEN_MODE_JS) { 562 $flickerfreeData = [ 563 'itemids' => $this->itemids, 564 'action' => ($this->action == HISTORY_BATCH_GRAPH) ? HISTORY_GRAPH : $this->action, 565 'filter' => $this->filter, 566 'filterTask' => $this->filterTask, 567 'markColor' => $this->markColor 568 ]; 569 570 if ($this->action == HISTORY_VALUES) { 571 $flickerfreeData['page'] = $this->page; 572 } 573 574 if ($this->graphid != 0) { 575 unset($flickerfreeData['itemids']); 576 $flickerfreeData['graphid'] = $this->graphid; 577 } 578 579 return $this->getOutput($output, true, $flickerfreeData); 580 } 581 582 return $output; 583 } 584 585 /** 586 * Return the URL for the graph. 587 * 588 * @param array $itemIds 589 * 590 * @return string 591 */ 592 protected function getGraphUrl(array $itemIds) { 593 $url = (new CUrl('chart.php')) 594 ->setArgument('from', $this->timeline['from']) 595 ->setArgument('to', $this->timeline['to']) 596 ->setArgument('itemids', $itemIds) 597 ->setArgument('type', $this->graphType) 598 ->setArgument('profileIdx', $this->profileIdx) 599 ->setArgument('profileIdx2', $this->profileIdx2); 600 601 if ($this->action == HISTORY_BATCH_GRAPH) { 602 $url->setArgument('batch', 1); 603 } 604 605 return $url->getUrl(); 606 } 607} 608