Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ ddbaec88

History | View | Annotate | Download (35.5 KB)

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

    
21
"""VigiBoard Controller"""
22

    
23
from datetime import datetime
24
from time import mktime
25

    
26
from pkg_resources import resource_filename
27

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

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

    
51
from vigilo.turbogears.controllers.auth import AuthController
52
from vigilo.turbogears.controllers.error import ErrorController
53
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
54
from vigilo.turbogears.controllers.proxy import ProxyController
55
from vigilo.turbogears.controllers.api.root import ApiRootController
56
from vigilo.turbogears.helpers import get_current_user
57

    
58
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
59
from vigiboard.controllers.feeds import FeedsController
60

    
61
from vigiboard.widgets.edit_event import edit_event_status_options, \
62
                                            EditEventForm
63
from vigiboard.widgets.search_form import create_search_form
64
import logging
65

    
66
LOGGER = logging.getLogger(__name__)
67

    
68
__all__ = ('RootController', 'get_last_modification_timestamp',
69
           'date_to_timestamp')
70

    
71
# pylint: disable-msg=R0201
72
class RootController(AuthController):
73
    """
74
    Le controller général de vigiboard
75
    """
76
    error = ErrorController()
77
    autocomplete = AutoCompleteController()
78
    nagios = ProxyController('nagios', '/nagios/',
79
        not_anonymous(l_('You need to be authenticated')))
80
    api = ApiRootController("/api")
81
    feeds = FeedsController()
82

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

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

    
102
    @expose('json')
103
    def handle_validation_errors_json(self, *args, **kwargs):
104
        kwargs['errors'] = tmpl_context.form_errors
105
        return dict(kwargs)
106

    
107
    class DefaultSchema(schema.Schema):
108
        """Schéma de validation de la méthode default."""
109
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
110

    
111
        # Nécessaire pour que les critères de recherche soient conservés.
112
        allow_extra_fields = True
113

    
114
        # 2ème validation, cette fois avec les champs
115
        # du formulaire de recherche.
116
        chained_validators = [create_search_form.validator]
117

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

130
        @param page: Numéro de la page souhaitée, commence à 1
131
        @type page: C{int}
132
        @param search: Dictionnaire contenant les critères de recherche.
133
        @type search: C{dict}
134

135
        Cette méthode permet de satisfaire les exigences suivantes :
136
            - VIGILO_EXIG_VIGILO_BAC_0040,
137
            - VIGILO_EXIG_VIGILO_BAC_0070,
138
            - VIGILO_EXIG_VIGILO_BAC_0100,
139
        """
140

    
141
        user = get_current_user()
142
        aggregates = VigiboardRequest(user, search=search)
143

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

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

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

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

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

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

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

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

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

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

    
217

    
218
    @expose()
219
    def i18n(self):
220
        import gettext
221
        import pylons
222
        import os.path
223

    
224
        # Repris de pylons.i18n.translation:_get_translator.
225
        conf = pylons.config.current_conf()
226
        try:
227
            rootdir = conf['pylons.paths']['root']
228
        except KeyError:
229
            rootdir = conf['pylons.paths'].get('root_path')
230
        localedir = os.path.join(rootdir, 'i18n')
231

    
232
        lang = get_lang()
233

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

    
240
        themes_filename = gettext.find(
241
            'vigilo-themes',
242
            resource_filename('vigilo.themes.i18n', ''),
243
            languages=lang)
244
        themes_js = themes_filename[:-3] + '.js'
245

    
246
        # Récupère et envoie le contenu du fichier de traduction *.js.
247
        fhandle = open(js, 'r')
248
        translations = fhandle.read()
249
        fhandle.close()
250

    
251
        fhandle = open(themes_js, 'r')
252
        translations += fhandle.read()
253
        fhandle.close()
254
        return translations
255

    
256

    
257
    class MaskedEventsSchema(schema.Schema):
258
        """Schéma de validation de la méthode masked_events."""
259
        idcorrevent = validators.Int(not_empty=True)
260
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
261

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

272
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
273
        @type idcorrevent: C{int}
274
        """
275
        user = get_current_user()
276

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

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

    
308
        if isinstance(cause_supitem, LowLevelService):
309
            hostname = cause_supitem.host.name
310
            servicename = cause_supitem.servicename
311
        elif isinstance(cause_supitem, Host):
312
            hostname = cause_supitem.name
