Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 8c93d88b

History | View | Annotate | Download (21.3 KB)

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

    
5
from tg import expose, validate, require, flash, \
6
    tmpl_context, request, config, session, redirect, url
7
from tw.forms import validators
8
from pylons.i18n import ugettext as _
9
from pylons.i18n import lazy_ugettext as l_
10
from pylons.controllers.util import abort
11
from sqlalchemy import not_, and_, asc
12
from sqlalchemy.orm import aliased
13
from sqlalchemy.sql import func
14
from datetime import datetime
15
from time import mktime
16
import math
17
import urllib
18

    
19
from vigiboard.model import DBSession
20
from vigiboard.model import Event, EventHistory, CorrEvent, \
21
                            Host, HostGroup, ServiceGroup, \
22
                            StateName, User, ServiceLowLevel
23
from repoze.what.predicates import Any, not_anonymous
24
from vigiboard.widgets.edit_event import edit_event_status_options
25
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
26
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
27
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
28
from vigilo.models.functions import sql_escape_like
29
from vigilo.models.secondary_tables import HOST_GROUP_TABLE, \
30
                                            SERVICE_GROUP_TABLE
31
from vigiboard.lib.base import BaseController
32

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

    
36
class RootController(VigiboardRootController):
37
    """
38
    Le controller général de vigiboard
39
    """
40
    autocomplete = AutoCompleteController(BaseController)
41

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

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

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

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

    
83
        if page < 1:
84
            page = 1
85

    
86
        username = request.environ['repoze.who.identity']['repoze.who.userid']
87
        user = User.by_user_name(username)
88
        
89
        aggregates = VigiboardRequest(user)
90
        
91
        search = {
92
            'host': '',
93
            'service': '',
94
            'output': '',
95
            'tt': '',
96
            'from_date': '',
97
            'to_date': '',
98
            'hostgroup': '',
99
            'servicegroup': '',
100
        }
101

    
102
        # Application des filtres si nécessaire
103
        if hostgroup:
104
            search['hostgroup'] = hostgroup
105
            hostgroup = sql_escape_like(hostgroup)
106
            hg_alias = aliased(HostGroup)
107
            aggregates.add_outer_join((hg_alias, hg_alias.idgroup == \
108
                HOST_GROUP_TABLE.c.idgroup))
109
            aggregates.add_filter(hg_alias.name.ilike('%%%s%%' % hostgroup))
110

    
111
        if servicegroup:
112
            search['servicegroup'] = servicegroup
113
            servicegroup = sql_escape_like(servicegroup)
114
            sg_alias = aliased(ServiceGroup)
115
            aggregates.add_outer_join((sg_alias, sg_alias.idgroup == \
116
                SERVICE_GROUP_TABLE.c.idgroup))
117
            aggregates.add_filter(sg_alias.name.ilike(
118
                '%%%s%%' % servicegroup))
119

    
120
        if host:
121
            search['host'] = host
122
            host = sql_escape_like(host)
123
            aggregates.add_filter(Host.name.ilike('%%%s%%' % host))
124

    
125
        if service:
126
            search['service'] = service
127
            service = sql_escape_like(service)
128
            aggregates.add_filter(ServiceLowLevel.servicename.ilike(
129
                '%%%s%%' % service))
130

    
131
        if output:
132
            search['output'] = output
133
            output = sql_escape_like(output)
134
            aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
135

    
136
        if trouble_ticket:
137
            search['tt'] = trouble_ticket
138
            trouble_ticket = sql_escape_like(trouble_ticket)
139
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
140
                '%%%s%%' % trouble_ticket))
141

    
142
        if from_date:
143
            search['from_date'] = from_date
144
            # TRANSLATORS: Format de date et heure.
145
            try:
146
                from_date = datetime.strptime(
147
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
148
            except ValueError:
149
                to_date = None
150
            aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
151

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

    
162
        # Calcul des éléments à afficher et du nombre de pages possibles
163
        total_rows = aggregates.num_rows()
164
        items_per_page = int(config['vigiboard_items_per_page'])
165

    
166
        id_first_row = items_per_page * (page-1)
167
        id_last_row = min(id_first_row + items_per_page, total_rows)
168

    
169
        aggregates.format_events(id_first_row, id_last_row)
170
        aggregates.generate_tmpl_context()
171

    
172
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
173
        if not total_rows:
174
            id_first_row = 0
175
        else:
176
            id_first_row += 1
177

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

206
        @param id: identifiant de l'événement
207
        """
208

    
209
        # Obtention de données sur l'événement et sur son historique
210
        username = request.environ.get('repoze.who.identity'
211
                    ).get('repoze.who.userid')
212
        user = User.by_user_name(username)
213
        user_groups = user.groups
214

    
215
#        try:
216
        event = DBSession.query(
217
                        CorrEvent.priority,
218
                        Event,
219
                 ).join(
220
                    (Event, CorrEvent.idcause == Event.idevent),
221
                    (ServiceLowLevel, Event.idsupitem == ServiceLowLevel.idservice),
222
                    (Host, Host.idhost == ServiceLowLevel.idhost),
223
                    (HOST_GROUP_TABLE, HOST_GROUP_TABLE.c.idhost == Host.idhost),
224
                    (SERVICE_GROUP_TABLE, SERVICE_GROUP_TABLE.c.idservice == \
225
                        ServiceLowLevel.idservice),
226
                 ).filter(HOST_GROUP_TABLE.c.idgroup.in_(user_groups)
227
                 ).filter(SERVICE_GROUP_TABLE.c.idgroup.in_(user_groups)
228
                 ).filter(
229
                    # On masque les événements avec l'état OK
230
                    # et traités (status == u'AAClosed').
231
                    not_(and_(
232
                        StateName.statename == u'OK',
233
                        CorrEvent.status == u'AAClosed'
234
                    ))
235
                ).filter(CorrEvent.idcorrevent == idcorrevent
236
                ).one()
237
#        except:
238
#            # XXX Raise some HTTP error.
239
#            return None
240

    
241
        history = DBSession.query(
242
                    EventHistory,
243
                 ).filter(EventHistory.idevent == event[1].idevent
244
                 ).order_by(asc(EventHistory.timestamp)
245
                 ).order_by(asc(EventHistory.type_action)).all()
246

    
247
        eventdetails = {}
248
        for edname, edlink in \
249
                config['vigiboard_links.eventdetails'].iteritems():
250

    
251
            # Rappel:
252
            # event[0] = priorité de l'alerte corrélée.
253
            # event[1] = alerte brute.
254
            eventdetails[edname] = edlink[1] % {
255
                'idcorrevent': idcorrevent,
256
                'host': urllib.quote(event[1].supitem.host.name),
257
                'service': urllib.quote(event[1].supitem.servicename),
258
                'message': urllib.quote(event[1].message),
259
            }
260

    
261
        return dict(
262
                current_state = StateName.value_to_statename(
263
                                    event[1].current_state),
264
                initial_state = StateName.value_to_statename(
265
                                    event[1].initial_state),
266
                peak_state = StateName.value_to_statename(
267
                                    event[1].peak_state),
268
                idcorrevent = idcorrevent,
269
                host = event[1].supitem.host.name,
270
                service = event[1].supitem.servicename,
271
                eventdetails = eventdetails,
272
            )
273

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

283
        @param idevent: identifiant de l'événement souhaité
284
        """
285

    
286
        username = request.environ['repoze.who.identity']['repoze.who.userid']
287
        events = VigiboardRequest(User.by_user_name(username))
288
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
289
        
290
        # Vérification que l'événement existe
291
        if events.num_rows() != 1 :
292
            flash(_('Error in DB'), 'error')
293
            redirect('/')
294
       
295
        events.format_events(0, 1)
296
        events.format_history()
297
        events.generate_tmpl_context() 
298

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

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

336
        @param host: Nom de l'hôte souhaité.
337
        @param service: Nom du service souhaité
338
        """
339

    
340
        username = request.environ['repoze.who.identity']['repoze.who.userid']
341
        events = VigiboardRequest(User.by_user_name(username))
342
        events.add_join((ServiceLowLevel, ServiceLowLevel.idservice == Event.idsupitem))
343
        events.add_join((Host, ServiceLowLevel.idhost == Host.idhost))
344
        events.add_filter(Host.name == host,
345
                ServiceLowLevel.servicename == service)
346

    
347
        # XXX On devrait avoir une autre API que ça !!!
348
        # Supprime le filtre qui empêche d'obtenir des événements fermés
349
        # (ie: ayant l'état Nagios 'OK' et le statut 'AAClosed').
350
        if len(events.filter) > 2:
351
            del events.filter[2]
352

    
353
        # Vérification qu'il y a au moins 1 événement qui correspond
354
        if events.num_rows() == 0 :
355
            redirect('/')
356

    
357
        events.format_events(0, events.num_rows())
358
        events.format_history()
359
        events.generate_tmpl_context()
360

    
361
        return dict(
362
                    events = events.events,
363
                    rows_info = {
364
                        'id_first_row': 1,
365
                        'id_last_row': 1,
366
                        'total_rows': 1,
367
                    },
368
                    nb_pages = 1,
369
                    page = 1,
370
                    event_edit_status_options = edit_event_status_options,
371
                    history = events.hist,
372
                    hist_error = True,
373
                    plugin_context = events.context_fct,
374
                    search = {
375
                        'host': None,
376
                        'service': None,
377
                        'output': None,
378
                        'tt': None,
379
                        'from_date': None,
380
                        'to_date': None,
381
                        'hostgroup': None,
382
                        'servicegroup': None,
383
                    },
384
                    refresh_times=config['vigiboard_refresh_times'],
385
                )
386

    
387
    @validate(validators={
388
        "id":validators.Regex(r'^[^,]+(,[^,]*)*,?$'),
389
#        "trouble_ticket":validators.Regex(r'^[0-9]*$'),
390
        "status": validators.OneOf([
391
            'NoChange',
392
            'None',
393
            'Acknowledged',
394
            'AAClosed'
395
        ])}, error_handler=process_form_errors)
396
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
397
    def update(self,**krgv):
398
        
399
        """
400
        Mise à jour d'un événement suivant les arguments passés.
401
        Cela peut être un changement de ticket ou un changement de statut.
402
        
403
        @param krgv['id']: Le ou les identifiants des événements à traiter
404
        @param krgv['last_modification']: La date de la dernière modification
405
        dont l'utilisateur est au courant.
406
        @param krgv['tt']: Nouveau numéro du ticket associé.
407
        @param krgv['status']: Nouveau status de/des événements.
408
        """
409

    
410
        # On vérifie que des identifiants ont bien été transmis via
411
        # le formulaire, et on informe l'utilisateur le cas échéant.
412
        if krgv['id'] is None:
413
            flash(_('No event has been selected'), 'warning')
414
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
415
        ids = krgv['id'].split(',')
416
        
417
        # Si des changements sont survenus depuis que la 
418
        # page est affichée, on en informe l'utilisateur.
419
        if datetime.fromtimestamp(float(krgv['last_modification'])) \
420
                                        < get_last_modification_timestamp(ids):
421
            flash(_('Changes have occurred since the page was displayed, '
422
                    'please refresh it.'), 'warning')
423
            print "\n\n\n\n ##### ", datetime.fromtimestamp(float(krgv['last_modification'])), " #####"
424
            print "##### ", get_last_modification_timestamp(ids), "\n\n\n\n"
425
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
426

    
427
        # Si l'utilisateur édite plusieurs événements à la fois,
428
        # il nous faut chacun des identifiants
429
       
430
        if len(ids) > 1 :
431
            ids = ids[:-1]
432
        
433
        username = request.environ['repoze.who.identity']['repoze.who.userid']
434
        events = VigiboardRequest(User.by_user_name(username))
435
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
436
        
437
        # Vérification que au moins un des identifiants existe et est éditable
438
        if events.num_rows() <= 0 :
439
            flash(_('No access to this event'), 'error')
440
            redirect('/')
441
        
442
        # Modification des événements et création d'un historique
443
        # pour chacun d'eux.
444
        for req in events.req:
445
            if isinstance(req, CorrEvent):
446
                event = req
447
            else:
448
                event = req[0]
449

    
450
            if krgv['trouble_ticket'] != '' :
451
                history = EventHistory(
452
                        type_action="Ticket change",
453
                        idevent=event.idcause,
454
                        value=krgv['trouble_ticket'],
455
                        text=_("Changed trouble ticket from '%s' to '%s'") % (
456
                            event.trouble_ticket, krgv['trouble_ticket']
457
                        ),
458
                        username=username,
459
                        timestamp=datetime.now(),
460
                    )
461
                DBSession.add(history)   
462
                event.trouble_ticket = krgv['trouble_ticket']
463

    
464
            if krgv['ack'] != 'NoChange' :
465
                history = EventHistory(
466
                        type_action="Acknowlegement change state",
467
                        idevent=event.idcause,
468
                        value=krgv['ack'],
469
                        text=_("Changed acknowledgement status from '%s' to '%s'") % (
470
                            event.status, krgv['ack']
471
                        ),
472
                        username=username,
473
                        timestamp=datetime.now(),
474
                    )
475
                DBSession.add(history)
476
                event.status = krgv['ack']
477

    
478
        DBSession.flush()
479
        flash(_('Updated successfully'))
480
        redirect(request.environ.get('HTTP_REFERER', url('/')))
481

    
482

    
483
    @validate(validators={"plugin_name": validators.OneOf(
484
        [i for [i, j] in config.get('vigiboard_plugins', [])])},
485
                error_handler = process_form_errors)
486
    @expose('json')
487
    def get_plugin_value(self, plugin_name, *arg, **krgv):
488
        """
489
        Permet aux plugins de pouvoir récupérer des valeurs Json
490
        """
491
        plugins = config['vigiboard_plugins']
492
        if plugins is None:
493
            return
494

    
495
        plugin = [i for i in plugins if i[0] == plugin_name][0]
496
        try:
497
            mypac = __import__(
498
                'vigiboard.controllers.vigiboard_plugin.' + plugin[0],
499
                globals(), locals(), [plugin[1]], -1)
500
            plug = getattr(mypac, plugin[1])()
501
            return plug.controller(*arg, **krgv)
502
        except:
503
            raise
504
    
505
#    @validate(validators= {"fontsize": validators.Int()},
506
#                    error_handler = process_form_errors)
507
    @expose('json')
508
    def set_fontsize(self, fontsize):
509
        """
510
        Save font size
511
        """
512
        session['fontsize'] = fontsize
513
        session.save()
514
        return dict()
515

    
516
    @validate(validators={"refresh": validators.Int()},
517
            error_handler=process_form_errors)
518
    @expose('json')
519
    def set_refresh(self, refresh):
520
        """
521
        Save refresh time
522
        """
523
        session['refresh'] = refresh
524
        session.save()
525
        return dict()
526

    
527
    @expose('json')
528
    def set_theme(self, theme):
529
        """
530
        Save theme to use time
531
        """
532
        session['theme'] = theme
533
        session.save()
534
        return dict()
535
    
536
def get_last_modification_timestamp(event_id_list):
537
    """
538
    Récupère le timestamp de la dernière modification 
539
    opérée sur l'un des événements dont l'identifiant
540
    fait partie de la liste passée en paramètre.
541
    """
542
    last_modification_timestamp = DBSession.query(
543
                                func.max(EventHistory.timestamp),
544
                         ).filter(EventHistory.idevent.in_(event_id_list)
545
                         ).scalar()
546
                         
547
    if last_modification_timestamp:
548
        return last_modification_timestamp
549
    return datetime.now()
550
    
551
def date_to_timestamp(date):
552
    """
553
    Convertit une date en timestamp (décimal)
554
    """
555
    return mktime(date.timetuple()) + date.microsecond / 1000000.0