contrib/php-collection: Save/load graph list and fix HTTPS handling. collectd-4.4
authorBruno Prémont <bonbons@linux-vserver.org>
Tue, 10 Feb 2009 20:38:23 +0000 (21:38 +0100)
committerFlorian Forster <octo@huhu.verplant.org>
Tue, 10 Feb 2009 20:38:23 +0000 (21:38 +0100)
Hi,

An update to the graphing interface. (incremental patch attached)

This fixes security complaint by a few browsers when page is accessed
via HTTPS and also makes sure the graphs and lists will load if HTTP
and HTTPS paths differ.
Thanks to Mullet- for spotting this issue.

In addition to the fix above this patch adds support for saving
current graph list to a cookie and appending graph list read from
cookie to the currently displayed list.

A future extension would be to allow saving/loading named graph lists
so multiple list can coexist (pretty useful when monitoring multiple
servers/services)

Bruno

contrib/php-collection/browser.js
contrib/php-collection/functions.php
contrib/php-collection/index.php

index 9fcdbab..6f4d8ec 100644 (file)
@@ -37,6 +37,8 @@ function toggleDiv(divID) {
        }
 }
 
+var req = null;
+
 // DHTML helper code to asynchronous loading of content
 function loadXMLDoc(url, query) {
        if (window.XMLHttpRequest) {
@@ -57,12 +59,13 @@ function loadXMLDoc(url, query) {
 }
 
 // DHTML new-content dispatcher
-function processReqChange() {
+function processReqChange(evt) {
        if (req.readyState == 4) {
                if (req.status == 200) {
-                       response = req.responseXML.documentElement;
-                       method = response.getElementsByTagName('method')[0].firstChild.data;
-                       result = response.getElementsByTagName('result')[0];
+                       var response = req.responseXML.documentElement;
+                       var method = response.getElementsByTagName('method')[0].firstChild.data;
+                       var result = response.getElementsByTagName('result')[0];
+                       req = null;
                        eval(method + '(result)');
                }
        }
@@ -265,9 +268,9 @@ function RefreshButtons() {
 }
 
 var nextGraphId = 1;
+var graphList = new Array();
 
 function GraphAppend() {
-       var graphs      = document.getElementById('graphs');
        var host_list   = document.getElementById('host_list');
        var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
        var plugin_list = document.getElementById('plugin_list');
@@ -278,6 +281,15 @@ function GraphAppend() {
        var type        = type_list.selectedIndex >= 0 ? type_list.options[type_list.selectedIndex].value : '/';
        var tinst_list  = document.getElementById('tinst_list');
        var tinst       = tinst_list.selectedIndex >= 0 ? tinst_list.options[tinst_list.selectedIndex].value : '/';
+       var time_list   = document.getElementById('timespan');
+       var timespan    = time_list.selectedIndex >= 0 ? time_list.options[time_list.selectedIndex].value : '';
+       var tinyLegend  = document.getElementById('tinylegend').checked;
+       var logarithmic = document.getElementById('logarithmic').checked
+       GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+}
+
+function GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic) {
+       var graphs      = document.getElementById('graphs');
 
        if (host != '/' && plugin != '/' && pinst != '/' && type != '/') {
                var graph_id   = 'graph_'+nextGraphId++;
@@ -292,14 +304,15 @@ function GraphAppend() {
                        graph_title = type+(tinst.length > 0 ? '-'+tinst : '')+' of '+plugin+(pinst.length > 0 ? '-'+pinst : '')+' plugin for '+host;
                        graph_src  += '&type_instance='+encodeURIComponent(tinst);
                }
-               if (document.getElementById('logarithmic').checked)
+               if (logarithmic)
                        graph_src += '&logarithmic=1';
-               if (document.getElementById('tinylegend').checked)
+               if (tinyLegend)
                        graph_src += '&tinylegend=1';
-               if (document.getElementById('timespan').selectedIndex >= 0)
-                       graph_src += '&timespan='+encodeURIComponent(document.getElementById('timespan').options[document.getElementById('timespan').selectedIndex].value);
+               if (timespan)
+                       graph_src += '&timespan='+encodeURIComponent(timespan);
                var now    = new Date();
                graph_src += '&ts='+now.getTime();
+               graphList.push(graph_id+' '+encodeURIComponent(graph_alt)+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'&timespan='+encodeURIComponent(timespan));
 
                // Graph container
                newGraph = document.createElement('div');
@@ -372,6 +385,7 @@ function GraphDropAll() {
                        graphs.removeChild(graphs.childNodes[childCnt]);
                else if (graphs.childNodes[childCnt].id == 'nograph')
                        graphs.childNodes[childCnt].style.display = 'block';
+       graphList = new Array();
        RefreshButtons();
 }
 
@@ -413,6 +427,14 @@ function GraphRemove(graph) {
                RefreshButtons();
                if (graphs.getElementsByTagName('div').length == 1)
                        document.getElementById('nograph').style.display = 'block';
+
+               var myList = Array();
+               for (i = 0; i < graphList.length; i++)
+                       if (graphList[i].substring(0, graph.length) == graph && graphList[i].charAt(graph.length) == ' ')
+                               continue;
+                       else
+                               myList.push(graphList[i]);
+               graphList = myList;
        }
 }
 
@@ -434,6 +456,15 @@ function GraphMoveUp(graph) {
                        } else
                                prevGraph = graphs.childNodes[i];
                }
+       for (i = 0; i < graphList.length; i++)
+               if (graphList[i].substring(0, graph.length) == graph && graphList[i].charAt(graph.length) == ' ') {
+                       if (i > 0) {
+                               var tmp = graphList[i-1];
+                               graphList[i-1] = graphList[i];
+                               graphList[i]   = tmp;
+                       }
+                       break;
+               }
 }
 
 function GraphMoveDown(graph) {
@@ -454,5 +485,94 @@ function GraphMoveDown(graph) {
                                break;
                        }
                }
+       for (i = 0; i < graphList.length; i++)
+               if (graphList[i].substring(0, graph.length) == graph && graphList[i].charAt(graph.length) == ' ') {
+                       if (i+1 < graphList.length) {
+                               var tmp = graphList[i+1];
+                               graphList[i+1] = graphList[i];
+                               graphList[i]   = tmp;
+                       }
+                       break;
+               }
+}
+
+function GraphListFromCookie() {
+       if (document.cookie.length > 0) {
+               cookies = document.cookie.split('; ');
+               for (i = 0; i < cookies.length; i++)
+                       if (cookies[i].substring(0, 9) == 'graphLst=')
+                               return cookies[i].substring(9).split('/');
+       }
+       return new Array();
+}
+
+function GraphSave() {
+       if (graphList.length > 0) {
+               // Save graph list to cookie
+               var str = '';
+               for (i = 0; i < graphList.length; i++) {
+                       var g = graphList[i].indexOf(' ');
+                       if (i > 0)
+                               str += '/';
+                       str += graphList[i].substring(g+1);
+               }
+
+               document.cookie = 'graphLst='+str;
+               if (GraphListFromCookie().length == 0)
+                       alert("Failed to save graph list to cookie.");
+               else
+                       alert("Successfully saved current graph list.");
+       } else {
+               document.cookie = 'graphLst=; expires='+new Date().toGMTString();
+               alert("Cleared saved graph list.");
+       }
+}
+
+function GraphLoad() {
+       // Load graph list from cookie
+       var grLst = GraphListFromCookie();
+       for (i = 0; i < grLst.length; i++) {
+               var host        = '';
+               var plugin      = '';
+               var pinst       = '';
+               var type        = '';
+               var tinst       = '';
+               var timespan    = '';
+               var logarithmic = false;
+               var tinyLegend  = false;
+               var graph = grLst[i].split('&');
+               for (j = 0; j < graph.length; j++)
+                       if (graph[j] == 'logarithmic=1')
+                               logarithmic = true;
+                       else if (graph[j] == 'tinylegend=1')
+                               tinyLegend = true;
+                       else if (graph[j].substring(0, 9) == 'timespan=')
+                               timespan = decodeURIComponent(graph[j].substring(9));
+               graph = decodeURIComponent(graph[0]).split('/');
+               host = graph[0];
+               if (graph.length > 1) {
+                       var g = graph[1].indexOf('-');
+                       if (g >= 0) {
+                               plugin = graph[1].substring(0, g);
+                               pinst  = graph[1].substring(g+1);
+                       } else
+                               plugin = graph[1];
+               }
+               if (graph.length > 2) {
+                       var g = graph[2].indexOf('-');
+                       if (g >= 0) {
+                               type  = graph[2].substring(0, g);
+                               tinst = graph[2].substring(g+1);
+                       } else
+                               type  = graph[2];
+               }
+
+               if (host && plugin && type)
+                       GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+       }
+       if (grLst.length == 0)
+               alert("No list found for loading.");
+       else if (grLst.length != graphList.length)
+               alert("Could not load all graphs, probably damaged cookie.");
 }
 
index f4d07e0..9fb6116 100644 (file)
@@ -75,7 +75,8 @@ function collectd_list_hosts() {
                                if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$dent) && preg_match(REGEXP_HOST, $dent))
                                        $hosts[] = $dent;
                        closedir($d);
-               }
+               } else
+                       error_log('Failed to open datadir: '.$datadir);
        $hosts = array_unique($hosts);
        usort($hosts, 'collectd_compare_host');
        return $hosts;
