Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ adb0e63f

History | View | Annotate | Download (32 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 tg.exceptions import HTTPNotFound
27
from tg import expose, validate, require, flash, url, \
28
    tmpl_context, request, config, session, redirect
29
from webhelpers import paginate
30
from tw.forms import validators
31
from pylons.i18n import ugettext as _, lazy_ugettext as l_
32
from sqlalchemy import asc
33
from sqlalchemy.sql import func
34
from sqlalchemy.orm import aliased
35
from sqlalchemy.sql.expression import or_
36
from repoze.what.predicates import Any, All, in_group, \
37
                                    has_permission, not_anonymous, \
38
                                    NotAuthorizedError
39
from formencode import schema
40

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

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

    
54
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
55
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
56
from vigiboard.controllers.feeds import FeedsController
57

    
58
from vigiboard.widgets.edit_event import edit_event_status_options, \
59
                                            EditEventForm
60
from vigiboard.widgets.search_form import create_search_form
61

    
62
__all__ = ('RootController', 'get_last_modification_timestamp',
63
           'date_to_timestamp')
64

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

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

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

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

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

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

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

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

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

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

    
134
        user = get_current_user()
135
        if 'supitemgroup' in search:
136
            aggregates = VigiboardRequest(
137
                user, supitemgroup=search['supitemgroup'])
138
        else:
139
            aggregates = VigiboardRequest(user)
140

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

    
153
        # Application des filtres des plugins si nécessaire.
154
        for plugin, instance in config.get('columns_plugins', []):
155
            instance.handle_search_fields(aggregates, search)
156

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

    
166
        # On ne garde que les champs effectivement renseignés.
167
        for column in search.copy():
168
            if not search[column]:
169
                del search[column]
170

    
171
        # On sérialise les champs de type dict.
172
        def serialize_dict(dct, key):
173
            if isinstance(dct[key], dict):
174
                for subkey in dct[key]:
175
                    serialize_dict(dct[key], subkey)
176
                    dct[key+'.'+subkey] = dct[key][subkey]
177
                del dct[key]
178
        fixed_search = search.copy()
179
        for column in fixed_search.copy():
180
            serialize_dict(fixed_search, column)
181

    
182
        # Pagination des résultats
183
        aggregates.generate_request()
184
        items_per_page = int(config['vigiboard_items_per_page'])
185
        page = paginate.Page(aggregates.req, page=page,
186
            items_per_page=items_per_page)
187

    
188
        # Récupération des données des plugins
189
        plugins_data = {}
190
        plugins = dict(config['columns_plugins'])
191

    
192
        ids_events = [event[0].idcause for event in page.items]
193
        ids_correvents = [event[0].idcorrevent for event in page.items]
194
        for plugin in plugins:
195
            plugin_data = plugins[plugin].get_bulk_data(ids_correvents)
196
            if plugin_data:
197
                plugins_data[plugin] = plugin_data
198

    
199
        # Ajout des formulaires et préparation
200
        # des données pour ces formulaires.
201
        tmpl_context.last_modification = \
202
            mktime(get_last_modification_timestamp(ids_events).timetuple())
203

    
204
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
205
            submit_text=_('Apply'), action=url('/update'))
206

    
207
        return dict(
208
            hostname = None,
209
            servicename = None,
210
            plugins_data = plugins_data,
211
            page = page,
212
            event_edit_status_options = edit_event_status_options,
213
            search_form = create_search_form,
214
            search = search,
215
            fixed_search = fixed_search,
216
        )
217

    
218

    
219
    class MaskedEventsSchema(schema.Schema):
220
        """Schéma de validation de la méthode masked_events."""
221
        idcorrevent = validators.Int(not_empty=True)
222
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
223

    
224
    @validate(
225
        validators=MaskedEventsSchema(),
226
        error_handler = process_form_errors)
227
    @expose('raw_events_table.html')
228
    @require(access_restriction)
229
    def masked_events(self, idcorrevent, page):
230
        """
231
        Affichage de la liste des événements bruts masqués d'un événement
232
        corrélé (événements agrégés dans l'événement corrélé).
233

234
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
235
        @type idcorrevent: C{int}
236
        """
