Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 180b869a

History | View | Annotate | Download (31.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 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 url_for().
158
        # On effectue les substitutions adéquates.
159
        # Par exemple: "host" devient "host_".
160
        reserved = ('host', )
161
        copy = search.copy()
162
        for column in copy:
163
            if column in reserved:
164
                search[column + '_'] = search[column]
165
                del search[column]
166

    
167
        # Pagination des résultats
168
        aggregates.generate_request()
169
        items_per_page = int(config['vigiboard_items_per_page'])
170
        page = paginate.Page(aggregates.req, page=page,
171
            items_per_page=items_per_page)
172

    
173
        # Récupération des données des plugins
174
        plugins_data = {}
175
        plugins = dict(config['columns_plugins'])
176

    
177
        ids_events = [event[0].idcause for event in page.items]
178
        ids_correvents = [event[0].idcorrevent for event in page.items]
179
        for plugin in plugins:
180
            plugin_data = plugins[plugin].get_bulk_data(ids_correvents)
181
            if plugin_data:
182
                plugins_data[plugin] = plugin_data
183

    
184
        # Ajout des formulaires et préparation
185
        # des données pour ces formulaires.
186
        tmpl_context.last_modification = \
187
            mktime(get_last_modification_timestamp(ids_events).timetuple())
188

    
189
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
190
            submit_text=_('Apply'), action=url('/update'))
191

    
192
        return dict(
193
            hostname = None,
194
            servicename = None,
195
            plugins_data = plugins_data,
196
            page = page,
197
            event_edit_status_options = edit_event_status_options,
198
            search_form = create_search_form,
199
            search = search,
200
        )
201

    
202

    
203
    class MaskedEventsSchema(schema.Schema):
204
        """Schéma de validation de la méthode masked_events."""
205
        idcorrevent = validators.Int(not_empty=True)
206
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
207

    
208
    @validate(
209
        validators=MaskedEventsSchema(),
210
        error_handler = process_form_errors)
211
    @expose('raw_events_table.html')
212
    @require(access_restriction)
213
    def masked_events(self, idcorrevent, page):
214
        """
215
        Affichage de la liste des événements bruts masqués d'un événement
216
        corrélé (événements agrégés dans l'événement corrélé).
217

218
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
219
        @type idcorrevent: C{int}
220
        """
221
        user = get_current_user()
222

    
223
        # Récupère la liste des événements masqués de l'événement
224
        # corrélé donné par idcorrevent.
225
        events = VigiboardRequest(user, False)
226
        events.add_table(
227
            Event,
228
            events.items.c.hostname,
229
            events.items.c.servicename,
230
        )