313

    
314
        # Pagination des résultats
315
        events.generate_request()
316
        items_per_page = int(config['vigiboard_items_per_page'])
317
        page = paginate.Page(events.req, page=page,
318
            items_per_page=items_per_page)
319

    
320
        # Vérification que l'événement existe
321
        if not page.item_count:
322
            flash(_('No masked event or access denied'), 'error')
323
            redirect('/')
324

    
325
        return dict(
326
            idcorrevent = idcorrevent,
327
            hostname = hostname,
328
            servicename = servicename,
329
            plugins_data = {},
330
            page = page,
331
            search_form = create_search_form,
332
            search = {},
333
            fixed_search = {},
334
        )
335

    
336

    
337
    class EventSchema(schema.Schema):
338
        """Schéma de validation de la méthode event."""
339
        idevent = validators.Int(not_empty=True)
340
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
341

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

352
        @param idevent: identifiant de l'événement brut souhaité.
353
        @type idevent: C{int}
354
        @param page: numéro de la page à afficher.
355
        @type page: C{int}
356

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

    
375
        if events.num_rows() != 1:
376
            flash(_('No such event or access denied'), 'error')
377
            redirect('/')
378

    
379
        events.format_events(0, 1)
380
        events.generate_tmpl_context()
381
        history = events.format_history()
382

    
383
        # Pagination des résultats
384
        items_per_page = int(config['vigiboard_items_per_page'])
385
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
386
        event = events.req[0]
387

    
388
        return dict(
389
            idevent = idevent,
390
            hostname = event.hostname,
391
            servicename = event.servicename,
392
            plugins_data = {},
393
            page = page,
394
            search_form = create_search_form,
395
            search = {},
396
            fixed_search = {},
397
        )
398

    
399

    
400
    class ItemSchema(schema.Schema):
401
        """Schéma de validation de la méthode item."""
402
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
403
        host = validators.String(not_empty=True)
404
        service = validators.String(if_missing=None)
405

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

417
        @param page: Numéro de la page à afficher.
418
        @param host: Nom de l'hôte souhaité.
419
        @param service: Nom du service souhaité
420

421
        Cette méthode permet de satisfaire l'exigence
422
        VIGILO_EXIG_VIGILO_BAC_0080.
423
        """
424
        idsupitem = SupItem.get_supitem(host, service)
425
        if not idsupitem:
426
            flash(_('No such host/service'), 'error')
427
            redirect('/')
428

    
429
        user = get_current_user()
430
        aggregates = VigiboardRequest(user, False)
431
        aggregates.add_table(
432
            CorrEvent,
433
            aggregates.items.c.hostname,
434
            aggregates.items.c.servicename,
435
        )
436
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
437
        aggregates.add_join((aggregates.items,
438
            Event.idsupitem == aggregates.items.c.idsupitem))
439
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
440

    
441
        # Pagination des résultats
442
        aggregates.generate_request()
443
        items_per_page = int(config['vigiboard_items_per_page'])
444
        page = paginate.Page(aggregates.req, page=page,
445
            items_per_page=items_per_page)
446

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

    
452
        # Ajout des formulaires et préparation
453
        # des données pour ces formulaires.
454
        ids_events = [event[0].idcause for event in page.items]
455
        tmpl_context.last_modification = \
456
            mktime(get_last_modification_timestamp(ids_events).timetuple())
457

    
458
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
459
            submit_text=_('Apply'), action=url('/update'))
460

    
461
        return dict(
462
            hostname = host,
463
            servicename = service,
464
            plugins_data = {},
465
            page = page,
466
            event_edit_status_options = edit_event_status_options,
467
            search_form = create_search_form,
468
            search = {},
469
            fixed_search = {},
470
        )
471

    
472

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

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

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

504
        Cette méthode permet de satisfaire les exigences suivantes :
505
            - VIGILO_EXIG_VIGILO_BAC_0020,
506
            - VIGILO_EXIG_VIGILO_BAC_0060,
507
            - VIGILO_EXIG_VIGILO_BAC_0110.
508
        """
509

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

    
516
        # On récupère la liste de tous les identifiants des événements
517
        # à mettre à jour.
518
        ids = map(int, id.strip(',').split(','))
519

    
520
        user = get_current_user()
521
        events = VigiboardRequest(user)
522
        events.add_table(CorrEvent)
