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.
515 lines
19 KiB
515 lines
19 KiB
<?php declare(strict_types = 0);
|
|
/*
|
|
** 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.
|
|
**/
|
|
|
|
|
|
namespace Widgets\Problems\Includes;
|
|
|
|
use CButtonIcon,
|
|
CCol,
|
|
CColHeader,
|
|
CHintBoxHelper,
|
|
CIcon,
|
|
CLink,
|
|
CLinkAction,
|
|
CMacrosResolverHelper,
|
|
CMenuPopupHelper,
|
|
CRow,
|
|
CScreenProblem,
|
|
CSeverityHelper,
|
|
CSpan,
|
|
CTableInfo,
|
|
CUrl;
|
|
|
|
class WidgetProblems extends CTableInfo {
|
|
private array $data;
|
|
|
|
public function __construct(array $data) {
|
|
$this->data = $data;
|
|
|
|
parent::__construct();
|
|
}
|
|
|
|
private function build(): void {
|
|
$sort_div = (new CSpan())->addClass(
|
|
($this->data['sortorder'] === ZBX_SORT_DOWN) ? ZBX_STYLE_ARROW_DOWN : ZBX_STYLE_ARROW_UP
|
|
);
|
|
|
|
$show_timeline = ($this->data['sortfield'] === 'clock' && $this->data['fields']['show_timeline']);
|
|
$show_recovery_data = in_array($this->data['fields']['show'],
|
|
[TRIGGERS_OPTION_RECENT_PROBLEM, TRIGGERS_OPTION_ALL]
|
|
);
|
|
|
|
$header_time = (new CColHeader(($this->data['sortfield'] === 'clock')
|
|
? [_x('Time', 'compact table header'), $sort_div]
|
|
: _x('Time', 'compact table header')))->addStyle('width: 120px;');
|
|
|
|
$header = [];
|
|
|
|
if ($this->data['show_three_columns']) {
|
|
$header[] = new CColHeader();
|
|
$header[] = (new CColHeader())->addClass(ZBX_STYLE_CELL_WIDTH);
|
|
}
|
|
elseif ($this->data['show_two_columns']) {
|
|
$header[] = new CColHeader();
|
|
}
|
|
|
|
if ($show_timeline) {
|
|
$header[] = $header_time->addClass(ZBX_STYLE_RIGHT);
|
|
$header[] = (new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH);
|
|
$header[] = (new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH);
|
|
}
|
|
else {
|
|
$header[] = $header_time;
|
|
}
|
|
|
|
$this->setHeader(array_merge($header, [
|
|
$show_recovery_data
|
|
? _x('Recovery time', 'compact table header')
|
|
: null,
|
|
$show_recovery_data
|
|
? _x('Status', 'compact table header')
|
|
: null,
|
|
_x('Info', 'compact table header'),
|
|
($this->data['sortfield'] === 'host')
|
|
? [_x('Host', 'compact table header'), $sort_div]
|
|
: _x('Host', 'compact table header'),
|
|
[
|
|
($this->data['sortfield'] === 'name')
|
|
? [_x('Problem', 'compact table header'), $sort_div]
|
|
: _x('Problem', 'compact table header'),
|
|
' ', BULLET(), ' ',
|
|
($this->data['sortfield'] === 'severity')
|
|
? [_x('Severity', 'compact table header'), $sort_div]
|
|
: _x('Severity', 'compact table header')
|
|
],
|
|
($this->data['fields']['show_opdata'] == OPERATIONAL_DATA_SHOW_SEPARATELY)
|
|
? _x('Operational data', 'compact table header')
|
|
: null,
|
|
_x('Duration', 'compact table header'),
|
|
_('Update'),
|
|
_x('Actions', 'compact table header'),
|
|
$this->data['fields']['show_tags'] ? _x('Tags', 'compact table header') : null
|
|
]));
|
|
|
|
$this->data['triggers_hosts'] = $this->data['problems']
|
|
? makeTriggersHostsList($this->data['triggers_hosts'])
|
|
: [];
|
|
|
|
$this->data += [
|
|
'today' => strtotime('today'),
|
|
'show_timeline' => $show_timeline,
|
|
'last_clock' => 0,
|
|
'show_recovery_data' => $show_recovery_data
|
|
];
|
|
|
|
$this->addProblemsToTable($this->data['problems'], $this->data, false);
|
|
|
|
if ($this->data['info'] !== '') {
|
|
$this->setFooter([
|
|
(new CCol($this->data['info']))
|
|
->setColSpan($this->getNumCols())
|
|
->addClass(ZBX_STYLE_LIST_TABLE_FOOTER)
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add problems and symptoms to table.
|
|
*
|
|
* @param array $problems List of problems.
|
|
* @param array $data Additional data to build the table.
|
|
* @param array $data['triggers'] List of triggers.
|
|
* @param int $data['today'] Timestamp of today's date.
|
|
* @param array $data['tasks'] List of tasks. Used to determine current problem status.
|
|
* @param array $data['users'] List of users.
|
|
* @param array $data['correlations'] List of event correlations.
|
|
* @param array $data['fields'] Problem widget filter fields.
|
|
* @param int $data['fields']['show'] "Show" filter option.
|
|
* @param int $data['fields']['show_tags'] "Show tags" filter option.
|
|
* @param int $data['fields']['show_opdata'] "Show operational data" filter option.
|
|
* @param array $data['fields']['tags'] "Tags" filter.
|
|
* @param int $data['fields']['tag_name_format'] "Tag name" filter.
|
|
* @param string $data['fields']['tag_priority'] "Tag display priority" filter.
|
|
* @param bool $data['show_timeline'] "Show timeline" filter option.
|
|
* @param bool $data['show_three_columns'] True if 3 columns should be displayed.
|
|
* @param bool $data['show_two_columns'] True if 2 column should be displayed.
|
|
* @param int $data['last_clock'] Problem time. Used to show timeline breaks.
|
|
* @param int $data['sortorder'] Sort problems in ascending or descending order.
|
|
* @param array $data['allowed'] An array of user role rules.
|
|
* @param bool $data['allowed']['ui_problems'] Whether user is allowed to access problem view.
|
|
* @param bool $data['allowed']['close'] Whether user is allowed to close problems.
|
|
* @param bool $data['allowed']['add_comments'] Whether user is allowed to add problems comments.
|
|
* @param bool $data['allowed']['change_severity'] Whether user is allowed to change problems severity.
|
|
* @param bool $data['allowed']['acknowledge'] Whether user is allowed to acknowledge problems.
|
|
* @param bool $data['allowed']['suppress_problems'] Whether user is allowed to manually suppress/unsuppress
|
|
* problems.
|
|
* @param bool $data['allowed']['rank_change'] Whether user is allowed to change problem ranking.
|
|
* @param bool $data['show_recovery_data'] True if filter "Show" option is "Recent problems"
|
|
* or History.
|
|
* @param array $data['triggers_hosts'] List of trigger hosts.
|
|
* @param array $data['actions'] List of actions.
|
|
* @param array $data['tags'] List of tags.
|
|
* @param bool $nested If true, show the symptom rows with indentation.
|
|
*/
|
|
private function addProblemsToTable(array $problems, array $data, $nested): void {
|
|
foreach ($problems as $problem) {
|
|
$trigger = $data['triggers'][$problem['objectid']];
|
|
|
|
$cell_clock = ($problem['clock'] >= $data['today'])
|
|
? zbx_date2str(TIME_FORMAT_SECONDS, $problem['clock'])
|
|
: zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']);
|
|
|
|
$cell_r_clock = '';
|
|
|
|
if ($data['allowed']['ui_problems']) {
|
|
$cell_clock = new CCol(new CLink($cell_clock,
|
|
(new CUrl('tr_events.php'))
|
|
->setArgument('triggerid', $problem['objectid'])
|
|
->setArgument('eventid', $problem['eventid'])
|
|
));
|
|
}
|
|
else {
|
|
$cell_clock = new CCol($cell_clock);
|
|
}
|
|
|
|
if ($problem['r_eventid'] != 0) {
|
|
$cell_r_clock = ($problem['r_clock'] >= $data['today'])
|
|
? zbx_date2str(TIME_FORMAT_SECONDS, $problem['r_clock'])
|
|
: zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['r_clock']);
|
|
|
|
if ($data['allowed']['ui_problems']) {
|
|
$cell_r_clock = new CCol(new CLink($cell_r_clock,
|
|
(new CUrl('tr_events.php'))
|
|
->setArgument('triggerid', $problem['objectid'])
|
|
->setArgument('eventid', $problem['eventid'])
|
|
));
|
|
}
|
|
else {
|
|
$cell_r_clock = new CCol($cell_r_clock);
|
|
}
|
|
|
|
$cell_r_clock
|
|
->addClass(ZBX_STYLE_NOWRAP)
|
|
->addClass(ZBX_STYLE_RIGHT);
|
|
}
|
|
|
|
$in_closing = false;
|
|
|
|
if ($problem['r_eventid'] != 0) {
|
|
$value = TRIGGER_VALUE_FALSE;
|
|
$value_clock = $problem['r_clock'];
|
|
$can_be_closed = false;
|
|
}
|
|
else {
|
|
$in_closing = hasEventCloseAction($problem['acknowledges']);
|
|
$can_be_closed = ($trigger['manual_close'] == ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED
|
|
&& $data['allowed']['close'] && !$in_closing
|
|
);
|
|
$value = $in_closing ? TRIGGER_VALUE_FALSE : TRIGGER_VALUE_TRUE;
|
|
$value_clock = $in_closing ? time() : $problem['clock'];
|
|
}
|
|
|
|
$value_str = getEventStatusString($in_closing, $problem);
|
|
$is_acknowledged = ($problem['acknowledged'] == EVENT_ACKNOWLEDGED);
|
|
$cell_status = new CSpan($value_str);
|
|
|
|
if (isEventUpdating($in_closing, $problem)) {
|
|
$cell_status->addClass('js-blink');
|
|
}
|
|
|
|
// Add colors and blinking to span depending on configuration and trigger parameters.
|
|
addTriggerValueStyle($cell_status, $value, $value_clock, $is_acknowledged);
|
|
|
|
// Info.
|
|
$info_icons = [];
|
|
|
|
if ($data['fields']['show'] == TRIGGERS_OPTION_IN_PROBLEM) {
|
|
$info_icons[] = getEventStatusUpdateIcon($problem);
|
|
}
|
|
|
|
if ($problem['r_eventid'] != 0) {
|
|
if ($problem['correlationid'] != 0) {
|
|
$info_icons[] = makeInformationIcon(
|
|
array_key_exists($problem['correlationid'], $data['correlations'])
|
|
? _s('Resolved by event correlation rule "%1$s".',
|
|
$data['correlations'][$problem['correlationid']]['name']
|
|
)
|
|
: _('Resolved by event correlation rule.')
|
|
);
|
|
}
|
|
elseif ($problem['userid'] != 0) {
|
|
$info_icons[] = makeInformationIcon(
|
|
array_key_exists($problem['userid'], $data['users'])
|
|
? _s('Resolved by user "%1$s".', getUserFullname($data['users'][$problem['userid']]))
|
|
: _('Resolved by inaccessible user.')
|
|
);
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('suppression_data', $problem)) {
|
|
if (count($problem['suppression_data']) == 1
|
|
&& $problem['suppression_data'][0]['maintenanceid'] == 0
|
|
&& isEventRecentlyUnsuppressed($problem['acknowledges'], $unsuppression_action)) {
|
|
// Show blinking button if the last manual suppression was recently revoked.
|
|
$user_unsuppressed = array_key_exists($unsuppression_action['userid'], $data['users'])
|
|
? getUserFullname($data['users'][$unsuppression_action['userid']])
|
|
: _('Inaccessible user');
|
|
|
|
$info_icons[] = (new CButtonIcon(ZBX_ICON_EYE))
|
|
->addClass(ZBX_STYLE_COLOR_ICON)
|
|
->addClass('js-blink')
|
|
->setHint(_s('Unsuppressed by: %1$s', $user_unsuppressed));
|
|
}
|
|
elseif ($problem['suppression_data']) {
|
|
$info_icons[] = makeSuppressedProblemIcon($problem['suppression_data'], false);
|
|
}
|
|
elseif (isEventRecentlySuppressed($problem['acknowledges'], $suppression_action)) {
|
|
// Show blinking button if suppression was made but is not yet processed by server.
|
|
$info_icons[] = makeSuppressedProblemIcon([[
|
|
'suppress_until' => $suppression_action['suppress_until'],
|
|
'username' => array_key_exists($suppression_action['userid'], $data['users'])
|
|
? getUserFullname($data['users'][$suppression_action['userid']])
|
|
: _('Inaccessible user')
|
|
]], true);
|
|
}
|
|
}
|
|
elseif ($problem['suppression_data']) {
|
|
$info_icons[] = makeSuppressedProblemIcon($problem['suppression_data']);
|
|
}
|
|
elseif (isEventRecentlySuppressed($problem['acknowledges'], $suppression_action)) {
|
|
// Show blinking button if suppression was made but is not yet processed by server.
|
|
$info_icons[] = makeSuppressedProblemIcon([[
|
|
'suppress_until' => $suppression_action['suppress_until'],
|
|
'username' => array_key_exists($suppression_action['userid'], $data['users'])
|
|
? getUserFullname($data['users'][$suppression_action['userid']])
|
|
: _('Inaccessible user')
|
|
]], true);
|
|
}
|
|
|
|
$opdata = null;
|
|
if ($data['fields']['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) {
|
|
// operational data
|
|
if ($trigger['opdata'] === '') {
|
|
if ($data['fields']['show_opdata'] == OPERATIONAL_DATA_SHOW_SEPARATELY) {
|
|
$opdata = (new CCol(
|
|
CScreenProblem::getLatestValues($trigger['items'])
|
|
))->addClass('latest-values');
|
|
}
|
|
}
|
|
else {
|
|
$opdata = CMacrosResolverHelper::resolveTriggerOpdata(
|
|
[
|
|
'triggerid' => $trigger['triggerid'],
|
|
'expression' => $trigger['expression'],
|
|
'opdata' => $trigger['opdata'],
|
|
'clock' => ($problem['r_eventid'] != 0) ? $problem['r_clock'] : $problem['clock'],
|
|
'ns' => ($problem['r_eventid'] != 0) ? $problem['r_ns'] : $problem['ns']
|
|
],
|
|
[
|
|
'events' => true,
|
|
'html' => true
|
|
]
|
|
);
|
|
|
|
if ($data['fields']['show_opdata'] == OPERATIONAL_DATA_SHOW_SEPARATELY) {
|
|
$opdata = (new CCol($opdata))->addClass('opdata');
|
|
}
|
|
}
|
|
}
|
|
|
|
$problem_link = [
|
|
(new CLinkAction($problem['name']))
|
|
->setMenuPopup(CMenuPopupHelper::getTrigger([
|
|
'triggerid' => $trigger['triggerid'],
|
|
'backurl' => (new CUrl('zabbix.php'))
|
|
->setArgument('action', 'dashboard.view')
|
|
->getUrl(),
|
|
'eventid' => $problem['eventid'],
|
|
'show_rank_change_cause' => true
|
|
]))
|
|
->setAttribute('aria-label', _xs('%1$s, Severity, %2$s', 'screen reader',
|
|
$problem['name'], CSeverityHelper::getName((int) $problem['severity'])
|
|
))
|
|
];
|
|
|
|
if ($data['fields']['show_opdata'] == OPERATIONAL_DATA_SHOW_WITH_PROBLEM && $opdata) {
|
|
$problem_link = array_merge($problem_link, [' (', $opdata, ')']);
|
|
}
|
|
|
|
$description = (new CCol($problem_link))->addClass(ZBX_STYLE_WORDBREAK);
|
|
$description_style = CSeverityHelper::getStyle((int) $problem['severity']);
|
|
|
|
if ($value == TRIGGER_VALUE_TRUE) {
|
|
$description->addClass($description_style);
|
|
}
|
|
|
|
if (!$data['show_recovery_data']
|
|
&& (($is_acknowledged && $data['config']['problem_ack_style'])
|
|
|| (!$is_acknowledged && $data['config']['problem_unack_style']))) {
|
|
// blinking
|
|
$duration = time() - $problem['clock'];
|
|
$blink_period = timeUnitToSeconds($data['config']['blink_period']);
|
|
|
|
if ($blink_period != 0 && $duration < $blink_period) {
|
|
$description
|
|
->addClass('js-blink')
|
|
->setAttribute('data-time-to-blink', $blink_period - $duration)
|
|
->setAttribute('data-toggle-class', ZBX_STYLE_BLINK_HIDDEN);
|
|
}
|
|
}
|
|
|
|
$symptom_col = (new CCol(
|
|
new CIcon(ZBX_ICON_ARROW_TOP_RIGHT, _('Symptom'))
|
|
))->addClass(ZBX_STYLE_RIGHT);
|
|
|
|
$empty_col = new CCol();
|
|
|
|
if ($data['show_timeline']) {
|
|
$symptom_col->addClass(ZBX_STYLE_PROBLEM_EXPAND_TD);
|
|
$empty_col->addClass(ZBX_STYLE_PROBLEM_EXPAND_TD);
|
|
}
|
|
|
|
// Build rows and columns.
|
|
if ($problem['cause_eventid'] == 0) {
|
|
$row = new CRow();
|
|
|
|
if ($problem['symptom_count'] > 0) {
|
|
// Show symptom counter and collapse/expand button.
|
|
$symptom_count_col = (new CCol(
|
|
(new CSpan($problem['symptom_count']))->addClass(ZBX_STYLE_ENTITY_COUNT)
|
|
))->addClass(ZBX_STYLE_RIGHT);
|
|
|
|
$collapse_expand_col = (new CCol(
|
|
(new CButtonIcon(ZBX_ICON_CHEVRON_DOWN, _('Expand')))
|
|
->addClass(ZBX_STYLE_COLLAPSED)
|
|
->setAttribute('data-eventid', $problem['eventid'])
|
|
->setAttribute('data-action', 'show_symptoms')
|
|
))->addClass(ZBX_STYLE_RIGHT);
|
|
|
|
if ($data['show_timeline']) {
|
|
$symptom_count_col->addClass(ZBX_STYLE_PROBLEM_EXPAND_TD);
|
|
$collapse_expand_col->addClass(ZBX_STYLE_PROBLEM_EXPAND_TD);
|
|
}
|
|
|
|
$row->addItem([$symptom_count_col, $collapse_expand_col]);
|
|
}
|
|
else {
|
|
if ($data['show_three_columns']) {
|
|
// Show two empty columns.
|
|
$row->addItem([$empty_col, $empty_col]);
|
|
}
|
|
elseif ($data['show_two_columns']) {
|
|
$row->addItem($empty_col);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ($nested) {
|
|
// First and second column empty for symptom event.
|
|
$row = (new CRow($empty_col))
|
|
->addClass(ZBX_STYLE_PROBLEM_NESTED)
|
|
->addClass(ZBX_STYLE_PROBLEM_NESTED_SMALL)
|
|
->addClass('hidden')
|
|
->setAttribute('data-cause-eventid', $problem['cause_eventid'])
|
|
->addItem($symptom_col);
|
|
}
|
|
else {
|
|
// First column empty stand-alone symptom event.
|
|
$row = new CRow($symptom_col);
|
|
}
|
|
|
|
// If there are causes as well, show additional empty column.
|
|
if (!$nested && $data['show_three_columns']) {
|
|
$row->addItem($empty_col);
|
|
}
|
|
}
|
|
|
|
if ($data['show_timeline']) {
|
|
if ($data['last_clock'] != 0) {
|
|
CScreenProblem::addTimelineBreakpoint($this, $data, $problem, $nested, false);
|
|
}
|
|
$data['last_clock'] = $problem['clock'];
|
|
|
|
$row->addItem([
|
|
$cell_clock->addClass(ZBX_STYLE_TIMELINE_DATE),
|
|
(new CCol())
|
|
->addClass(ZBX_STYLE_TIMELINE_AXIS)
|
|
->addClass(ZBX_STYLE_TIMELINE_DOT),
|
|
(new CCol())->addClass(ZBX_STYLE_TIMELINE_TD)
|
|
]);
|
|
}
|
|
else {
|
|
$row->addItem($cell_clock
|
|
->addClass(ZBX_STYLE_NOWRAP)
|
|
->addClass(ZBX_STYLE_RIGHT)
|
|
);
|
|
}
|
|
|
|
// Create acknowledge link.
|
|
$problem_update_link = ($data['allowed']['add_comments'] || $data['allowed']['change_severity']
|
|
|| $data['allowed']['acknowledge'] || $can_be_closed || $data['allowed']['suppress_problems']
|
|
|| $data['allowed']['rank_change'])
|
|
? (new CLink(_('Update')))
|
|
->addClass(ZBX_STYLE_LINK_ALT)
|
|
->setAttribute('data-eventid', $problem['eventid'])
|
|
->onClick('acknowledgePopUp({eventids: [this.dataset.eventid]}, this);')
|
|
: new CSpan(_('Update'));
|
|
|
|
$row->addItem([
|
|
$data['show_recovery_data'] ? $cell_r_clock : null,
|
|
$data['show_recovery_data'] ? $cell_status : null,
|
|
makeInformationList($info_icons),
|
|
$data['triggers_hosts'][$trigger['triggerid']],
|
|
$description,
|
|
($data['fields']['show_opdata'] == OPERATIONAL_DATA_SHOW_SEPARATELY)
|
|
? $opdata->addClass(ZBX_STYLE_WORDBREAK)
|
|
: null,
|
|
(new CCol(
|
|
(new CLinkAction(zbx_date2age($problem['clock'], ($problem['r_eventid'] != 0)
|
|
? $problem['r_clock']
|
|
: 0
|
|
)))
|
|
->setAjaxHint(CHintBoxHelper::getEventList($trigger['triggerid'], $problem['eventid'],
|
|
$data['show_timeline'], $data['fields']['show_tags'], $data['fields']['tags'],
|
|
$data['fields']['tag_name_format'], $data['fields']['tag_priority']
|
|
))
|
|
))->addClass(ZBX_STYLE_NOWRAP),
|
|
$problem_update_link,
|
|
makeEventActionsIcons($problem['eventid'], $data['actions'], $data['users'], $is_acknowledged),
|
|
$data['fields']['show_tags'] ? $data['tags'][$problem['eventid']] : null
|
|
]);
|
|
|
|
$this->addRow($row);
|
|
|
|
if ($problem['cause_eventid'] == 0 && $problem['symptoms']) {
|
|
$this->addProblemsToTable($problem['symptoms'], $data, true);
|
|
|
|
CScreenProblem::addSymptomLimitToTable($this, $problem, $data);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function toString($destroy = true): string {
|
|
$this->build();
|
|
|
|
return parent::toString($destroy);
|
|
}
|
|
}
|