Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

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
});