231
        events.add_join((EVENTSAGGREGATE_TABLE, \
232
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
233
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
234
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
235
        events.add_join((events.items,
236
            Event.idsupitem == events.items.c.idsupitem))
237
        events.add_filter(Event.idevent != CorrEvent.idcause)
238
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
239

    
240
        # Récupère l'instance de SupItem associé à la cause de
241
        # l'événement corrélé. Cette instance est utilisé pour
242
        # obtenir le nom d'hôte/service auquel la cause est
243
        # rattachée (afin de fournir un contexte à l'utilisateur).
244
        hostname = None
245
        servicename = None
246
        cause_supitem = DBSession.query(
247
                SupItem,
248
            ).join(
249
                (Event, Event.idsupitem == SupItem.idsupitem),
250
                (CorrEvent, Event.idevent == CorrEvent.idcause),
251
            ).filter(CorrEvent.idcorrevent == idcorrevent
252
            ).one()
253

    
254
        if isinstance(cause_supitem, LowLevelService):
255
            hostname = cause_supitem.host.name
256
            servicename = cause_supitem.servicename
257
        elif isinstance(cause_supitem, Host):
258
            hostname = cause_supitem.name
259

    
260
        # Pagination des résultats
261
        events.generate_request()
262
        items_per_page = int(config['vigiboard_items_per_page'])
263
        page = paginate.Page(events.req, page=page,
264
            items_per_page=items_per_page)
265

    
266
        # Vérification que l'événement existe
267
        if not page.item_count:
268
            flash(_('No masked event or access denied'), 'error')
269
            redirect('/')
270

    
271
        return dict(
272
            idcorrevent = idcorrevent,
273
            hostname = hostname,
274
            servicename = servicename,
275
            plugins_data = {},
276
            page = page,
277
            search_form = create_search_form,
278
            search = {},
279
        )
280

    
281

    
282
    class EventSchema(schema.Schema):
283
        """Schéma de validation de la méthode event."""
284
        idevent = validators.Int(not_empty=True)
285
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
286

    
287
    @validate(
288
        validators=EventSchema(),
289
        error_handler = process_form_errors)
290
    @expose('history_table.html')
291
    @require(access_restriction)
292
    def event(self, idevent, page):
293
        """
294
        Affichage de l'historique d'un événement brut.
295
        Pour accéder à cette page, l'utilisateur doit être authentifié.
296

297
        @param idevent: identifiant de l'événement brut souhaité.
298
        @type idevent: C{int}
299
        @param page: numéro de la page à afficher.
300
        @type page: C{int}
301

302
        Cette méthode permet de satisfaire l'exigence
303
        VIGILO_EXIG_VIGILO_BAC_0080.
304
        """
305
        user = get_current_user()
306
        events = VigiboardRequest(user, False)
307
        events.add_table(
308
            Event,
309
            events.items.c.hostname.label('hostname'),
310
            events.items.c.servicename.label('servicename'),
311
        )
312
        events.add_join((EVENTSAGGREGATE_TABLE, \
313
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
314
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
315
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
316
        events.add_join((events.items,
317
            Event.idsupitem == events.items.c.idsupitem))
318
        events.add_filter(Event.idevent == idevent)
319

    
320
        if events.num_rows() != 1:
321
            flash(_('No such event or access denied'), 'error')
322
            redirect('/')
323

    
324
        events.format_events(0, 1)
325
        events.generate_tmpl_context()
326
        history = events.format_history()
327

    
328
        # Pagination des résultats
329
        items_per_page = int(config['vigiboard_items_per_page'])
330
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
331
        event = events.req[0]
332

    
333
        return dict(
334
            idevent = idevent,
335
            hostname = event.hostname,
336
            servicename = event.servicename,
337
            plugins_data = {},
338
            page = page,
339
            search_form = create_search_form,
340
            search = {},
341
        )
342

    
343

    
344
    class ItemSchema(schema.Schema):
345
        """Schéma de validation de la méthode item."""
346
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
347
        host = validators.String(not_empty=True)
348
        service = validators.String(if_missing=None)
349

    
350
    @validate(
351
        validators=ItemSchema(),
352
        error_handler = process_form_errors)
353
    @expose('events_table.html')
354
    @require(access_restriction)
355
    def item(self, page, host, service):
356
        """
357
        Affichage de l'historique de l'ensemble des événements corrélés
358
        jamais ouverts sur l'hôte / service demandé.
359
        Pour accéder à cette page, l'utilisateur doit être authentifié.
360

361
        @param page: Numéro de la page à afficher.
362
        @param host: Nom de l'hôte souhaité.
363
        @param service: Nom du service souhaité
364

365
        Cette méthode permet de satisfaire l'exigence
366
        VIGILO_EXIG_VIGILO_BAC_0080.
367
        """
368
        idsupitem = SupItem.get_supitem(host, service)
369
        if not idsupitem:
370
            flash(_('No such host/service'), 'error')
371
            redirect('/')
372

    
373
        user = get_current_user()
374
        aggregates = VigiboardRequest(user, False)
375
        aggregates.add_table(
376
            CorrEvent,
377
            aggregates.items.c.hostname,
378
            aggregates.items.c.servicename,
379
        )
380
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
381
        aggregates.add_join((aggregates.items,
382
            Event.idsupitem == aggregates.items.c.idsupitem))
383
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
384

    
385
        # Pagination des résultats
386
        aggregates.generate_request()
387
        items_per_page = int(config['vigiboard_items_per_page'])
388
        page = paginate.Page(aggregates.req, page=page,
389
            items_per_page=items_per_page)
390

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

    
396
        # Ajout des formulaires et préparation
397
        # des données pour ces formulaires.
398
        ids_events = [event[0].idcause for event in page.items]
399
        tmpl_context.last_modification = \
400
            mktime(get_last_modification_timestamp(ids_events).timetuple())
401

    
402
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
403
            submit_text=_('Apply'), action=url('/update'))
404

    
405
        return dict(
406
            hostname = host,
407
            servicename = service,
408
            plugins_data = {},
409
            page = page,
410
            event_edit_status_options = edit_event_status_options,
411
            search_form = create_search_form,
412
            search = {},
413
        )
414

    
415

    
416
    class UpdateSchema(schema.Schema):
417
        """Schéma de validation de la méthode update."""
418
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
419
        last_modification = validators.Number(not_empty=True)
420
        trouble_ticket = validators.String(if_missing='')
421
        ack = validators.OneOf(
422
            [unicode(s[0]) for s in edit_event_status_options],
423
            not_empty=True)
424

    
425
    @validate(
426
        validators=UpdateSchema(),
427
        error_handler = process_form_errors)
428
    @require(
429
        All(
430
            not_anonymous(msg=l_("You need to be authenticated")),
431
            Any(in_group('managers'),
432
                has_permission('vigiboard-update'),
433
                msg=l_("You don't have write access to VigiBoard"))
434
        ))
435
    @expose()
436
    def update(self, id, last_modification, trouble_ticket, ack):
437
        """
438
        Mise à jour d'un événement suivant les arguments passés.
439
        Cela peut être un changement de ticket ou un changement de statut.
440

441
        @param id: Le ou les identifiants des événements à traiter
442
        @param last_modification: La date de la dernière modification
443
            dont l'utilisateur est au courant.
444
        @param trouble_ticket: Nouveau numéro du ticket associé.
445
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
446

447
        Cette méthode permet de satisfaire les exigences suivantes :
448
            - VIGILO_EXIG_VIGILO_BAC_0020,
449
            - VIGILO_EXIG_VIGILO_BAC_0060,
450
            - VIGILO_EXIG_VIGILO_BAC_0110.
451
        """
452

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

    
459
        # On récupère la liste de tous les identifiants des événements
460
        # à mettre à jour.
461
        ids = map(int, id.strip(',').split(','))
462

    
463
        user = get_current_user()
464
        events = VigiboardRequest(user)
465
        events.add_table(CorrEvent)
466
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
467
        events.add_join((events.items,
468
            Event.idsupitem == events.items.c.idsupitem))
469
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
470

    
471
        events.generate_request()
472
        idevents = [cause.idcause for cause in events.req]
473

    
474
        # Si des changements sont survenus depuis que la
475
        # page est affichée, on en informe l'utilisateur.
476
        last_modification = datetime.fromtimestamp(last_modification)
477
        cur_last_modification = get_last_modification_timestamp(idevents, None)
478
        if cur_last_modification and last_modification < cur_last_modification:
479
            flash(_('Changes have occurred since the page was last displayed, '
480
                    'your changes HAVE NOT been saved.'), 'warning')
481
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
482

    
483
        # Vérification que au moins un des identifiants existe et est éditable
484
        if not events.num_rows():
485
            flash(_('No access to this event'), 'error')
486
            redirect('/')
487

    
488
        if ack == u'Forced':
489
            condition = Any(
490
                in_group('managers'),
491
                has_permission('vigiboard-admin'),
492
                msg=l_("You don't have administrative access "
493
                        "to VigiBoard"))
494
            try:
495
                condition.check_authorization(request.environ)
496
            except NotAuthorizedError, e:
497
                reason = unicode(e)
498
                flash(reason, 'error')
499
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
500

    
501
        # Modification des événements et création d'un historique
502
        # chaque fois que cela est nécessaire.
503
        for event in events.req:
504
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
505
                history = EventHistory(
506
                        type_action=u"Ticket change",
507
                        idevent=event.idcause,
508
                        value=unicode(trouble_ticket),
509
                        text="Changed trouble ticket from '%(from)s' "
510
                             "to '%(to)s'" % {
511
                            'from': event.trouble_ticket,
512
                            'to': trouble_ticket,
513
                        },
514
                        username=user.user_name,
515
                        timestamp=datetime.now(),
516
                    )
517
                DBSession.add(history)
518
                event.trouble_ticket = trouble_ticket
519

    
520
            # Changement du statut d'acquittement.
521
            if ack != u'NoChange':
522
                changed_ack = ack
523
                # Pour forcer l'acquittement d'un événement,
524
                # il faut en plus avoir la permission
525
                # "vigiboard-admin".
526
                if ack == u'Forced':
527
                    changed_ack = u'AAClosed'
528
                    cause = event.cause
529
                    # On met systématiquement l'événement à l'état "OK",
530
                    # même s'il s'agit d'un hôte.
531
                    # Techniquement, c'est incorrect, mais on fait ça
532
                    # pour masquer l'événement de toutes façons...
533
                    cause.current_state = \
534
                        StateName.statename_to_value(u'OK')
535

    
536
                    # Mise à jour de l'état dans State, pour que
537
                    # VigiMap soit également mis à jour.
538
                    DBSession.query(State).filter(
539
                            State.idsupitem == cause.idsupitem,
540
                        ).update({
541
                            'state': StateName.statename_to_value(u'OK'),
542
                        })
543

    
544
                    history = EventHistory(
545
                            type_action="Forced change state",
546
                            idevent=event.idcause,
547
                            value=u'OK',
548
                            text="Forced state to 'OK'",
549
                            username=user.user_name,
550
                            timestamp=datetime.now(),
551
                        )
552
                    DBSession.add(history)
553

    
554
                history = EventHistory(
555
                        type_action=u"Acknowledgement change state",
556
                        idevent=event.idcause,
557
                        value=ack,
558
                        text="Changed acknowledgement status "
559
                            "from '%s' to '%s'" % (
560
                            event.status, changed_ack
561
                        ),
562
                        username=user.user_name,
563
                        timestamp=datetime.now(),
564
                    )
565
                DBSession.add(history)
566
                event.status = changed_ack
567

    
568
        DBSession.flush()
569
        flash(_('Updated successfully'))
570
        redirect(request.environ.get('HTTP_REFERER', '/'))
571

    
572

    
573
    class GetPluginValueSchema(schema.Schema):
574
        """Schéma de validation de la méthode get_plugin_value."""
575
        idcorrevent = validators.Int(not_empty=True)
576
        plugin_name = validators.String(not_empty=True)
577
        # Permet de passer des paramètres supplémentaires au plugin.
578
        allow_extra_fields = True
579

    
580
    @validate(
581
        validators=GetPluginValueSchema(),
582
        error_handler = handle_validation_errors_json)
583
    @expose('json')
584
    @require(access_restriction)
585
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
586
        """
587
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
588
        donné via JSON.
589
        """
590

    
591
        # Vérification de l'existence du plugin
592
        plugins = dict(config['columns_plugins'])
593
        if plugin_name not in plugins:
594
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
595

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

    
599
        # Filtrage des évènements en fonction des permissions de
600
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
601
        is_manager = in_group('managers').is_met(request.environ)
602
        if not is_manager:
603

    
604
            user = get_current_user()
605

    
606
            events = events.join(
607
                (Event, Event.idevent == CorrEvent.idcause),
608
            ).outerjoin(
609
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
610
            ).join(
611
                (SUPITEM_GROUP_TABLE,
612
                    or_(
613
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
614
                            LowLevelService.idhost,
615
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
616
                            Event.idsupitem,
617
                    )
618
                ),
619
            ).join(
620
                (GroupHierarchy,
621
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
622
            ).join(
623
                (DataPermission,
624
                    DataPermission.idgroup == GroupHierarchy.idparent),
625
            ).join(
626
                (USER_GROUP_TABLE,
627
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
628
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
629

    
630
        # Filtrage des évènements en fonction
631
        # de l'identifiant passé en paramètre
632
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
633

    
634
        # Pas d'événement ou permission refusée. On ne distingue pas
635
        # les 2 cas afin d'éviter la divulgation d'informations.
636
        if events == 0:
637
            raise HTTPNotFound(_('No such incident or insufficient '
638
                                'permissions'))
639

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

    
645
    @validate(validators={
646
        "fontsize": validators.Regex(
647
            r'[0-9]+(pt|px|em|%)',
648
            regexOps = ('I',)
649
        )}, error_handler = handle_validation_errors_json)
650
    @expose('json')
651
    def set_fontsize(self, fontsize):
652
        """Enregistre la taille de la police dans les préférences."""
653
        session['fontsize'] = fontsize
654
        session.save()
655
        return dict()
656

    
657
    @validate(validators={"refresh": validators.Int()},
658
            error_handler = handle_validation_errors_json)
659
    @expose('json')
660
    def set_refresh(self, refresh):
661
        """Enregistre le temps de rafraichissement dans les préférences."""
662
        session['refresh'] = refresh
663
        session.save()
664
        return dict()
665

    
666
    @expose('json')
667
    def set_theme(self, theme):
668
        """Enregistre le thème à utiliser dans les préférences."""
669
        # On sauvegarde l'ID du thème sans vérifications
670
        # car les thèmes (styles CSS) sont définies dans
671
        # les packages de thèmes (ex: vigilo-themes-default).
672
        # La vérification de la valeur est faite dans les templates.
673
        session['theme'] = theme
674
        session.save()
675
        return dict()
676

    
677
    @require(access_restriction)
678
    @expose('json')
679
    def get_groups(self, parent_id=None):
680
        """
681
        Affiche un étage de l'arbre de
682
        sélection des hôtes et groupes d'hôtes.
683

684
        @param parent_id: identifiant du groupe d'hôte parent
685
        @type  parent_id: C{int} or None
686
        """
687

    
688
        # Si l'identifiant du groupe parent n'est pas
689
        # spécifié, on retourne la liste des groupes racines,
690
        # fournie par la méthode get_root_hosts_groups.
691
        if parent_id is None:
692
            return self.get_root_host_groups()
693

    
694
        # TODO: Utiliser un schéma de validation
695
        parent_id = int(parent_id)
696

    
697
        # On récupère la liste des groupes de supitems dont
698
        # l'identifiant du parent est passé en paramètre.
699
        supitem_groups = DBSession.query(
700
                SupItemGroup.idgroup,
701
                SupItemGroup.name,
702
            ).join(
703
                (GroupHierarchy,
704
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
705
            ).filter(GroupHierarchy.idparent == parent_id
706
            ).filter(GroupHierarchy.idchild != parent_id)
707

    
708
        # Si l'utilisateur n'appartient pas au groupe 'managers',
709
        # on filtre les résultats en fonction de ses permissions.
710
        is_manager = in_group('managers').is_met(request.environ)
711
        if not is_manager:
712
            user = get_current_user()
713
            GroupHierarchy_aliased = aliased(GroupHierarchy,
714
                name='GroupHierarchy_aliased')
715
            supitem_groups.join(
716
                (GroupHierarchy_aliased,
717
                    GroupHierarchy_aliased.idchild == SupItemGroup.idgroup),
718
                (DataPermission,
719
                    DataPermission.idgroup == GroupHierarchy_aliased.idparent),
720
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
721
                    DataPermission.idusergroup),
722
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
723

    
724
        groups = []
725
        for group in supitem_groups.distinct().all():
726
            groups.append({
727
                'id'   : group.idgroup,
728
                'name' : group.name,
729
            })
730

    
731
        return dict(groups = groups, leaves = [])
732

    
733
    def get_root_host_groups(self):
734
        """
735
        Retourne tous les groupes racines (c'est à dire n'ayant
736
        aucun parent) d'hôtes auquel l'utilisateur a accès.
737

738
        @return: Un dictionnaire contenant la liste de ces groupes.
739
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
740
        """
741

    
742
        # On récupère tous les groupes qui ont un parent.
743
        children = DBSession.query(
744
            SupItemGroup,
745
        ).distinct(
746
        ).join(
747
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
748
        ).filter(GroupHierarchy.hops > 0)
749

    
750
        # Ensuite on les exclut de la liste des groupes,
751
        # pour ne garder que ceux qui sont au sommet de
752
        # l'arbre et qui constituent nos "root groups".
753
        root_groups = DBSession.query(
754
            SupItemGroup,
755
        ).except_(children
756
        ).order_by(SupItemGroup.name)
757

    
758
        # On filtre ces groupes racines afin de ne
759
        # retourner que ceux auquels l'utilisateur a accès
760
        user = get_current_user()
761
        is_manager = in_group('managers').is_met(request.environ)
762
        if not is_manager:
763

    
764
            root_groups = root_groups.join(
765
                (GroupHierarchy,
766
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
767
                (DataPermission,
768
                    DataPermission.idgroup == GroupHierarchy.idparent),
769
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
770
                    DataPermission.idusergroup),
771
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
772

    
773
        groups = []
774
        for group in root_groups.all():
775
            groups.append({
776
                'id'   : group.idgroup,
777
                'name' : group.name,
778
            })
779

    
780
        return dict(groups = groups, leaves=[])
781

    
782
def get_last_modification_timestamp(event_id_list,
783
                                    value_if_none=datetime.now()):
784
    """
785
    Récupère le timestamp de la dernière modification
786
    opérée sur l'un des événements dont l'identifiant
787
    fait partie de la liste passée en paramètre.
788
    """
789
    last_modification_timestamp = DBSession.query(
790
                                func.max(EventHistory.timestamp),
791
                         ).filter(EventHistory.idevent.in_(event_id_list)
792
                         ).scalar()
793
    if not last_modification_timestamp:
794
        if not value_if_none:
795
            return None
796
        else:
797
            last_modification_timestamp = value_if_none
798
    return datetime.fromtimestamp(mktime(
799
        last_modification_timestamp.timetuple()))