Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ c1ce3d6a

History | View | Annotate | Download (15.8 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4: 
3
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
4

    
5
from vigiboard.model import Event, EventsAggregate, EventHistory, \
6
        Host, HostGroup, Service, ServiceGroup
7
from tg import tmpl_context, url, config
8
from vigiboard.model import DBSession
9
from sqlalchemy import not_ , and_ , asc , desc
10
from tw.jquery.ui_dialog import JQueryUIDialog
11
from vigiboard.widgets.edit_event import EditEventForm , SearchForm
12
from vigiboard.controllers.vigiboard_plugin import VigiboardRequestPlugin
13
from pylons.i18n import ugettext as _
14

    
15
class VigiboardRequest():
16
    
17
    """
18
    Classe gérant la génération de la requête finale,
19
    le préformatage des évènements et celui des historiques
20
    """
21

    
22
    def __init__(self, user):
23

    
24
        """
25
        Initialisation de toutes les variables nécessaires: Liste des groupes de
26
        l'utilisateur, les classes à appliquer suivant la sévérité, les
27
        différentes étapes de la génération de la requête et la liste des
28
        plugins appliqués.
29
        """
30

    
31
        self.user_groups = user.groups
32

    
33
        self.bouton_severity = (
34
                'Minor', 'Minor', 'Minor', 'Minor',
35
                'Minor', 'Minor', 'Major', 'Critical'
36
            )
37

    
38
        self.class_severity = (
39
                'None', 'None', 'None', 'None',
40
                'None', 'Minor', 'Major', 'Critical'
41
            )
42

    
43
        self.severity = (
44
                _('None'),          # 0
45
                _('OK'),
46
                _('Suppressed'),
47
                _('Initial'),
48
                _('Maintenance'),
49
                _('Minor'),
50
                _('Major'),
51
                _('Critical'),      # 7
52
            )
53

    
54
        self.class_ack = {
55
                'Acknowledged': 'Ack',
56
                'None': '',
57
                'AAClosed': 'Ack'
58
            }
59

    
60
        self.generaterq = False
61

    
62
        self.table = [EventsAggregate]
63

    
64
        self.join = [
65
                (Event, EventsAggregate.idcause == Event.idevent),
66
                (Host, Event.hostname == Host.name),
67
                (Service, Event.servicename == Service.name),
68
                (HostGroup, Host.name == HostGroup.hostname),
69
                (ServiceGroup, Service.name == ServiceGroup.servicename),
70
            ]
71

    
72
        self.outerjoin = []
73

    
74
        self.filter = [
75
                HostGroup.groupname.in_(self.user_groups),
76
                ServiceGroup.groupname.in_(self.user_groups),
77
                not_(and_(Event.active == False,
78
                    EventsAggregate.status == 'AAClosed')),
79
                EventsAggregate.timestamp_active != None#,
80
                #not_(Event.timestamp_active.like('0000-00-00 00:00:00'))
81
            ]
82

    
83
        self.orderby = [
84
                desc(EventsAggregate.status),
85
                desc(Event.active),
86
                desc(EventsAggregate.severity),
87
                asc(Event.hostname),
88
                desc(Event.timestamp),
89
            ]
90

    
91
        self.groupby = [
92
                EventsAggregate.idaggregate,
93
                EventsAggregate,
94
                Event.active,
95
                Event.hostname,
96
                Event.timestamp,
97
            ]
98

    
99
        self.plugin = []
100
        self.events = []
101
        self.idevents = []
102
        self.hist = []
103
        self.req = DBSession
104
        self.context_fct = []
105

    
106
    def add_plugin(self, *argv):
107

    
108
        """
109
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
110
        """
111
        for i in argv :
112
            if isinstance(i, VigiboardRequestPlugin):
113
                if i.table :
114
                    self.add_table(*i.table)
115
                if i.join :
116
                    self.add_join(*i.join)
117
                if i.outerjoin :
118
                    self.add_outer_join(*i.outerjoin)
119
                if i.filter :
120
                    self.add_filter(*i.filter)
121
                if i.groupby :    
122
                    self.add_group_by(*i.groupby)
123
                if i.orderby :
124
                    self.add_order_by(*i.orderby)
125
                self.plugin.append(i)
126

    
127
    def generate_request(self):
128
        
129
        """
130
        Génération de la requête avec l'ensemble des données stockées
131
        et la place dans la variable rq de la classe
132
        """
133
        for plug in config.get('vigiboard_plugins', []):
134
            try:
135
                mypac = __import__(
136
                    'vigiboard.controllers.vigiboard_plugin.' +\
137
                            plug[0],globals(), locals(), [plug[1]],-1)
138
                self.add_plugin(getattr(mypac, plug[1])())
139
            except:
140
                raise
141

    
142
        # query et join ont besoin de referrence
143
        self.req = self.req.query(*self.table)
144
        self.req = self.req.join(*self.join)
145

    
146
        # le reste, non
147
        for i in self.outerjoin:
148
            self.req = self.req.outerjoin(i)
149
        for i in self.filter:
150
            self.req = self.req.filter(i)
151
        for i in self.groupby:
152
            self.req = self.req.group_by(i)
153
        for i in self.orderby:
154
            self.req = self.req.order_by(i)
155

    
156
    def num_rows(self):
157

    
158
        """
159
        Retourne le nombre de lignes de la requête.
160
        Si celle-ci n'est pas encore générée, on le fait.
161

162
        @return: Nombre de ligne
163
        """
164

    
165
        if not self.generaterq :
166
            self.generate_request()
167
            self.generaterq = True
168
        return self.req.count()
169

    
170
    def add_table(self, *argv):
171
        
172
        """
173
        Ajoute une ou plusieurs tables/élément d'une table à
174
        la requête.
175

176
        @param argv: Liste des tables à ajouter
177
        """
178
        
179
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
180
        # des tables.
181
        
182
        for i in argv :
183
            for j in self.table:
184
                if str(i) == str(j):
185
                    break
186
            self.table.append(i)
187

    
188
    def add_join(self, *argv):
189
        
190
        """
191
        Ajoute une ou plusieurs jointures à
192
        la requête.
193

194
        @param argv: Liste des jointures à ajouter
195
        """
196
        
197
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
198
        # des jointures.
199
        
200
        for i in argv:
201
            for j in self.join:
202
                if str(i) == str(j):
203
                    break
204
            self.join.append(i)
205

    
206
    def add_outer_join(self, *argv):
207
        
208
        """
209
        Ajoute une ou plusieurs jointures externes à
210
        la requête.
211

212
        @param argv: Liste des jointures externes à ajouter
213
        """
214
        
215
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
216
        # des jointures externes.
217
        
218
        for i in argv:
219
            for j in self.outerjoin:
220
                if str(i) == str(j):
221
                    break
222
            self.outerjoin.append(i)    
223

    
224
    def add_filter(self, *argv):
225

    
226
        """
227
        Ajoute un ou plusieurs filtres à la requête.
228

229
        @param argv: Liste des filtres à ajouter
230
        """
231
        
232
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
233
        # des filtres.
234
        
235
        for i in argv:
236
            for j in self.filter:
237
                if str(i) == str(j):
238
                    break
239
            self.filter.append(i)
240

    
241
    def add_group_by(self, *argv):
242

    
243
        """
244
        Ajoute un ou plusieurs groupements à la requête.
245

246
        @param argv: Liste des groupements à ajouter
247
        """
248
        
249
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
250
        # des groupements.
251
        
252
        for i in argv:
253
            for j in self.groupby:
254
                if str(i) == str(j):
255
                    break
256
            self.groupby.append(i)
257

    
258
    def add_order_by(self, *argv):
259

    
260
        """
261
        Ajoute un ou plusieurs orders à la requête.
262

263
        @param argv: Liste des ordres à ajouter
264
        """
265
        
266
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
267
        # des ordres.
268
        
269
        for i in argv:
270
            for j in self.orderby:
271
                if str(i) == str(j):
272
                    break
273
            self.orderby.append(i)
274

    
275
    def format_events_img_status(self, event):
276
        
277
        """
278
        Suivant l'état de l'évènement, retourne la classe à appliquer
279
        à l'image indiquant si l'évènement est pris en compte ou non.
280

281
        @param event: l'évènement à analyser
282

283
        @return: Dictionnaire représentant la classe à appliquer
284
        """
285

    
286
        if event.cause.active and event.status == 'AAClosed':
287
            return { 'src': url('/images/crossed.png') }
288
        elif event.status == 'Acknowledged' :
289
            return { 'src': url('/images/checked.png') }
290
        else:
291
            return None
292

    
293
    def format_events(self, first_row, last_row):
294
        """
295
        Formate la réponse de la requête et y applique les plugins
296
        pour un affichage simple du résultat par Genshi.
297
        On génère une liste de liste, chaqu'une étant la description de
298
        l'affichage pour un évènement donné.
299

300
        @param first_row: Indice de début de la liste des évènements
301
        @param last_row: Indice de fin de la liste des évènements
302
        """
303
        
304
        # Si la requête n'est pas générée, on le fait
305
        if not self.generaterq :
306
            self.generate_request()
307
            self.generaterq = True
308

    
309
        # Liste des éléments pour la tête du tableau
310

    
311
        lst_title = [
312
                ['',{}],
313
                [_('Date')+ '<span style="font-weight:normal">' + \
314
                        '<br />['+_('Duration') + ']</span>',
315
                        {'style':'text-align:left'}],
316
                ['#', {'title':_('Occurrence count')}],
317
                [_('Host'), {'style':'text-align:left'}],
318
                [_('Service Type')+'<br />'+_('Service Name'),
319
                    {'style':'text-align:left'}], 
320
                [_('Output'), {'style':'text-align:left'}]
321
                ]
322
        lst_title.extend([[plug.name, plug.style] for plug in self.plugin])
323
        lst_title.extend([['['+_('TT')+']', {'title': _('Trouble Ticket')}],
324
                            ['', {}]])
325
        events = [lst_title]
326
        i = 0
327
        class_tr = ['odd', 'even']
328
        ids = []
329
        for req in self.req[first_row : last_row]:
330
            # Si il y a plus d'un élément dans la liste des tables,
331
            # rq devient une liste plutôt que d'être directement la
332
            # table souhaité
333
            
334
            if isinstance(req, EventsAggregate) :
335
                event = req
336
            else:
337
                event = req[0]
338
            ids.append(event.idcause)
339

    
340
            # La liste pour l'évènement actuel comporte dans l'ordre :
341
            #   L'évènement en lui-même
342
            #   La classe à appliquer sur la ligne (permet d'alterner les
343
            #       couleurs suivant les lignes)
344
            #   La classe pour la case comportant la flèche de détails
345
            #   La classe pour la date, l'occurence et l'édition
346
            #   L'image à afficher pour la flèche de détails
347
            #   Une liste (une case par plugin) de ce que le plugin souhaite
348
            #       afficher en fonction de l'évènement
349

    
350
            if event.cause.active:
351
                events.append([
352
                        event,
353
                        {'class': class_tr[i % 2]},
354
                        {'class' : self.bouton_severity[event.severity] + \
355
                                self.class_ack[event.status]},
356
                        {'class' : self.bouton_severity[event.severity] + \
357
                                self.class_ack[event.status]},
358
                        {'src' : '/images/%s2.png' % \
359
                                self.bouton_severity[event.severity].upper()},
360
                        self.format_events_img_status(event),
361
                        [[j.__show__(req), j.style] for j in self.plugin]
362
                    ])
363

    
364
            else:
365
                events.append([
366
                        event,
367
                        {'class': class_tr[i % 2]},
368
                        {'class' : self.bouton_severity[event.severity] + \
369
                                self.class_ack[event.status] },
370
                        {'class' : 'Cleared' + self.class_ack[event.status] },
371
                        {'src' : '/images/%s2.png' % \
372
                                self.bouton_severity[event.severity].upper()},
373
                        self.format_events_img_status(event),
374
                        [[j.__show__(req), j.style] for j in self.plugin]
375
                    ])
376
            i += 1
377

    
378
        # On sauvegarde la liste précédemment créée puis on remplit
379
        # le TmplContext
380
        self.events = events
381
        self.idevents = ids
382

    
383
    def format_history(self):
384
        
385
        """
386
        Formate les historiques correspondant aux évènements sélectionnés
387
        pour un affichage simple du résultat par Genshi.
388
        On génère une liste de liste, chaqu'une étant la description
389
        de l'affichage pour un historique donné.
390
        """
391

    
392
        history = DBSession.query(EventHistory
393
                ).filter(EventHistory.idevent.in_(self.idevents)
394
                ).order_by(desc(EventHistory.timestamp)
395
                ).order_by(desc(EventHistory.idhistory))
396
        if history.count() == 0:
397
            self.hist = {}
398
            for i in self.idevents:
399
                self.hist[i] = []
400
            return
401
        hists = {}
402
        i = 0
403
        class_tr = ['odd', 'even']
404
        hist_tmp = []
405
        last_idevent = history[0].idevent
406
        for hist in history :
407
            
408
            if last_idevent != hist.idevent:
409
                hists[last_idevent] = hist_tmp
410
                last_idevent = hist.idevent
411
                hist_tmp = []
412

    
413
            # La liste pour l'historique actuel comporte dans l'ordre :
414
            #   Le moment où il a été généré
415
            #   Qui l'a généré
416
            #   Le type d'action qui a été appliqué
417
            #   La sévérité de l'action si besoin est
418
            #   Le détail de l'action
419
            #   La classe à appliquer à la ligne (permet d'alterner
420
            #       les couleurs)
421
            #   La classe de la sévérité s'il y a
422

    
423
            if hist.value :
424
                hist_tmp.append([
425
                    hist.timestamp,
426
                    hist.username,
427
                    hist.type_action,
428
                    self.severity[min(int(hist.value),7)],
429
                    hist.text,
430
                    {'class': class_tr[i%2]},
431
                    {'class': self.class_severity[min(int(hist.value),7)]}
432
                ])
433

    
434
            else:
435
                hist_tmp.append([
436
                    hist.timestamp,
437
                    hist.username,
438
                    hist.type_action,
439
                    self.severity[0],
440
                    hist.text,
441
                    {'class': class_tr[i%2]},
442
                    {'class': self.class_severity[0]}
443
                ])    
444
            i += 1
445
        
446
        hists[last_idevent] = hist_tmp
447
        self.hist = hists
448

    
449
    def generate_tmpl_context(self):
450
        
451
        """
452
        Génère et peuple la variable tmpl_context avec les Dialogs et
453
        formulaires nécessaire au fonctionnement de Vigiboard
454
        """
455

    
456
        # Dialogue d'édition
457
        tmpl_context.edit_event_form = EditEventForm('edit_event_form',
458
                action=url('/update'))
459
        tmpl_context.edit_eventdialog = JQueryUIDialog(id='Edit_EventsDialog',
460
                autoOpen=False,title=_('Edit Event'))
461
    
462
        # Dialogue de recherche
463
        tmpl_context.search_form = SearchForm('search_form',
464
                action=url('/'))
465
        tmpl_context.searchdialog = JQueryUIDialog(id='SearchDialog',
466
                autoOpen=False, title=_('Search Event'))
467
        
468
        # Dialogue de détail d'un évènement
469
        tmpl_context.historydialog = JQueryUIDialog(id='HistoryDialog',
470
                autoOpen=False, title=_('History'))
471

    
472
        # Exécution des contexts des plugins
473
        for j in self.plugin:
474
            j.context(self.context_fct)
475