523
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
524
        events.add_join((events.items,
525
            Event.idsupitem == events.items.c.idsupitem))
526
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
527

    
528
        events.generate_request()
529
        idevents = [cause.idcause for cause in events.req]
530

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

    
540
        # Vérification que au moins un des identifiants existe et est éditable
541
        if not events.num_rows():
542
            flash(_('No access to this event'), 'error')
543
            redirect('/')
544

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

    
558
        # Modification des événements et création d'un historique
559
        # chaque fois que cela est nécessaire.
560
        for event in events.req:
561
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
562
                history = EventHistory(
563
                        type_action=u"Ticket change",
564
                        idevent=event.idcause,
565
                        value=unicode(trouble_ticket),
566
                        text="Changed trouble ticket from '%(from)s' "
567
                             "to '%(to)s'" % {
568
                            'from': event.trouble_ticket,
569
                            'to': trouble_ticket,
570
                        },
571
                        username=user.user_name,
572
                        timestamp=datetime.now(),
573
                    )
574
                DBSession.add(history)
575
                LOGGER.info(_('User "%(user)s" (%(address)s) changed the '
576
                            'trouble ticket from "%(previous)s" to "%(new)s" '
577
                            'on event #%(idevent)d') % {
578
                                'user': request.identity['repoze.who.userid'],
579
                                'address': request.remote_addr,
580
                                'previous': event.trouble_ticket,
581
                                'new': trouble_ticket,
582
                                'idevent': event.idcause,
583
                            })
584
                event.trouble_ticket = trouble_ticket
585

    
586
            # Changement du statut d'acquittement.
587
            if ack != u'NoChange':
588
                changed_ack = ack
589
                # Pour forcer l'acquittement d'un événement,
590
                # il faut en plus avoir la permission
591
                # "vigiboard-admin".
592
                if ack == u'Forced':
593
                    changed_ack = u'AAClosed'
594
                    cause = event.cause
595
                    # On met systématiquement l'événement à l'état "OK",
596
                    # même s'il s'agit d'un hôte.
597
                    # Techniquement, c'est incorrect, mais on fait ça
598
                    # pour masquer l'événement de toutes façons...
599
                    cause.current_state = \
600
                        StateName.statename_to_value(u'OK')
601

    
602
                    # Mise à jour de l'état dans State, pour que
603
                    # VigiMap soit également mis à jour.
604
                    DBSession.query(State).filter(
605
                            State.idsupitem == cause.idsupitem,
606
                        ).update({
607
                            'state': StateName.statename_to_value(u'OK'),
608
                        })
609

    
610
                    history = EventHistory(
611
                            type_action=u"Forced change state",
612
                            idevent=event.idcause,
613
                            value=u'OK',
614
                            text="Forced state to 'OK'",
615
                            username=user.user_name,
616
                            timestamp=datetime.now(),
617
                        )
618
                    DBSession.add(history)
619
                    LOGGER.info(_('User "%(user)s" (%(address)s) forcefully '
620
                                'closed event #%(idevent)d') % {
621
                                    'user': request. \
622
                                            identity['repoze.who.userid'],
623
                                    'address': request.remote_addr,
624
                                    'idevent': event.idcause,
625
                                })
626

    
627
                history = EventHistory(
628
                        type_action=u"Acknowledgement change state",
629
                        idevent=event.idcause,
630
                        value=ack,
631
                        text="Changed acknowledgement status "
632
                            "from '%s' to '%s'" % (
633
                            event.status, changed_ack
634
                        ),
635
                        username=user.user_name,
636
                        timestamp=datetime.now(),
637
                    )
638
                DBSession.add(history)
639
                LOGGER.info(_('User "%(user)s" (%(address)s) changed the state '
640
                            'from "%(previous)s" to "%(new)s" on event '
641
                            '#%(idevent)d') % {
642
                                'user': request.identity['repoze.who.userid'],
643
                                'address': request.remote_addr,
644
                                'previous': event.status,
645
                                'new': changed_ack,
646
                                'idevent': event.idcause,
647
                            })
648
                event.status = changed_ack
649

    
650
        DBSession.flush()
651
        flash(_('Updated successfully'))
652
        redirect(request.environ.get('HTTP_REFERER', '/'))
653

    
654

    
655
    class GetPluginValueSchema(schema.Schema):
656
        """Schéma de validation de la méthode get_plugin_value."""
