vigigraph / vigigraph / public / js / graph.js @ aebd30a6
History | View | Annotate | Download (17.8 KB)
1 |
// Copyright (C) 2006-2012 CS-SI
|
---|---|
2 |
// License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
|
3 |
|
4 |
var refresh_delay = 30; |
5 |
var graphs = [];
|
6 |
|
7 |
var old_fragment = ''; |
8 |
var skip_detection = 0; |
9 |
|
10 |
var logger = new Log(); |
11 |
logger.enableLog(); |
12 |
|
13 |
var Graph = new Class({ |
14 |
Implements: [Events, Options],
|
15 |
|
16 |
options: {
|
17 |
duration: 86400, |
18 |
start: null, |
19 |
autoRefresh: 0, |
20 |
refreshDelay: null, |
21 |
left: null, |
22 |
top: null, |
23 |
overlap: 10 // Pourcentage de recouvrement (voir #730) |
24 |
}, |
25 |
|
26 |
initialize: function (options, host, graph) { |
27 |
this.setOptions(options);
|
28 |
this.host = host;
|
29 |
this.graph = graph;
|
30 |
this.refreshTimer = null; |
31 |
this.destroyed = false; |
32 |
|
33 |
new Request.JSON({
|
34 |
url: app_path + 'rpc/startTime', |
35 |
onSuccess: function (data) { |
36 |
this.startTime = data.starttime.toInt();
|
37 |
}.bind(this)
|
38 |
}).get({'host': this.host}); |
39 |
|
40 |
var toolbar = new Jx.Toolbar({position:'top'}); |
41 |
var periods = [
|
42 |
[_('Last 12 hours'), 12], |
43 |
[_('Last 24 hours'), 24], |
44 |
[_('Last 48 hours'), 48], |
45 |
[_('Last 7 days'), 7*24], |
46 |
[_('Last 14 days'), 14*24], |
47 |
[_('Last month'), 30*24], |
48 |
[_('Last 3 months'), 3*30*24], |
49 |
[_('Last 6 months'), 6*30*24], |
50 |
[_('Last year'), 365*24] |
51 |
]; |
52 |
|
53 |
var timeframe = new Jx.Menu({ |
54 |
label: _("Timeframe"), |
55 |
image: app_path + 'images/history.png', |
56 |
tooltip: _("Timeframe menu") |
57 |
}); |
58 |
|
59 |
periods.each(function (period) {
|
60 |
var menuItem = new Jx.Menu.Item({ |
61 |
label: period[0] |
62 |
}); |
63 |
menuItem.options.period = period[1] * 60 * 60; |
64 |
menuItem.addEvent('click', function () { |
65 |
this[0].options.start = null; |
66 |
this[0].options.duration = (this[1].options.period).toInt(); |
67 |
this[0].updateGraph(); |
68 |
}.bind([this, menuItem]));
|
69 |
timeframe.add(menuItem); |
70 |
}, this);
|
71 |
|
72 |
// Indicateur d'alerte en cas d'erreur
|
73 |
var alert_msg = _(
|
74 |
'Could not load the graph for "%(graph)s" on "%(host)s". ' +
|
75 |
'Make sure VigiRRD is running and receives performance data.'
|
76 |
); |
77 |
// Le pattern donné à substitute permet de garder une syntaxe
|
78 |
// cohérente avec Python (facilite le travail des traducteurs).
|
79 |
alert_msg = alert_msg.substitute({ |
80 |
'graph': this.graph, |
81 |
'host': this.host |
82 |
}, (/\\?%\(([^()]+)\)s/g));
|
83 |
this.alert_indicator = new Element("img"); |
84 |
this.alert_indicator.addClass("alert-indicator"); |
85 |
this.alert_indicator.setProperty("src", app_path + 'images/messagebox_warning.png'); |
86 |
this.alert_indicator.setProperty("title", alert_msg); |
87 |
|
88 |
this.indicators = new Jx.Menu({ |
89 |
label: _("Export to CSV"), |
90 |
image: app_path + 'images/document-export.png', |
91 |
tooltip: _("Export the content of this graph to CSV") |
92 |
}); |
93 |
|
94 |
new Request.JSON({
|
95 |
url: app_path + 'rpc/getIndicators', |
96 |
onSuccess: function (data) { |
97 |
data.items.each(function (item) {
|
98 |
this.indicators.add(new Jx.Menu.Item({ |
99 |
name: item[0], |
100 |
label: item[1], |
101 |
onClick: this.exportCSV.bind(this), |
102 |
indicator: true |
103 |
})); |
104 |
}, this);
|
105 |
|
106 |
this.indicators.add(new Jx.Menu.Item({ |
107 |
label: _('All'), |
108 |
onClick: this.exportCSV.bind(this), |
109 |
indicator: false |
110 |
})); |
111 |
}.bind(this)
|
112 |
}).get({ |
113 |
'host': this.host, |
114 |
'graph': this.graph |
115 |
}); |
116 |
|
117 |
this.refresh_button = new Jx.Button({ |
118 |
image: app_path + 'images/refresh.png', |
119 |
tooltip: _("Automatically refresh the graph"), |
120 |
toggle: true, |
121 |
onDown: function() { |
122 |
// On s'assure qu'il n'y a pas déjà un timer lancé.
|
123 |
if ($chk(this.refreshTimer)) |
124 |
return;
|
125 |
var delay =
|
126 |
this.options.refreshDelay ||
|
127 |
window.refresh_delay; |
128 |
this.refreshTimer =
|
129 |
this.updateGraph.periodical(delay * 1000, this); |
130 |
this.options.autoRefresh = 1; |
131 |
logger.log((_( |
132 |
'Auto-refresh enabled on graph "{graph}" for host "{host}"'
|
133 |
)).substitute({ |
134 |
'graph': this.graph, |
135 |
'host': this.host |
136 |
})); |
137 |
window.updateURI(); |
138 |
}.bind(this),
|
139 |
onUp: function() { |
140 |
clearInterval(this.refreshTimer);
|
141 |
// clearInterval arrête le timer, mais n'invalide pas
|
142 |
// la référence, ce dont on a besoin (cf. onDown).
|
143 |
this.refreshTimer = null; |
144 |
this.options.autoRefresh = 0; |
145 |
logger.log((_( |
146 |
'Auto-refresh disabled on graph "{graph}" for host "{host}"'
|
147 |
)).substitute({ |
148 |
'graph': this.graph, |
149 |
'host': this.host |
150 |
})); |
151 |
window.updateURI(); |
152 |
}.bind(this)
|
153 |
}); |
154 |
|
155 |
this.zoom_in = new Jx.Button({ |
156 |
image: app_path + 'images/zoom-in.png', |
157 |
tooltip: _("Zoom in"), |
158 |
onClick: function() { |
159 |
this.updateZoom(true); |
160 |
}.bind(this)
|
161 |
}); |
162 |
|
163 |
this.zoom_out = new Jx.Button({ |
164 |
image: app_path + 'images/zoom-out.png', |
165 |
tooltip: _("Zoom out"), |
166 |
onClick: function() { |
167 |
this.updateZoom(false); |
168 |
}.bind(this)
|
169 |
}); |
170 |
|
171 |
toolbar.add( |
172 |
this.refresh_button,
|
173 |
timeframe, |
174 |
new Jx.Button({
|
175 |
image: app_path + 'images/start.png', |
176 |
tooltip: _("Graph start"), |
177 |
onClick: function() { |
178 |
this.options.start = this.startTime; |
179 |
this.updateGraph();
|
180 |
}.bind(this)
|
181 |
}), |
182 |
new Jx.Button({
|
183 |
image: app_path + 'images/previous.png', |
184 |
tooltip: _("Previous section"), |
185 |
onClick: function() { |
186 |
this.options.start =
|
187 |
this.getStartTime() -
|
188 |
this.options.duration +
|
189 |
(this.options.overlap * this.options.duration / 100).toInt(); |
190 |
this.updateGraph();
|
191 |
}.bind(this)
|
192 |
}), |
193 |
new Jx.Button({
|
194 |
image: app_path + 'images/next.png', |
195 |
tooltip: _("Next section"), |
196 |
onClick: function() { |
197 |
this.options.start =
|
198 |
this.getStartTime() +
|
199 |
this.options.duration -
|
200 |
(this.options.overlap * this.options.duration / 100).toInt(); |
201 |
this.updateGraph();
|
202 |
}.bind(this)
|
203 |
}), |
204 |
new Jx.Button({
|
205 |
image: app_path + 'images/end.png', |
206 |
tooltip: _("Graph end"), |
207 |
onClick: function() { |
208 |
this.options.start = null; |
209 |
this.updateGraph();
|
210 |
}.bind(this)
|
211 |
}), |
212 |
this.zoom_in,
|
213 |
this.zoom_out,
|
214 |
this.indicators,
|
215 |
new Jx.Button({
|
216 |
image: app_path + 'images/document-print-small.png', |
217 |
tooltip: _("Print graph"), |
218 |
onClick: this.print.bind(this) |
219 |
}) |
220 |
); |
221 |
|
222 |
var label = _("Graph for \"%(graph)s\" on \"%(host)s\""); |
223 |
// Le pattern donné à substitute permet de garder une syntaxe
|
224 |
// cohérente avec Python (facilite le travail des traducteurs).
|
225 |
label = label.substitute({ |
226 |
'graph': this.graph, |
227 |
'host': this.host |
228 |
}, (/\\?%\(([^()]+)\)s/g));
|
229 |
|
230 |
this.graph_window = new Jx.Dialog({ |
231 |
label: label,
|
232 |
modal: false, |
233 |
move: true, |
234 |
close: true, |
235 |
horizontal: this.options.left + ' left', |
236 |
vertical: this.options.top + ' top', |
237 |
width: 575, |
238 |
height: 75, |
239 |
vertical_overflow: true, |
240 |
horizontal_overflow: true, |
241 |
toolbars: [toolbar]
|
242 |
}); |
243 |
// Empêche l'affichage d'une barre de défilement vertical
|
244 |
// désactivée sous Chromium.
|
245 |
this.graph_window.content.setStyle('overflow', 'hidden'); |
246 |
|
247 |
this.alert_indicator.inject(this.graph_window.content.parentNode); |
248 |
|
249 |
// mise à jour
|
250 |
this.updateGraph();
|
251 |
this.graph_window.open();
|
252 |
|
253 |
this.refresh_button.setActive(parseInt(this.options.autoRefresh, 10)); |
254 |
|
255 |
var onClose = function () { |
256 |
if (this.destroyed) return; |
257 |
this.destroyed = true; |
258 |
this.graph_window.domObj.dispose();
|
259 |
window.graphs.erase(this);
|
260 |
window.updateURI(); |
261 |
}; |
262 |
|
263 |
this.graph_window.addEvent('close', onClose.bind(this)); |
264 |
// sizeChange est déclenché à la fois après un redimensionnement
|
265 |
// et après un déplacement. Ce cas est mal documenté dans JxLib.
|
266 |
this.graph_window.addEvent('sizeChange', this.dialogMoved.bind(this)); |
267 |
|
268 |
// Simule un déplacement de la fenêtre,
|
269 |
// pour mettre à jour les coordonnées.
|
270 |
this.dialogMoved();
|
271 |
window.graphs.push(this);
|
272 |
return this; |
273 |
}, |
274 |
|
275 |
destroy: function () { |
276 |
this.graph_window.close();
|
277 |
}, |
278 |
|
279 |
dialogMoved: function () { |
280 |
// Repris de l'API interne de JxLib (création du Drag).
|
281 |
this.options.left = parseInt(this.graph_window.domObj.style.left, 10); |
282 |
this.options.top = parseInt(this.graph_window.domObj.style.top, 10); |
283 |
window.updateURI(); |
284 |
}, |
285 |
|
286 |
updateZoom: function (zoom_in) { |
287 |
var start = this.options.start; |
288 |
var factor = zoom_in ? 0.5 : 2; |
289 |
this.options.duration = parseInt(this.options.duration, 10); |
290 |
if (start !== null) { |
291 |
// On zoom sur la partie centrale du graphe.
|
292 |
if (zoom_in) start += this.options.duration * 0.25; |
293 |
// On dézoome "par les 2 côtés".
|
294 |
else start -= this.options.duration * 0.5; |
295 |
this.options.start = start;
|
296 |
} |
297 |
this.options.duration *= factor;
|
298 |
// Période minimale d'affichage : 1 minute.
|
299 |
if (this.options.duration < 60) |
300 |
this.options.duration = 60; |
301 |
// On (dés)active le bouton de zoom en avant au besoin.
|
302 |
this.zoom_in.setEnabled(this.options.duration != 60); |
303 |
this.updateGraph();
|
304 |
}, |
305 |
|
306 |
getStartTime: function () { |
307 |
var start = this.options.start; |
308 |
if (start === null) |
309 |
// On génère un horodatage UNIX correspondant
|
310 |
// à l'heure courante en UTC.
|
311 |
start = (new Date() / 1000).toInt() - this.options.duration; |
312 |
if (start < 0) |
313 |
return 0; |
314 |
return start;
|
315 |
}, |
316 |
|
317 |
exportCSV: function (menuItem) { |
318 |
var uri = new URI(app_path + 'vigirrd/' + |
319 |
encodeURIComponent(this.host) + '/export'); |
320 |
|
321 |
var start = this.getStartTime(); |
322 |
|
323 |
uri.setData({ |
324 |
host: this.host, |
325 |
graphtemplate: this.graph, |
326 |
start: start,
|
327 |
end: start + this.options.duration, |
328 |
// Décalage par rapport à UTC, ex: 60 = UTC+01:00.
|
329 |
timezone: (new Date()).getTimezoneOffset(), |
330 |
nocache: (new Date() / 1) |
331 |
}); |
332 |
|
333 |
if (menuItem.options.indicator)
|
334 |
uri.setData({ds: menuItem.options.name}, true); |
335 |
|
336 |
window.open(uri.toString()); |
337 |
}, |
338 |
|
339 |
// Cette fonction est aussi utilisée dans print.js
|
340 |
// pour gérer l'impression globale.
|
341 |
getPrintParams: function () { |
342 |
var img = this.graph_window.content.getElement('img'); |
343 |
var img_uri = new URI(img.src); |
344 |
var params = img_uri.getData();
|
345 |
return {
|
346 |
host: params.host,
|
347 |
start: params.start,
|
348 |
duration: params.duration,
|
349 |
graph: params.graphtemplate,
|
350 |
nocache: params.nocache
|
351 |
}; |
352 |
}, |
353 |
|
354 |
print: function () { |
355 |
var uri = new URI(app_path + 'rpc/graphsList'); |
356 |
uri.setData({graphs: [this.getPrintParams()]}); |
357 |
var print_window = window.open(uri.toString());
|
358 |
print_window.onload = function () { |
359 |
this.print();
|
360 |
}.bind(print_window); |
361 |
}, |
362 |
|
363 |
updateGraph: function () { |
364 |
var uri = new URI(app_path + 'vigirrd/' + |
365 |
encodeURIComponent(this.host) + '/graph.png'); |
366 |
|
367 |
var start = this.getStartTime(); |
368 |
|
369 |
uri.setData({ |
370 |
host: this.host, |
371 |
start: start,
|
372 |
duration: this.options.duration, |
373 |
graphtemplate: this.graph, |
374 |
// Permet d'empêcher la mise en cache du graphe.
|
375 |
// Nécessaire car le graphe évolue dynamiquement au fil de l'eau.
|
376 |
nocache: (new Date() / 1) |
377 |
}); |
378 |
// On génère dynamiquement une balise "img" pour charger le graphe.
|
379 |
this.graph_window.setContent(
|
380 |
'<img src="' + uri.toString() + '"/' + '>'); |
381 |
var img = this.graph_window.content.getElement('img'); |
382 |
img.addEvent('load', function () { |
383 |
this[0].graph_window.resize( |
384 |
this[1].width + 25, |
385 |
this[1].height + 73 |
386 |
); |
387 |
this[0].hideAlert(); |
388 |
}.bind([this, img]));
|
389 |
img.addEvent('error', function () { |
390 |
// if (this.refreshTimer) clearInterval(this.refreshTimer);
|
391 |
this.showAlert();
|
392 |
//this.graph_window.close();
|
393 |
}.bind(this));
|
394 |
}, |
395 |
|
396 |
showAlert: function() { |
397 |
this.alert_indicator.setStyle("display", "block"); |
398 |
var zindex = parseInt(this.graph_window.domObj.getStyle("z-index"), 10) + 1; |
399 |
this.alert_indicator.setStyle("z-index", zindex); |
400 |
return;
|
401 |
}, |
402 |
|
403 |
hideAlert: function() { |
404 |
this.alert_indicator.setStyle("display", "none"); |
405 |
} |
406 |
}); |
407 |
|
408 |
var updateURI = function () { |
409 |
var graphs_uri = [];
|
410 |
var uri = new URI(); |
411 |
|
412 |
// Section critique.
|
413 |
window.skip_detection++; |
414 |
uri.set('fragment', ''); |
415 |
|
416 |
window.graphs.each(function (graph) {
|
417 |
var props = new Hash(graph.options); |
418 |
props.extend({host: graph.host, graph: graph.graph}); |
419 |
// Sous Firefox, l'apostrophe n'est pas échappée via JavaScript,
|
420 |
// alors qu'elle l'est par le navigateur dans l'URL.
|
421 |
// Afin d'éviter une boucle de rechargement infinie, on échappe
|
422 |
// manuellement l'apostrophe.
|
423 |
this.push(props.toQueryString().replace(/'/, '%27')); |
424 |
}, graphs_uri); |
425 |
|
426 |
uri.setData({'graphs': graphs_uri, safety: 1}, false, 'fragment'); |
427 |
uri.go(); |
428 |
window.old_fragment = uri.toString(); |
429 |
|
430 |
// Fin de section critique.
|
431 |
window.skip_detection--; |
432 |
}; |
433 |
|
434 |
var update_visible_graphs = function (new_fragment) { |
435 |
// On réouvre les graphes précédemment chargés.
|
436 |
var new_graphs = [];
|
437 |
var qs = new Hash(new_fragment.get('fragment').parseQueryString()); |
438 |
if (qs.has('graphs')) { |
439 |
new_graphs = (new Hash(qs.get('graphs'))).getValues(); |
440 |
} |
441 |
|
442 |
// Section critique.
|
443 |
window.skip_detection++; |
444 |
|
445 |
var prev_graphs = window.graphs;
|
446 |
window.graphs = []; |
447 |
prev_graphs.each(function (graph) { graph.destroy(); });
|
448 |
|
449 |
new_graphs.each(function (graph) {
|
450 |
var uri = new URI('?' + graph); |
451 |
var qs = new Hash(uri.getData()); |
452 |
if (qs.has('host') && qs.has('graph')) { |
453 |
var options = new Hash(); |
454 |
var params = [
|
455 |
'start',
|
456 |
'duration',
|
457 |
'left',
|
458 |
'top',
|
459 |
'autoRefresh',
|
460 |
'refreshDelay'
|
461 |
]; |
462 |
|
463 |
params.each(function (param) {
|
464 |
if (this[0].has(param)) |
465 |
this[1].set(param, (this[0].get(param)).toInt()); |
466 |
}, [qs, options]); |
467 |
|
468 |
new Graph(
|
469 |
options.getClean(), |
470 |
qs.get('host'),
|
471 |
qs.get('graph')
|
472 |
); |
473 |
} |
474 |
}); |
475 |
|
476 |
window.updateURI(); |
477 |
|
478 |
// Fin de section critique.
|
479 |
window.skip_detection--; |
480 |
|
481 |
if (window.graphs.length == 1) { |
482 |
new Request.JSON({
|
483 |
url: app_path + 'rpc/selectHostAndGraph', |
484 |
onSuccess: function (results) { |
485 |
window.toolbar.host_picker.setItem(results.idhost, this[0]); |
486 |
window.toolbar.graph_picker.idselection = results.idgraph; |
487 |
window.toolbar.graph_picker.setLabel(this[1]); |
488 |
}.bind([ |
489 |
window.graphs[0].host,
|
490 |
window.graphs[0].graph
|
491 |
]) |
492 |
}).get({ |
493 |
host: window.graphs[0].host, |
494 |
graph: window.graphs[0].graph |
495 |
}); |
496 |
} |
497 |
}; |
498 |
|
499 |
var hash_change_detector = function() { |
500 |
var new_fragment;
|
501 |
|
502 |
// Pour les moments où on a besoin de mettre à jour
|
503 |
// volontairement l'URI (à l'ouverture d'un graphe).
|
504 |
if (window.skip_detection) return; |
505 |
|
506 |
// Force mootools à analyser l'URL courante de nouveau,
|
507 |
// ce qui mettra à jour la partie "fragment" de l'URI.
|
508 |
URI.base = new URI(
|
509 |
document.getElements('base[href]', true).getLast(), |
510 |
{base: document.location}
|
511 |
); |
512 |
|
513 |
new_fragment = new URI();
|
514 |
if (old_fragment.toString() != new_fragment.toString()) {
|
515 |
update_visible_graphs(new_fragment, old_fragment); |
516 |
} |
517 |
}; |
518 |
|
519 |
if ('onhashchange' in window) { |
520 |
window.onhashchange = hash_change_detector; |
521 |
} else {
|
522 |
hash_change_detector.periodical(100);
|
523 |
} |
524 |
|
525 |
window.addEvent('load', function () { |
526 |
new Request.JSON({
|
527 |
url: app_path + 'rpc/tempoDelayRefresh', |
528 |
onSuccess: function (data) { |
529 |
window.refresh_delay = data.delay; |
530 |
} |
531 |
}).get(); |
532 |
|
533 |
hash_change_detector(); |
534 |
}); |