LibreNMS/LibreNMS/Util/Graph.php

310 lines
10 KiB
PHP

<?php
/**
* Graph.php
*
* -Description-
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Util;
use App\Facades\DeviceCache;
use App\Models\Device;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use LibreNMS\Config;
use LibreNMS\Data\Graphing\GraphImage;
use LibreNMS\Data\Graphing\GraphParameters;
use LibreNMS\Enum\ImageFormat;
use LibreNMS\Exceptions\RrdGraphException;
use Rrd;
class Graph
{
const BASE64_OUTPUT = 1; // BASE64 encoded image data
const INLINE_BASE64 = 2; // img src inline base64 image
const IMAGE_PNG = 4; // img src inline base64 image
const IMAGE_SVG = 8; // img src inline base64 image
/**
* Convenience helper to specify desired image output
*
* @param array|string $vars
* @param int $flags
* @return string
*/
public static function getImageData($vars, int $flags = 0): string
{
if ($flags & self::IMAGE_PNG) {
$vars['graph_type'] = 'png';
}
if ($flags & self::IMAGE_SVG) {
$vars['graph_type'] = 'svg';
}
if ($flags & self::INLINE_BASE64) {
return self::getImage($vars)->inline();
}
if ($flags & self::BASE64_OUTPUT) {
return self::getImage($vars)->base64();
}
return self::getImage($vars)->data;
}
/**
* Fetch a GraphImage based on the given $vars
* Catches errors generated and always returns GraphImage
*
* @param array|string $vars
* @return GraphImage
*/
public static function getImage($vars): GraphImage
{
try {
return self::get($vars);
} catch (RrdGraphException $e) {
if (Debug::isEnabled()) {
throw $e;
}
return new GraphImage(ImageFormat::forGraph(), 'Error', $e->generateErrorImage());
}
}
/**
* Fetch a GraphImage based on the given $vars
*
* @param array|string $vars
* @return GraphImage
*
* @throws \LibreNMS\Exceptions\RrdGraphException
*/
public static function get($vars): GraphImage
{
define('IGNORE_ERRORS', true);
chdir(base_path());
include_once base_path('includes/dbFacile.php');
include_once base_path('includes/common.php');
include_once base_path('includes/html/functions.inc.php');
include_once base_path('includes/rewrites.php');
// handle possible graph url input
if (is_string($vars)) {
$vars = Url::parseLegacyPathVars($vars);
}
if (isset($vars['device'])) {
$device = device_by_id_cache(is_numeric($vars['device']) ? $vars['device'] : getidbyname($vars['device']));
DeviceCache::setPrimary($device['device_id']);
}
// variables for included graphs
$graph_params = new GraphParameters($vars);
// set php variables for legacy graphs
$type = $graph_params->type;
$subtype = $graph_params->subtype;
$height = $graph_params->height;
$width = $graph_params->width;
$from = $graph_params->from;
$to = $graph_params->to;
$period = $graph_params->period;
$prev_from = $graph_params->prev_from;
$inverse = $graph_params->inverse;
$in = $graph_params->in;
$out = $graph_params->out;
$float_precision = $graph_params->float_precision;
$title = $graph_params->visible('title');
$nototal = ! $graph_params->visible('total');
$nodetails = ! $graph_params->visible('details');
$noagg = ! $graph_params->visible('aggregate');
$rrd_options = '';
$rrd_filename = null;
$auth = Auth::guest(); // if user not logged in, assume we authenticated via signed url, allow_unauth_graphs or allow_unauth_graphs_cidr
require base_path("/includes/html/graphs/$type/auth.inc.php");
if (! $auth) {
// We are unauthenticated :(
throw new RrdGraphException('No Authorization', 'No Auth', $width, $height);
}
if (is_customoid_graph($type, $subtype)) {
$unit = $vars['unit'];
require base_path('/includes/html/graphs/customoid/customoid.inc.php');
} elseif (is_file(base_path("/includes/html/graphs/$type/$subtype.inc.php"))) {
require base_path("/includes/html/graphs/$type/$subtype.inc.php");
} else {
throw new RrdGraphException("{$type}_$subtype template missing", "{$type}_$subtype missing", $width, $height);
}
if (empty($rrd_options)) {
throw new RrdGraphException('Graph Definition Error', 'Def Error', $width, $height);
}
$rrd_options = $graph_params . ' ' . $rrd_options;
// Generating the graph!
try {
$image_data = Rrd::graph($rrd_options);
return new GraphImage($graph_params->imageFormat, $graph_params->getTitle(), $image_data);
} catch (RrdGraphException $e) {
// preserve original error if debug is enabled, otherwise make it a little more user friendly
if (Debug::isEnabled()) {
throw $e;
}
if (isset($rrd_filename) && ! Rrd::checkRrdExists($rrd_filename)) {
throw new RrdGraphException('No Data file' . basename($rrd_filename), 'No Data', $width, $height, $e->getCode(), $e->getImage());
}
throw new RrdGraphException('Error: ' . $e->getMessage(), 'Draw Error', $width, $height, $e->getCode(), $e->getImage());
}
}
public static function getTypes(): array
{
return ['device', 'port', 'application', 'munin', 'service'];
}
/**
* Get an array of all graph subtypes for the given type
*
* @param string $type
* @param Device $device
* @return array
*/
public static function getSubtypes($type, $device = null): array
{
$types = [];
// find the subtypes defined in files
foreach (glob(base_path("/includes/html/graphs/$type/*.inc.php")) as $file) {
$type = basename($file, '.inc.php');
if ($type != 'auth') {
$types[] = $type;
}
}
if ($device != null) {
// find the MIB subtypes
$graphs = $device->graphs->pluck('graph');
foreach (Config::get('graph_types') as $type => $type_data) {
foreach (array_keys($type_data) as $subtype) {
if ($graphs->contains($subtype) && self::isMibGraph($type, $subtype)) {
$types[] = $subtype;
}
}
}
}
sort($types);
return $types;
}
/**
* Check if the given graph is a mib graph
*
* @param string $type
* @param string $subtype
* @return bool
*/
public static function isMibGraph($type, $subtype): bool
{
return Config::get("graph_types.$type.$subtype.section") == 'mib';
}
public static function getOverviewGraphsForDevice(Device $device): array
{
if ($device->snmp_disable) {
return Arr::wrap(Config::getOsSetting('ping', 'over'));
}
if ($graphs = Config::getOsSetting($device->os, 'over')) {
return Arr::wrap($graphs);
}
$os_group = Config::getOsSetting($device->os, 'group');
return Arr::wrap(Config::get("os_group.$os_group.over", Config::get('os.default.over')));
}
/**
* Create image to output text instead of a graph.
*
* @param string $text Error message to display
* @param string|null $short_text Error message for smaller graph images
* @param int $width Width of graph image (defaults to 300)
* @param int|null $height Height of graph image (defaults to width / 3)
* @param int[] $color Color of text, defaults to dark red
* @return string the generated image
*/
public static function error(string $text, ?string $short_text, int $width = 300, ?int $height = null, array $color = [128, 0, 0]): string
{
$type = Config::get('webui.graph_type');
$height = $height ?? $width / 3;
if ($short_text !== null && $width < 200) {
$text = $short_text;
}
if ($type === 'svg') {
$rgb = implode(', ', $color);
return <<<SVG
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
viewBox="0 0 $width $height"
preserveAspectRatio="xMinYMin">
<foreignObject x="0" y="0" width="$width" height="$height" transform="translate(0,0)">
<xhtml:div style="display:table; width:{$width}px; height:{$height}px; overflow:hidden;">
<xhtml:div style="display:table-cell; vertical-align:middle;">
<xhtml:div style="color:rgb($rgb); text-align:center; font-family:sans-serif; font-size:0.6em;">$text</xhtml:div>
</xhtml:div>
</xhtml:div>
</foreignObject>
</svg>
SVG;
}
$img = imagecreate($width, $height);
imagecolorallocatealpha($img, 255, 255, 255, 127); // transparent background
$px = (int) ((imagesx($img) - 7.5 * strlen($text)) / 2);
$font = $width < 200 ? 3 : 5;
imagestring($img, $font, $px, $height / 2 - 8, $text, imagecolorallocate($img, ...$color));
// Output the image
ob_start();
imagepng($img);
$output = ob_get_clean();
ob_end_clean();
imagedestroy($img);
return $output;
}
}