Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ db6fbc92

History | View | Annotate | Download (33.2 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4:
3
################################################################################
4
#
5
# Copyright (C) 2007-2011 CS-SI
6
#
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License version 2 as
9
# published by the Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
################################################################################
20

    
21
"""VigiBoard Controller"""
22

    
23
from datetime import datetime
24
from time import mktime
25

    
26
from pkg_resources import resource_filename
27

    
28
from tg.exceptions import HTTPNotFound
29
from tg import expose, validate, require, flash, url, \
30
    tmpl_context, request, config, session, redirect
31
from webhelpers import paginate
32
from tw.forms import validators
33
from pylons.i18n import ugettext as _, lazy_ugettext as l_, get_lang
34
from sqlalchemy import asc
35
from sqlalchemy.sql import func
36
from sqlalchemy.orm import aliased
37
from sqlalchemy.sql.expression import or_
38
from repoze.what.predicates import Any, All, in_group, \
39
                                    has_permission, not_anonymous, \
40
                                    NotAuthorizedError
41
from formencode import schema
42

    
43
from vigilo.models.session import DBSession
44
from vigilo.models.tables import Event, EventHistory, CorrEvent, Host, \
45
                                    SupItem, SupItemGroup, LowLevelService, \
46
                                    StateName, State, DataPermission
47
from vigilo.models.tables.grouphierarchy import GroupHierarchy
48
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE, \
49
        USER_GROUP_TABLE, SUPITEM_GROUP_TABLE
50

    
51
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
52
from vigilo.turbogears.controllers.proxy import ProxyController
53
from vigilo.turbogears.controllers.api.root import ApiRootController
54
from vigilo.turbogears.helpers import get_current_user
55

    
56
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
57
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
58
from vigiboard.controllers.feeds import FeedsController
59

    
60
from vigiboard.widgets.edit_event import edit_event_status_options, \
61
                                            EditEventForm
62
from vigiboard.widgets.search_form import create_search_form
63

    
64
__all__ = ('RootController', 'get_last_modification_timestamp',
65
           'date_to_timestamp')
66

    
67
# pylint: disable-msg=R0201
68
class RootController(VigiboardRootController):
69
    """
70
    Le controller général de vigiboard
71
    """
72
    autocomplete = AutoCompleteController()
73
    nagios = ProxyController('nagios', '/nagios/',
74
        not_anonymous(l_('You need to be authenticated')))
75
    api = ApiRootController("/api")
76
    feeds = FeedsController()
77

    
78
    # Prédicat pour la restriction de l'accès aux interfaces.
79
    # L'utilisateur doit avoir la permission "vigiboard-access"
80
    # ou appartenir au groupe "managers" pour accéder à VigiBoard.
81
    access_restriction = All(
82
        not_anonymous(msg=l_("You need to be authenticated")),
83
        Any(in_group('managers'),
84
            has_permission('vigiboard-access'),
85
            msg=l_("You don't have access to VigiBoard"))
86
    )
87

    
88
    def process_form_errors(self, *argv, **kwargv):
89
        """
90
        Gestion des erreurs de validation : on affiche les erreurs
91
        puis on redirige vers la dernière page accédée.
92
        """
93
        for k in tmpl_context.form_errors:
94
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
95
        redirect(request.environ.get('HTTP_REFERER', '/'))
96

    
97
    @expose('json')
98
    def handle_validation_errors_json(self, *args, **kwargs):
99
        kwargs['errors'] = tmpl_context.form_errors
100
        return dict(kwargs)
101

    
102
    class DefaultSchema(schema.Schema):
103
        """Schéma de validation de la méthode default."""
104
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
105

    
106
        # Nécessaire pour que les critères de recherche soient conservés.
107
        allow_extra_fields = True
108

    
109
        # 2ème validation, cette fois avec les champs
110
        # du formulaire de recherche.
111
        chained_validators = [create_search_form.validator]
112

    
113
    @validate(
114
        validators=DefaultSchema(),
115
        error_handler = process_form_errors)
116
    @expose('events_table.html')
117
    @require(access_restriction)
118
    def default(self, page, **search):
119
        """
120
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
121
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
122
        en compte, puis de sévérité.
123
        Pour accéder à cette page, l'utilisateur doit être authentifié.
124

125
        @param page: Numéro de la page souhaitée, commence à 1
126
        @type page: C{int}
127
        @param search: Dictionnaire contenant les critères de recherche.
128
        @type search: C{dict}
129

130
        Cette méthode permet de satisfaire les exigences suivantes :
131
            - VIGILO_EXIG_VIGILO_BAC_0040,
132
            - VIGILO_EXIG_VIGILO_BAC_0070,
133
            - VIGILO_EXIG_VIGILO_BAC_0100,
134
        """
135

    
136
        user = get_current_user()
137
        aggregates = VigiboardRequest(user, search=search)
138

    
139
        aggregates.add_table(
140
            CorrEvent,
141
            aggregates.items.c.hostname,
142
            aggregates.items.c.servicename
143
        )
144
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
145
        aggregates.add_contains_eager(CorrEvent.cause)
146
        aggregates.add_group_by(Event)
147
        aggregates.add_join((aggregates.items,
148
            Event.idsupitem == aggregates.items.c.idsupitem))
149
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
150

    
151
        # Certains arguments sont réservés dans routes.util.url_for().
152
        # On effectue les substitutions adéquates.
153
        # Par exemple: "host" devient "host_".
154
        reserved = ('host', 'anchor', 'protocol', 'qualified')
155
        for column in search.copy():
156
            if column in reserved:
157
                search[column + '_'] = search[column]
158
                del search[column]
159

    
160
        # On ne garde que les champs effectivement renseignés.
161
        for column in search.copy():
162
            if not search[column]:
163
                del search[column]
164

    
165
        # On sérialise les champs de type dict.
166
        def serialize_dict(dct, key):
167
            if isinstance(dct[key], dict):
168
                for subkey in dct[key]:
169
                    serialize_dict(dct[key], subkey)
170
                    dct[key+'.'+subkey] = dct[key][subkey]
171
                del dct[key]
172
        fixed_search = search.copy()
173
        for column in fixed_search.copy():
174
            serialize_dict(fixed_search, column)
175

    
176
        # Pagination des résultats
177
        aggregates.generate_request()
178
        items_per_page = int(config['vigiboard_items_per_page'])
179
        page = paginate.Page(aggregates.req, page=page,
180
            items_per_page=items_per_page)
181

    
182
        # Récupération des données des plugins
183
        plugins_data = {}
184
        plugins = dict(config['columns_plugins'])
185

    
186
        ids_events = [event[0].idcause for event in page.items]
187
        ids_correvents = [event[0].idcorrevent for event in page.items]
188
        for plugin in plugins:
189
            plugin_data = plugins[plugin].get_bulk_data(ids_correvents)
190
            if plugin_data:
191
                plugins_data[plugin] = plugin_data
192

    
193
        # Ajout des formulaires et préparation
194
        # des données pour ces formulaires.
195
        tmpl_context.last_modification = \
196
            mktime(get_last_modification_timestamp(ids_events).timetuple())
197

    
198
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
199
            submit_text=_('Apply'), action=url('/update'))
