Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 1101e03e

History | View | Annotate | Download (19.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 tg.i18n import get_lang
11
from pylons.controllers.util import abort
12
from sqlalchemy import not_, and_, asc
13
from datetime import datetime
14
import math
15
import urllib
16

    
17
from vigiboard.model import DBSession
18
from vigiboard.model import Event, EventHistory, CorrEvent, \
19
                            Host, HostGroup, \
20
                            StateName, User, ServiceLowLevel
21
from repoze.what.predicates import Any, not_anonymous
22
from vigiboard.widgets.edit_event import edit_event_status_options
23
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
24
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
25
from vigilo.models.secondary_tables import HOST_GROUP_TABLE, \
26
                                            SERVICE_GROUP_TABLE
27

    
28
__all__ = ('RootController', )
29

    
30
def sql_escape_like(s):
31
    return s.replace('%', '\\%').replace('_', '\\_') \
32
                .replace('*', '%').replace('?', '_')
33

    
34
class RootController(VigiboardRootController):
35
    """
36
    Le controller général de vigiboard
37
    """
38

    
39
    # XXX Mettre ça dans un fichier de configuration.
40
    refresh_times = (
41
        (0, l_('Never')),
42
        (30, l_('30 seconds')),
43
        (60, l_('1 minute')),
44
        (300, l_('5 minutes')),
45
        (600, l_('10 minutes')),
46
    )
47

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

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

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

    
84
        try:
85
            page = int(page)
86
        except ValueError:
87
            abort(404)
88

    
89
        if page < 1:
90
            page = 1
91

    
92
        username = request.environ['repoze.who.identity']['repoze.who.userid']
93
        user = User.by_user_name(username)
94
        
95
        # On récupère la langue de l'utilisateur
96
        lang = get_lang()
97
        if not lang:
98
            lang = ['fr']
99
        lang = lang[0]
100
        
101
        aggregates = VigiboardRequest(user, lang)
102
        
103
        search = {
104
            'host': '',
105
            'service': '',
106
            'output': '',
107
            'tt': '',
108
            'from_date': '',
109
            'to_date': '',
110
        }
111
        # Application des filtres si nécessaire
112
        if host:
113
            search['host'] = host
114
            host = sql_escape_like(host)
115
            aggregates.add_filter(Host.name.ilike('%%%s%%' % host))
116

    
117
        if service:
118
            search['service'] = service
119
            service = sql_escape_like(service)
120
            aggregates.add_filter(ServiceLowLevel.servicename.ilike(
121
                '%%%s%%' % service))
122

    
123
        if output:
124
            search['output'] = output
125
            output = sql_escape_like(output)
126
            aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
127

    
128
        if trouble_ticket:
129
            search['tt'] = trouble_ticket
130
            trouble_ticket = sql_escape_like(trouble_ticket)
131
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
132
                '%%%s%%' % trouble_ticket))
133

    
134
        if from_date:
135
            search['from_date'] = from_date
136
            # TRANSLATORS: Format de date et heure.
137
            try:
