Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigigraph / vigigraph / public / js / graph.js @ bd5905c3

History | View | Annotate | Download (17.3 KB)

1
// Copyright (C) 2006-2011 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
            // @TODO: cette heure est en localtime a priori.
310
            start = (new Date() / 1000).toInt() - this.options.duration;
311
        if (start < 0)
312
            return 0;
313
        return start;
314
    },
315

    
316
    exportCSV: function (menuItem) {
317
        var uri = new URI(app_path + 'vigirrd/' +
318
            encodeURIComponent(this.host) + '/export');
319

    
320
        var start = this.getStartTime();
321

    
322
        uri.setData({
323
            host: this.host,
324
            graphtemplate: this.graph,
325
            start: start,
326
            end: start + this.options.duration,
327
            nocache: (new Date() / 1)
328
        });
329

    
330
        if (menuItem.options.indicator)
331
            uri.setData({ds: menuItem.options.name}, true);
332

    
333
        window.open(uri.toString());
334
    },
335

    
336
    // Cette fonction est aussi utilisée dans print.js
337
    // pour gérer l'impression globale.
338
    getPrintParams: function () {
339
        var img = this.graph_window.content.getElement('img');
340
        var img_uri = new URI(img.src);
341
        var params = img_uri.getData();
342
        return {
343
            host: params.host,
344
            start: params.start,
345
            duration: params.duration,
346
            graph: params.graphtemplate,
347
            nocache: params.nocache
348
        };
349
    },
350

    
351
    print: function () {
352
        var uri = new URI(app_path + 'rpc/graphsList');
353
        uri.setData({graphs: [this.getPrintParams()]});
354
        var print_window = window.open(uri.toString());
355
        print_window.onload = function () {
356
            this.print();
357
        }.bind(print_window);
358
    },
359

    
360
    updateGraph: function () {
361
        var uri = new URI(app_path + 'vigirrd/' +
362
            encodeURIComponent(this.host) + '/graph.png');
363

    
364
        var start = this.getStartTime();
365

    
366
        uri.setData({
367
            host: this.host,
368
            start: start,
369
            duration: this.options.duration,
370
            graphtemplate: this.graph,
371
            // Permet d'empêcher la mise en cache du graphe.
372
            // Nécessaire car le graphe évolue dynamiquement au fil de l'eau.
373
            nocache: (new Date() / 1)
374
        });
375
        // On génère dynamiquement une balise "img" pour charger le graphe.
376
        this.graph_window.setContent(
377
            '<img src="' + uri.toString() + '"/' + '>');
378
        var img = this.graph_window.content.getElement('img');
379
        img.addEvent('load', function () {
380
            this[0].graph_window.resize(
381
                this[1].width + 25,
382
                this[1].height + 73
383
            );
384
            this[0].hideAlert();
385
        }.bind([this, img]));
386
        img.addEvent('error', function () {
387
            // if (this.refreshTimer) clearInterval(this.refreshTimer);
388
            this.showAlert();
389
            //this.graph_window.close();
390
        }.bind(this));
391
    },
392

    
393
    showAlert: function() {
394
        this.alert_indicator.setStyle("display", "block");
395
        var zindex = parseInt(this.graph_window.domObj.getStyle("z-index"), 10) + 1;
396
        this.alert_indicator.setStyle("z-index", zindex);
397
        return;
398
    },
399

    
400
    hideAlert: function() {
401
        this.alert_indicator.setStyle("display", "none");
402
    }
403
});
404

    
405
var updateURI = function () {
406
    var graphs_uri = [];
407
    var uri = new URI();
408

    
409
    // Section critique.
410
    window.skip_detection++;
411
    uri.set('fragment', '');
412

    
413
    window.graphs.each(function (graph) {
414
        var props = new Hash(graph.options);
415
        props.extend({host: graph.host, graph: graph.graph});
416
        this.push(props.toQueryString());
417
    }, graphs_uri);
418

    
419
    uri.setData({'graphs': graphs_uri, safety: 1}, false, 'fragment');
420
    uri.go();
421
    window.old_fragment = uri.toString();
422

    
423
    // Fin de section critique.
424
    window.skip_detection--;
425
};
426

    
427
var update_visible_graphs = function (new_fragment) {
428
    // On réouvre les graphes précédemment chargés.
429
    var new_graphs = [];
430
    var qs = new Hash(new_fragment.get('fragment').parseQueryString());
431
    if (qs.has('graphs')) {
432
        new_graphs = (new Hash(qs.get('graphs'))).getValues();
433
    }
434

    
435
    // Section critique.
436
    window.skip_detection++;
437

    
438
    var prev_graphs = window.graphs;
439
    window.graphs = [];
440
    prev_graphs.each(function (graph) { graph.destroy(); });
441

    
442
    new_graphs.each(function (graph) {
443
        var uri = new URI('?' + graph);
444
        var qs = new Hash(uri.getData());
445
        if (qs.has('host') && qs.has('graph')) {
446
            var options = new Hash();
447
            var params = [
448
                'start',
449
                'duration',
450
                'left',
451
                'top',
452
                'autoRefresh',
453
                'refreshDelay'
454
            ];
455

    
456
            params.each(function (param) {
457
                if (this[0].has(param))
458
                    this[1].set(param, (this[0].get(param)).toInt());
459
            }, [qs, options]);
460

    
461
            new Graph(
462
                options.getClean(),
463
                qs.get('host'),
464
                qs.get('graph')
465
            );
466
        }
467
    });
468

    
469
    window.updateURI();
470

    
471
    // Fin de section critique.
472
    window.skip_detection--;
473

    
474
    if (window.graphs.length == 1) {
475
        new Request.JSON({
476
            url: app_path + 'rpc/selectHostAndGraph',
477
            onSuccess: function (results) {
478
                window.toolbar.host_picker.setItem(results.idhost, this[0]);
479
                window.toolbar.graph_picker.idselection = results.idgraph;
480
                window.toolbar.graph_picker.setLabel(this[1]);
481
            }.bind([
482
                window.graphs[0].host,
483
                window.graphs[0].graph
484
            ])
485
        }).get({
486
            host: window.graphs[0].host,
487
            graph: window.graphs[0].graph
488
        });
489
    }
490
};
491

    
492
var hash_change_detector = function() {
493
    var new_fragment;
494

    
495
    // Pour les moments où on a besoin de mettre à jour
496
    // volontairement l'URI (à l'ouverture d'un graphe).
497
    if (window.skip_detection) return;
498

    
499
    // Force mootools à analyser l'URL courante de nouveau,
500
    // ce qui mettra à jour la partie "fragment" de l'URI.
501
    URI.base = new URI(
502
        document.getElements('base[href]', true).getLast(),
503
        {base: document.location}
504
    );
505

    
506
    new_fragment = new URI();
507
    if (old_fragment.toString() != new_fragment.toString()) {
508
        update_visible_graphs(new_fragment, old_fragment);
509
    }
510
};
511

    
512
if ('onhashchange' in window) {
513
    window.onhashchange = hash_change_detector;
514
} else {
515
    hash_change_detector.periodical(100);
516
}
517

    
518
window.addEvent('load', function () {
519
    new Request.JSON({
520
        url: app_path + 'rpc/tempoDelayRefresh',
521
        onSuccess: function (data) {
522
            window.refresh_delay = data.delay;
523
        }
524
    }).get();
525

    
526
    hash_change_detector();
527
});