200

    
201
        return dict(
202
            hostname = None,
203
            servicename = None,
204
            plugins_data = plugins_data,
205
            page = page,
206
            event_edit_status_options = edit_event_status_options,
207
            search_form = create_search_form,
208
            search = search,
209
            fixed_search = fixed_search,
210
        )
211

    
212

    
213
    @expose()
214
    def i18n(self):
215
        import gettext
216
        import pylons
217
        import os.path
218

    
219
        # Repris de pylons.i18n.translation:_get_translator.
220
        conf = pylons.config.current_conf()
221
        try:
222
            rootdir = conf['pylons.paths']['root']
223
        except KeyError:
224
            rootdir = conf['pylons.paths'].get('root_path')
225
        localedir = os.path.join(rootdir, 'i18n')
226

    
227
        lang = get_lang()
228

    
229
        # Localise le fichier *.mo actuellement chargé
230
        # et génère le chemin jusqu'au *.js correspondant.
231
        filename = gettext.find(conf['pylons.package'], localedir,
232
            languages=lang)
233
        js = filename[:-3] + '.js'
234

    
235
        themes_filename = gettext.find(
236
            'vigilo-themes',
237
            resource_filename('vigilo.themes.i18n', ''),
238
            languages=lang)
239
        themes_js = themes_filename[:-3] + '.js'