138
                from_date = datetime.strptime(
139
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
140
            except ValueError:
141
                to_date = None
142
            aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
143

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

    
154
        # Calcul des éléments à afficher et du nombre de pages possibles
155
        total_rows = aggregates.num_rows()
156
        items_per_page = int(config['vigiboard_items_per_page'])
157

    
158
        id_first_row = items_per_page * (page-1)
159
        id_last_row = min(id_first_row + items_per_page, total_rows)
160

    
161
        aggregates.format_events(id_first_row, id_last_row)
162
        aggregates.generate_tmpl_context()
163

    
164
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
165
        if not total_rows:
166
            id_first_row = 0
167
        else:
168
            id_first_row += 1
169

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

198
        @param id: identifiant de l'événement
199
        """
200

    
201
        # Obtention de données sur l'événement et sur son historique
202
        username = request.environ.get('repoze.who.identity'
203
                    ).get('repoze.who.userid')
204
        user = User.by_user_name(username)
205
        user_groups = user.groups
206

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

    
233
        history = DBSession.query(
234
                    EventHistory,
235
                 ).filter(EventHistory.idevent == event[1].idevent
236
                 ).order_by(asc(EventHistory.timestamp)
237
                 ).order_by(asc(EventHistory.type_action)).all()
238

    
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
            eventdetails[edname] = edlink[1] % {
247
                'idcorrevent': idcorrevent,
248
                'host': urllib.quote(event[1].supitem.host.name),
249
                'service': urllib.quote(event[1].supitem.servicename),
250
                'message': urllib.quote(event[1].message),
251
            }
252

    
253
        return dict(
254
                current_state = StateName.value_to_statename(
255
                                    event[1].current_state),
256
                initial_state = StateName.value_to_statename(
257
                                    event[1].initial_state),
258
                peak_state = StateName.value_to_statename(
259
                                    event[1].peak_state),
260
                idcorrevent = idcorrevent,
261
                host = event[1].supitem.host.name,
262
                service = event[1].supitem.servicename,
263
                eventdetails = eventdetails,
264
            )
265

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

275
        @param idevent: identifiant de l'événement souhaité
276
        """
277

    
278
        username = request.environ['repoze.who.identity']['repoze.who.userid']
279
        events = VigiboardRequest(User.by_user_name(username))
280
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
281
        
282
        # Vérification que l'événement existe
283
        if events.num_rows() != 1 :
284
            flash(_('Error in DB'), 'error')
285
            redirect('/')
286
       
287
        events.format_events(0, 1)
288
        events.format_history()
289
        events.generate_tmpl_context() 
290

    
291
        return dict(
292
                    events = events.events,
293
                    rows_info = {
294
                        'id_first_row': 1,
295
                        'id_last_row': 1,
296
                        'total_rows': 1,
297
                    },
298
                    nb_pages = 1,
299
                    page = 1,
300
                    event_edit_status_options = edit_event_status_options,
301
                    history = events.hist,
302
                    hist_error = True,
303
                    plugin_context = events.context_fct,
304
                    search = {
305
                        'host': None,
306
                        'service': None,
307
                        'output': None,
308
                        'tt': None,
309
                        'from_date': None,
310
                        'to_date': None,
311
                    },
312
                   refresh_times=self.refresh_times,
313
                )
314

    
315
    @validate(validators={'host': validators.NotEmpty(),
316
        'service': validators.NotEmpty()}, error_handler=process_form_errors)
317
    @expose('vigiboard.html')
318
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
319
    def host_service(self, host, service):
320
        
321
        """
322
        Affichage de l'historique de l'ensemble des événements correspondant
323
        au host et service demandé.
324
        Pour accéder à cette page, l'utilisateur doit être authentifié.
325

326
        @param host: Nom de l'hôte souhaité.
327
        @param service: Nom du service souhaité
328
        """
329

    
330
        username = request.environ['repoze.who.identity']['repoze.who.userid']
331
        events = VigiboardRequest(User.by_user_name(username))
332
        events.add_join((ServiceLowLevel, ServiceLowLevel.idservice == Event.idsupitem))
333
        events.add_join((Host, ServiceLowLevel.idhost == Host.idhost))
334
        events.add_filter(Host.name == host,
335
                ServiceLowLevel.servicename == service)
336

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

    
343
        # Vérification qu'il y a au moins 1 événement qui correspond
344
        if events.num_rows() == 0 :
345
            redirect('/')
346

    
347
        events.format_events(0, events.num_rows())
348
        events.format_history()
349
        events.generate_tmpl_context()
350

    
351
        return dict(
352
                    events = events.events,
353
                    rows_info = {
354
                        'id_first_row': 1,
355
                        'id_last_row': 1,
356
                        'total_rows': 1,
357
                    },
358
                    nb_pages = 1,
359
                    page = 1,
360
                    event_edit_status_options = edit_event_status_options,
361
                    history = events.hist,
362
                    hist_error = True,
363
                    plugin_context = events.context_fct,
364
                    search = {
365
                        'host': None,
366
                        'service': None,
367
                        'output': None,
368
                        'tt': None,
369
                        'from_date': None,
370
                        'to_date': None,
371
                    },
372
                    refresh_times=self.refresh_times,
373
                )
374

    
375
    @validate(validators={
376
        "id":validators.Regex(r'^[^,]+(,[^,]*)*,?$'),
377
#        "trouble_ticket":validators.Regex(r'^[0-9]*$'),
378
        "status": validators.OneOf([
379
            'NoChange',
380
            'None',
381
            'Acknowledged',
382
            'AAClosed'
383
        ])}, error_handler=process_form_errors)
384
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
385
    def update(self,**krgv):
386
        
387
        """
388
        Mise à jour d'un événement suivant les arguments passés.
389
        Cela peut être un changement de ticket ou un changement de statut.
390
        
391
        @param krgv['id']: Le ou les identifiants des événements à traiter
392
        @param krgv['tt']: Nouveau numéro du ticket associé.
393
        @param krgv['status']: Nouveau status de/des événements.
394
        """
395
        
396
        # Si l'utilisateur édite plusieurs événements à la fois,
397
        # il nous faut chacun des identifiants
398

    
399
        if krgv['id'] is None:
400
            flash(_('No event has been selected'), 'warning')
401
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
402

    
403
        ids = krgv['id'].split(',')
404
       
405
        if len(ids) > 1 :
406
            ids = ids[:-1]
407
        
408
        username = request.environ['repoze.who.identity']['repoze.who.userid']
409
        events = VigiboardRequest(User.by_user_name(username))
410
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
411
        
412
        # Vérification que au moins un des identifiants existe et est éditable
413
        if events.num_rows() <= 0 :
414
            flash(_('No access to this event'), 'error')
415
            redirect('/')
416
        
417
        # Modification des événements et création d'un historique
418
        # pour chacun d'eux.
419
        username = request.environ['repoze.who.identity']['repoze.who.userid']
420

    
421
        for req in events.req:
422
            if isinstance(req, CorrEvent):
423
                event = req
424
            else:
425
                event = req[0]
426

    
427
            if krgv['trouble_ticket'] != '' :
428
                history = EventHistory(
429
                        type_action="Ticket change",
430
                        idevent=event.idcause,
431
                        value=krgv['trouble_ticket'],
432
                        text=_("Changed trouble ticket from '%s' to '%s'") % (
433
                            event.trouble_ticket, krgv['trouble_ticket']
434
                        ),
435
                        username=username,
436
                        timestamp=datetime.now(),
437
                    )
438
                DBSession.add(history)   
439
                event.trouble_ticket = krgv['trouble_ticket']
440

    
441
            if krgv['status'] != 'NoChange' :
442
                history = EventHistory(
443
                        type_action="Acknowlegement change state",
444
                        idevent=event.idcause,
445
                        value=krgv['status'],
446
                        text=_("Changed acknowledgement status from '%s' to '%s'") % (
447
                            event.status, krgv['status']
448
                        ),
449
                        username=username,
450
                        timestamp=datetime.now(),
451
                    )
452
                DBSession.add(history)
453
                event.status = krgv['status']
454

    
455
        DBSession.flush()
456
        flash(_('Updated successfully'))
457
        redirect(request.environ.get('HTTP_REFERER', url('/')))
458

    
459

    
460
    @validate(validators={"plugin_name": validators.OneOf(
461
        [i for [i, j] in config.get('vigiboard_plugins', [])])},
462
                error_handler = process_form_errors)
463
    @expose('json')
464
    def get_plugin_value(self, plugin_name, *arg, **krgv):
465
        """
466
        Permet aux plugins de pouvoir récupérer des valeurs Json
467
        """
468
        plugins = config['vigiboard_plugins']
469
        if plugins is None:
470
            return
471

    
472
        plugin = [i for i in plugins if i[0] == plugin_name][0]
473
        try:
474
            mypac = __import__(
475
                'vigiboard.controllers.vigiboard_plugin.' + plugin[0],
476
                globals(), locals(), [plugin[1]], -1)
477
            plug = getattr(mypac, plugin[1])()
478
            return plug.controller(*arg, **krgv)
479
        except:
480
            raise
481
    
482
#    @validate(validators= {"fontsize": validators.Int()},
483
#                    error_handler = process_form_errors)
484
    @expose('json')
485
    def set_fontsize(self, fontsize):
486
        """
487
        Save font size
488
        """
489
        session['fontsize'] = fontsize
490
        session.save()
491
        return dict(ret= 'ok')
492

    
493
    @validate(validators={"refresh": validators.Int()},
494
            error_handler=process_form_errors)
495
    @expose('json')
496
    def set_refresh(self, refresh):
497
        """
498
        Save refresh time
499
        """
500
        session['refresh'] = refresh
501
        session.save()
502
        return dict(ret= 'ok')
503

    
504
    @expose('json')
505
    def autocomplete_host(self, value):
506
        value = sql_escape_like(value)
507
        hostnames = DBSession.query(
508
                        Host.name.distinct()).filter(
509
                        Host.name.ilike('%' + value + '%')).all()
510
        return dict(results=[h[0] for h in hostnames])
511

    
512
    @expose('json')
513
    def autocomplete_service(self, value):
514
        value = sql_escape_like(value)
515
        services = DBSession.query(
516
                        ServiceLowLevel.servicename.distinct()).filter(
517
                        ServiceLowLevel.servicename.ilike('%' + value + '%')).all()
518
        return dict(results=[s[0] for s in services])
519