Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

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

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 = filter(len, krgv['id'].split(','))
434
        
435
        # Si des changements sont survenus depuis que la 
436
        # page est affichée, on en informe l'utilisateur.
437
        last_modification = get_last_modification_timestamp(ids, None)
438
        if last_modification and datetime.fromtimestamp(\
439
            float(krgv['last_modification'])) < last_modification:
440
            flash(_('Changes have occurred since the page was last displayed, '
441
                    'your changes HAVE NOT been saved.'), 'warning')
442
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
443

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

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

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

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

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

    
504

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

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

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

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

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

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

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