Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 5dbfa80d

History | View | Annotate | Download (22.5 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
        
100
        search = {
101
            'host': '',
102
            'service': '',
103
            'output': '',
104
            'tt': '',
105
            'from_date': '',
106
            'to_date': '',
107
            'hostgroup': '',
108
            'servicegroup': '',
109
        }
110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
424
        # On vérifie que des identifiants ont bien été transmis via
425
        # le formulaire, et on informe l'utilisateur le cas échéant.
426
        if krgv['id'] is None:
427
            flash(_('No event has been selected'), 'warning')
428
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
429
        ids = krgv['id'].split(',')
430
        
431
        # Si des changements sont survenus depuis que la 
432
        # page est affichée, on en informe l'utilisateur.
433
        last_modification = get_last_modification_timestamp(ids, None)
434
        if last_modification and datetime.fromtimestamp(\
435
            float(krgv['last_modification'])) < last_modification:
436
            flash(_('Changes have occurred since the page was last displayed, '
437
                    'your changes HAVE NOT been saved.'), 'warning')
438
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
439

    
440
        # Si l'utilisateur édite plusieurs événements à la fois,
441
        # il nous faut chacun des identifiants
442
       
443
        if len(ids) > 1 :
444
            ids = ids[:-1]
445

    
446
        username = request.environ['repoze.who.identity']['repoze.who.userid']
447
        events = VigiboardRequest(User.by_user_name(username))
448
        events.add_table(CorrEvent)
449
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
450
        events.add_join((aggregates.items, 
451
            Event.idsupitem == events.items.c.idsupitem))
452
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
453
        
454
        # Vérification que au moins un des identifiants existe et est éditable
455
        if events.num_rows() <= 0 :
456
            flash(_('No access to this event'), 'error')
457
            redirect('/')
458
        
459
        # Modification des événements et création d'un historique
460
        # pour chacun d'eux.
461
        for req in events.req:
462
            if isinstance(req, CorrEvent):
463
                event = req
464
            else:
465
                event = req[0]
466

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

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

    
496
        DBSession.flush()
497
        flash(_('Updated successfully'))
498
        redirect(request.environ.get('HTTP_REFERER', url('/')))
499

    
500

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

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

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

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

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

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

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