index 93f1fcf..1f788fc 100644 (file)
@@ -58,6 +58,7 @@ function build_page() {
                print('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'."\n");
                print('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">'."\n");
        }
+       $url_base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/';
 ?>
        <head>
                <title>Collectd graph viewer</title>
@@ -65,7 +66,8 @@ function build_page() {
                <style type="text/css">
                        body, html { background-color: #EEEEEE; color: #000000; }
                        h1 { text-align: center; }
-                       div.body { margin: auto; width: <?php echo 125+$config['rrd_width'] ?>px; background: #FFFFFF; border: 1px solid #DDDDDD; }
+                       div.body { margin: auto; width: <?php echo isset($config['rrd_width']) ? 125+(int)$config['rrd_width'] : 600; ?>px; background: #FFFFFF; border: 1px solid #DDDDDD; }
+                       p.error { color: #CC0000; margin: 0em; }
                        div.selector { margin: 0.5em 2em; }
                        div.selectorbox { padding: 5px; border: 1px solid #CCCCCC; background-color: #F8F8F8; }
                        div.selectorbox table { border: none; }
@@ -82,10 +84,10 @@ function build_page() {
                        select { width: 100%; }
                </style>
                <script type="text/javascript">// <![CDATA[
-var dhtml_url = '<?php echo addslashes('http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']); ?>';
-var graph_url = '<?php echo addslashes('http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/graph.php'); ?>';
+var dhtml_url = '<?php echo addslashes($url_base.basename($_SERVER['PHP_SELF'])); ?>';
+var graph_url = '<?php echo addslashes($url_base.'graph.php'); ?>';
 //             ]]></script>
-               <script type="text/javascript" src="browser.js"></script>
+               <script type="text/javascript" src="<?php echo htmlspecialchars($url_base.'browser.js'); ?>"></script>
        </head>
 
        <body onload="ListRefreshHost()"><div class="body">
@@ -135,12 +137,52 @@ var graph_url = '<?php echo addslashes('http://'.$_SERVER['HTTP_HOST'].dirname($
                                <tr>
                                        <td class="sc" colspan="3"><input id="btnAdd"     name="btnAdd"     type="button" disabled="disabled" onclick="GraphAppend()" value="Add graph" />
                                        <input id="btnClear"   name="btnClear"   type="button" disabled="disabled" onclick="GraphDropAll()" value="Remove all graphs" />
-                                       <input id="btnRefresh" name="btnRefresh" type="button" disabled="disabled" onclick="GraphRefresh(null)" value="Refresh all graphs" /></td>
+                                       <input id="btnRefresh" name="btnRefresh" type="button" disabled="disabled" onclick="GraphRefresh(null)" value="Refresh all graphs" />
+                                       <input id="btnSave"    name="btnSave"    type="button" onclick="GraphSave()" value="Save" title="Save graph list to cookie" />
+                                       <input id="btnLoad"    name="btnLoad"    type="button" onclick="GraphLoad()" value="Load" title="Load graph list from cookie" /></td>
                                </tr>
                        </table>
                </div></div>
                <div class="graphs"><div id="graphs" class="graphs_t">
-                       <div id="nograph">Please use above graph selection tool to add graphs to this area.</div>
+                       <div id="nograph">Please use above graph selection tool to add graphs to this area.<?php
+                       // Config checking
+                       if (!isset($config['datadirs']))
+                               echo '<p class="error">Config error: $config["datadirs"] is not set</p>';
+                       else if (!is_array($config['datadirs']))
+                               echo '<p class="error">Config error: $config["datadirs"] is not an array</p>';
+                       else if (count($config['datadirs']) == 0)
+                               echo '<p class="error">Config error: $config["datadirs"] is empty</p>';
+                       else foreach ($config['datadirs'] as $datadir)
+                               if (!is_dir($datadir))
+                                       echo '<p class="error">Config error: $config["datadirs"], '.htmlspecialchars($datadir).' does not exist</p>';
+                       if (!isset($config['rrd_width']))
+                               echo '<p class="error">Config error: $config["rrd_width"] is not set</p>';
+                       else if (10 > (int)$config['rrd_width'])
+                               echo '<p class="error">Config error: $config["rrd_width"] is invalid. Integer >= 10 expected</p>';
+                       if (!isset($config['rrd_height']))
+                               echo '<p class="error">Config error: $config["rrd_height"] is not set</p>';
+                       else if (10 > (int)$config['rrd_height'])
+                               echo '<p class="error">Config error: $config["rrd_height"] is invalid. Integer >= 10 expected</p>';
+                       if (!isset($config['rrd_opts']))
+                               echo '<p class="error">Config error: $config["rrd_opts"] is not set</p>';
+                       else if (!is_array($config['rrd_opts']))
+                               echo '<p class="error">Config error: $config["rrd_opts"] is not an array</p>';
+                       if (!isset($config['timespan']))
+                               echo '<p class="error">Config error: $config["timespan"] is not set</p>';
+                       else if (!is_array($config['timespan']))
+                               echo '<p class="error">Config error: $config["timespan"] is not an array</p>';
+                       else if (count($config['timespan']) == 0)
+                               echo '<p class="error">Config error: $config["timespan"] is empty</p>';
+                       else foreach ($config['timespan'] as &$timespan)
+                               if (!is_array($timespan) || !isset($timespan['name']) || !isset($timespan['label']) || !isset($timespan['seconds']) || 10 > (int)$timespan['seconds'])
+                                       echo '<p class="error">Config error: $config["timespan"], invalid entry found</p>';
+                       if (!is_null($config['collectd_sock']) && strncmp('unix://', $config['collectd_sock'], 7) != 0)
+                               echo '<p class="error">Config error: $config["collectd_sock"] is not valid</p>';
+                       if (!defined('RRDTOOL'))
+                               echo '<p class="error">Config error: RRDTOOL is not defined</p>';
+                       else if (!is_executable(RRDTOOL))
+                               echo '<p class="error">Config error: RRDTOOL ('.htmlspecialchars(RRDTOOL).') is not executable</p>';
+                       ?></div>
                </div></div>
        </div></body>
 </html><?php