240

    
241
        # Récupère et envoie le contenu du fichier de traduction *.js.
242
        fhandle = open(js, 'r')
243
        translations = fhandle.read()
244
        fhandle.close()
245

    
246
        fhandle = open(themes_js, 'r')
247
        translations += fhandle.read()
248
        fhandle.close()
249
        return translations
250

    
251

    
252
    class MaskedEventsSchema(schema.Schema):
253
        """Schéma de validation de la méthode masked_events."""
254
        idcorrevent = validators.Int(not_empty=True)
255
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
256

    
257
    @validate(
258
        validators=MaskedEventsSchema(),
259
        error_handler = process_form_errors)
260
    @expose('raw_events_table.html')
261
    @require(access_restriction)
262
    def masked_events(self, idcorrevent, page):
263
        """
264
        Affichage de la liste des événements bruts masqués d'un événement
265
        corrélé (événements agrégés dans l'événement corrélé).
266

267
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
268
        @type idcorrevent: C{int}
269
        """
270
        user = get_current_user()
271

    
272
        # Récupère la liste des événements masqués de l'événement
273
        # corrélé donné par idcorrevent.
274
        events = VigiboardRequest(user, False)
275
        events.add_table(
276
            Event,
277
            events.items.c.hostname,
278
            events.items.c.servicename,
279
        )
