From 3855d2b29ffb9c8171b8aeb8bcb2848e2803cc7d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Mar 2022 18:05:58 +0100 Subject: [PATCH] Make graphs themeable --- application/controllers/GraphController.php | 7 +- application/views/scripts/list/hosts.phtml | 1 + application/views/scripts/list/services.phtml | 1 + .../Monitoring/DetailviewExtension.php | 4 +- library/Graphite/Web/Widget/Graphs.php | 12 +- public/css/module.less | 23 ++ public/js/module.js | 279 ++++++------------ 7 files changed, 126 insertions(+), 201 deletions(-) diff --git a/application/controllers/GraphController.php b/application/controllers/GraphController.php index 5f8a739..c2c147d 100644 --- a/application/controllers/GraphController.php +++ b/application/controllers/GraphController.php @@ -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()); diff --git a/application/views/scripts/list/hosts.phtml b/application/views/scripts/list/hosts.phtml index bf73337..ce0e37c 100644 --- a/application/views/scripts/list/hosts.phtml +++ b/application/views/scripts/list/hosts.phtml @@ -24,6 +24,7 @@ if (! $compact): ?>
'; echo '
'; foreach ($hosts as $host) { $hostGraphs = (string) (new Host($host))->setPreloadDummy()->handleRequest(); diff --git a/application/views/scripts/list/services.phtml b/application/views/scripts/list/services.phtml index fda22e1..90ca03c 100644 --- a/application/views/scripts/list/services.phtml +++ b/application/views/scripts/list/services.phtml @@ -25,6 +25,7 @@ if (! $compact): ?>
'; echo '
'; foreach ($services as $service) { echo '
' diff --git a/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php index 3db1813..bfc2fa1 100644 --- a/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php +++ b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php @@ -30,7 +30,9 @@ class DetailviewExtension extends DetailviewExtensionHook if ($graphs !== '') { $this->handleTimeRangePickerRequest(); return '

' . mt('graphite', 'Graphs') . '

' - . $this->renderTimeRangePicker($this->getView()) . $graphs; + . $this->renderTimeRangePicker($this->getView()) + . '
' + . $graphs; } return ''; diff --git a/library/Graphite/Web/Widget/Graphs.php b/library/Graphite/Web/Widget/Graphs.php index 464f25e..55a2d97 100644 --- a/library/Graphite/Web/Widget/Graphs.php +++ b/library/Graphite/Web/Widget/Graphs.php @@ -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 = '\"\"width\" height=\"$this->height\"" . " style=\"min-width: {$this->width}px; min-height: {$this->height}px;\">"; } diff --git a/public/css/module.less b/public/css/module.less index 0a9881c..384c86b 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -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; +} diff --git a/public/js/module.js b/public/js/module.js index a4d9618..a2a32f2 100644 --- a/public/js/module.js +++ b/public/js/module.js @@ -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);