Make graphs themeable

This commit is contained in:
Johannes Meyer 2022-03-07 18:05:58 +01:00
parent 7b5263f7c9
commit 3855d2b29f
7 changed files with 126 additions and 201 deletions

View file

@ -26,7 +26,8 @@ class GraphController extends MonitoringAwareController
'width', 'height',
'legend',
'template', 'default_template',
'cachebuster'
'bgcolor', 'fgcolor',
'majorGridLineColor', 'minorGridLineColor'
];
/**
@ -136,6 +137,10 @@ class GraphController extends MonitoringAwareController
->setUntil($this->graphParams['end'])
->setWidth($this->graphParams['width'])
->setHeight($this->graphParams['height'])
->setBackgroundColor($this->graphParams['bgcolor'])
->setForegroundColor($this->graphParams['fgcolor'])
->setMajorGridLineColor($this->graphParams['majorGridLineColor'])
->setMinorGridLineColor($this->graphParams['minorGridLineColor'])
->setShowLegend((bool) $this->graphParams['legend'])
->serveImage($this->getResponse());

View file

@ -24,6 +24,7 @@ if (! $compact): ?>
<div class="content">
<?php
if (! empty($hosts)) {
echo '<div class="graphite-graph-color-registry"></div>';
echo '<div class="grid">';
foreach ($hosts as $host) {
$hostGraphs = (string) (new Host($host))->setPreloadDummy()->handleRequest();

View file

@ -25,6 +25,7 @@ if (! $compact): ?>
<div class="content">
<?php
if (! empty($services)) {
echo '<div class="graphite-graph-color-registry"></div>';
echo '<div class="grid">';
foreach ($services as $service) {
echo '<div class="grid-item">'

View file

@ -30,7 +30,9 @@ class DetailviewExtension extends DetailviewExtensionHook
if ($graphs !== '') {
$this->handleTimeRangePickerRequest();
return '<h2>' . mt('graphite', 'Graphs') . '</h2>'
. $this->renderTimeRangePicker($this->getView()) . $graphs;
. $this->renderTimeRangePicker($this->getView())
. '<div class="graphite-graph-color-registry"></div>'
. $graphs;
}
return '';

View file

@ -271,6 +271,10 @@ abstract class Graphs extends AbstractWidget
->setUntil($this->end)
->setWidth($this->width)
->setHeight($this->height)
->setBackgroundColor('white')
->setForegroundColor('black')
->setMajorGridLineColor('grey')
->setMinorGridLineColor('white')
->setShowLegend(! $this->compact);
$img = new InlineGraphImage($chart);
@ -280,8 +284,7 @@ abstract class Graphs extends AbstractWidget
->setParam('start', $this->start)
->setParam('end', $this->end)
->setParam('width', $this->width)
->setParam('height', $this->height)
->setParam('cachebuster', time() * 65536 + mt_rand(0, 65535));
->setParam('height', $this->height);
if (! $this->compact) {
$imageUrl->setParam('legend', 1);
@ -295,9 +298,8 @@ abstract class Graphs extends AbstractWidget
$src = $imageUrl;
}
$img = '<img id="graphiteImg-'
. md5((string) $imageUrl->without('cachebuster'))
. "\" src=\"$src\" data-actualimageurl=\"$imageUrl\" class=\"detach graphiteImg\""
$img = '<img id="graphiteImg-' . md5((string) $imageUrl) . '"'
. " src=\"$src\" data-actualimageurl=\"$imageUrl\" class=\"detach graphiteImg\""
. " alt=\"\" width=\"$this->width\" height=\"$this->height\""
. " style=\"min-width: {$this->width}px; min-height: {$this->height}px;\">";
}

View file

@ -125,3 +125,26 @@ p.load-more {
margin-right: 1em;
text-align: right;
}
/* Graph colors */
@graphite-graph-bg-color: @body-bg-color;
@graphite-graph-fg-color: @text-color;
@graphite-graph-major-grid-color: @gray-light;
@graphite-graph-minor-grid-color: @graphite-graph-bg-color;
@light-mode: {
--graphite-graph-bg-color: var(--body-bg-color);
--graphite-graph-fg-color: var(--text-color);
--graphite-graph-major-grid-color: var(--gray-light);
--graphite-graph-minor-grid-color: var(--graphite-graph-bg-color);
};
.graphite-graph-color-registry {
display: none;
background-color: @graphite-graph-bg-color;
color: @graphite-graph-fg-color;
border-top-color: @graphite-graph-major-grid-color;
border-bottom-color: @graphite-graph-minor-grid-color;
}