280
        events.add_join((EVENTSAGGREGATE_TABLE, \
281
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
282
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
283
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
284
        events.add_join((events.items,
285
            Event.idsupitem == events.items.c.idsupitem))
286
        events.add_filter(Event.idevent != CorrEvent.idcause)
287
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
288

    
289
        # Récupère l'instance de SupItem associé à la cause de
290
        # l'événement corrélé. Cette instance est utilisé pour
291
        # obtenir le nom d'hôte/service auquel la cause est
292
        # rattachée (afin de fournir un contexte à l'utilisateur).
293
        hostname = None
294
        servicename = None
295
        cause_supitem = DBSession.query(
296
                SupItem,
297
            ).join(
298
                (Event, Event.idsupitem == SupItem.idsupitem),
299
                (CorrEvent, Event.idevent == CorrEvent.idcause),
300
            ).filter(CorrEvent.idcorrevent == idcorrevent
301
            ).one()
302

    
303
        if isinstance(cause_supitem, LowLevelService):
304
            hostname = cause_supitem.host.name
305
            servicename = cause_supitem.servicename
306
        elif isinstance(cause_supitem, Host):
307
            hostname = cause_supitem.name
308

    
309
        # Pagination des résultats
310
        events.generate_request()
311
        items_per_page = int(config['vigiboard_items_per_page'])
312
        page = paginate.Page(events.req, page=page,
313
            items_per_page=items_per_page)
314

    
315
        # Vérification que l'événement existe
316
        if not page.item_count:
317
            flash(_('No masked event or access denied'), 'error')
318
            redirect('/')
319

    
320
        return dict(
321
            idcorrevent = idcorrevent,
322
            hostname = hostname,
323
            servicename = servicename,
324
            plugins_data = {},
325
            page = page,
326
            search_form = create_search_form,
327
            search = {},
328
            fixed_search = {},
329
        )
330

    
331

    
332
    class EventSchema(schema.Schema):
333
        """Schéma de validation de la méthode event."""
334
        idevent = validators.Int(not_empty=True)
335
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
336

    
337
    @validate(
338
        validators=EventSchema(),
339
        error_handler = process_form_errors)
340
    @expose('history_table.html')
341
    @require(access_restriction)
342
    def event(self, idevent, page):
343
        """
344
        Affichage de l'historique d'un événement brut.
345
        Pour accéder à cette page, l'utilisateur doit être authentifié.
346

347
        @param idevent: identifiant de l'événement brut souhaité.
348
        @type idevent: C{int}
349
        @param page: numéro de la page à afficher.
350
        @type page: C{int}
351

352
        Cette méthode permet de satisfaire l'exigence
353
        VIGILO_EXIG_VIGILO_BAC_0080.
354
        """
355
        user = get_current_user()
356
        events = VigiboardRequest(user, False)
357
        events.add_table(
358
            Event,
359
            events.items.c.hostname.label('hostname'),
360
            events.items.c.servicename.label('servicename'),
361
        )
362
        events.add_join((EVENTSAGGREGATE_TABLE, \
363
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
364
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
365
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
366
        events.add_join((events.items,
367
            Event.idsupitem == events.items.c.idsupitem))
368
        events.add_filter(Event.idevent == idevent)
369

    
370
        if events.num_rows() != 1:
371
            flash(_('No such event or access denied'), 'error')
372
            redirect('/')
373

    
374
        events.format_events(0, 1)
375
        events.generate_tmpl_context()
376
        history = events.format_history()
377

    
378
        # Pagination des résultats
379
        items_per_page = int(config['vigiboard_items_per_page'])
380
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
381
        event = events.req[0]
382

    
383
        return dict(
384
            idevent = idevent,
385
            hostname = event.hostname,
386
            servicename = event.servicename,
387
            plugins_data = {},
388
            page = page,
389
            search_form = create_search_form,
390
            search = {},
391
            fixed_search = {},
392
        )
393

    
394

    
395
    class ItemSchema(schema.Schema):
396
        """Schéma de validation de la méthode item."""
397
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
398
        host = validators.String(not_empty=True)
399
        service = validators.String(if_missing=None)
400

    
401
    @validate(
402
        validators=ItemSchema(),
403
        error_handler = process_form_errors)
404
    @expose('events_table.html')
405
    @require(access_restriction)
406
    def item(self, page, host, service):
407
        """
408
        Affichage de l'historique de l'ensemble des événements corrélés
409
        jamais ouverts sur l'hôte / service demandé.
410
        Pour accéder à cette page, l'utilisateur doit être authentifié.
411

412
        @param page: Numéro de la page à afficher.
413
        @param host: Nom de l'hôte souhaité.
414
        @param service: Nom du service souhaité
415

416
        Cette méthode permet de satisfaire l'exigence
417
        VIGILO_EXIG_VIGILO_BAC_0080.
418
        """
419
        idsupitem = SupItem.get_supitem(host, service)
420
        if not idsupitem:
421
            flash(_('No such host/service'), 'error')
422
            redirect('/')
423

    
424
        user = get_current_user()
425
        aggregates = VigiboardRequest(user, False)
426
        aggregates.add_table(
427
            CorrEvent,
428
            aggregates.items.c.hostname,
429
            aggregates.items.c.servicename,
430
        )
431
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
432
        aggregates.add_join((aggregates.items,
433
            Event.idsupitem == aggregates.items.c.idsupitem))
434
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
435

    
436
        # Pagination des résultats
437
        aggregates.generate_request()
438
        items_per_page = int(config['vigiboard_items_per_page'])
439
        page = paginate.Page(aggregates.req, page=page,
440
            items_per_page=items_per_page)
441

    
442
        # Vérification qu'il y a au moins 1 événement qui correspond
443
        if not page.item_count:
444
            flash(_('No access to this host/service or no event yet'), 'error')
445
            redirect('/')
446

    
447
        # Ajout des formulaires et préparation
448
        # des données pour ces formulaires.
449
        ids_events = [event[0].idcause for event in page.items]
450
        tmpl_context.last_modification = \
451
            mktime(get_last_modification_timestamp(ids_events).timetuple())
452

    
453
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
454
            submit_text=_('Apply'), action=url('/update'))
455

    
456
        return dict(
457
            hostname = host,
458
            servicename = service,
459
            plugins_data = {},
460
            page = page,
461
            event_edit_status_options = edit_event_status_options,
462
            search_form = create_search_form,
463
            search = {},
464
            fixed_search = {},
465
        )
466

    
467

    
468
    class UpdateSchema(schema.Schema):
469
        """Schéma de validation de la méthode update."""
470
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
471
        last_modification = validators.Number(not_empty=True)
472
        trouble_ticket = validators.String(if_missing='')
473
        ack = validators.OneOf(
474
            [unicode(s[0]) for s in edit_event_status_options],
475
            not_empty=True)
476

    
477
    @validate(
478
        validators=UpdateSchema(),
479
        error_handler = process_form_errors)
480
    @require(
481
        All(
482
            not_anonymous(msg=l_("You need to be authenticated")),
483
            Any(in_group('managers'),
484
                has_permission('vigiboard-update'),
485
                msg=l_("You don't have write access to VigiBoard"))
486
        ))
487
    @expose()
488
    def update(self, id, last_modification, trouble_ticket, ack):
489
        """
490
        Mise à jour d'un événement suivant les arguments passés.
491
        Cela peut être un changement de ticket ou un changement de statut.
492

493
        @param id: Le ou les identifiants des événements à traiter
494
        @param last_modification: La date de la dernière modification
495
            dont l'utilisateur est au courant.
496
        @param trouble_ticket: Nouveau numéro du ticket associé.
497
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
498

499
        Cette méthode permet de satisfaire les exigences suivantes :
500
            - VIGILO_EXIG_VIGILO_BAC_0020,
501
            - VIGILO_EXIG_VIGILO_BAC_0060,
502
            - VIGILO_EXIG_VIGILO_BAC_0110.
503
        """
504

    
505
        # On vérifie que des identifiants ont bien été transmis via
506
        # le formulaire, et on informe l'utilisateur le cas échéant.
507
        if id is None:
508
            flash(_('No event has been selected'), 'warning')
509
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
510

    
511
        # On récupère la liste de tous les identifiants des événements
512
        # à mettre à jour.
513
        ids = map(int, id.strip(',').split(','))
514

    
515
        user = get_current_user()
516
        events = VigiboardRequest(user)
517
        events.add_table(CorrEvent)
518
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
519
        events.add_join((events.items,
520
            Event.idsupitem == events.items.c.idsupitem))
521
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
522

    
523
        events.generate_request()
524
        idevents = [cause.idcause for cause in events.req]
525

    
526
        # Si des changements sont survenus depuis que la
527
        # page est affichée, on en informe l'utilisateur.
528
        last_modification = datetime.fromtimestamp(last_modification)
529
        cur_last_modification = get_last_modification_timestamp(idevents, None)
530
        if cur_last_modification and last_modification < cur_last_modification:
531
            flash(_('Changes have occurred since the page was last displayed, '
532
                    'your changes HAVE NOT been saved.'), 'warning')
533
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
534

    
535
        # Vérification que au moins un des identifiants existe et est éditable
536
        if not events.num_rows():
537
            flash(_('No access to this event'), 'error')
538
            redirect('/')
539

    
540
        if ack == u'Forced':
541
            condition = Any(
542
                in_group('managers'),
543
                has_permission('vigiboard-admin'),
544
                msg=l_("You don't have administrative access "
545
                        "to VigiBoard"))
546
            try:
547
                condition.check_authorization(request.environ)
548
            except NotAuthorizedError, e:
549
                reason = unicode(e)
550
                flash(reason, 'error')
551
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
552

    
553
        # Modification des événements et création d'un historique
554
        # chaque fois que cela est nécessaire.
555
        for event in events.req:
556
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
557
                history = EventHistory(
558
                        type_action=u"Ticket change",
559
                        idevent=event.idcause,
560
                        value=unicode(trouble_ticket),
561
                        text="Changed trouble ticket from '%(from)s' "
562
                             "to '%(to)s'" % {
563
                            'from': event.trouble_ticket,
564
                            'to': trouble_ticket,
565
                        },
566
                        username=user.user_name,
567
                        timestamp=datetime.now(),
568
                    )
569
                DBSession.add(history)
570
                event.trouble_ticket = trouble_ticket
571

    
572
            # Changement du statut d'acquittement.
573
            if ack != u'NoChange':
574
                changed_ack = ack
575
                # Pour forcer l'acquittement d'un événement,
576
                # il faut en plus avoir la permission
577
                # "vigiboard-admin".
578
                if ack == u'Forced':
579
                    changed_ack = u'AAClosed'
580
                    cause = event.cause
581
                    # On met systématiquement l'événement à l'état "OK",
582
                    # même s'il s'agit d'un hôte.
583
                    # Techniquement, c'est incorrect, mais on fait ça
584
                    # pour masquer l'événement de toutes façons...
585
                    cause.current_state = \
586
                        StateName.statename_to_value(u'OK')
587

    
588
                    # Mise à jour de l'état dans State, pour que
589
                    # VigiMap soit également mis à jour.
590
                    DBSession.query(State).filter(
591
                            State.idsupitem == cause.idsupitem,
592
                        ).update({
593
                            'state': StateName.statename_to_value(u'OK'),
594
                        })
595

    
596
                    history = EventHistory(
597
                            type_action="Forced change state",
598
                            idevent=event.idcause,
599
                            value=u'OK',
600
                            text="Forced state to 'OK'",
601
                            username=user.user_name,
602
                            timestamp=datetime.now(),
603
                        )
604
                    DBSession.add(history)
605

    
606
                history = EventHistory(
607
                        type_action=u"Acknowledgement change state",
608
                        idevent=event.idcause,
609
                        value=ack,
610
                        text="Changed acknowledgement status "
611
                            "from '%s' to '%s'" % (
612
                            event.status, changed_ack
613
                        ),
614
                        username=user.user_name,
615
                        timestamp=datetime.now(),
616
                    )
617
                DBSession.add(history)
618
                event.status = changed_ack
619

    
620
        DBSession.flush()
621
        flash(_('Updated successfully'))
622
        redirect(request.environ.get('HTTP_REFERER', '/'))
623

    
624

    
625
    class GetPluginValueSchema(schema.Schema):
626
        """Schéma de validation de la méthode get_plugin_value."""
627
        idcorrevent = validators.Int(not_empty=True)
628
        plugin_name = validators.String(not_empty=True)
629
        # Permet de passer des paramètres supplémentaires au plugin.
630
        allow_extra_fields = True
631

    
632
    @validate(
633
        validators=GetPluginValueSchema(),
634
        error_handler = handle_validation_errors_json)
635
    @expose('json')
636
    @require(access_restriction)
637
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
638
        """
639
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
640
        donné via JSON.
641
        """
642

    
643
        # Vérification de l'existence du plugin
644
        plugins = dict(config['columns_plugins'])
645
        if plugin_name not in plugins:
646
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
647

    
648
        # Récupération de la liste des évènements corrélés
649
        events = DBSession.query(CorrEvent.idcorrevent)
650

    
651
        # Filtrage des évènements en fonction des permissions de
652
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
653
        is_manager = in_group('managers').is_met(request.environ)
654
        if not is_manager:
655

    
656
            user = get_current_user()
657

    
658
            events = events.join(
659
                (Event, Event.idevent == CorrEvent.idcause),
660
            ).outerjoin(
661
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
662
            ).join(
663
                (SUPITEM_GROUP_TABLE,
664
                    or_(
665
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
666
                            LowLevelService.idhost,
667
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
668
                            Event.idsupitem,
669
                    )
670
                ),
671
            ).join(
672
                (GroupHierarchy,
673
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
674
            ).join(
675
                (DataPermission,
676
                    DataPermission.idgroup == GroupHierarchy.idparent),
677
            ).join(
678
                (USER_GROUP_TABLE,
679
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
680
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
681

    
682
        # Filtrage des évènements en fonction
683
        # de l'identifiant passé en paramètre
684
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
685

    
686
        # Pas d'événement ou permission refusée. On ne distingue pas
687
        # les 2 cas afin d'éviter la divulgation d'informations.
688
        if events == 0:
689
            raise HTTPNotFound(_('No such incident or insufficient '
690
                                'permissions'))
691

    
692
        # L'évènement existe bien, et l'utilisateur dispose
693
        # des permissions appropriées. On fait alors appel au
694
        # plugin pour récupérer les informations à retourner.
695
        return plugins[plugin_name].get_json_data(idcorrevent, *arg, **krgv)
696

    
697
    @validate(validators={
698
        "fontsize": validators.Regex(
699
            r'[0-9]+(pt|px|em|%)',
700
            regexOps = ('I',)
701
        )}, error_handler = handle_validation_errors_json)
702
    @expose('json')
703
    def set_fontsize(self, fontsize):
704
        """Enregistre la taille de la police dans les préférences."""
705
        session['fontsize'] = fontsize
706
        session.save()
707
        return dict()
708

    
709
    @validate(validators={"refresh": validators.Int()},
710
            error_handler = handle_validation_errors_json)
711
    @expose('json')
712
    def set_refresh(self, refresh):
713
        """Enregistre le temps de rafraichissement dans les préférences."""
714
        session['refresh'] = refresh
715
        session.save()
716
        return dict()
717

    
718
    @expose('json')
719
    def set_theme(self, theme):
720
        """Enregistre le thème à utiliser dans les préférences."""
721
        # On sauvegarde l'ID du thème sans vérifications
722
        # car les thèmes (styles CSS) sont définies dans
723
        # les packages de thèmes (ex: vigilo-themes-default).
724
        # La vérification de la valeur est faite dans les templates.
725
        session['theme'] = theme
726
        session.save()
727
        return dict()
728

    
729
    @require(access_restriction)
730
    @expose('json')
731
    def get_groups(self, parent_id=None):
732
        """
733
        Affiche un étage de l'arbre de
734
        sélection des hôtes et groupes d'hôtes.
735

736
        @param parent_id: identifiant du groupe d'hôte parent
737
        @type  parent_id: C{int} or None
738
        """
739

    
740
        # Si l'identifiant du groupe parent n'est pas
741
        # spécifié, on retourne la liste des groupes
742
        # racines, fournie par la méthode get_root_groups.
743
        if parent_id is None:
744
            return self.get_root_groups()
745

    
746
        # TODO: Utiliser un schéma de validation
747
        parent_id = int(parent_id)
748

    
749
        # On récupère la liste des groupes de supitems dont
750
        # l'identifiant du parent est passé en paramètre.
751
        supitem_groups = DBSession.query(
752
                SupItemGroup.idgroup,
753
                SupItemGroup.name,
754
            ).join(
755
                (GroupHierarchy,
756
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
757
            ).filter(GroupHierarchy.idparent == parent_id
758
            ).filter(GroupHierarchy.hops == 1
759
            ).order_by(SupItemGroup.name)
760

    
761
        # Si l'utilisateur n'appartient pas au groupe 'managers',
762
        # on filtre les résultats en fonction de ses permissions.
763
        is_manager = in_group('managers').is_met(request.environ)
764
        if not is_manager:
765
            user = get_current_user()
766
            GroupHierarchy_aliased = aliased(GroupHierarchy,
767
                name='GroupHierarchy_aliased')
768
            supitem_groups = supitem_groups.join(
769
                (GroupHierarchy_aliased,
770
                    or_(
771
                        GroupHierarchy_aliased.idchild == SupItemGroup.idgroup,
772
                        GroupHierarchy_aliased.idparent == SupItemGroup.idgroup
773
                    )),
774
                (DataPermission,
775
                    or_(
776
                        DataPermission.idgroup == \
777
                            GroupHierarchy_aliased.idparent,
778
                        DataPermission.idgroup == \
779
                            GroupHierarchy_aliased.idchild,
780
                    )),
781
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
782
                    DataPermission.idusergroup),
783
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
784

    
785
        groups = []
786
        for group in supitem_groups.distinct().all():
787
            groups.append({
788
                'id'   : group.idgroup,
789
                'name' : group.name,
790
            })
791

    
792
        return dict(groups = groups, leaves = [])
793

    
794
    def get_root_groups(self):
795
        """
796
        Retourne tous les groupes racines (c'est à dire n'ayant
797
        aucun parent) d'hôtes auquel l'utilisateur a accès.
798

799
        @return: Un dictionnaire contenant la liste de ces groupes.
800
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
801
        """
802

    
803
        # On récupère tous les groupes qui ont un parent.
804
        children = DBSession.query(
805
            SupItemGroup,
806
        ).distinct(
807
        ).join(
808
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
809
        ).filter(GroupHierarchy.hops > 0)
810

    
811
        # Ensuite on les exclut de la liste des groupes,
812
        # pour ne garder que ceux qui sont au sommet de
813
        # l'arbre et qui constituent nos "root groups".
814
        root_groups = DBSession.query(
815
            SupItemGroup,
816
        ).except_(children
817
        ).order_by(SupItemGroup.name)
818

    
819
        # On filtre ces groupes racines afin de ne
820
        # retourner que ceux auquels l'utilisateur a accès
821
        user = get_current_user()
822
        is_manager = in_group('managers').is_met(request.environ)
823
        if not is_manager:
824

    
825
            root_groups = root_groups.join(
826
                (GroupHierarchy,
827
                    GroupHierarchy.idparent == SupItemGroup.idgroup),
828
                (DataPermission,
829
                    DataPermission.idgroup == GroupHierarchy.idchild),
830
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
831
                    DataPermission.idusergroup),
832
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
833

    
834
        groups = []
835
        for group in root_groups.all():
836
            groups.append({
837
                'id'   : group.idgroup,
838
                'name' : group.name,
839
            })
840

    
841
        return dict(groups = groups, leaves=[])
842

    
843
def get_last_modification_timestamp(event_id_list,
844
                                    value_if_none=datetime.now()):
845
    """
846
    Récupère le timestamp de la dernière modification
847
    opérée sur l'un des événements dont l'identifiant
848
    fait partie de la liste passée en paramètre.
849
    """
850
    last_modification_timestamp = DBSession.query(
851
                                func.max(EventHistory.timestamp),
852
                         ).filter(EventHistory.idevent.in_(event_id_list)
853
                         ).scalar()
854
    if not last_modification_timestamp:
855
        if not value_if_none:
856
            return None
857
        else:
858
            last_modification_timestamp = value_if_none
859
    return datetime.fromtimestamp(mktime(
860
        last_modification_timestamp.timetuple()))