Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.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, State, \
6
                            Host, HostGroup, ServiceLowLevel, ServiceGroup, \
7
                            StateName
8
from tg import tmpl_context, url, config
9
from vigiboard.model import DBSession
10
from sqlalchemy import not_, and_, asc, desc, sql
11
from tw.jquery.ui_dialog import JQueryUIDialog
12
from vigiboard.widgets.edit_event import EditEventForm , SearchForm
13
from vigiboard.controllers.vigiboard_plugin import VigiboardRequestPlugin
14
from pylons.i18n import ugettext as _
15

    
16
class VigiboardRequest():
17
    class_ack = {
18
        'None': '',
19
        'Acknowledged': '_Ack',
20
        'AAClosed': '_Ack',
21
    }
22

    
23
    """
24
    Classe gérant la génération de la requête finale,
25
    le préformatage des événements et celui des historiques
26
    """
27

    
28
    def __init__(self, user):
29

    
30
        """
31
        Initialisation de toutes les variables nécessaires: Liste des groupes
32
        de l'utilisateur, les classes à appliquer suivant la sévérité, les
33
        différentes étapes de la génération de la requête et la liste des
34
        plugins appliqués.
35
        """
36

    
37
        self.user_groups = user.groups
38
        self.generaterq = False
39

    
40
        self.table = [
41
            EventsAggregate,
42
            sql.func.count(EventsAggregate.idaggregate)
43
        ]
44

    
45
        self.join = [
46
            (Event, EventsAggregate.idcause == Event.idevent),
47
            (Host, Event.hostname == Host.name),
48
            (ServiceLowLevel, Event.servicename == ServiceLowLevel.name),
49
            (HostGroup, Host.name == HostGroup.hostname),
50
            (ServiceGroup, ServiceLowLevel.name == ServiceGroup.servicename),
51
            (StateName, StateName.idstatename == Event.current_state),
52
        ]
53

    
54
        self.outerjoin = [
55
        ]
56

    
57
        self.filter = [
58
                HostGroup.idgroup.in_(self.user_groups),
59
                ServiceGroup.idgroup.in_(self.user_groups),
60

    
61
                # On masque les événements avec l'état OK
62
                # et traités (status == u'AAClosed').
63
                not_(and_(
64
                    StateName.statename == u'OK',
65
                    EventsAggregate.status == u'AAClosed'
66
                )),
67
                EventsAggregate.timestamp_active != None,
68
            ]
69

    
70
        # Permet de définir le sens de tri pour la priorité.
71
        if config['vigiboard_priority_order'] == 'asc':
72
            priority_order = asc(EventsAggregate.priority)
73
        else:
74
            priority_order = desc(EventsAggregate.priority)
75

    
76
        self.orderby = [
77
                desc(EventsAggregate.status),   # None, Acknowledged, AAClosed
78
                priority_order,                 # Priorité ITIL (entier).
79
                desc(StateName.order),          # Etat courant (entier).
80
                desc(Event.timestamp),
81
                asc(Event.hostname),
82
            ]
83

    
84
        self.groupby = [
85
                EventsAggregate.idaggregate,
86
                EventsAggregate,
87
                Event.hostname,
88
                StateName.order,
89
                Event.timestamp,
90
            ]
91

    
92
        self.plugin = []
93
        self.events = []
94
        self.idevents = []
95
        self.hist = []
96
        self.req = DBSession
97
        self.context_fct = []
98

    
99
    def add_plugin(self, *argv):
100

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

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

    
135
        # query et join ont besoin de referrence
136
        self.req = self.req.query(*self.table)
137
        self.req = self.req.join(*self.join)
138

    
139
        # le reste, non
140
        for i in self.outerjoin:
141
            self.req = self.req.outerjoin(i)
142
        for i in self.filter:
143
            self.req = self.req.filter(i)
144
        for i in self.groupby:
145
            self.req = self.req.group_by(i)
146
        for i in self.orderby:
147
            self.req = self.req.order_by(i)
148

    
149
    def num_rows(self):
150

    
151
        """
152
        Retourne le nombre de lignes de la requête.
153
        Si celle-ci n'est pas encore générée, on le fait.
154

155
        @return: Nombre de ligne
156
        """
157

    
158
        if not self.generaterq:
159
            self.generate_request()
160
            self.generaterq = True
161
        return self.req.count()
162

    
163
    def add_table(self, *argv):
164
        
165
        """
166
        Ajoute une ou plusieurs tables/élément d'une table à
167
        la requête.
168

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

    
181
    def add_join(self, *argv):
182
        
183
        """
184
        Ajoute une ou plusieurs jointures à
185
        la requête.
186

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

    
199
    def add_outer_join(self, *argv):
200
        
201
        """
202
        Ajoute une ou plusieurs jointures externes à
203
        la requête.
204

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

    
217
    def add_filter(self, *argv):
218

    
219
        """
220
        Ajoute un ou plusieurs filtres à la requête.