View file

@ -1,221 +1,112 @@
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
(function(Icinga) {
var Graphite = function(module) {
/**
* YES, we need Icinga
*/
this.module = module;
"use strict";
this.imgClones = {
'col1': [],
'col2': []
};
class Graphite extends Icinga.EventListener {
constructor(icinga) {
super(icinga);
this.lastImgId = 0;
this._colorParams = null;
this._resizeTimer = null;
this._onResizeBound = this.onResize.bind(this);
this._onModeChangeBound = this.onModeChange.bind(this);
this._mediaQueryList = window.matchMedia('(prefers-color-scheme: light)');
this.initialize();
this.on('css-reloaded', 'head', this.onCssReloaded, this);
this.on('rendered', '#main > .container', this.onRendered, this);
window.addEventListener('resize', this._onResizeBound, { passive: true });
this._mediaQueryList.addEventListener('change', this._onModeChangeBound, { passive: true });
}
this.timer;
get colorParams() {
if (this._colorParams === null) {
let colorRegistry = document.querySelector('.graphite-graph-color-registry');
let registryStyle = window.getComputedStyle(colorRegistry);
this.module.icinga.logger.debug('Graphite module loaded');
};
this._colorParams = {
bgcolor: this.rgbToHex(registryStyle.backgroundColor, 'black'),
fgcolor: this.rgbToHex(registryStyle.color, 'white'),
majorGridLineColor: this.rgbToHex(registryStyle.borderTopColor, '0000003F'),
minorGridLineColor: this.rgbToHex(registryStyle.borderBottomColor, 'black')
};
}
Graphite.prototype = {
return this._colorParams;
}
initialize: function()
{
this.module.on('rendered', this.onRenderedContainer);
this.registerTimer();
this.module.icinga.logger.debug('Graphite module initialized');
},
unbind(emitter) {
super.unbind(emitter);
registerTimer: function () {
this.timer = this.module.icinga.timer.register(
this.timerTriggered,
this,
8000
);
window.removeEventListener('resize', this._onResizeBound);
this._mediaQueryList.removeEventListener('change', this._onModeChangeBound);
return this;
},
this._onResizeBound = null;
this._onModeChangeBound = null;
this._mediaQueryList = null;
}
timerTriggered: function () {
/// console.log('Graphite timer fired');
var self = this;
$.each(this.imgClones, this.reloadContainerImgs.bind(self));
},
onCssReloaded(event) {
let _this = event.data.self;
reloadContainerImgs: function(idx, imgs)
{
$.each(imgs, this.reloadImg);
},
_this._colorParams = null;
_this.updateImages(document);
}
reloadImg: function(idx, img)
{
// console.log('Schedule reload for ', img);
var realId = img.attr('id').replace(/_clone$/, '');
$('#' + realId).attr('src', img.attr('src'));
img.attr(
'src',
img.attr('src').replace(
/\&r=\d+/,
'&r=' + (new Date()).getTime()
)
);
},
onRendered(event, autorefresh, scripted, autosubmit) {
let _this = event.data.self;
let container = event.target;
onRenderedContainer: function(event) {
var $container = $(event.currentTarget);
var self = this;
var cId = $container.attr('id');
self.imgClones[cId] = [];
$('#' + cId + ' img.graphiteImg').each(function(idx, img) {
var $img = $(img);
if (! $img.attr('id')) {
self.lastImgId++;
$(img).attr('id', 'graphiteImg' + self.lastImgId);
}
_this.updateImages(container);
}
self.imgClones[cId].push(
$(
$img.clone()
.addClass('graphiteClone')
.attr('id', $img.attr('id') + '_clone')
// .data('
.attr(
'src',
$img.attr('src') + '&r=' + (new Date()).getTime()
)
onResize() {
// Images are not updated instantly, the user might not yet be finished resizing the window
if (this._resizeTimer !== null) {
clearTimeout(this._resizeTimer);
}
)
);
this._resizeTimer = setTimeout(() => this.updateImages(document), 200);
}
onModeChange() {
this._colorParams = null;
this.updateImages(document);
}
updateImages(container) {
container.querySelectorAll('img.graphiteImg[data-actualimageurl]').forEach(img => {
let params = { ...this.colorParams }; // Theming ftw!
params.r = (new Date()).getTime(); // To bypass the browser cache
params.width = img.scrollWidth; // It's either fixed or dependent on parent width
img.src = this.icinga.utils.addUrlParams(img.dataset.actualimageurl, params);
});
}
};
Icinga.availableModules.graphite = Graphite;
}(Icinga));
(function(Icinga, $) {
'use strict';
var extractUrlParams = /^([^?]*)\?(.+)$/;
var parseUrlParam = /^([^=]+)=(.*)$/;
function GraphiteCachebusterUpdater(icinga) {
Icinga.EventListener.call(this, icinga);
this.on('rendered', this.onRendered, this);
}
GraphiteCachebusterUpdater.prototype = new Icinga.EventListener();
GraphiteCachebusterUpdater.prototype.onRendered = function(event) {
$(event.target).find('img.graphiteImg').each(function() {
var e = $(this);
var src = e.attr('src');
if (typeof(src) !== 'undefined') {
var matchParams = extractUrlParams.exec(src);
if (matchParams !== null) {
var urlParams = Object.create(null);
matchParams[2].split('&').forEach(function(urlParam) {
var matchParam = parseUrlParam.exec(urlParam);
if (matchParam !== null) {
urlParams[matchParam[1]] = matchParam[2];
}
});
if (typeof(urlParams.cachebuster) !== 'undefined') {
var cachebuster = parseInt(urlParams.cachebuster);
if (cachebuster === cachebuster) {
urlParams.cachebuster = (cachebuster + 1).toString();
var renderedUrlParams = [];
for (var urlParam in urlParams) {
renderedUrlParams.push(urlParam + '=' + urlParams[urlParam]);
}
e.attr('src', matchParams[1] + '?' + renderedUrlParams.join('&'));
}
}
}
rgbToHex(rgb, def) {
if (! rgb) {
return def;
}
});
};
Icinga.Behaviors = Icinga.Behaviors || {};
Icinga.Behaviors.GraphiteCachebusterUpdater = GraphiteCachebusterUpdater;
}(Icinga, jQuery));
(function(Icinga, $) {
'use strict';
var extractUrlParams = /^([^?]*)\?(.+)$/;
var parseUrlParam = /^([^=]+)=(.*)$/;
function updateGraphSizes() {
$("div.images.monitored-object-detail-view img.graphiteImg").each(function() {
var e = $(this);
var src = e.attr("data-actualimageurl");
if (typeof(src) !== "undefined") {
var matchParams = extractUrlParams.exec(src);
if (matchParams !== null) {
var urlParams = Object.create(null);
matchParams[2].split("&").forEach(function(urlParam) {
var matchParam = parseUrlParam.exec(urlParam);
if (matchParam !== null) {
urlParams[matchParam[1]] = matchParam[2];
}
});
if (typeof(urlParams.width) !== "undefined") {
var realWidth = e.width().toString();
if (urlParams.width !== realWidth) {
urlParams.width = realWidth;
var renderedUrlParams = [];
for (var urlParam in urlParams) {
renderedUrlParams.push(urlParam + "=" + urlParams[urlParam]);
}
src = matchParams[1] + "?" + renderedUrlParams.join("&");
e.attr("data-actualimageurl", src);
e.attr("src", src);
}
}
}
let match = rgb.match(/rgba?\((\d+), (\d+), (\d+)(?:, ([\d.]+))?\)/);
if (match === null) {
return def;
}
});
let alpha = '';
if (typeof match[4] !== 'undefined') {
alpha = Math.round(parseFloat(match[4]) * 255).toString(16);
}
return parseInt(match[1], 10).toString(16).padStart(2, '0')
+ parseInt(match[2], 10).toString(16).padStart(2, '0')
+ parseInt(match[3], 10).toString(16).padStart(2, '0')
+ alpha;
}
}
function MonitoredObjectDetailViewExtensionUpdater(icinga) {
Icinga.EventListener.call(this, icinga);
Icinga.Behaviors.Graphite = Graphite;
this.on('rendered', this.onRendered, this);
}
MonitoredObjectDetailViewExtensionUpdater.prototype = Object.create(Icinga.EventListener.prototype);
MonitoredObjectDetailViewExtensionUpdater.prototype.onRendered = function() {
$(window).on('resize', updateGraphSizes);
updateGraphSizes();
};
Icinga.Behaviors = Icinga.Behaviors || {};
Icinga.Behaviors.MonitoredObjectDetailViewGraphiteExtensionUpdater = MonitoredObjectDetailViewExtensionUpdater;
}(Icinga, jQuery));
})(Icinga);