Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 8484b8bd

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, User
7
from tg import tmpl_context, url, config, request
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):
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
        username = request.environ['repoze.who.identity']['repoze.who.userid']
32
        self.user_groups = User.by_user_name(username).groups
33

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

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

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

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

    
61
        self.generaterq = False
62

    
63
        self.table = [EventsAggregate]
64

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

    
73
        self.outerjoin = []
74

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

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

    
92
        self.groupby = [
93
                EventsAggregate.idaggregate,
94
                EventsAggregate,
95
            ]
96

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

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

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

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

    
154
    def num_rows(self):
155

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

160
        @return: Nombre de ligne
161
        """
162

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

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

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

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

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

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

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

    
222
    def add_filter(self, *argv):
223

    
224
        """
225
        Ajoute un ou plusieurs filtres à la requête.
226

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

    
239
    def add_group_by(self, *argv):
240

    
241
        """
242
        Ajoute un ou plusieurs groupements à la requête.
243

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

    
256
    def add_order_by(self, *argv):
257

    
258
        """
259
        Ajoute un ou plusieurs orders à la requête.
260

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

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

279
        @param event: l'évènement à analyser
280

281
        @return: Dictionnaire représentant la classe à appliquer
282
        """
283

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

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

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

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

    
310
        lst_title = [
311
                ['',{}],
312
                [_('Date')+ '<span style="font-weight:normal">' + \
313
                        '<br />['+_('Duration') + ']</span>',
314
                        {'style':'text-align:left'}],
315
                ['#', {'title':_('Occurrence count')}],
316
                [_('Host'), {'style':'text-align:left'}],
317
                [_('Service Type')+'<br />'+_('Service Name'),
318
                    {'style':'text-align:left'}], 
319
                [_('Output'), {'style':'text-align:left'}]
320
                ]
321
        lst_title.extend([[plug.name, plug.style] for plug in self.plugin])
322
        lst_title.extend([['['+_('TT')+']', {'title': _('Trouble Ticket')}],
323
                            ['', {}]])
324
        events = [lst_title]
325
        i = 0
326
        class_tr = ['odd', 'even']
327
        ids = []
328
        for req in self.req[first_row : last_row]:
329

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

    
377
        # On sauvegarde la liste précédemment créée puis rempli
378
        # le TmplContext
379

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

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

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

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