221

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

    
234
    def add_group_by(self, *argv):
235

    
236
        """
237
        Ajoute un ou plusieurs groupements à la requête.
238

239
        @param argv: Liste des groupements à ajouter
240
        """
241
        
242
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
243
        # des groupements.
244
        
245
        for i in argv:
246
            for j in self.groupby:
247
                try:
248
                    if str(i) == str(j):
249
                        break
250
                # SQLAlchemy lève cette exception pour certains attributes,
251
                # par exemple les attributs définis avec synonym().
252
                except AttributeError:
253
                    pass
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.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
        Formate la réponse de la requête et y applique les plugins
294
        pour un affichage simple du résultat par Genshi.
295
        On génère une liste de liste, chaqu'une étant la description de
296
        l'affichage pour un événement donné.
297

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

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

    
309
        lst_title = [
310
                ['',{}],
311
                [_('Date')+ '<span style="font-weight:normal">' + \
312
                        '<br />['+_('Duration') + ']</span>',
313
                        {'style':'text-align:left'}],
314
                [_('Priority'), {'title':_('ITIL Priority')}],
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
            # Si il y a plus d'un élément dans la liste des tables,
330
            # req devient une liste plutôt que d'être directement la
331
            # table souhaitée
332

    
333
            if isinstance(req, EventsAggregate):
334
                event = req
335
            else:
336
                event = req[0]
337
            ids.append(event.idcause)
338

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

    
349
            cause = event.cause.first()
350

    
351
            events.append([
352
                    event,
353
                    {'class': class_tr[i % 2]},
354
                    {'class': StateName.value_to_statename(
355
                        cause.initial_state) +
356
                        self.class_ack[event.status]},
357
                    {'class': StateName.value_to_statename(
358
                        cause.current_state) +
359
                        self.class_ack[event.status]},
360
                    {'src': '/images/%s2.png' %
361
                        StateName.value_to_statename(
362
                        cause.current_state)},
363
                    self.format_events_img_status(event),
364
                    [[j.__show__(event), j.style] for j in self.plugin]
365
                ])
366
            i += 1
367

    
368
        # On sauvegarde la liste précédemment créée puis on remplit
369
        # le TmplContext
370
        self.events = events
371
        self.idevents = ids
372

    
373
    def format_history(self):
374
        
375
        """
376
        Formate les historiques correspondant aux événements sélectionnés
377
        pour un affichage simple du résultat par Genshi.
378
        On génère une liste de liste, chaqu'une étant la description
379
        de l'affichage pour un historique donné.
380
        """
381

    
382
        history = DBSession.query(
383
                    EventHistory,
384
                ).filter(EventHistory.idevent.in_(self.idevents)
385
                ).order_by(desc(EventHistory.timestamp)
386
                ).order_by(desc(EventHistory.idhistory))
387
        if history.count() == 0:
388
            self.hist = {}
389
            for i in self.idevents:
390
                self.hist[i] = []
391
            return
392

    
393
        hists = {}
394
        i = 0
395
        class_tr = ['odd', 'even']
396

    
397
        for hist in history:
398
            if not hist.idevent in hists:
399
                hists[hist.idevent] = []
400

    
401
            # La liste pour l'historique actuel comporte dans l'ordre :
402
            #   Le moment où il a été généré
403
            #   Qui l'a généré
404
            #   Le type d'action qui a été appliqué
405
            #   La valeur de l'action
406
            #   Le détail de l'action
407
            #   La classe à appliquer à la ligne
408
            #       (permet d'alterner les couleurs)
409

    
410
            hists[hist.idevent].append([
411
                hist.timestamp,
412
                hist.username,
413
                hist.type_action,
414
                hist.value,
415
                hist.text,
416
                {'class': class_tr[i % 2]},
417
            ])
418
            i += 1
419
        
420
        self.hist = hists
421

    
422
    def generate_tmpl_context(self):
423
        
424
        """
425
        Génère et peuple la variable tmpl_context avec les Dialogs et
426
        formulaires nécessaire au fonctionnement de Vigiboard
427
        """
428

    
429
        # Dialogue d'édition
430
        tmpl_context.edit_event_form = EditEventForm('edit_event_form',
431
                action=url('/update'))
432
        tmpl_context.edit_eventdialog = JQueryUIDialog(id='Edit_EventsDialog',
433
                autoOpen=False, title=_('Edit Event'))
434
    
435
        # Dialogue de recherche
436
        tmpl_context.search_form = SearchForm('search_form',
437
                action=url('/'))
438
        tmpl_context.searchdialog = JQueryUIDialog(id='SearchDialog',
439
                autoOpen=False, title=_('Search Event'))
440
        
441
        # Dialogue de détail d'un événement
442
        tmpl_context.historydialog = JQueryUIDialog(id='HistoryDialog',
443
                autoOpen=False, title=_('History'))
444

    
445
        # Exécution des contexts des plugins
446
        for j in self.plugin:
447
            j.context(self.context_fct)
448