Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 858d88aa

History | View | Annotate | Download (22.8 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4: 
3
"""Vigiboard Controller"""
4

    
5
from datetime import datetime
6
from time import mktime
7
import math
8
import urllib
9

    
10
from tg.exceptions import HTTPNotFound
11
from tg import expose, validate, require, flash, \
12
    tmpl_context, request, config, session, redirect, url
13
from tw.forms import validators
14
from pylons.i18n import ugettext as _
15
from pylons.i18n import lazy_ugettext as l_
16
from pylons.controllers.util import abort
17
from sqlalchemy import asc
18
from sqlalchemy.sql import func
19
from repoze.what.predicates import Any, not_anonymous
20

    
21
from vigilo.models.configure import DBSession
22
from vigilo.models import Event, EventHistory, CorrEvent, SupItem, \
23
                            HostGroup, ServiceGroup, StateName, User
24
from vigilo.models.functions import sql_escape_like
25

    
26
from vigilo.turbogears.controllers.autocomplete \
27
    import make_autocomplete_controller
28
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
29
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
30
from vigiboard.widgets.edit_event import edit_event_status_options
31
from vigiboard.lib.base import BaseController
32

    
33
__all__ = ('RootController', 'get_last_modification_timestamp', 
34
           'date_to_timestamp')
35

    
36
# pylint: disable-msg=R0201
37
class RootController(VigiboardRootController):
38
    """
39
    Le controller général de vigiboard
40
    """
41
    autocomplete = make_autocomplete_controller(BaseController)
42

    
43
    def process_form_errors(self, *argv, **kwargv):
44
        """
45
        Gestion des erreurs de validation : On affiche les erreurs
46
        puis on redirige vers la dernière page accédée.
47
        """
48
        for k in tmpl_context.form_errors:
49
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
50
        if request.environ.get('HTTP_REFERER') :
51
            redirect(request.environ.get('HTTP_REFERER'
52
                ).split(request.environ.get('HTTP_HOST'))[1])
53
        else :
54
            redirect('/')
55

    
56
    @expose('vigiboard.html')
57
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
58
    def default(self, page=None, hostgroup=None, servicegroup=None,
59
            host=None, service=None, output=None, trouble_ticket=None,
60
            from_date=None, to_date=None, *argv, **krgv):
61
            
62
        """
63
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
64
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
65
        en compte, puis de sévérité.
66
        Pour accéder à cette page, l'utilisateur doit être authentifié.
67

68
        @param page: Numéro de la page souhaitée, commence à 1
69
        @param host: Si l'utilisateur souhaite sélectionner seulement certains
70
                     événements suivant leur hôte, il peut placer une expression
71
                     ici en suivant la structure du LIKE en SQL
72
        @param service: Idem que host mais sur les services
73
        @param output: Idem que host mais sur le text explicatif
74
        @param trouble_ticket: Idem que host mais sur les tickets attribués
75
        """
76
        if page is None:
77
            page = 1
78

    
79
        try:
80
            page = int(page)
81
        except ValueError:
82
            abort(404)
83

    
84
        if page < 1:
85
            page = 1
86

    
87
        username = request.environ['repoze.who.identity']['repoze.who.userid']
88
        user = User.by_user_name(username)
89
        
90
        aggregates = VigiboardRequest(user)
91
        aggregates.add_table(
92
            CorrEvent,
93
            aggregates.items.c.hostname,
94
            aggregates.items.c.servicename
95
        )
96
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
97
        aggregates.add_join((aggregates.items, 
98
            Event.idsupitem == aggregates.items.c.idsupitem))
99
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
100
        
101
        search = {
102
            'host': '',
103
            'service': '',
104
            'output': '',
105
            'tt': '',
106
            'from_date': '',
107
            'to_date': '',
108
            'hostgroup': '',
109
            'servicegroup': '',
110
        }
111

    
112
        # Application des filtres si nécessaire
113
        if hostgroup:
114
            search['hostgroup'] = hostgroup
115
            hostgroup = sql_escape_like(hostgroup)
116
            aggregates.add_join((HostGroup, HostGroup.idgroup == \
117
                aggregates.items.c.idhostgroup))
118
            aggregates.add_filter(HostGroup.name.ilike('%%%s%%' % hostgroup))
119

    
120
        if servicegroup:
121
            search['servicegroup'] = servicegroup
122
            servicegroup = sql_escape_like(servicegroup)
123
            aggregates.add_join((ServiceGroup, ServiceGroup.idgroup == \
124
                aggregates.items.c.idservicegroup))
125
            aggregates.add_filter(
126
                ServiceGroup.name.ilike('%%%s%%' % servicegroup))
127

    
128
        if host:
129
            search['host'] = host
130
            host = sql_escape_like(host)
131
            aggregates.add_filter(aggregates.items.c.hostname.ilike(
132
                '%%%s%%' % host))
133

    
134
        if service:
135
            search['service'] = service
136
            service = sql_escape_like(service)
137
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
138
                '%%%s%%' % service))