657
        idcorrevent = validators.Int(not_empty=True)
658
        plugin_name = validators.String(not_empty=True)
659
        # Permet de passer des paramètres supplémentaires au plugin.
660
        allow_extra_fields = True
661

    
662
    @validate(
663
        validators=GetPluginValueSchema(),
664
        error_handler = handle_validation_errors_json)
665
    @expose('json')
666
    @require(access_restriction)
667
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
668
        """
669
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
670
        donné via JSON.
671
        """
672

    
673
        # Vérification de l'existence du plugin
674
        plugins = dict(config['columns_plugins'])
675
        if plugin_name not in plugins:
676
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
677

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

    
681
        # Filtrage des évènements en fonction des permissions de
682
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
683
        is_manager = in_group('managers').is_met(request.environ)
684
        if not is_manager:
685

    
686
            user = get_current_user()
687

    
688
            events = events.join(
689
                (Event, Event.idevent == CorrEvent.idcause),
690
            ).outerjoin(
691
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
692
            ).join(
693
                (SUPITEM_GROUP_TABLE,
694
                    or_(
695
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
696
                            LowLevelService.idhost,
697
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
698
                            Event.idsupitem,
699
                    )
700
                ),
701
            ).join(
702
                (GroupHierarchy,
703
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
704
            ).join(
705
                (DataPermission,
706
                    DataPermission.idgroup == GroupHierarchy.idparent),
707
            ).join(
708
                (USER_GROUP_TABLE,
709
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
710
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
711

    
712
        # Filtrage des évènements en fonction
713
        # de l'identifiant passé en paramètre
714
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
715

    
716
        # Pas d'événement ou permission refusée. On ne distingue pas
717
        # les 2 cas afin d'éviter la divulgation d'informations.
718
        if events == 0:
719
            raise HTTPNotFound(_('No such incident or insufficient '
720
                                'permissions'))
721

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

    
727
    @validate(validators={
728
        "fontsize": validators.Regex(
729
            r'[0-9]+(pt|px|em|%)',
730
            regexOps = ('I',)
731
        )}, error_handler = handle_validation_errors_json)
732
    @expose('json')
733
    def set_fontsize(self, fontsize):
734
        """Enregistre la taille de la police dans les préférences."""
735
        session['fontsize'] = fontsize
736
        session.save()
737
        return dict()
738

    
739
    @validate(validators={"refresh": validators.Int()},
740
            error_handler = handle_validation_errors_json)
741
    @expose('json')
742
    def set_refresh(self, refresh):
743
        """Enregistre le temps de rafraichissement dans les préférences."""
744
        session['refresh'] = bool(refresh)
745
        session.save()
746
        return dict()
747

    
748
    @expose('json')
749
    def set_theme(self, theme):
750
        """Enregistre le thème à utiliser dans les préférences."""
751
        # On sauvegarde l'ID du thème sans vérifications
752
        # car les thèmes (styles CSS) sont définies dans
753
        # les packages de thèmes (ex: vigilo-themes-default).
754
        # La vérification de la valeur est faite dans les templates.
755
        session['theme'] = theme
756
        session.save()
757
        return dict()
758

    
759
    @require(access_restriction)
760
    @expose('json')
761
    def get_groups(self, parent_id=None, onlytype="", offset=0, noCache=None):
762
        """
763
        Affiche un étage de l'arbre de
764
        sélection des hôtes et groupes d'hôtes.
765

766
        @param parent_id: identifiant du groupe d'hôte parent
767
        @type  parent_id: C{int} or None
768
        """
769

    
770
        # Si l'identifiant du groupe parent n'est pas
771
        # spécifié, on retourne la liste des groupes
772
        # racines, fournie par la méthode get_root_groups.
773
        if parent_id is None:
774
            return self.get_root_groups()
775

    
776
        # TODO: Utiliser un schéma de validation
777
        parent_id = int(parent_id)
778
        offset = int(offset)
779

    
780
        # On récupère la liste des groupes de supitems dont
781
        # l'identifiant du parent est passé en paramètre.
782
        supitem_groups = DBSession.query(
783
                SupItemGroup.idgroup,
784
                SupItemGroup.name,
785
            ).join(
786
                (GroupHierarchy,
787
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
788
            ).filter(GroupHierarchy.idparent == parent_id
789
            ).filter(GroupHierarchy.hops == 1
790
            ).order_by(SupItemGroup.name)
791

    
792
        # Si l'utilisateur n'appartient pas au groupe 'managers',
793
        # on filtre les résultats en fonction de ses permissions.
794
        is_manager = in_group('managers').is_met(request.environ)
795
        if not is_manager:
796
            user = get_current_user()
797
            GroupHierarchy_aliased = aliased(GroupHierarchy,
798
                name='GroupHierarchy_aliased')
799
            supitem_groups = supitem_groups.join(
800
                (GroupHierarchy_aliased,
801
                    or_(
802
                        GroupHierarchy_aliased.idchild == SupItemGroup.idgroup,
803
                        GroupHierarchy_aliased.idparent == SupItemGroup.idgroup
804
                    )),
805
                (DataPermission,
806
                    or_(
807
                        DataPermission.idgroup == \
808
                            GroupHierarchy_aliased.idparent,
809
                        DataPermission.idgroup == \
810
                            GroupHierarchy_aliased.idchild,
811
                    )),
812
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
813
                    DataPermission.idusergroup),
814
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
815

    
816
        limit = int(config.get("max_menu_entries", 20))
817
        result = {"groups": [], "items": []}
818
        num_children_left = supitem_groups.distinct().count() - offset
819
        if offset:
820
            result["continued_from"] = offset
821
            result["continued_type"] = "group"
822
        all_grs = supitem_groups.distinct().limit(limit).offset(offset).all()
823
        for group in all_grs:
824
            result["groups"].append({
825
                'id'   : group.idgroup,
826
                'name' : group.name,
827
                'type' : "group",
828
            })
829
        if num_children_left > limit:
830
            result["groups"].append({
831
                'name': _("Next %(limit)s") % {"limit": limit},
832
                'offset': offset + limit,
833
                'parent_id': parent_id,
834
                'type': 'continued',
835
                'for_type': 'group',
836
            })
837

    
838
        return result
839

    
840
    def get_root_groups(self):
841
        """
842
        Retourne tous les groupes racines (c'est à dire n'ayant
843
        aucun parent) d'hôtes auquel l'utilisateur a accès.
844

845
        @return: Un dictionnaire contenant la liste de ces groupes.
846
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
847
        """
848

    
849
        # On récupère tous les groupes qui ont un parent.
850
        children = DBSession.query(
851
            SupItemGroup,
852
        ).distinct(
853
        ).join(
854
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
855
        ).filter(GroupHierarchy.hops > 0)
856

    
857
        # Ensuite on les exclut de la liste des groupes,
858
        # pour ne garder que ceux qui sont au sommet de
859
        # l'arbre et qui constituent nos "root groups".
860
        root_groups = DBSession.query(
861
            SupItemGroup,
862
        ).except_(children
863
        ).order_by(SupItemGroup.name)
864

    
865
        # On filtre ces groupes racines afin de ne
866
        # retourner que ceux auquels l'utilisateur a accès
867
        user = get_current_user()
868
        is_manager = in_group('managers').is_met(request.environ)
869
        if not is_manager:
870

    
871
            root_groups = root_groups.join(
872
                (GroupHierarchy,
873
                    GroupHierarchy.idparent == SupItemGroup.idgroup),
874
                (DataPermission,
875
                    DataPermission.idgroup == GroupHierarchy.idchild),
876
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
877
                    DataPermission.idusergroup),
878
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
879

    
880
        groups = []
881
        for group in root_groups.all():
882
            groups.append({
883
                'id'   : group.idgroup,
884
                'name' : group.name,
885
                'type' : "group",
886
            })
887

    
888
        return dict(groups=groups, items=[])
889

    
890
def get_last_modification_timestamp(event_id_list,
891
                                    value_if_none=datetime.now()):
892
    """
893
    Récupère le timestamp de la dernière modification
894
    opérée sur l'un des événements dont l'identifiant
895
    fait partie de la liste passée en paramètre.
896
    """
897
    last_modification_timestamp = DBSession.query(
898
                                func.max(EventHistory.timestamp),
899
                         ).filter(EventHistory.idevent.in_(event_id_list)
900
                         ).scalar()
901
    if not last_modification_timestamp:
902
        if not value_if_none:
903
            return None
904
        else:
905
            last_modification_timestamp = value_if_none
906
    return datetime.fromtimestamp(mktime(
907
        last_modification_timestamp.timetuple()))