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.

454 lines
16 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\Item\Actions;
use API,
CControllerDashboardWidgetView,
CControllerResponseData,
CMacrosResolverHelper,
CNumberParser,
CSettingsHelper,
CUrl,
Manager;
use Widgets\Item\Widget;
use Zabbix\Core\CWidget;
class WidgetView extends CControllerDashboardWidgetView {
protected function init(): void {
parent::init();
$this->addValidationRules([
'dynamic_hostid' => 'db hosts.hostid'
]);
}
protected function doAction(): void {
$name = $this->widget->getDefaultName();
$cells = [];
$url = null;
$error = '';
$history_period = timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD));
$description = '';
$value = null;
$change_indicator = null;
$time = '';
$units = '';
$decimals = null;
$last_value = null;
$is_binary_units = true;
$options = [
'output' => ['value_type'],
'selectValueMap' => ['mappings'],
'itemids' => $this->fields_values['itemid'],
'webitems' => true,
'preservekeys' => true
];
$is_dynamic = ($this->hasInput('dynamic_hostid')
&& ($this->isTemplateDashboard() || $this->fields_values['dynamic'] == CWidget::DYNAMIC_ITEM)
);
$tmp_items = [];
if ($is_dynamic) {
$tmp_items = API::Item()->get([
'output' => ['key_'],
'itemids' => $this->fields_values['itemid'],
'webitems' => true
]);
if ($tmp_items) {
$options = [
'output' => ['value_type'],
'selectValueMap' => ['mappings'],
'hostids' => [$this->getInput('dynamic_hostid')],
'webitems' => true,
'filter' => [
'key_' => $tmp_items[0]['key_']
],
'preservekeys' => true
];
}
}
$show = array_flip($this->fields_values['show']);
/*
* Select original item name in several cases: if user is in normal dashboards or in template dashboards when
* user is in view mode to display that item name in widget name. Item name should be select only if it is not
* overwritten. Host name can be attached to item name with delimiter when user is in normal dashboards.
*/
if ($this->getInput('name', '') === '') {
if (!$this->isTemplateDashboard() || ($this->hasInput('dynamic_hostid') && $tmp_items)) {
$options['output'] = array_merge($options['output'], ['name']);
}
if (!$this->isTemplateDashboard()) {
$options['selectHosts'] = ['name'];
}
}
// Add other fields in case current widget is set in dynamic mode, template dashboard or has a specified host.
if (($is_dynamic && $tmp_items) || !$is_dynamic) {
// If description contains user macros, we need "itemid" and "hostid" to resolve them.
if (array_key_exists(Widget::SHOW_DESCRIPTION, $show)) {
$options['output'] = array_merge($options['output'], ['itemid', 'hostid']);
}
if ($this->fields_values['units_show'] != 1 || $this->fields_values['units'] === '') {
$options['output'][] = 'units';
}
}
if ($is_dynamic) {
if ($tmp_items) {
$items = API::Item()->get($options);
$itemid = key($items);
}
else {
$items = [];
}
}
else {
$items = API::Item()->get($options);
if ($this->fields_values['itemid']) {
$itemid = $this->fields_values['itemid'][0];
}
}
if ($items) {
$item = $items[$itemid];
$history_limit = array_key_exists(Widget::SHOW_CHANGE_INDICATOR, $show) ? 2 : 1;
$history = Manager::History()->getLastValues($items, $history_limit, $history_period);
$value_type = $item['value_type'];
if ($history) {
$last_value = $history[$itemid][0]['value'];
$prev_value = array_key_exists(1, $history[$itemid]) ? $history[$itemid][1]['value'] : null;
if (array_key_exists(Widget::SHOW_TIME, $show)) {
$time = date(ZBX_FULL_DATE_TIME, (int) $history[$itemid][0]['clock']);
}
switch ($value_type) {
case ITEM_VALUE_TYPE_FLOAT:
case ITEM_VALUE_TYPE_UINT64:
$item_units = $this->fields_values['units_show'] == 1 && $this->fields_values['units'] !== ''
? $this->fields_values['units']
: $item['units'];
$is_binary_units = isBinaryUnits($item_units);
if ($this->fields_values['units_show'] == 1) {
if ($this->fields_values['units'] !== '') {
$item['units'] = $this->fields_values['units'];
}
}
else {
$item['units'] = '';
}
$formatted_value = formatHistoryValueRaw($last_value, $item, false, [
'decimals' => $this->fields_values['decimal_places'],
'decimals_exact' => true,
'small_scientific' => false,
'zero_as_zero' => false
]);
$value = $formatted_value['value'];
$units = $formatted_value['units'];
if (!$formatted_value['is_mapped']) {
$numeric_formatting = getNumericFormatting();
$decimal_pos = strrpos($value, $numeric_formatting['decimal_point']);
if ($decimal_pos !== false) {
$decimals = substr($value, $decimal_pos);
$value = substr($value, 0, $decimal_pos);
}
}
if (array_key_exists(Widget::SHOW_CHANGE_INDICATOR, $show) && $prev_value !== null) {
if ($formatted_value['is_mapped']) {
if ($last_value != $prev_value) {
$change_indicator = Widget::CHANGE_INDICATOR_UP_DOWN;
}
}
elseif ($last_value > $prev_value) {
$change_indicator = Widget::CHANGE_INDICATOR_UP;
}
elseif ($last_value < $prev_value) {
$change_indicator = Widget::CHANGE_INDICATOR_DOWN;
}
}
break;
case ITEM_VALUE_TYPE_STR:
case ITEM_VALUE_TYPE_TEXT:
case ITEM_VALUE_TYPE_LOG:
case ITEM_VALUE_TYPE_BINARY:
$value = $value_type == ITEM_VALUE_TYPE_BINARY
? italic(_('binary value'))
: formatHistoryValue($last_value, $items[$itemid], false);
if (array_key_exists(Widget::SHOW_CHANGE_INDICATOR, $show) && $prev_value !== null
&& $last_value !== $prev_value) {
$change_indicator = Widget::CHANGE_INDICATOR_UP_DOWN;
}
break;
}
}
else {
$value_type = ITEM_VALUE_TYPE_TEXT;
// Since there is no value, we can still show time.
if (array_key_exists(Widget::SHOW_TIME, $show)) {
$time = date(ZBX_FULL_DATE_TIME);
}
}
if ($this->getInput('name', '') === '') {
if (!$this->isTemplateDashboard() || $this->hasInput('dynamic_hostid')) {
// Resolve original item name when user is in normal dashboards or template dashboards view mode.
$name = $items[$itemid]['name'];
}
if (!$this->isTemplateDashboard()) {
$name = $items[$itemid]['hosts'][0]['name'].NAME_DELIMITER.$name;
}
}
/*
* It doesn't matter if item has value or not, description can be resolved separately if needed. If item
* will have value, it will resolve, otherwise it will not.
*/
if (array_key_exists(Widget::SHOW_DESCRIPTION, $show)) {
// Overwrite item name with the custom description.
$items[$itemid]['widget_description'] = $this->fields_values['description'];
// Do not resolve macros if using template dashboard. Template dashboards only have edit mode.
if (!$this->isTemplateDashboard() || $this->hasInput('dynamic_hostid')) {
$items = CMacrosResolverHelper::resolveItemWidgetDescriptions($items);
}
// All macros in item name are resolved here.
$description = $items[$itemid]['widget_description'];
}
$cells = self::arrangeByCells($this->fields_values, [
'description' => $description,
'value_type' => $value_type,
'units' => $units,
'value' => $value,
'decimals' => $decimals,
'change_indicator' => $change_indicator,
'time' => $time,
'items' => $items,
'itemid' => $itemid
]);
// Use the real item value type.
$url = (new CUrl('history.php'))
->setArgument('action',
($items[$itemid]['value_type'] == ITEM_VALUE_TYPE_FLOAT
|| $items[$itemid]['value_type'] == ITEM_VALUE_TYPE_UINT64)
? HISTORY_GRAPH
: HISTORY_VALUES
)
->setArgument('itemids[]', $itemid);
}
else {
$error = _('No permissions to referred object or it does not exist!');
}
$bg_color = $this->fields_values['bg_color'];
if ($last_value !== null) {
$number_parser = new CNumberParser([
'with_size_suffix' => true,
'with_time_suffix' => true,
'is_binary_size' => $is_binary_units
]);
foreach ($this->fields_values['thresholds'] as $threshold) {
$number_parser->parse($threshold['threshold']);
$threshold_value = $number_parser->calcValue();
if ($threshold_value > $last_value) {
break;
}
$bg_color = $threshold['color'];
}
}
$this->setResponse(new CControllerResponseData([
'name' => $this->getInput('name', $name),
'cells' => $cells,
'url' => $url,
'bg_color' => $bg_color,
'error' => $error,
'user' => [
'debug_mode' => $this->getDebugMode()
]
]));
}
/**
* Arrange all widget parts by cells, apply all related configuration settings to each part.
*
* @static
*
* @param array $fields_values Input fields from the form.
* @param array $fields_values ['show'] Flags to show description, value, time and change indicator.
* @param int $fields_values ['desc_v_pos'] Vertical position of the description.
* @param int $fields_values ['desc_h_pos'] Horizontal position of the description.
* @param int $fields_values ['desc_bold'] Font weight of the description (0 - normal, 1 - bold).
* @param int $fields_values ['desc_size'] Font size of the description.
* @param string $fields_values ['desc_color'] Font color of the description.
* @param int $fields_values ['value_v_pos'] Vertical position of the value.
* @param int $fields_values ['value_h_pos'] Horizontal position of the value.
* @param int $fields_values ['value_bold'] Font weight of the value (0 - normal, 1 - bold).
* @param int $fields_values ['value_size'] Font size of the value.
* @param string $fields_values ['value_color'] Font color of the value.
* @param int $fields_values ['units_show'] Display units or not (0 - hide, 1 - show).
* @param int $fields_values ['units_pos'] Position of the units.
* @param int $fields_values ['units_bold'] Font weight of the units (0 - normal, 1 - bold).
* @param int $fields_values ['units_size'] Font size of the units.
* @param string $fields_values ['units_color'] Font color of the units.
* @param int $fields_values ['decimal_size'] Font size of the fraction.
* @param int $fields_values ['time_v_pos'] Vertical position of the time.
* @param int $fields_values ['time_h_pos'] Horizontal position of the time.
* @param int $fields_values ['time_bold'] Font weight of the time (0 - normal, 1 - bold).
* @param int $fields_values ['time_size'] Font size of the time.
* @param string $fields_values ['time_color'] Font color of the time.
* @param array $data Array of pre-processed data that needs to be displayed.
* @param string $data ['description'] Item description with all macros resolved.
* @param string $data ['value_type'] Calculated value type. It can be integer or text.
* @param string $data ['units'] Units of the item. Can be empty string if nothing to show.
* @param string|null $data ['value'] Value of the item or NULL if there is no value.
* @param string|null $data ['decimals'] Decimal places or NULL if there is no decimals to show.
* @param int|null $data ['change_indicator'] Change indicator type or NULL if indicator should not be shown.
* @param string $data ['time'] Time when item received the value or current time if no data.
* @param array $data ['items'] The original array of items.
* @param string $data ['itemid'] Item ID from the host.
*
* @return array
*/
private static function arrangeByCells(array $fields_values, array $data): array {
$cells = [];
$show = array_flip($fields_values['show']);
if (array_key_exists(Widget::SHOW_DESCRIPTION, $show)) {
$cells[$fields_values['desc_v_pos']][$fields_values['desc_h_pos']] = [
'item_description' => [
'text' => $data['description'],
'font_size' => $fields_values['desc_size'],
'bold' => ($fields_values['desc_bold'] == 1),
'color' => $fields_values['desc_color']
]
];
}
if (array_key_exists(Widget::SHOW_VALUE, $show)) {
$item_value_cell = [
'value_type' => $data['value_type']
];
if ($fields_values['units_show'] == 1 && $data['units'] !== '') {
$item_value_cell['parts']['units'] = [
'text' => $data['units'],
'font_size' => $fields_values['units_size'],
'bold' => ($fields_values['units_bold'] == 1),
'color' => $fields_values['units_color']
];
$item_value_cell['units_pos'] = $fields_values['units_pos'];
}
$item_value_cell['parts']['value'] = [
'text' => $data['value'],
'font_size' => $fields_values['value_size'],
'bold' => ($fields_values['value_bold'] == 1),
'color' => $fields_values['value_color']
];
if ($data['decimals'] !== null) {
$item_value_cell['parts']['decimals'] = [
'text' => $data['decimals'],
'font_size' => $fields_values['decimal_size'],
'bold' => ($fields_values['value_bold'] == 1),
'color' => $fields_values['value_color']
];
}
$cells[$fields_values['value_v_pos']][$fields_values['value_h_pos']] = [
'item_value' => $item_value_cell
];
}
if (array_key_exists(Widget::SHOW_CHANGE_INDICATOR, $show) && $data['change_indicator'] !== null) {
$colors = [
Widget::CHANGE_INDICATOR_UP => $fields_values['up_color'],
Widget::CHANGE_INDICATOR_DOWN => $fields_values['down_color'],
Widget::CHANGE_INDICATOR_UP_DOWN => $fields_values['updown_color']
];
// Change indicator can be displayed with or without value.
$cells[$fields_values['value_v_pos']][$fields_values['value_h_pos']]['item_value']['parts']['change_indicator'] = [
'type' => $data['change_indicator'],
'font_size' => ($data['decimals'] !== null)
? max($fields_values['value_size'], $fields_values['decimal_size'])
: $fields_values['value_size'],
'color' => $colors[$data['change_indicator']]
];
}
if (array_key_exists(Widget::SHOW_TIME, $show)) {
$cells[$fields_values['time_v_pos']][$fields_values['time_h_pos']] = [
'item_time' => [
'text' => $data['time'],
'font_size' => $fields_values['time_size'],
'bold' => ($fields_values['time_bold'] == 1),
'color' => $fields_values['time_color']
]
];
}
// Sort data column blocks in order - left, center, right.
foreach ($cells as &$row) {
ksort($row);
}
unset($row);
return $cells;
}
}