Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 3d0d254c

History | View | Annotate | Download (15.9 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, sql
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 = [
63
            EventsAggregate,
64
            sql.func.count(EventsAggregate.idaggregate)
65
        ]
66

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

    
75
        self.outerjoin = []
76

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

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

    
94
        self.groupby = [
95
                EventsAggregate.idaggregate,
96
                EventsAggregate,
97
                Event.active,
98
                Event.hostname,
99
                Event.timestamp,
100
            ]
101

    
102
        self.plugin = []
103
        self.events = []
104
        self.idevents = []
105
        self.hist = []
106
        self.req = DBSession
107
        self.context_fct = []
108

    
109
    def add_plugin(self, *argv):
110

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

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

    
145
        # query et join ont besoin de referrence
146
        self.req = self.req.query(*self.table)
147
        self.req = self.req.join(*self.join)
148

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

    
159
    def num_rows(self):
160

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

165
        @return: Nombre de ligne
166
        """
167

    
168
        if not self.generaterq:
169
            self.generate_request()
170
            self.generaterq = True
171
        return self.req.count()
172

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

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

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

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

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

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

    
227
    def add_filter(self, *argv):
228

    
229
        """
230
        Ajoute un ou plusieurs filtres à la requête.
231

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

    
244
    def add_group_by(self, *argv):
245

    
246
        """
247
        Ajoute un ou plusieurs groupements à la requête.
248

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

    
261
    def add_order_by(self, *argv):
262

    
263
        """
264
        Ajoute un ou plusieurs orders à la requête.
265

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

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

284
        @param event: l'évènement à analyser
285

286
        @return: Dictionnaire représentant la classe à appliquer
287
        """
288

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

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

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

    
312
        # Liste des éléments pour la tête du tableau
313

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

    
337
            if isinstance(req, EventsAggregate):
338
                event = req
339
            else:
340
                event = req[0]
341
            ids.append(event.idcause)
342

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

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

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

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

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

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

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

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

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

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

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

    
475
        # Exécution des contexts des plugins
476
        for j in self.plugin:
477
            j.context(self.context_fct)
478