Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 0f0e32ed

History | View | Annotate | Download (31.6 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
        aggregates = VigiboardRequest(user, search=search)
136

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

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

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

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

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

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

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

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

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

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

    
210

    
211
    class MaskedEventsSchema(schema.Schema):
212
        """Schéma de validation de la méthode masked_events."""
213
        idcorrevent = validators.Int(not_empty=True)
214
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
215

    
216
    @validate(
217
        validators=MaskedEventsSchema(),
218
        error_handler = process_form_errors)
219
    @expose('raw_events_table.html')
220
    @require(access_restriction)
221
    def masked_events(self, idcorrevent, page):
222
        """
223
        Affichage de la liste des événements bruts masqués d'un événement
224
        corrélé (événements agrégés dans l'événement corrélé).
225

226
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
227
        @type idcorrevent: C{int}
228
        """
229
        user = get_current_user()
230

    
231
        # Récupère la liste des événements masqués de l'événement
232
        # corrélé donné par idcorrevent.
233
        events = VigiboardRequest(user, False)
234
        events.add_table(
235
            Event,
236
            events.items.c.hostname,
237
            events.items.c.servicename,
238
        )
239
        events.add_join((EVENTSAGGREGATE_TABLE, \
240
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
241
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
242
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
243
        events.add_join((events.items,
244
            Event.idsupitem == events.items.c.idsupitem))
245
        events.add_filter(Event.idevent != CorrEvent.idcause)
246
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
247

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

    
262
        if isinstance(cause_supitem, LowLevelService):
263
            hostname = cause_supitem.host.name
264
            servicename = cause_supitem.servicename
265
        elif isinstance(cause_supitem, Host):
266
            hostname = cause_supitem.name
267

    
268
        # Pagination des résultats
269
        events.generate_request()
270
        items_per_page = int(config['vigiboard_items_per_page'])
271
        page = paginate.Page(events.req, page=page,
272
            items_per_page=items_per_page)
273

    
274
        # Vérification que l'événement existe
275
        if not page.item_count:
276
            flash(_('No masked event or access denied'), 'error')
277
            redirect('/')
278

    
279
        return dict(
280
            idcorrevent = idcorrevent,
281
            hostname = hostname,
282
            servicename = servicename,
283
            plugins_data = {},
284
            page = page,
285
            search_form = create_search_form,
286
            search = {},
287
            fixed_search = {},
288
        )
289

    
290

    
291
    class EventSchema(schema.Schema):
292
        """Schéma de validation de la méthode event."""
293
        idevent = validators.Int(not_empty=True)
294
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
295

    
296
    @validate(
297
        validators=EventSchema(),
298
        error_handler = process_form_errors)
299
    @expose('history_table.html')
300
    @require(access_restriction)
301
    def event(self, idevent, page):
302
        """
303
        Affichage de l'historique d'un événement brut.
304
        Pour accéder à cette page, l'utilisateur doit être authentifié.
305

306
        @param idevent: identifiant de l'événement brut souhaité.
307
        @type idevent: C{int}
308
        @param page: numéro de la page à afficher.
309
        @type page: C{int}
310

311
        Cette méthode permet de satisfaire l'exigence
312
        VIGILO_EXIG_VIGILO_BAC_0080.
313
        """
314
        user = get_current_user()
315
        events = VigiboardRequest(user, False)
316
        events.add_table(
317
            Event,
318
            events.items.c.hostname.label('hostname'),
319
            events.items.c.servicename.label('servicename'),
320
        )
321
        events.add_join((EVENTSAGGREGATE_TABLE, \
322
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
323
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
324
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
325
        events.add_join((events.items,
326
            Event.idsupitem == events.items.c.idsupitem))
327
        events.add_filter(Event.idevent == idevent)
328

    
329
        if events.num_rows() != 1:
330
            flash(_('No such event or access denied'), 'error')
331
            redirect('/')
332

    
333
        events.format_events(0, 1)
334
        events.generate_tmpl_context()
335
        history = events.format_history()
336

    
337
        # Pagination des résultats
338
        items_per_page = int(config['vigiboard_items_per_page'])
339
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
340
        event = events.req[0]
341

    
342
        return dict(
343
            idevent = idevent,
344
            hostname = event.hostname,
345
            servicename = event.servicename,
346
            plugins_data = {},
347
            page = page,
348
            search_form = create_search_form,
349
            search = {},
350
            fixed_search = {},
351
        )
352

    
353

    
354
    class ItemSchema(schema.Schema):
355
        """Schéma de validation de la méthode item."""
356
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
357
        host = validators.String(not_empty=True)
358
        service = validators.String(if_missing=None)
359

    
360
    @validate(
361
        validators=ItemSchema(),
362
        error_handler = process_form_errors)
363
    @expose('events_table.html')
364
    @require(access_restriction)
365
    def item(self, page, host, service):
366
        """
367
        Affichage de l'historique de l'ensemble des événements corrélés
368
        jamais ouverts sur l'hôte / service demandé.
369
        Pour accéder à cette page, l'utilisateur doit être authentifié.
370

371
        @param page: Numéro de la page à afficher.
372
        @param host: Nom de l'hôte souhaité.
373
        @param service: Nom du service souhaité
374

375
        Cette méthode permet de satisfaire l'exigence
376
        VIGILO_EXIG_VIGILO_BAC_0080.
377
        """
378
        idsupitem = SupItem.get_supitem(host, service)
379
        if not idsupitem:
380
            flash(_('No such host/service'), 'error')
381
            redirect('/')
382

    
383
        user = get_current_user()
384
        aggregates = VigiboardRequest(user, False)
385
        aggregates.add_table(
386
            CorrEvent,
387
            aggregates.items.c.hostname,
388
            aggregates.items.c.servicename,
389
        )
390
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
391
        aggregates.add_join((aggregates.items,
392
            Event.idsupitem == aggregates.items.c.idsupitem))
393
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
394

    
395
        # Pagination des résultats
396
        aggregates.generate_request()
397
        items_per_page = int(config['vigiboard_items_per_page'])
398
        page = paginate.Page(aggregates.req, page=page,
399
            items_per_page=items_per_page)
400

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

    
406
        # Ajout des formulaires et préparation
407
        # des données pour ces formulaires.
408
        ids_events = [event[0].idcause for event in page.items]
409
        tmpl_context.last_modification = \
410
            mktime(get_last_modification_timestamp(ids_events).timetuple())
411

    
412
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
413
            submit_text=_('Apply'), action=url('/update'))
414

    
415
        return dict(
416
            hostname = host,
417
            servicename = service,
418
            plugins_data = {},
419
            page = page,
420
            event_edit_status_options = edit_event_status_options,
421
            search_form = create_search_form,
422
            search = {},
423
            fixed_search = {},
424
        )
425

    
426

    
427
    class UpdateSchema(schema.Schema):
428
        """Schéma de validation de la méthode update."""
429
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
430
        last_modification = validators.Number(not_empty=True)
431
        trouble_ticket = validators.String(if_missing='')
432
        ack = validators.OneOf(
433
            [unicode(s[0]) for s in edit_event_status_options],
434
            not_empty=True)
435

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

452
        @param id: Le ou les identifiants des événements à traiter
453
        @param last_modification: La date de la dernière modification
454
            dont l'utilisateur est au courant.
455
        @param trouble_ticket: Nouveau numéro du ticket associé.
456
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
457

458
        Cette méthode permet de satisfaire les exigences suivantes :
459
            - VIGILO_EXIG_VIGILO_BAC_0020,
460
            - VIGILO_EXIG_VIGILO_BAC_0060,
461
            - VIGILO_EXIG_VIGILO_BAC_0110.
462
        """
463

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

    
470
        # On récupère la liste de tous les identifiants des événements
471
        # à mettre à jour.
472
        ids = map(int, id.strip(',').split(','))
473

    
474
        user = get_current_user()
475
        events = VigiboardRequest(user)
476
        events.add_table(CorrEvent)
477
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
478
        events.add_join((events.items,
479
            Event.idsupitem == events.items.c.idsupitem))
480
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
481

    
482
        events.generate_request()
483
        idevents = [cause.idcause for cause in events.req]
484

    
485
        # Si des changements sont survenus depuis que la
486
        # page est affichée, on en informe l'utilisateur.
487
        last_modification = datetime.fromtimestamp(last_modification)
488
        cur_last_modification = get_last_modification_timestamp(idevents, None)
489
        if cur_last_modification and last_modification < cur_last_modification:
490
            flash(_('Changes have occurred since the page was last displayed, '
491
                    'your changes HAVE NOT been saved.'), 'warning')
492
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
493

    
494
        # Vérification que au moins un des identifiants existe et est éditable
495
        if not events.num_rows():
496
            flash(_('No access to this event'), 'error')
497
            redirect('/')
498

    
499
        if ack == u'Forced':
500
            condition = Any(
501
                in_group('managers'),
502
                has_permission('vigiboard-admin'),
503
                msg=l_("You don't have administrative access "
504
                        "to VigiBoard"))
505
            try:
506
                condition.check_authorization(request.environ)
507
            except NotAuthorizedError, e:
508
                reason = unicode(e)
509
                flash(reason, 'error')
510
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
511

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

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

    
547
                    # Mise à jour de l'état dans State, pour que
548
                    # VigiMap soit également mis à jour.
549
                    DBSession.query(State).filter(
550
                            State.idsupitem == cause.idsupitem,
551
                        ).update({
552
                            'state': StateName.statename_to_value(u'OK'),
553
                        })
554

    
555
                    history = EventHistory(
556
                            type_action="Forced change state",
557
                            idevent=event.idcause,
558
                            value=u'OK',
559
                            text="Forced state to 'OK'",
560
                            username=user.user_name,
561
                            timestamp=datetime.now(),
562
                        )
563
                    DBSession.add(history)
564

    
565
                history = EventHistory(
566
                        type_action=u"Acknowledgement change state",
567
                        idevent=event.idcause,
568
                        value=ack,
569
                        text="Changed acknowledgement status "
570
                            "from '%s' to '%s'" % (
571
                            event.status, changed_ack
572
                        ),
573
                        username=user.user_name,
574
                        timestamp=datetime.now(),
575
                    )
576
                DBSession.add(history)
577
                event.status = changed_ack
578

    
579
        DBSession.flush()
580
        flash(_('Updated successfully'))
581
        redirect(request.environ.get('HTTP_REFERER', '/'))
582

    
583

    
584
    class GetPluginValueSchema(schema.Schema):
585
        """Schéma de validation de la méthode get_plugin_value."""
586
        idcorrevent = validators.Int(not_empty=True)
587
        plugin_name = validators.String(not_empty=True)
588
        # Permet de passer des paramètres supplémentaires au plugin.
589
        allow_extra_fields = True
590

    
591
    @validate(
592
        validators=GetPluginValueSchema(),
593
        error_handler = handle_validation_errors_json)
594
    @expose('json')
595
    @require(access_restriction)
596
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
597
        """
598
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
599
        donné via JSON.
600
        """
601

    
602
        # Vérification de l'existence du plugin
603
        plugins = dict(config['columns_plugins'])
604
        if plugin_name not in plugins:
605
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
606

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

    
610
        # Filtrage des évènements en fonction des permissions de
611
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
612
        is_manager = in_group('managers').is_met(request.environ)
613
        if not is_manager:
614

    
615
            user = get_current_user()
616

    
617
            events = events.join(
618
                (Event, Event.idevent == CorrEvent.idcause),
619
            ).outerjoin(
620
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
621
            ).join(
622
                (SUPITEM_GROUP_TABLE,
623
                    or_(
624
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
625
                            LowLevelService.idhost,
626
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
627
                            Event.idsupitem,
628
                    )
629
                ),
630
            ).join(
631
                (GroupHierarchy,
632
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
633
            ).join(
634
                (DataPermission,
635
                    DataPermission.idgroup == GroupHierarchy.idparent),
636
            ).join(
637
                (USER_GROUP_TABLE,
638
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
639
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
640

    
641
        # Filtrage des évènements en fonction
642
        # de l'identifiant passé en paramètre
643
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
644

    
645
        # Pas d'événement ou permission refusée. On ne distingue pas
646
        # les 2 cas afin d'éviter la divulgation d'informations.
647
        if events == 0:
648
            raise HTTPNotFound(_('No such incident or insufficient '
649
                                'permissions'))
650

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

    
656
    @validate(validators={
657
        "fontsize": validators.Regex(
658
            r'[0-9]+(pt|px|em|%)',
659
            regexOps = ('I',)
660
        )}, error_handler = handle_validation_errors_json)
661
    @expose('json')
662
    def set_fontsize(self, fontsize):
663
        """Enregistre la taille de la police dans les préférences."""
664
        session['fontsize'] = fontsize
665
        session.save()
666
        return dict()
667

    
668
    @validate(validators={"refresh": validators.Int()},
669
            error_handler = handle_validation_errors_json)
670
    @expose('json')
671
    def set_refresh(self, refresh):
672
        """Enregistre le temps de rafraichissement dans les préférences."""
673
        session['refresh'] = refresh
674
        session.save()
675
        return dict()
676

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

    
688
    @require(access_restriction)
689
    @expose('json')
690
    def get_groups(self, parent_id=None):
691
        """
692
        Affiche un étage de l'arbre de
693
        sélection des hôtes et groupes d'hôtes.
694

695
        @param parent_id: identifiant du groupe d'hôte parent
696
        @type  parent_id: C{int} or None
697
        """
698

    
699
        # Si l'identifiant du groupe parent n'est pas
700
        # spécifié, on retourne la liste des groupes racines,
701
        # fournie par la méthode get_root_hosts_groups.
702
        if parent_id is None:
703
            return self.get_root_host_groups()
704

    
705
        # TODO: Utiliser un schéma de validation
706
        parent_id = int(parent_id)
707

    
708
        # On récupère la liste des groupes de supitems dont
709
        # l'identifiant du parent est passé en paramètre.
710
        supitem_groups = DBSession.query(
711
                SupItemGroup.idgroup,
712
                SupItemGroup.name,
713
            ).join(
714
                (GroupHierarchy,
715
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
716
            ).filter(GroupHierarchy.idparent == parent_id
717
            ).filter(GroupHierarchy.idchild != parent_id)
718

    
719
        # Si l'utilisateur n'appartient pas au groupe 'managers',
720
        # on filtre les résultats en fonction de ses permissions.
721
        is_manager = in_group('managers').is_met(request.environ)
722
        if not is_manager:
723
            user = get_current_user()
724
            GroupHierarchy_aliased = aliased(GroupHierarchy,
725
                name='GroupHierarchy_aliased')
726
            supitem_groups.join(
727
                (GroupHierarchy_aliased,
728
                    GroupHierarchy_aliased.idchild == SupItemGroup.idgroup),
729
                (DataPermission,
730
                    DataPermission.idgroup == GroupHierarchy_aliased.idparent),
731
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
732
                    DataPermission.idusergroup),
733
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
734

    
735
        groups = []
736
        for group in supitem_groups.distinct().all():
737
            groups.append({
738
                'id'   : group.idgroup,
739
                'name' : group.name,
740
            })
741

    
742
        return dict(groups = groups, leaves = [])
743

    
744
    def get_root_host_groups(self):
745
        """
746
        Retourne tous les groupes racines (c'est à dire n'ayant
747
        aucun parent) d'hôtes auquel l'utilisateur a accès.
748

749
        @return: Un dictionnaire contenant la liste de ces groupes.
750
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
751
        """
752

    
753
        # On récupère tous les groupes qui ont un parent.
754
        children = DBSession.query(
755
            SupItemGroup,
756
        ).distinct(
757
        ).join(
758
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
759
        ).filter(GroupHierarchy.hops > 0)
760

    
761
        # Ensuite on les exclut de la liste des groupes,
762
        # pour ne garder que ceux qui sont au sommet de
763
        # l'arbre et qui constituent nos "root groups".
764
        root_groups = DBSession.query(
765
            SupItemGroup,
766
        ).except_(children
767
        ).order_by(SupItemGroup.name)
768

    
769
        # On filtre ces groupes racines afin de ne
770
        # retourner que ceux auquels l'utilisateur a accès
771
        user = get_current_user()
772
        is_manager = in_group('managers').is_met(request.environ)
773
        if not is_manager:
774

    
775
            root_groups = root_groups.join(
776
                (GroupHierarchy,
777
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
778
                (DataPermission,
779
                    DataPermission.idgroup == GroupHierarchy.idparent),
780
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
781
                    DataPermission.idusergroup),
782
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
783

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

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

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