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.

605 lines
17 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.
**/
/**
* Class for converting template screens to template dashboards.
*/
class CTemplateScreenConverter extends CConverter {
/**
* Reference display width for widget position and size calculations.
*/
private const DISPLAY_WIDTH = 1920;
/**
* Widget row height on dashboard.
*/
private const WIDGET_ROW_HEIGHT = 70;
/**
* Average height of screen legend.
*/
private const SCREEN_LEGEND_HEIGHT = 215;
/**
* Legacy screen resource types.
*/
private const SCREEN_RESOURCE_TYPE_GRAPH = 0;
private const SCREEN_RESOURCE_TYPE_SIMPLE_GRAPH = 1;
private const SCREEN_RESOURCE_TYPE_PLAIN_TEXT = 3;
private const SCREEN_RESOURCE_TYPE_CLOCK = 7;
private const SCREEN_RESOURCE_TYPE_URL = 11;
private const SCREEN_RESOURCE_TYPE_LLD_SIMPLE_GRAPH = 19;
private const SCREEN_RESOURCE_TYPE_LLD_GRAPH = 20;
/**
* Convert template screen definition to template dashboard definition.
*
* @param array $screen
*
* @return array
*/
public function convert($screen): array {
$widgets = [];
$screen_items = array_key_exists('screen_items', $screen)
? self::getValidScreenItems($screen['screen_items'])
: [];
if ($screen_items) {
$screen_items = self::normalizePositions($screen_items);
[$dimensions_x, $dimensions_y] = self::getDashboardDimensions($screen_items);
$grid_x = [0];
foreach ($dimensions_x as $index => $size) {
$grid_x[$index + 1] = ($index == 0) ? $size : $grid_x[$index] + $size;
}
$grid_y = [0];
foreach ($dimensions_y as $index => $size) {
$grid_y[$index + 1] = ($index == 0) ? $size : $grid_y[$index] + $size;
}
foreach ($screen_items as $item) {
$widget_pos = [
'x' => $grid_x[$item['x']],
'y' => $grid_y[$item['y']],
'width' => $grid_x[$item['x'] + $item['colspan']] - $grid_x[$item['x']],
'height' => $grid_y[$item['y'] + $item['rowspan']] - $grid_y[$item['y']]
];
// Skip screen items not fitting on dashboard.
if (($widget_pos['x'] + $widget_pos['width'] > DASHBOARD_MAX_COLUMNS)
|| ($widget_pos['y'] + $widget_pos['height'] > DASHBOARD_MAX_ROWS)) {
continue;
}
$widget = self::makeWidget($item);
$widget += [
'x' => (string) $widget_pos['x'],
'y' => (string) $widget_pos['y'],
'width' => (string) $widget_pos['width'],
'height' => (string) $widget_pos['height']
];
$widgets[] = $widget;
}
}
$dashboard = [
'name' => $screen['name']
];
if ($widgets) {
$dashboard['widgets'] = $widgets;
}
return $dashboard;
}
/**
* Filter screen items by valid resourcetype.
*
* @static
*
* @param array $screen_items
*
* @return array valid screen items
*/
private static function getValidScreenItems(array $screen_items): array {
return array_filter($screen_items, function(array $screen_item): bool {
return in_array($screen_item['resourcetype'], [self::SCREEN_RESOURCE_TYPE_CLOCK,
self::SCREEN_RESOURCE_TYPE_GRAPH, self::SCREEN_RESOURCE_TYPE_SIMPLE_GRAPH,
self::SCREEN_RESOURCE_TYPE_LLD_GRAPH, self::SCREEN_RESOURCE_TYPE_LLD_SIMPLE_GRAPH,
self::SCREEN_RESOURCE_TYPE_PLAIN_TEXT, self::SCREEN_RESOURCE_TYPE_URL
]);
});
}
/**
* Remove empty rows and columns and simplify rowspan and colspan usage in the screen items.
*
* @static
*
* @param array $screen_items
*
* @return array Optimized screen items.
*/
private static function normalizePositions(array $screen_items): array {
$used_x = [];
$used_y = [];
$keep_x = [];
$keep_y = [];
foreach ($screen_items as $screen_item) {
$used_x += array_fill((int) $screen_item['x'], (int) $screen_item['colspan'], true);
$used_y += array_fill((int) $screen_item['y'], (int) $screen_item['rowspan'], true);
$keep_x[$screen_item['x']] = true;
$keep_x[$screen_item['x'] + $screen_item['colspan']] = true;
$keep_y[$screen_item['y']] = true;
$keep_y[$screen_item['y'] + $screen_item['rowspan']] = true;
}
for ($x = max(array_keys($keep_x)); $x >= 0; $x--) {
if (array_key_exists($x, $keep_x) && array_key_exists($x, $used_x)) {
continue;
}
foreach ($screen_items as &$screen_item) {
if ($x < $screen_item['x']) {
$screen_item['x']--;
}
if ($x > $screen_item['x'] && $x < $screen_item['x'] + $screen_item['colspan']) {
$screen_item['colspan']--;
}
}
unset($screen_item);
}
for ($y = max(array_keys($keep_y)); $y >= 0; $y--) {
if (array_key_exists($y, $keep_y) && array_key_exists($y, $used_y)) {
continue;
}
foreach ($screen_items as &$screen_item) {
if ($y < $screen_item['y']) {
$screen_item['y']--;
}
if ($y > $screen_item['y'] && $y < $screen_item['y'] + $screen_item['rowspan']) {
$screen_item['rowspan']--;
}
}
unset($screen_item);
}
return $screen_items;
}
/**
* Get final dashboard dimensions for screen table rows and columns.
*
* @static
*
* @param array $screen_items
*
* @return array Dashboard dimensions
*/
private static function getDashboardDimensions(array $screen_items): array {
$items_x_preferred = [];
$items_y_preferred = [];
$items_x_min = [];
$items_y_min = [];
foreach ($screen_items as $key => $screen_item) {
$preferred_size = self::getPreferredWidgetSize($screen_item);
$min_size = self::getMinWidgetSize((int) $screen_item['resourcetype']);
$items_x_preferred[$key] = [
'position' => (int) $screen_item['x'],
'span' => (int) $screen_item['colspan'],
'size' => max($preferred_size['width'], $min_size['width'])
];
$items_y_preferred[$key] = [
'position' => (int) $screen_item['y'],
'span' => (int) $screen_item['rowspan'],
'size' => max($preferred_size['height'], $min_size['height'])
];
$items_x_min[$key] = [
'position' => (int) $screen_item['x'],
'span' => (int) $screen_item['colspan'],
'size' => $min_size['width']
];
$items_y_min[$key] = [
'position' => (int) $screen_item['y'],
'span' => (int) $screen_item['rowspan'],
'size' => $min_size['height']
];
}
$dimensions_x_preferred = self::getAxisDimensions($items_x_preferred);
$dimensions_x_min = self::getAxisDimensions($items_x_min);
$dimensions_x = self::adjustAxisDimensions($dimensions_x_preferred, $dimensions_x_min, DASHBOARD_MAX_COLUMNS);
$dimensions_y_preferred = self::getAxisDimensions($items_y_preferred);
$dimensions_y_min = self::getAxisDimensions($items_y_min);
if (array_sum($dimensions_y_preferred) > DASHBOARD_MAX_ROWS) {
$dimensions_y = self::adjustAxisDimensions($dimensions_y_preferred, $dimensions_y_min, DASHBOARD_MAX_ROWS);
}
else {
$dimensions_y = $dimensions_y_preferred;
}
return [$dimensions_x, $dimensions_y];
}
/**
* Get axis dimensions based on prepared items.
*
* @static
*
* @param array $items Prepared items.
* @param int $items[]['position'] Item starting position.
* @param int $items[]['span'] Item cell span.
* @param int $items[]['size'] Item inner size.
*
* @return array
*/
private static function getAxisDimensions(array $items): array {
$blocks = [];
foreach ($items as $key => $item) {
$blocks[$key] = array_fill($item['position'], $item['span'], true);
}
$dimensions = [];
while ($blocks) {
uasort($blocks,
function(array $block_a, array $block_b) use ($dimensions): int {
$block_a_unsized_count = count(array_diff_key($block_a, $dimensions));
$block_b_unsized_count = count(array_diff_key($block_b, $dimensions));
return ($block_a_unsized_count <=> $block_b_unsized_count);
}
);
$key = array_keys($blocks)[0];
$block = $blocks[$key];
$block_dimensions = array_intersect_key($dimensions, $block);
$block_dimensions_sum = array_sum($block_dimensions);
$block_unsized = array_diff_key($block, $dimensions);
$block_unsized_count = count($block_unsized);
$size_overflow = $items[$key]['size'] - $block_dimensions_sum;
if ($block_unsized_count > 0) {
foreach (array_keys($block_unsized) as $index) {
$dimensions[$index] = max(1, round($size_overflow / $block_unsized_count));
$size_overflow -= $dimensions[$index];
$block_unsized_count--;
}
}
elseif ($size_overflow > 0) {
foreach (array_keys($block) as $index) {
$factor = ($size_overflow + $block_dimensions_sum) / $block_dimensions_sum;
$new_dimension = round($dimensions[$index] * $factor);
$block_dimensions_sum -= $dimensions[$index];
$size_overflow -= $new_dimension - $dimensions[$index];
$dimensions[$index] = $new_dimension;
}
}
unset($blocks[$key]);
}
ksort($dimensions, SORT_NUMERIC);
return $dimensions;
}
/**
* Adjust axis dimensions to the target summary size whether possible.
*
* @static
*
* @param array $dimensions Preferred axis dimensions.
* @param array $dimensions_min Minimal axis dimensions.
* @param int $target Target summary size.
*
* @return array
*/
private static function adjustAxisDimensions(array $dimensions, array $dimensions_min, int $target): array {
$dimensions_sum = array_sum($dimensions);
while ($dimensions_sum != $target) {
$potential_index = null;
$potential_value = null;
foreach ($dimensions as $index => $size) {
$value = $size / $dimensions_min[$index];
if ($potential_index === null
|| ($dimensions_sum > $target && $value > $potential_value)
|| ($dimensions_sum < $target && $value < $potential_value)) {
$potential_index = $index;
$potential_value = $value;
}
}
// Further shrinking not possible?
if ($dimensions_sum > $target && $dimensions[$potential_index] == $dimensions_min[$potential_index]) {
break;
}
if ($dimensions_sum > $target) {
$dimensions[$potential_index]--;
$dimensions_sum--;
}
else {
$dimensions[$potential_index]++;
$dimensions_sum++;
}
}
return $dimensions;
}
/**
* Get preferred widget size on dashboard for given screen item type and size.
*
* @static
*
* @param array $screen_item
*
* @return array
*/
private static function getPreferredWidgetSize(array $screen_item): array {
$width = $screen_item['width'];
$height = $screen_item['height'];
// Convert graph inner height to outer height.
if (in_array($screen_item['resourcetype'], [self::SCREEN_RESOURCE_TYPE_GRAPH,
self::SCREEN_RESOURCE_TYPE_SIMPLE_GRAPH, self::SCREEN_RESOURCE_TYPE_LLD_GRAPH,
self::SCREEN_RESOURCE_TYPE_LLD_SIMPLE_GRAPH])) {
// Transform graph screen item with height of at least 100px to occupy minimum 5 grid rows.
$height += self::SCREEN_LEGEND_HEIGHT;
}
if ($screen_item['resourcetype'] == self::SCREEN_RESOURCE_TYPE_PLAIN_TEXT) {
// Constants are synchronised with the same algorithm in DB upgrade patch converter.
$show_lines = min($screen_item['elements'], 25);
$rows = 2 + $show_lines / 2.5;
}
else {
$rows = $height / self::WIDGET_ROW_HEIGHT;
}
return self::limitWidgetSize([
'width' => round($width / self::DISPLAY_WIDTH * DASHBOARD_MAX_COLUMNS),
'height' => round($rows)
]);
}
/**
* Get minimal widget size on dashboard for given screen item type.
*
* @static
*
* @param int $resourcetype
*
* @return array
*/
private static function getMinWidgetSize(int $resourcetype): array {
switch ($resourcetype) {
case self::SCREEN_RESOURCE_TYPE_CLOCK:
return [
'width' => 1,
'height' => 2
];
case self::SCREEN_RESOURCE_TYPE_GRAPH:
case self::SCREEN_RESOURCE_TYPE_SIMPLE_GRAPH:
case self::SCREEN_RESOURCE_TYPE_LLD_GRAPH:
case self::SCREEN_RESOURCE_TYPE_LLD_SIMPLE_GRAPH:
return [
'width' => 4,
'height' => 4
];
case self::SCREEN_RESOURCE_TYPE_PLAIN_TEXT:
case self::SCREEN_RESOURCE_TYPE_URL:
return [
'width' => 4,
'height' => 2
];
}
}
/**
* Limit widget size not to exceed the size of dashboard.
*
* @static
*
* @param array $size
* @param int $size['width']
* @param int $size['height']
*
* @return array
*/
private static function limitWidgetSize(array $size): array {
return [
'width' => min(DASHBOARD_MAX_COLUMNS, max(1, $size['width'])),
'height' => min(DASHBOARD_WIDGET_MAX_ROWS, max(DASHBOARD_WIDGET_MIN_ROWS, $size['height']))
];
}
/**
* Make widget definition based on screen item.
*
* @static
*
* @param array $screen_item
*
* @return array Full widget definition except the positional data.
*/
private static function makeWidget(array $screen_item): array {
$widget = [
'name' => '',
'hide_header' => CXmlConstantName::NO
];
switch ($screen_item['resourcetype']) {
case self::SCREEN_RESOURCE_TYPE_CLOCK:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_CLOCK;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'time_type',
'value' => $screen_item['style']
];
if ($screen_item['style'] == TIME_TYPE_HOST) {
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_ITEM,
'name' => 'itemid',
'value' => $screen_item['resource']
];
}
break;
case self::SCREEN_RESOURCE_TYPE_GRAPH:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_GRAPH_CLASSIC;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'source_type',
'value' => (string) ZBX_WIDGET_FIELD_RESOURCE_GRAPH
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_GRAPH,
'name' => 'graphid',
'value' => $screen_item['resource']
];
break;
case self::SCREEN_RESOURCE_TYPE_SIMPLE_GRAPH:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_GRAPH_CLASSIC;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'source_type',
'value' => (string) ZBX_WIDGET_FIELD_RESOURCE_SIMPLE_GRAPH
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_ITEM,
'name' => 'itemid',
'value' => $screen_item['resource']
];
break;
case self::SCREEN_RESOURCE_TYPE_LLD_GRAPH:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_GRAPH_PROTOTYPE;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'source_type',
'value' => (string) ZBX_WIDGET_FIELD_RESOURCE_GRAPH_PROTOTYPE
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE,
'name' => 'graphid',
'value' => $screen_item['resource']
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'columns',
'value' => '1'
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'rows',
'value' => '1'
];
break;
case self::SCREEN_RESOURCE_TYPE_LLD_SIMPLE_GRAPH:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_GRAPH_PROTOTYPE;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'source_type',
'value' => (string) ZBX_WIDGET_FIELD_RESOURCE_SIMPLE_GRAPH_PROTOTYPE
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE,
'name' => 'itemid',
'value' => $screen_item['resource']
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'columns',
'value' => '1'
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'rows',
'value' => '1'
];
break;
case self::SCREEN_RESOURCE_TYPE_PLAIN_TEXT:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_PLAIN_TEXT;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_ITEM,
'name' => 'itemids',
'value' => $screen_item['resource']
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'show_as_html',
'value' => $screen_item['style']
];
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_INTEGER,
'name' => 'show_lines',
'value' => $screen_item['elements']
];
break;
case self::SCREEN_RESOURCE_TYPE_URL:
$widget['type'] = CXmlConstantName::DASHBOARD_WIDGET_TYPE_URL;
$widget['fields'][] = [
'type' => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_STRING,
'name' => 'url',
'value' => $screen_item['url']
];
break;
}
return $widget;
}
}