mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Return GraphImage to include more metadata (#14307)
* Return GraphImage to include more metadata Allows things like including title. Implements __toString for backwards compatability getImageData to allow controlling the output through flags * Style and Lint
This commit is contained in:
91
LibreNMS/Data/Graphing/GraphImage.php
Normal file
91
LibreNMS/Data/Graphing/GraphImage.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GraphImage.php
|
||||||
|
*
|
||||||
|
* Wrapper around a graph image to include metadata and control output format
|
||||||
|
*
|
||||||
|
* 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 2022 Tony Murray
|
||||||
|
* @author Tony Murray <murraytony@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace LibreNMS\Data\Graphing;
|
||||||
|
|
||||||
|
class GraphImage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $type;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $title;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $data;
|
||||||
|
|
||||||
|
public function __construct(string $type, string $title, string $data)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): string
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function base64(): string
|
||||||
|
{
|
||||||
|
return base64_encode($this->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function inline(): string
|
||||||
|
{
|
||||||
|
return 'data:' . $this->imageType() . ';base64,' . $this->base64();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileExtension(): string
|
||||||
|
{
|
||||||
|
switch ($this->imageType()) {
|
||||||
|
case 'image/svg+xml':
|
||||||
|
return 'svg';
|
||||||
|
case 'image/png':
|
||||||
|
// fallthrough
|
||||||
|
default:
|
||||||
|
return 'png';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function imageType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->data();
|
||||||
|
}
|
||||||
|
}
|
@@ -29,6 +29,7 @@ use App\Facades\DeviceCache;
|
|||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use LibreNMS\Config;
|
use LibreNMS\Config;
|
||||||
|
use LibreNMS\Data\Graphing\GraphImage;
|
||||||
use LibreNMS\Exceptions\RrdGraphException;
|
use LibreNMS\Exceptions\RrdGraphException;
|
||||||
use Rrd;
|
use Rrd;
|
||||||
|
|
||||||
@@ -36,19 +37,66 @@ class Graph
|
|||||||
{
|
{
|
||||||
const BASE64_OUTPUT = 1; // BASE64 encoded image data
|
const BASE64_OUTPUT = 1; // BASE64 encoded image data
|
||||||
const INLINE_BASE64 = 2; // img src inline base64 image
|
const INLINE_BASE64 = 2; // img src inline base64 image
|
||||||
const COMMAND_ONLY = 4; // just print the command
|
const IMAGE_PNG = 4; // img src inline base64 image
|
||||||
|
const IMAGE_SVG = 8; // img src inline base64 image
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a graph image (as string) based on the given $vars
|
* Convenience helper to specify desired image output
|
||||||
* Optionally, override the output format to base64
|
|
||||||
*
|
*
|
||||||
* @param array|string $vars
|
* @param array|string $vars
|
||||||
* @param int $flags Flags for controlling graph generating options.
|
* @param int $flags
|
||||||
* @return string
|
* @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(self::imageType(), 'Error', $e->generateErrorImage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a GraphImage based on the given $vars
|
||||||
|
*
|
||||||
|
* @param array|string $vars
|
||||||
|
* @return GraphImage
|
||||||
*
|
*
|
||||||
* @throws \LibreNMS\Exceptions\RrdGraphException
|
* @throws \LibreNMS\Exceptions\RrdGraphException
|
||||||
*/
|
*/
|
||||||
public static function get($vars, int $flags = 0): string
|
public static function get($vars): GraphImage
|
||||||
{
|
{
|
||||||
define('IGNORE_ERRORS', true);
|
define('IGNORE_ERRORS', true);
|
||||||
chdir(base_path());
|
chdir(base_path());
|
||||||
@@ -80,13 +128,11 @@ class Graph
|
|||||||
$title = $vars['title'] ?? '';
|
$title = $vars['title'] ?? '';
|
||||||
$vertical = $vars['vertical'] ?? '';
|
$vertical = $vars['vertical'] ?? '';
|
||||||
$legend = $vars['legend'] ?? false;
|
$legend = $vars['legend'] ?? false;
|
||||||
$output = $vars['output'] ?? 'default';
|
|
||||||
$from = parse_at_time($vars['from'] ?? '-1d');
|
$from = parse_at_time($vars['from'] ?? '-1d');
|
||||||
$to = empty($vars['to']) ? time() : parse_at_time($vars['to']);
|
$to = empty($vars['to']) ? time() : parse_at_time($vars['to']);
|
||||||
$period = ($to - $from);
|
$period = ($to - $from);
|
||||||
$prev_from = ($from - $period);
|
$prev_from = ($from - $period);
|
||||||
$graph_image_type = $vars['graph_type'] ?? Config::get('webui.graph_type');
|
$graph_image_type = $vars['graph_type'] ?? Config::get('webui.graph_type');
|
||||||
Config::set('webui.graph_type', $graph_image_type); // set in case accessed elsewhere
|
|
||||||
$rrd_options = '';
|
$rrd_options = '';
|
||||||
$rrd_filename = null;
|
$rrd_filename = null;
|
||||||
|
|
||||||
@@ -113,26 +159,6 @@ class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// command output requested
|
|
||||||
if ($flags & self::COMMAND_ONLY) {
|
|
||||||
$cmd_output = "<div class='infobox'>";
|
|
||||||
$cmd_output .= "<p style='font-size: 16px; font-weight: bold;'>RRDTool Command</p>";
|
|
||||||
$cmd_output .= "<pre class='rrd-pre'>";
|
|
||||||
$cmd_output .= escapeshellcmd('rrdtool ' . Rrd::buildCommand('graph', Config::get('temp_dir') . '/' . strgen(), $rrd_options));
|
|
||||||
$cmd_output .= '</pre>';
|
|
||||||
try {
|
|
||||||
$cmd_output .= Rrd::graph($rrd_options);
|
|
||||||
} catch (RrdGraphException $e) {
|
|
||||||
$cmd_output .= "<p style='font-size: 16px; font-weight: bold;'>RRDTool Output</p>";
|
|
||||||
$cmd_output .= "<pre class='rrd-pre'>";
|
|
||||||
$cmd_output .= $e->getMessage();
|
|
||||||
$cmd_output .= '</pre>';
|
|
||||||
}
|
|
||||||
$cmd_output .= '</div>';
|
|
||||||
|
|
||||||
return $cmd_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($rrd_options)) {
|
if (empty($rrd_options)) {
|
||||||
throw new RrdGraphException('Graph Definition Error', 'Def Error', $width, $height);
|
throw new RrdGraphException('Graph Definition Error', 'Def Error', $width, $height);
|
||||||
}
|
}
|
||||||
@@ -141,16 +167,7 @@ class Graph
|
|||||||
try {
|
try {
|
||||||
$image_data = Rrd::graph($rrd_options);
|
$image_data = Rrd::graph($rrd_options);
|
||||||
|
|
||||||
// output the graph int the desired format
|
return new GraphImage(self::imageType($graph_image_type), $title ?? $graph_title, $image_data);
|
||||||
if (Debug::isEnabled()) {
|
|
||||||
return '<img src="data:' . self::imageType($graph_image_type) . ';base64,' . base64_encode($image_data) . '" alt="graph" />';
|
|
||||||
} elseif ($flags & self::BASE64_OUTPUT || $output == 'base64') {
|
|
||||||
return base64_encode($image_data);
|
|
||||||
} elseif ($flags & self::INLINE_BASE64 || $output == 'inline-base64') {
|
|
||||||
return 'data:' . self::imageType($graph_image_type) . ';base64,' . base64_encode($image_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image_data; // raw data
|
|
||||||
} catch (RrdGraphException $e) {
|
} catch (RrdGraphException $e) {
|
||||||
// preserve original error if debug is enabled, otherwise make it a little more user friendly
|
// preserve original error if debug is enabled, otherwise make it a little more user friendly
|
||||||
if (Debug::isEnabled()) {
|
if (Debug::isEnabled()) {
|
||||||
@@ -237,11 +254,15 @@ class Graph
|
|||||||
/**
|
/**
|
||||||
* Get the http content type of the image
|
* Get the http content type of the image
|
||||||
*
|
*
|
||||||
* @param string $type svg or png
|
* @param string|null $type svg or png
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function imageType(string $type): string
|
public static function imageType(?string $type = null): string
|
||||||
{
|
{
|
||||||
|
if ($type === null) {
|
||||||
|
$type = Config::get('webui.graph_type');
|
||||||
|
}
|
||||||
|
|
||||||
return $type === 'svg' ? 'image/svg+xml' : 'image/png';
|
return $type === 'svg' ? 'image/svg+xml' : 'image/png';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,7 +91,7 @@ class Mail
|
|||||||
$mail->WordWrap = 76;
|
$mail->WordWrap = 76;
|
||||||
$mail->Body = $message;
|
$mail->Body = $message;
|
||||||
if ($embedGraphs ?? Config::get('email_attach_graphs')) {
|
if ($embedGraphs ?? Config::get('email_attach_graphs')) {
|
||||||
self::embedGraphs($mail);
|
self::embedGraphs($mail, $html);
|
||||||
}
|
}
|
||||||
if ($html) {
|
if ($html) {
|
||||||
$mail->isHTML();
|
$mail->isHTML();
|
||||||
@@ -132,32 +132,37 @@ class Mail
|
|||||||
/**
|
/**
|
||||||
* Search for generated graph links, generate them, attach them to the email and update the url to a cid link
|
* Search for generated graph links, generate them, attach them to the email and update the url to a cid link
|
||||||
*/
|
*/
|
||||||
private static function embedGraphs(PHPMailer $mail): void
|
private static function embedGraphs(PHPMailer $mail, bool $html = false): void
|
||||||
{
|
{
|
||||||
$body = $mail->Body;
|
$body = $mail->Body;
|
||||||
|
|
||||||
// search for generated graphs
|
// search for generated graphs
|
||||||
preg_match_all('/ class=\"librenms-graph\" src=\"(.*?)\"/', $body, $match);
|
preg_match_all('#<img class=\"librenms-graph\" src=\"(.*?)\" ?/?>#', $body, $matches);
|
||||||
|
|
||||||
foreach (array_values(array_unique($match[1])) as $attachment_id => $url) {
|
$count = 0;
|
||||||
|
foreach (array_combine($matches[1], $matches[0]) as $url => $tag) {
|
||||||
try {
|
try {
|
||||||
$cid = "graph$attachment_id";
|
$cid = 'graph' . ++$count;
|
||||||
|
|
||||||
// fetch image, do not debug as it will return the wrong format.
|
// fetch image data
|
||||||
$prev = Debug::isEnabled();
|
$image = Graph::getImage($url);
|
||||||
Debug::set(false);
|
|
||||||
$image = Graph::get(Url::parseLegacyPathVars($url));
|
|
||||||
Debug::set($prev);
|
|
||||||
|
|
||||||
// attach image
|
// attach image
|
||||||
if (Config::get('webui.graph_type') == 'svg') {
|
$fileName = substr(Clean::fileName($image->title() ?: $cid), 0, 250);
|
||||||
$mail->addStringEmbeddedImage($image, $cid, "$cid.svg", PHPMailer::ENCODING_BASE64, 'image/svg+xml');
|
$mail->addStringEmbeddedImage(
|
||||||
} else {
|
$image->data(),
|
||||||
$mail->addStringEmbeddedImage($image, $cid, "$cid.png", PHPMailer::ENCODING_BASE64, 'image/png');
|
$cid,
|
||||||
}
|
$fileName . '.' . $image->fileExtension(),
|
||||||
|
PHPMailer::ENCODING_BASE64,
|
||||||
|
$image->imageType()
|
||||||
|
);
|
||||||
|
|
||||||
// update image tag to link to attached image
|
// update image tag to link to attached image, or just the image name
|
||||||
$body = str_replace($url, "cid:$cid", $body);
|
if ($html) {
|
||||||
|
$body = str_replace($url, "cid:$cid", $body);
|
||||||
|
} else {
|
||||||
|
$body = str_replace($tag, "[$fileName]", $body);
|
||||||
|
}
|
||||||
} catch (RrdGraphException|\PHPMailer\PHPMailer\Exception $e) {
|
} catch (RrdGraphException|\PHPMailer\PHPMailer\Exception $e) {
|
||||||
report($e);
|
report($e);
|
||||||
}
|
}
|
||||||
|
@@ -18,25 +18,35 @@ class GraphController extends Controller
|
|||||||
public function __invoke(Request $request, string $path = ''): Response
|
public function __invoke(Request $request, string $path = ''): Response
|
||||||
{
|
{
|
||||||
$vars = array_merge(Url::parseLegacyPathVars($request->path()), $request->except(['username', 'password']));
|
$vars = array_merge(Url::parseLegacyPathVars($request->path()), $request->except(['username', 'password']));
|
||||||
$vars['graph_type'] = $vars['graph_type'] ?? Config::get('webui.graph_type');
|
$output = $vars['graph_type'] ?? Config::get('webui.graph_type', 'default');
|
||||||
|
|
||||||
if (\Auth::check()) {
|
if (\Auth::check()) {
|
||||||
// only allow debug for logged in users
|
// only allow debug for logged in users
|
||||||
Debug::set(! empty($vars['debug']));
|
Debug::set(! empty($vars['debug']));
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Content-type' => Graph::imageType($vars['graph_type']),
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return response(Graph::get($vars), 200, Debug::isEnabled() ? [] : $headers);
|
$graph = Graph::get($vars);
|
||||||
|
|
||||||
|
if (Debug::isEnabled()) {
|
||||||
|
return response('<img src="' . $graph->inline() . '" alt="graph" />');
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Content-type' => $graph->imageType(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($output == 'base64') {
|
||||||
|
return response($graph, 200, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response($graph->data(), 200, $headers);
|
||||||
} catch (RrdGraphException $e) {
|
} catch (RrdGraphException $e) {
|
||||||
if (Debug::isEnabled()) {
|
if (Debug::isEnabled()) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response($e->generateErrorImage(), 500, $headers);
|
return response($e->generateErrorImage(), 500, ['Content-type' => Graph::imageType()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
Blade::directive('graphImage', function ($vars, $flags = 0) {
|
Blade::directive('graphImage', function ($vars, $flags = 0) {
|
||||||
return "<?php echo \LibreNMS\Util\Graph::get($vars, $flags); ?>";
|
return "<?php echo \LibreNMS\Util\Graph::getImageData($vars, $flags); ?>";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user