237
        user = get_current_user()
238

    
239
        # Récupère la liste des événements masqués de l'événement
240
        # corrélé donné par idcorrevent.
241
        events = VigiboardRequest(user, False)
242
        events.add_table(
243
            Event,
244
            events.items.c.hostname,
245
            events.items.c.servicename,
246
        )
247
        events.add_join((EVENTSAGGREGATE_TABLE, \
248
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
249
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
250
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
251
        events.add_join((events.items,
252
            Event.idsupitem == events.items.c.idsupitem))
253
        events.add_filter(Event.idevent != CorrEvent.idcause)
254
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
255

    
256
        # Récupère l'instance de SupItem associé à la cause de
257
        # l'événement corrélé. Cette instance est utilisé pour
258
        # obtenir le nom d'hôte/service auquel la cause est
259
        # rattachée (afin de fournir un contexte à l'utilisateur).
260
        hostname = None
261
        servicename = None
262
        cause_supitem = DBSession.query(
263
                SupItem,
264
            ).join(
265
                (Event, Event.idsupitem == SupItem.idsupitem),
266
                (CorrEvent, Event.idevent == CorrEvent.idcause),
267
            ).filter(CorrEvent.idcorrevent == idcorrevent
268
            ).one()
269

    
270
        if isinstance(cause_supitem, LowLevelService):
271
            hostname = cause_supitem.host.name
272
            servicename = cause_supitem.servicename
273
        elif isinstance(cause_supitem, Host):
274
            hostname = cause_supitem.name
275

    
276
        # Pagination des résultats
277
        events.generate_request()
278
        items_per_page = int(config['vigiboard_items_per_page'])
279
        page = paginate.Page(events.req, page=page,
280
            items_per_page=items_per_page)
281

    
282
        # Vérification que l'événement existe
283
        if not page.item_count:
284
            flash(_('No masked event or access denied'), 'error')
285
            redirect('/')
286

    
287
        return dict(
288
            idcorrevent = idcorrevent,
289
            hostname = hostname,
290
            servicename = servicename,
291
            plugins_data = {},
292
            page = page,
293
            search_form = create_search_form,
294
            search = {},
295
            fixed_search = {},
296
        )
297

    
298

    
299
    class EventSchema(schema.Schema):
300
        """Schéma de validation de la méthode event."""
301
        idevent = validators.Int(not_empty=True)
302
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
303

    
304
    @validate(
305
        validators=EventSchema(),
306
        error_handler = process_form_errors)
307
    @expose('history_table.html')
308
    @require(access_restriction)
309
    def event(self, idevent, page):
310
        """
311
        Affichage de l'historique d'un événement brut.
312
        Pour accéder à cette page, l'utilisateur doit être authentifié.
313

314
        @param idevent: identifiant de l'événement brut souhaité.
315
        @type idevent: C{int}
316
        @param page: numéro de la page à afficher.
317
        @type page: C{int}
318

319
        Cette méthode permet de satisfaire l'exigence
320
        VIGILO_EXIG_VIGILO_BAC_0080.
321
        """
322
        user = get_current_user()
323
        events = VigiboardRequest(user, False)
324
        events.add_table(
325
            Event,
326
            events.items.c.hostname.label('hostname'),
327
            events.items.c.servicename.label('servicename'),
328
        )
329
        events.add_join((EVENTSAGGREGATE_TABLE, \
330
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
331
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
332
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
333
        events.add_join((events.items,
334
            Event.idsupitem == events.items.c.idsupitem))
335
        events.add_filter(Event.idevent == idevent)
336

    
337
        if events.num_rows() != 1:
338
            flash(_('No such event or access denied'), 'error')
339
            redirect('/')
340

    
341
        events.format_events(0, 1)
342
        events.generate_tmpl_context()
343
        history = events.format_history()
344

    
345
        # Pagination des résultats
346
        items_per_page = int(config['vigiboard_items_per_page'])
347
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
348
        event = events.req[0]
349

    
350
        return dict(
351
            idevent = idevent,
352
            hostname = event.hostname,
353
            servicename = event.servicename,
354
            plugins_data = {},
355
            page = page,
356
            search_form = create_search_form,
357
            search = {},
358
            fixed_search = {},
359
        )
360

    
361

    
362
    class ItemSchema(schema.Schema):
363
        """Schéma de validation de la méthode item."""
364
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
365
        host = validators.String(not_empty=True)
366
        service = validators.String(if_missing=None)
367

    
368
    @validate(
369
        validators=ItemSchema(),
370
        error_handler = process_form_errors)
371
    @expose('events_table.html')
372
    @require(access_restriction)
373
    def item(self, page, host, service):
374
        """
375
        Affichage de l'historique de l'ensemble des événements corrélés
376
        jamais ouverts sur l'hôte / service demandé.
377
        Pour accéder à cette page, l'utilisateur doit être authentifié.
378

379
        @param page: Numéro de la page à afficher.
380
        @param host: Nom de l'hôte souhaité.
381
        @param service: Nom du service souhaité
382

383
        Cette méthode permet de satisfaire l'exigence
384
        VIGILO_EXIG_VIGILO_BAC_0080.
385
        """
386
        idsupitem = SupItem.get_supitem(host, service)
387
        if not idsupitem:
388
            flash(_('No such host/service'), 'error')
389
            redirect('/')
390

    
391
        user = get_current_user()
392
        aggregates = VigiboardRequest(user, False)
393
        aggregates.add_table(
394
            CorrEvent,
395
            aggregates.items.c.hostname,
396
            aggregates.items.c.servicename,
397
        )
398
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
399
        aggregates.add_join((aggregates.items,
400
            Event.idsupitem == aggregates.items.c.idsupitem))
401
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
402

    
403
        # Pagination des résultats
404
        aggregates.generate_request()
405
        items_per_page = int(config['vigiboard_items_per_page'])
406
        page = paginate.Page(aggregates.req, page=page,
407
            items_per_page=items_per_page)
408

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

    
414
        # Ajout des formulaires et préparation
415
        # des données pour ces formulaires.
416
        ids_events = [event[0].idcause for event in page.items]
417
        tmpl_context.last_modification = \
418
            mktime(get_last_modification_timestamp(ids_events).timetuple())
419

    
420
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
421
            submit_text=_('Apply'), action=url('/update'))
422

    
423
        return dict(
424
            hostname = host,
425
            servicename = service,
426
            plugins_data = {},
427
            page = page,
428
            event_edit_status_options = edit_event_status_options,
429
            search_form = create_search_form,
430
            search = {},
431
            fixed_search = {},
432
        )
433

    
434

    
435
    class UpdateSchema(schema.Schema):
436
        """Schéma de validation de la méthode update."""
437
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
438
        last_modification = validators.Number(not_empty=True)
439
        trouble_ticket = validators.String(if_missing='')
440
        ack = validators.OneOf(
441
            [unicode(s[0]) for s in edit_event_status_options],
442
            not_empty=True)
443

    
444
    @validate(
445
        validators=UpdateSchema(),
446
        error_handler = process_form_errors)
447
    @require(
448
        All(
449
            not_anonymous(msg=l_("You need to be authenticated")),
450
            Any(in_group('managers'),
451
                has_permission('vigiboard-update'),
452
                msg=l_("You don't have write access to VigiBoard"))
453
        ))
454
    @expose()
455
    def update(self, id, last_modification, trouble_ticket, ack):
456
        """
457
        Mise à jour d'un événement suivant les arguments passés.
458
        Cela peut être un changement de ticket ou un changement de statut.
459

460
        @param id: Le ou les identifiants des événements à traiter
461
        @param last_modification: La date de la dernière modification
462
            dont l'utilisateur est au courant.
463
        @param trouble_ticket: Nouveau numéro du ticket associé.
464
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
465

466
        Cette méthode permet de satisfaire les exigences suivantes :
467
            - VIGILO_EXIG_VIGILO_BAC_0020,
468
            - VIGILO_EXIG_VIGILO_BAC_0060,
469
            - VIGILO_EXIG_VIGILO_BAC_0110.
470
        """
471

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

    
478
        # On récupère la liste de tous les identifiants des événements
479
        # à mettre à jour.
480
        ids = map(int, id.strip(',').split(','))
481

    
482
        user = get_current_user()
483
        events = VigiboardRequest(user)
484
        events.add_table(CorrEvent)
485
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
486
        events.add_join((events.items,
487
            Event.idsupitem == events.items.c.idsupitem))
488
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
489

    
490
        events.generate_request()
491
        idevents = [cause.idcause for cause in events.req]
492

    
493
        # Si des changements sont survenus depuis que la
494
        # page est affichée, on en informe l'utilisateur.
495
        last_modification = datetime.fromtimestamp(last_modification)
496
        cur_last_modification = get_last_modification_timestamp(idevents, None)
497
        if cur_last_modification and last_modification < cur_last_modification:
498
            flash(_('Changes have occurred since the page was last displayed, '
499
                    'your changes HAVE NOT been saved.'), 'warning')
500
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
501

    
502
        # Vérification que au moins un des identifiants existe et est éditable
503
        if not events.num_rows():
504
            flash(_('No access to this event'), 'error')
505
            redirect('/')
506

    
507
        if ack == u'Forced':
508
            condition = Any(
509
                in_group('managers'),
510
                has_permission('vigiboard-admin'),
511
                msg=l_("You don't have administrative access "
512
                        "to VigiBoard"))
513
            try:
514
                condition.check_authorization(request.environ)
515
            except NotAuthorizedError, e:
516
                reason = unicode(e)
517
                flash(reason, 'error')
518
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
519

    
520
        # Modification des événements et création d'un historique
521
        # chaque fois que cela est nécessaire.
522
        for event in events.req:
523
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
524
                history = EventHistory(
525
                        type_action=u"Ticket change",
526
                        idevent=event.idcause,
527
                        value=unicode(trouble_ticket),
528
                        text="Changed trouble ticket from '%(from)s' "
529
                             "to '%(to)s'" % {
530
                            'from': event.trouble_ticket,
531
                            'to': trouble_ticket,
532
                        },
533
                        username=user.user_name,
534
                        timestamp=datetime.now(),
535
                    )
536
                DBSession.add(history)
537
                event.trouble_ticket = trouble_ticket
538

    
539
            # Changement du statut d'acquittement.
540
            if ack != u'NoChange':
541
                changed_ack = ack
542
                # Pour forcer l'acquittement d'un événement,
543
                # il faut en plus avoir la permission
544
                # "vigiboard-admin".
545
                if ack == u'Forced':
546
                    changed_ack = u'AAClosed'
547
                    cause = event.cause
548
                    # On met systématiquement l'événement à l'état "OK",
549
                    # même s'il s'agit d'un hôte.
550
                    # Techniquement, c'est incorrect, mais on fait ça
551
                    # pour masquer l'événement de toutes façons...
552
                    cause.current_state = \
553
                        StateName.statename_to_value(u'OK')
554

    
555
                    # Mise à jour de l'état dans State, pour que
556
                    # VigiMap soit également mis à jour.
557
                    DBSession.query(State).filter(
558
                            State.idsupitem == cause.idsupitem,
559
                        ).update({
560
                            'state': StateName.statename_to_value(u'OK'),
561
                        })
562

    
563
                    history = EventHistory(
564
                            type_action="Forced change state",
565
                            idevent=event.idcause,
566
                            value=u'OK',
567
                            text="Forced state to 'OK'",
568
                            username=user.user_name,
569
                            timestamp=datetime.now(),
570
                        )
571
                    DBSession.add(history)
572

    
573
                history = EventHistory(
574
                        type_action=u"Acknowledgement change state",
575
                        idevent=event.idcause,
576
                        value=ack,
577
                        text="Changed acknowledgement status "
578
                            "from '%s' to '%s'" % (
579
                            event.status, changed_ack
580
                        ),
581
                        username=user.user_name,
582
                        timestamp=datetime.now(),
583
                    )
584
                DBSession.add(history)
585
                event.status = changed_ack
586

    
587
        DBSession.flush()
588
        flash(_('Updated successfully'))
589
        redirect(request.environ.get('HTTP_REFERER', '/'))
590

    
591

    
592
    class GetPluginValueSchema(schema.Schema):
593
        """Schéma de validation de la méthode get_plugin_value."""
594
        idcorrevent = validators.Int(not_empty=True)
595
        plugin_name = validators.String(not_empty=True)
596
        # Permet de passer des paramètres supplémentaires au plugin.
597
        allow_extra_fields = True
598

    
599
    @validate(
600
        validators=GetPluginValueSchema(),
601
        error_handler = handle_validation_errors_json)
602
    @expose('json')
603
    @require(access_restriction)
604
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
605
        """
606
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
607
        donné via JSON.
608
        """
609

    
610
        # Vérification de l'existence du plugin
611
        plugins = dict(config['columns_plugins'])
612
        if plugin_name not in plugins:
613
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
614

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

    
618
        # Filtrage des évènements en fonction des permissions de
619
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
620
        is_manager = in_group('managers').is_met(request.environ)
621
        if not is_manager:
622

    
623
            user = get_current_user()
624

    
625
            events = events.join(
626
                (Event, Event.idevent == CorrEvent.idcause),
627
            ).outerjoin(
628
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
629
            ).join(
630
                (SUPITEM_GROUP_TABLE,
631
                    or_(
632
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
633
                            LowLevelService.idhost,
634
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
635
                            Event.idsupitem,
636
                    )
637
                ),
638
            ).join(
639
                (GroupHierarchy,
640
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
641
            ).join(
642
                (DataPermission,
643
                    DataPermission.idgroup == GroupHierarchy.idparent),
644
            ).join(
645
                (USER_GROUP_TABLE,
646
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
647
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
648

    
649
        # Filtrage des évènements en fonction
650
        # de l'identifiant passé en paramètre
651
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
652

    
653
        # Pas d'événement ou permission refusée. On ne distingue pas
654
        # les 2 cas afin d'éviter la divulgation d'informations.
655
        if events == 0:
656
            raise HTTPNotFound(_('No such incident or insufficient '
657
                                'permissions'))
658

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

    
664
    @validate(validators={
665
        "fontsize": validators.Regex(
666
            r'[0-9]+(pt|px|em|%)',
667
            regexOps = ('I',)
668
        )}, error_handler = handle_validation_errors_json)
669
    @expose('json')
670
    def set_fontsize(self, fontsize):
671
        """Enregistre la taille de la police dans les préférences."""
672
        session['fontsize'] = fontsize
673
        session.save()
674
        return dict()
675

    
676
    @validate(validators={"refresh": validators.Int()},
677
            error_handler = handle_validation_errors_json)
678
    @expose('json')
679
    def set_refresh(self, refresh):
680
        """Enregistre le temps de rafraichissement dans les préférences."""
681
        session['refresh'] = refresh
682
        session.save()
683
        return dict()
684

    
685
    @expose('json')
686
    def set_theme(self, theme):
687
        """Enregistre le thème à utiliser dans les préférences."""
688
        # On sauvegarde l'ID du thème sans vérifications
689
        # car les thèmes (styles CSS) sont définies dans
690
        # les packages de thèmes (ex: vigilo-themes-default).
691
        # La vérification de la valeur est faite dans les templates.
692
        session['theme'] = theme
693
        session.save()
694
        return dict()
695

    
696
    @require(access_restriction)
697
    @expose('json')
698
    def get_groups(self, parent_id=None):
699
        """
700
        Affiche un étage de l'arbre de
701
        sélection des hôtes et groupes d'hôtes.
702

703
        @param parent_id: identifiant du groupe d'hôte parent
704
        @type  parent_id: C{int} or None
705
        """
706

    
707
        # Si l'identifiant du groupe parent n'est pas
708
        # spécifié, on retourne la liste des groupes racines,
709
        # fournie par la méthode get_root_hosts_groups.
710
        if parent_id is None:
711
            return self.get_root_host_groups()
712

    
713
        # TODO: Utiliser un schéma de validation
714
        parent_id = int(parent_id)
715

    
716
        # On récupère la liste des groupes de supitems dont
717
        # l'identifiant du parent est passé en paramètre.
718
        supitem_groups = DBSession.query(
719
                SupItemGroup.idgroup,
720
                SupItemGroup.name,
721
            ).join(
722
                (GroupHierarchy,
723
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
724
            ).filter(GroupHierarchy.idparent == parent_id
725
            ).filter(GroupHierarchy.idchild != parent_id)
726

    
727
        # Si l'utilisateur n'appartient pas au groupe 'managers',
728
        # on filtre les résultats en fonction de ses permissions.
729
        is_manager = in_group('managers').is_met(request.environ)
730
        if not is_manager:
731
            user = get_current_user()
732
            GroupHierarchy_aliased = aliased(GroupHierarchy,
733
                name='GroupHierarchy_aliased')
734
            supitem_groups.join(
735
                (GroupHierarchy_aliased,
736
                    GroupHierarchy_aliased.idchild == SupItemGroup.idgroup),
737
                (DataPermission,
738
                    DataPermission.idgroup == GroupHierarchy_aliased.idparent),
739
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
740
                    DataPermission.idusergroup),
741
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
742

    
743
        groups = []
744
        for group in supitem_groups.distinct().all():
745
            groups.append({
746
                'id'   : group.idgroup,
747
                'name' : group.name,
748
            })
749

    
750
        return dict(groups = groups, leaves = [])
751

    
752
    def get_root_host_groups(self):
753
        """
754
        Retourne tous les groupes racines (c'est à dire n'ayant
755
        aucun parent) d'hôtes auquel l'utilisateur a accès.
756

757
        @return: Un dictionnaire contenant la liste de ces groupes.
758
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
759
        """
760

    
761
        # On récupère tous les groupes qui ont un parent.
762
        children = DBSession.query(
763
            SupItemGroup,
764
        ).distinct(
765
        ).join(
766
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
767
        ).filter(GroupHierarchy.hops > 0)
768

    
769
        # Ensuite on les exclut de la liste des groupes,
770
        # pour ne garder que ceux qui sont au sommet de
771
        # l'arbre et qui constituent nos "root groups".
772
        root_groups = DBSession.query(
773
            SupItemGroup,
774
        ).except_(children
775
        ).order_by(SupItemGroup.name)
776

    
777
        # On filtre ces groupes racines afin de ne
778
        # retourner que ceux auquels l'utilisateur a accès
779
        user = get_current_user()
780
        is_manager = in_group('managers').is_met(request.environ)
781
        if not is_manager:
782

    
783
            root_groups = root_groups.join(
784
                (GroupHierarchy,
785
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
786
                (DataPermission,
787
                    DataPermission.idgroup == GroupHierarchy.idparent),
788
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
789
                    DataPermission.idusergroup),
790
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
791

    
792
        groups = []
793
        for group in root_groups.all():
794
            groups.append({
795
                'id'   : group.idgroup,
796
                'name' : group.name,
797
            })
798

    
799
        return dict(groups = groups, leaves=[])
800

    
801
def get_last_modification_timestamp(event_id_list,
802
                                    value_if_none=datetime.now()):
803
    """
804
    Récupère le timestamp de la dernière modification
805
    opérée sur l'un des événements dont l'identifiant
806
    fait partie de la liste passée en paramètre.
807
    """
808
    last_modification_timestamp = DBSession.query(
809
                                func.max(EventHistory.timestamp),
810
                         ).filter(EventHistory.idevent.in_(event_id_list)
811
                         ).scalar()
812
    if not last_modification_timestamp:
813
        if not value_if_none:
814
            return None
815
        else:
816
            last_modification_timestamp = value_if_none
817
    return datetime.fromtimestamp(mktime(
818
        last_modification_timestamp.timetuple()))