Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 434b574d

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
        self.filter = [
57
                HostGroup.idgroup.in_(self.user_groups),
58
                ServiceGroup.idgroup.in_(self.user_groups),
59

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

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

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

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

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

    
98
    def add_plugin(self, *argv):
99

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

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

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

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

    
148
    def num_rows(self):
149

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

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

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

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

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

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

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

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

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

    
216
    def add_filter(self, *argv):
217

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

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

    
233
    def add_group_by(self, *argv):
234

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

238
        @param argv: Liste des groupements à ajouter
239
        """
240
        
241
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
242
        # des groupements.
243
        
244
        for i in argv:
245
            for j in self.groupby:
246
                try:
247
                    if str(i) == str(j):
248
                        break
249
                # SQLAlchemy lève cette exception pour certains attributes,
250
                # par exemple les attributs définis avec synonym().
251
                except AttributeError:
252
                    pass
253
            self.groupby.append(i)
254

    
255
    def add_order_by(self, *argv):
256

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

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

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

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

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

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

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

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

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

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

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

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

    
348
            cause = event.cause.first()
349

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

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

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

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

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

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

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

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

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

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

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