139

    
140
        if output:
141
            search['output'] = output
142
            output = sql_escape_like(output)
143
            aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
144

    
145
        if trouble_ticket:
146
            search['tt'] = trouble_ticket
147
            trouble_ticket = sql_escape_like(trouble_ticket)
148
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
149
                '%%%s%%' % trouble_ticket))
150

    
151
        if from_date:
152
            search['from_date'] = from_date
153
            # TRANSLATORS: Format de date et heure.
154
            try:
155
                from_date = datetime.strptime(
156
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
157
            except ValueError:
158
                to_date = None
159
            aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
160

    
161
        if to_date:
162
            search['to_date'] = to_date
163
            # TRANSLATORS: Format de date et heure.
164
            try:
165
                to_date = datetime.strptime(
166
                    to_date, _('%Y-%m-%d %I:%M:%S %p'))
167
            except ValueError:
168
                to_date = None
169
            aggregates.add_filter(CorrEvent.timestamp_active <= to_date)
170

    
171
        # Calcul des éléments à afficher et du nombre de pages possibles
172
        total_rows = aggregates.num_rows()
173
        items_per_page = int(config['vigiboard_items_per_page'])
174

    
175
        id_first_row = items_per_page * (page-1)
176
        id_last_row = min(id_first_row + items_per_page, total_rows)
177

    
178
        aggregates.format_events(id_first_row, id_last_row)
179
        aggregates.generate_tmpl_context()
180

    
181
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
182
        if not total_rows:
183
            id_first_row = 0
184
        else:
185
            id_first_row += 1
186

    
187
        return dict(
188
            events = aggregates.events,
189
            rows_info = {
190
                'id_first_row': id_first_row,
191
                'id_last_row': id_last_row,
192
                'total_rows': total_rows,
193
            },
194
            nb_pages = nb_pages,
195
            page = page,
196
            event_edit_status_options = edit_event_status_options,
197
            history = [],
198
            hist_error = False,
199
            plugin_context = aggregates.context_fct,
200
            search = search,
201
            refresh_times = config['vigiboard_refresh_times'],
202
        )
203
      
204
    @validate(validators={'idcorrevent': validators.Int(not_empty=True)},
205
            error_handler=process_form_errors)
206
    @expose('json')
207
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
208
    def history_dialog(self, idcorrevent):
209
        
210
        """
211
        JSon renvoyant les éléments pour l'affichage de la fenêtre de dialogue
212
        contenant des liens internes et externes.
213
        Pour accéder à cette page, l'utilisateur doit être authentifié.
214

215
        @param id: identifiant de l'événement
216
        """
217

    
218
        # Obtention de données sur l'événement et sur son historique
219
        username = request.environ.get('repoze.who.identity'
220
                    ).get('repoze.who.userid')
221

    
222
        username = request.environ['repoze.who.identity']['repoze.who.userid']
223
        events = VigiboardRequest(User.by_user_name(username))
224
        events.add_table(
225
            Event,
226
            events.items.c.hostname,
227
            events.items.c.servicename,
228
        )
229
        events.add_join((CorrEvent, CorrEvent.idcause == Event.idevent))
230
        events.add_join((events.items, 
231
            Event.idsupitem == events.items.c.idsupitem))
232
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
233

    
234
        # Vérification que au moins un des identifiants existe et est éditable
235
        if events.num_rows() != 1:
236
            flash(_('No access to this event'), 'error')
237
            redirect('/')
238

    
239
        event = events.req[0]
240
        eventdetails = {}
241
        for edname, edlink in \
242
                config['vigiboard_links.eventdetails'].iteritems():
243

    
244
            # Rappel:
245
            # event[0] = priorité de l'alerte corrélée.
246
            # event[1] = alerte brute.
247
            if event.servicename:
248
                service = urllib.quote(event.servicename)
249
            else:
250
                service = None
251
            eventdetails[edname] = edlink[1] % {
252
                'idcorrevent': idcorrevent,
253
                'host': urllib.quote(event.hostname),
254
                'service': service,
255
                'message': urllib.quote(event[0].message),
256
            }
257

    
258
        return dict(
259
                current_state = StateName.value_to_statename(
260
                                    event[0].current_state),
261
                initial_state = StateName.value_to_statename(
262
                                    event[0].initial_state),
263
                peak_state = StateName.value_to_statename(
264
                                    event[0].peak_state),
265
                idcorrevent = idcorrevent,
266
                host = event.hostname,
267
                service = event.servicename,
268
                eventdetails = eventdetails,
269
            )
270

    
271
    @validate(validators={'idcorrevent': validators.Int(not_empty=True)},
272
            error_handler=process_form_errors)
273
    @expose('vigiboard.html')
274
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
275
    def event(self, idcorrevent):
276
        """
277
        Affichage de l'historique d'un événement.
278
        Pour accéder à cette page, l'utilisateur doit être authentifié.
279

280
        @param idevent: identifiant de l'événement souhaité
281
        """
282

    
283
        username = request.environ['repoze.who.identity']['repoze.who.userid']
284
        events = VigiboardRequest(User.by_user_name(username))
285
        events.add_table(
286
            CorrEvent,
287
            events.items.c.hostname,
288
            events.items.c.servicename,
289
        )
290
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
291
        events.add_join((events.items, 
292
            Event.idsupitem == events.items.c.idsupitem))
293
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
294
        
295
        # Vérification que l'événement existe
296
        if events.num_rows() != 1 :
297
            flash(_('No access to this event'), 'error')
298
            redirect('/')
299
       
300
        events.format_events(0, 1)
301
        events.format_history()
302
        events.generate_tmpl_context() 
303

    
304
        return dict(
305
            events = events.events,
306
            rows_info = {
307
                'id_first_row': 1,
308
                'id_last_row': 1,
309
                'total_rows': 1,
310
            },
311
            nb_pages = 1,
312
            page = 1,
313
            event_edit_status_options = edit_event_status_options,
314
            history = events.hist,
315
            hist_error = True,
316
            plugin_context = events.context_fct,
317
            search = {
318
                'host': '',
319
                'service': '',
320
                'output': '',
321
                'tt': '',
322
                'from_date': '',
323
                'to_date': '',
324
                'hostgroup': '',
325
                'servicegroup': '',
326
            },
327
           refresh_times=config['vigiboard_refresh_times'],
328
        )
329

    
330
    @validate(
331
        validators={
332
            'host': validators.NotEmpty(),
333
#            'service': validators.NotEmpty()
334
        }, 
335
        error_handler = process_form_errors)
336
    @expose('vigiboard.html')
337
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
338
    def host_service(self, host, service=None):
339
        """
340
        Affichage de l'historique de l'ensemble des événements correspondant
341
        au host et service demandé.
342
        Pour accéder à cette page, l'utilisateur doit être authentifié.
343

344
        @param host: Nom de l'hôte souhaité.
345
        @param service: Nom du service souhaité
346
        """
347

    
348
        idsupitem = SupItem.get_supitem(host, service)
349

    
350
        username = request.environ['repoze.who.identity']['repoze.who.userid']
351
        events = VigiboardRequest(User.by_user_name(username))
352
        events.add_table(
353
            CorrEvent,
354
            events.items.c.hostname,
355
            events.items.c.servicename,
356
        )
357
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
358
        events.add_join((events.items, 
359
            Event.idsupitem == events.items.c.idsupitem))
360
        events.add_filter(events.items.c.idsupitem == idsupitem)
361

    
362
        # XXX On devrait avoir une autre API que ça !!!
363
        # Supprime le filtre qui empêche d'obtenir des événements fermés
364
        # (ie: ayant l'état Nagios 'OK' et le statut 'AAClosed').
365
        if len(events.filter) > 0:
366
            del events.filter[0]
367

    
368
        # Vérification qu'il y a au moins 1 événement qui correspond
369
        if events.num_rows() == 0 :
370
            flash(_('No access to this host/service or no event yet'), 'error')
371
            redirect('/')
372

    
373
        events.format_events(0, events.num_rows())
374
        events.format_history()
375
        events.generate_tmpl_context()
376

    
377
        return dict(
378
                    events = events.events,
379
                    rows_info = {
380
                        'id_first_row': 1,
381
                        'id_last_row': 1,
382
                        'total_rows': 1,
383
                    },
384
                    nb_pages = 1,
385
                    page = 1,
386
                    event_edit_status_options = edit_event_status_options,
387
                    history = events.hist,
388
                    hist_error = True,
389
                    plugin_context = events.context_fct,
390
                    search = {
391
                        'host': '',
392
                        'service': '',
393
                        'output': '',
394
                        'tt': '',
395
                        'from_date': '',
396
                        'to_date': '',
397
                        'hostgroup': '',
398
                        'servicegroup': '',
399
                    },
400
                    refresh_times=config['vigiboard_refresh_times'],
401
                )
402

    
403
    @validate(validators={
404
        "id": validators.Regex(r'^[0-9]+(,[0-9]+)*,?$'),
405
#        "trouble_ticket": validators.Regex(r'^[0-9]*$'),
406
        "ack": validators.OneOf([
407
            u'NoChange',
408
            u'None',
409
            u'Acknowledged',
410
            u'AAClosed'
411
        ])}, error_handler=process_form_errors)
412
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
413
    def update(self,**krgv):
414
        """
415
        Mise à jour d'un événement suivant les arguments passés.
416
        Cela peut être un changement de ticket ou un changement de statut.
417
        
418
        @param krgv['id']: Le ou les identifiants des événements à traiter
419
        @param krgv['last_modification']: La date de la dernière modification
420
        dont l'utilisateur est au courant.
421
        @param krgv['tt']: Nouveau numéro du ticket associé.
422
        @param krgv['status']: Nouveau status de/des événements.
423
        """
424

    
425
        # On vérifie que des identifiants ont bien été transmis via
426
        # le formulaire, et on informe l'utilisateur le cas échéant.
427
        if krgv['id'] is None:
428
            flash(_('No event has been selected'), 'warning')
429
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
430

    
431
        # Le filtre permet d'éliminer les chaines vides contenues dans le
432
        # tableau ('a,b,' -> split -> ['a','b',''] -> filter -> ['a','b']).
433
        ids = map(int, filter(len, krgv['id'].split(',')))
434

    
435
        # Si l'utilisateur édite plusieurs événements à la fois,
436
        # il nous faut chacun des identifiants
437
       
438
        username = request.environ['repoze.who.identity']['repoze.who.userid']
439
        events = VigiboardRequest(User.by_user_name(username))
440
        events.add_table(CorrEvent)
441
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
442
        events.add_join((events.items, 
443
            Event.idsupitem == events.items.c.idsupitem))
444
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
445
        
446
        events.generate_request()
447
        idevents = [cause.idcause for cause in events.req]
448
        # Si des changements sont survenus depuis que la 
449
        # page est affichée, on en informe l'utilisateur.
450
        last_modification = get_last_modification_timestamp(idevents, None)
451
        if last_modification and datetime.fromtimestamp(\
452
            float(krgv['last_modification'])) < last_modification:
453
            flash(_('Changes have occurred since the page was last displayed, '
454
                    'your changes HAVE NOT been saved.'), 'warning')
455
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
456
        
457
        # Vérification que au moins un des identifiants existe et est éditable
458
        if events.num_rows() <= 0:
459
            flash(_('No access to this event'), 'error')
460
            redirect('/')
461
        
462
        # Modification des événements et création d'un historique
463
        # pour chacun d'eux.
464
        for req in events.req:
465
            if isinstance(req, CorrEvent):
466
                event = req
467
            else:
468
                event = req[0]
469

    
470
            if krgv['trouble_ticket'] != '' :
471
                history = EventHistory(
472
                        type_action="Ticket change",
473
                        idevent=event.idcause,
474
                        value=krgv['trouble_ticket'],
475
                        text="Changed trouble ticket from '%s' to '%s'" % (
476
                            event.trouble_ticket, krgv['trouble_ticket']
477
                        ),
478
                        username=username,
479
                        timestamp=datetime.now(),
480
                    )
481
                DBSession.add(history)   
482
                event.trouble_ticket = krgv['trouble_ticket']
483

    
484
            if krgv['ack'] != 'NoChange' :
485
                history = EventHistory(
486
                        type_action="Acknowledgement change state",
487
                        idevent=event.idcause,
488
                        value=krgv['ack'],
489
                        text="Changed acknowledgement status "
490
                            "from '%s' to '%s'" % (
491
                            event.status, krgv['ack']
492
                        ),
493
                        username=username,
494
                        timestamp=datetime.now(),
495
                    )
496
                DBSession.add(history)
497
                event.status = krgv['ack']
498

    
499
        DBSession.flush()
500
        flash(_('Updated successfully'))
501
        redirect(request.environ.get('HTTP_REFERER', url('/')))
502

    
503

    
504
    @validate(validators={"plugin_name": validators.OneOf(
505
        [i for [i, j] in config.get('vigiboard_plugins', [])])},
506
                error_handler = process_form_errors)
507
    @expose('json')
508
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
509
    def get_plugin_value(self, idcorrevent, plugin_name, *arg, **krgv):
510
        """
511
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
512
        donné via JSON.
513
        """
514
        plugins = config['vigiboard_plugins']
515
        if plugins is None:
516
            raise HTTPNotFound()
517

    
518
        # Permet de vérifier si l'utilisateur a bien les permissions
519
        # pour accéder à cet événement et si l'événement existe.
520
        username = request.environ['repoze.who.identity']['repoze.who.userid']
521
        events = VigiboardRequest(User.by_user_name(username))
522
        events.add_table(CorrEvent.idcorrevent)
523
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
524
        events.add_join((events.items, 
525
            Event.idsupitem == events.items.c.idsupitem))
526
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
527

    
528
        # Pas d'événement ou permission refusée. On ne distingue pas
529
        # les 2 cas afin d'éviter la divulgation d'informations.
530
        if not events.num_rows():
531
            raise HTTPNotFound()
532

    
533
        plugin = [i for i in plugins if i[0] == plugin_name][0]
534
        try:
535
            mypac = __import__(
536
                'vigiboard.controllers.vigiboard_plugin.' + plugin[0],
537
                globals(), locals(), [plugin[1]], -1)
538
            plug = getattr(mypac, plugin[1])()
539
            return plug.controller(idcorrevent, *arg, **krgv)
540
        except ImportError:
541
            raise HTTPNotFound()
542

    
543
    @validate(validators={
544
        "fontsize": validators.Regex(
545
            r'[0-9]+(pt|px|em|%)',
546
            regexOps = ('I',)
547
        )}, error_handler = process_form_errors)
548
    @expose('json')
549
    def set_fontsize(self, fontsize):
550
        """Enregistre la taille de la police dans les préférences."""
551
        session['fontsize'] = fontsize
552
        session.save()
553
        return dict()
554

    
555
    @validate(validators={"refresh": validators.Int()},
556
            error_handler=process_form_errors)
557
    @expose('json')
558
    def set_refresh(self, refresh):
559
        """Enregistre le temps de rafraichissement dans les préférences."""
560
        session['refresh'] = refresh
561
        session.save()
562
        return dict()
563

    
564
    @expose('json')
565
    def set_theme(self, theme):
566
        """Enregistre le thème à utiliser dans les préférences."""
567
        # On sauvegarde l'ID du thème sans vérifications
568
        # car les thèmes (styles CSS) sont définies dans
569
        # les packages de thèmes (ex: vigilo-themes-default).
570
        # La vérification de la valeur est faite dans les templates.
571
        session['theme'] = theme
572
        session.save()
573
        return dict()
574
    
575
def get_last_modification_timestamp(event_id_list, 
576
                                    value_if_none=datetime.now()):
577
    """
578
    Récupère le timestamp de la dernière modification 
579
    opérée sur l'un des événements dont l'identifiant
580
    fait partie de la liste passée en paramètre.
581
    """
582
    last_modification_timestamp = DBSession.query(
583
                                func.max(EventHistory.timestamp),
584
                         ).filter(EventHistory.idevent.in_(event_id_list)
585
                         ).scalar()
586
    if not last_modification_timestamp:
587
        if not value_if_none:
588
            return None
589
        else:
590
            last_modification_timestamp = value_if_none
591
    return datetime.fromtimestamp(mktime(
592
        last_modification_timestamp.timetuple()))