mirror of
https://github.com/Icinga/icingaweb2-module-graphite.git
synced 2026-02-18 18:25:09 -05:00
Make graphs themeable
This commit is contained in:
parent
7b5263f7c9
commit
3855d2b29f
7 changed files with 126 additions and 201 deletions
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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">'
|
||||
|
|
|
|||
|
|
@ -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 '';
|
||||
|
|
|
|||
|
|
@ -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;\">";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue