Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ c49defb8

History | View | Annotate | Download (16 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
26
        de 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 get_bouton_severity(self, severity):
110
        if severity is None:
111
            return 'Unknown'
112
        return self.bouton_severity[severity]
113

    
114
    def add_plugin(self, *argv):
115

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

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

    
150
        # query et join ont besoin de referrence
151
        self.req = self.req.query(*self.table)
152
        self.req = self.req.join(*self.join)
153

    
154
        # le reste, non
155
        for i in self.outerjoin:
156
            self.req = self.req.outerjoin(i)
157
        for i in self.filter:
158
            self.req = self.req.filter(i)
159
        for i in self.groupby:
160
            self.req = self.req.group_by(i)
161
        for i in self.orderby:
162
            self.req = self.req.order_by(i)
163

    
164
    def num_rows(self):
165

    
166
        """
167
        Retourne le nombre de lignes de la requête.
168
        Si celle-ci n'est pas encore générée, on le fait.
169

170
        @return: Nombre de ligne
171
        """
172

    
173
        if not self.generaterq:
174
            self.generate_request()
175
            self.generaterq = True
176
        return self.req.count()
177

    
178
    def add_table(self, *argv):
179
        
180
        """
181
        Ajoute une ou plusieurs tables/élément d'une table à
182
        la requête.
183

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

    
196
    def add_join(self, *argv):
197
        
198
        """
199
        Ajoute une ou plusieurs jointures à
200
        la requête.
201

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

    
214
    def add_outer_join(self, *argv):
215
        
216
        """
217
        Ajoute une ou plusieurs jointures externes à
218
        la requête.
219

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

    
232
    def add_filter(self, *argv):
233

    
234
        """
235
        Ajoute un ou plusieurs filtres à la requête.
236

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

    
249
    def add_group_by(self, *argv):
250

    
251
        """
252
        Ajoute un ou plusieurs groupements à la requête.
253

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

    
266
    def add_order_by(self, *argv):
267

    
268
        """
269
        Ajoute un ou plusieurs orders à la requête.
270

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

    
283
    def format_events_img_status(self, event):
284
        
285
        """
286
        Suivant l'état de l'évènement, retourne la classe à appliquer
287
        à l'image indiquant si l'évènement est pris en compte ou non.
288

289
        @param event: l'évènement à analyser
290

291
        @return: Dictionnaire représentant la classe à appliquer
292
        """
293

    
294
        if event.cause.active and event.status == 'AAClosed':
295
            return { 'src': url('/images/crossed.png') }
296
        elif event.status == 'Acknowledged' :
297
            return { 'src': url('/images/checked.png') }
298
        else:
299
            return None
300

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

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

    
317
        # Liste des éléments pour la tête du tableau
318

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

    
342
            if isinstance(req, EventsAggregate):
343
                event = req
344
            else:
345
                event = req[0]
346
            ids.append(event.idcause)
347

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

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

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

    
386
        # On sauvegarde la liste précédemment créée puis on remplit
387
        # le TmplContext
388
        self.events = events
389
        self.idevents = ids
390

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

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

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

    
431
            if hist.value :
432
                hist_tmp.append([
433
                    hist.timestamp,
434
                    hist.username,
435
                    hist.type_action,
436
                    self.severity[min(int(hist.value), 7)],
437
                    hist.text,
438
                    {'class': class_tr[i % 2]},
439
                    {'class': self.class_severity[min(int(hist.value), 7)]}
440
                ])
441

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

    
457
    def generate_tmpl_context(self):
458
        
459
        """
460
        Génère et peuple la variable tmpl_context avec les Dialogs et
461
        formulaires nécessaire au fonctionnement de Vigiboard
462
        """
463

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

    
480
        # Exécution des contexts des plugins
481
        for j in self.plugin:
482
            j.context(self.context_fct)
483