Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ b8700112

History | View | Annotate | Download (41 KB)

1 19e88cb8 Thomas ANDREJAK
# -*- coding: utf-8 -*-
2 e3c52cfd Aurelien BOMPARD
# vim:set expandtab tabstop=4 shiftwidth=4:
3 2bcebf54 Francois POIROTTE
# Copyright (C) 2007-2016 CS-SI
4 9b8d9497 Francois POIROTTE
# License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
5 a77de887 Francois POIROTTE
6 e181e86c Francois POIROTTE
"""VigiBoard Controller"""
7 19e88cb8 Thomas ANDREJAK
8 7365fb51 Francois POIROTTE
from datetime import datetime
9
from time import mktime
10
11 915f3245 Francois POIROTTE
from pkg_resources import resource_filename, working_set
12 db6fbc92 Aurelien BOMPARD
13 27140946 Francois POIROTTE
from tg.exceptions import HTTPNotFound
14 377a9c23 Francois POIROTTE
from tg.controllers import CUSTOM_CONTENT_TYPE
15 4b573169 Francois POIROTTE
from tg import expose, validate, require, flash, url, \
16 a2fa6a5b Francois POIROTTE
    tmpl_context, request, response, config, session, redirect
17 4b573169 Francois POIROTTE
from webhelpers import paginate
18 3e6ee4db Francois POIROTTE
from tw.forms import validators
19 db6fbc92 Aurelien BOMPARD
from pylons.i18n import ugettext as _, lazy_ugettext as l_, get_lang
20 6ab72614 Vincent QUEMENER
from sqlalchemy import asc
21 97f6d842 Vincent QUEMENER
from sqlalchemy.sql import func
22 eec46cb0 Vincent QUEMENER
from sqlalchemy.orm import aliased
23 6520dbc0 Vincent QUEMENER
from sqlalchemy.sql.expression import or_
24 73119f8a Francois POIROTTE
from repoze.what.predicates import Any, All, NotAuthorizedError, \
25
                                    has_permission, not_anonymous
26 1a1e8c17 Francois POIROTTE
from formencode import schema
27 ee3ae8c8 Francois POIROTTE
28 e7e3d45e Francois POIROTTE
from vigilo.models.session import DBSession
29 a05b9a37 Francois POIROTTE
from vigilo.models.tables import Event, EventHistory, CorrEvent, Host, \
30 9e0ea30e Francois POIROTTE
                                    SupItem, SupItemGroup, LowLevelService, \
31 eec46cb0 Vincent QUEMENER
                                    StateName, State, DataPermission
32 0bd9c069 Francois POIROTTE
from vigilo.models.tables.grouphierarchy import GroupHierarchy
33 eec46cb0 Vincent QUEMENER
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE, \
34 6520dbc0 Vincent QUEMENER
        USER_GROUP_TABLE, SUPITEM_GROUP_TABLE
35 7365fb51 Francois POIROTTE
36 ddbaec88 Francois POIROTTE
from vigilo.turbogears.controllers.auth import AuthController
37 f1886725 Vincent QUEMENER
from vigilo.turbogears.controllers.selfmonitoring import SelfMonitoringController
38 ff8eda22 Francois POIROTTE
from vigilo.turbogears.controllers.custom import CustomController
39 ddbaec88 Francois POIROTTE
from vigilo.turbogears.controllers.error import ErrorController
40 e307e626 Francois POIROTTE
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
41 ea0e5dfb Francois POIROTTE
from vigilo.turbogears.controllers.proxy import ProxyController
42 98a40b9f Aurelien BOMPARD
from vigilo.turbogears.controllers.api.root import ApiRootController
43 195aa50d Francois POIROTTE
from vigilo.turbogears.helpers import get_current_user
44
45 7365fb51 Francois POIROTTE
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
46 bc31210c Francois POIROTTE
from vigiboard.controllers.feeds import FeedsController
47 d5a41c9b Vincent QUEMENER
from vigiboard.controllers.silence import SilenceController
48 2dbc5942 Francois POIROTTE
49 00ece25a Francois POIROTTE
from vigiboard.lib import export_csv, dateformat
50 4b573169 Francois POIROTTE
from vigiboard.widgets.edit_event import edit_event_status_options, \
51
                                            EditEventForm
52 27140946 Francois POIROTTE
from vigiboard.widgets.search_form import create_search_form
53 7f26a756 Francois POIROTTE
import logging
54
55
LOGGER = logging.getLogger(__name__)
56 19e88cb8 Thomas ANDREJAK
57 e3c52cfd Aurelien BOMPARD
__all__ = ('RootController', 'get_last_modification_timestamp',
58 97f6d842 Vincent QUEMENER
           'date_to_timestamp')
59 19e88cb8 Thomas ANDREJAK
60 8b2edebe Aurelien BOMPARD
# pylint: disable-msg=R0201,W0613,W0622
61
# R0201: Method could be a function
62
# W0613: Unused arguments: les arguments sont la query-string
63
# W0622: Redefining built-in 'id': élément de la query-string
64
65 f1886725 Vincent QUEMENER
class RootController(AuthController, SelfMonitoringController):
66 19e88cb8 Thomas ANDREJAK
    """
67
    Le controller général de vigiboard
68
    """
69 915f3245 Francois POIROTTE
    _tickets = None
70
71 ddbaec88 Francois POIROTTE
    error = ErrorController()
72 e307e626 Francois POIROTTE
    autocomplete = AutoCompleteController()
73 ea0e5dfb Francois POIROTTE
    nagios = ProxyController('nagios', '/nagios/',
74
        not_anonymous(l_('You need to be authenticated')))
75 17516734 Francois Poirotte
    api = ApiRootController()
76 bc31210c Francois POIROTTE
    feeds = FeedsController()
77 d5a41c9b Vincent QUEMENER
    silence = SilenceController()
78 ff8eda22 Francois POIROTTE
    custom = CustomController()
79 ef31cc13 Francois POIROTTE
80 f2e30877 Francois POIROTTE
    # Prédicat pour la restriction de l'accès aux interfaces.
81 a5f99051 Francois POIROTTE
    # L'utilisateur doit avoir la permission "vigiboard-access"
82 f2e30877 Francois POIROTTE
    # ou appartenir au groupe "managers" pour accéder à VigiBoard.
83
    access_restriction = All(
84
        not_anonymous(msg=l_("You need to be authenticated")),
85 73119f8a Francois POIROTTE
        Any(config.is_manager,
86 a5f99051 Francois POIROTTE
            has_permission('vigiboard-access'),
87
            msg=l_("You don't have access to VigiBoard"))
88 f2e30877 Francois POIROTTE
    )
89
90 aa0788a2 Francois POIROTTE
    def process_form_errors(self, *argv, **kwargv):
91 19e88cb8 Thomas ANDREJAK
        """
92 27140946 Francois POIROTTE
        Gestion des erreurs de validation : on affiche les erreurs
93 19e88cb8 Thomas ANDREJAK
        puis on redirige vers la dernière page accédée.
94
        """
95 aa0788a2 Francois POIROTTE
        for k in tmpl_context.form_errors:
96
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
97 a9a4679d Francois POIROTTE
        redirect(request.environ.get('HTTP_REFERER', '/'))
98 19e88cb8 Thomas ANDREJAK
99 4c08cd96 Francois POIROTTE
    @expose('json')
100
    def handle_validation_errors_json(self, *args, **kwargs):
101
        kwargs['errors'] = tmpl_context.form_errors
102
        return dict(kwargs)
103 e3c52cfd Aurelien BOMPARD
104 915f3245 Francois POIROTTE
    def __init__(self, *args, **kwargs):
105
        """Initialisation du contrôleur."""
106
        super(RootController, self).__init__(*args, **kwargs)
107
        # Si un module de gestion des tickets a été indiqué dans
108
        # le fichier de configuration, on tente de le charger.
109
        if config.get('tickets.plugin'):
110
            plugins = working_set.iter_entry_points('vigiboard.tickets', config['tickets.plugin'])
111
            if plugins:
112
                # La classe indiquée par la première valeur de l'itérateur
113
                # correspond au plugin que l'on veut instancier.
114
                pluginCls = plugins.next().load()
115
                self._tickets = pluginCls()
116
117 0081c9f2 Francois POIROTTE
    class IndexSchema(schema.Schema):
118
        """Schéma de validation de la méthode index."""
119 338575f6 Francois POIROTTE
        # Si on ne passe pas le paramètre "page" ou qu'on passe une valeur
120
        # invalide ou pas de valeur du tout, alors on affiche la 1ère page.
121 5a845c93 Vincent QUEMENER
        page = validators.Int(
122
            min=1,
123
            if_missing=1,
124
            if_invalid=1,
125
            not_empty=True
126
        )
127
128
        # Paramètres de tri
129
        sort = validators.String(if_missing=None)
130
        order = validators.OneOf(['asc', 'desc'], if_missing='asc')
131 27140946 Francois POIROTTE
132
        # Nécessaire pour que les critères de recherche soient conservés.
133
        allow_extra_fields = True
134
135
        # 2ème validation, cette fois avec les champs
136
        # du formulaire de recherche.
137
        chained_validators = [create_search_form.validator]
138 e307e626 Francois POIROTTE
139
    @validate(
140 0081c9f2 Francois POIROTTE
        validators=IndexSchema(),
141 e307e626 Francois POIROTTE
        error_handler = process_form_errors)
142 377a9c23 Francois POIROTTE
    @expose('events_table.html', content_type=CUSTOM_CONTENT_TYPE)
143 f2e30877 Francois POIROTTE
    @require(access_restriction)
144 5a845c93 Vincent QUEMENER
    def index(self, page, sort=None, order=None, **search):
145 19e88cb8 Thomas ANDREJAK
        """
146 bc94248f Francois POIROTTE
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
147 a2a22ade Francois POIROTTE
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
148 bc94248f Francois POIROTTE
        en compte, puis de sévérité.
149 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
150

151 bc94248f Francois POIROTTE
        @param page: Numéro de la page souhaitée, commence à 1
152 27140946 Francois POIROTTE
        @type page: C{int}
153 5a845c93 Vincent QUEMENER
        @param sort: Colonne de tri
154
        @type sort: C{str} or C{None}
155
        @param order: Ordre du tri (asc ou desc)
156
        @type order: C{str} or C{None}
157 27140946 Francois POIROTTE
        @param search: Dictionnaire contenant les critères de recherche.
158
        @type search: C{dict}
159 c9245ffc Vincent QUEMENER

160 e3c52cfd Aurelien BOMPARD
        Cette méthode permet de satisfaire les exigences suivantes :
161
            - VIGILO_EXIG_VIGILO_BAC_0040,
162 e181e86c Francois POIROTTE
            - VIGILO_EXIG_VIGILO_BAC_0070,
163
            - VIGILO_EXIG_VIGILO_BAC_0100,
164 19e88cb8 Thomas ANDREJAK
        """
165 f1886725 Vincent QUEMENER
166
        # Auto-supervision
167
        self.get_failures()
168
169 195aa50d Francois POIROTTE
        user = get_current_user()
170 5a845c93 Vincent QUEMENER
        aggregates = VigiboardRequest(user, search=search, sort=sort, order=order)
171 cf3c2494 Vincent QUEMENER
172 911069bc Francois POIROTTE
        aggregates.add_table(
173
            CorrEvent,
174
            aggregates.items.c.hostname,
175
            aggregates.items.c.servicename
176
        )
177
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
178 cf3c2494 Vincent QUEMENER
        aggregates.add_contains_eager(CorrEvent.cause)
179
        aggregates.add_group_by(Event)
180 e3c52cfd Aurelien BOMPARD
        aggregates.add_join((aggregates.items,
181 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == aggregates.items.c.idsupitem))
182 5d20c2c5 Francois POIROTTE
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
183 e3c52cfd Aurelien BOMPARD
184 adb0e63f Francois POIROTTE
        # Certains arguments sont réservés dans routes.util.url_for().
185 27140946 Francois POIROTTE
        # On effectue les substitutions adéquates.
186
        # Par exemple: "host" devient "host_".
187 adb0e63f Francois POIROTTE
        reserved = ('host', 'anchor', 'protocol', 'qualified')
188
        for column in search.copy():
189 27140946 Francois POIROTTE
            if column in reserved:
190
                search[column + '_'] = search[column]
191
                del search[column]
192 1101e03e Francois POIROTTE
193 adb0e63f Francois POIROTTE
        # On ne garde que les champs effectivement renseignés.
194
        for column in search.copy():
195
            if not search[column]:
196
                del search[column]
197
198
        # On sérialise les champs de type dict.
199
        def serialize_dict(dct, key):
200
            if isinstance(dct[key], dict):
201
                for subkey in dct[key]:
202
                    serialize_dict(dct[key], subkey)
203 00ece25a Francois POIROTTE
                    dct['%s.%s' % (key, subkey)] = dct[key][subkey]
204 adb0e63f Francois POIROTTE
                del dct[key]
205 00ece25a Francois POIROTTE
            elif isinstance(dct[key], datetime):
206
                dct[key] = dct[key].strftime(dateformat.get_date_format())
207 adb0e63f Francois POIROTTE
        fixed_search = search.copy()
208
        for column in fixed_search.copy():
209
            serialize_dict(fixed_search, column)
210
211 4b573169 Francois POIROTTE
        # Pagination des résultats
212
        aggregates.generate_request()
213 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
214 27140946 Francois POIROTTE
        page = paginate.Page(aggregates.req, page=page,
215
            items_per_page=items_per_page)
216 3d0d254c Francois POIROTTE
217 cf3c2494 Vincent QUEMENER
        # Récupération des données des plugins
218
        plugins_data = {}
219
        plugins = dict(config['columns_plugins'])
220 4b573169 Francois POIROTTE
221
        ids_events = [event[0].idcause for event in page.items]
222
        ids_correvents = [event[0].idcorrevent for event in page.items]
223 cf3c2494 Vincent QUEMENER
        for plugin in plugins:
224 4b573169 Francois POIROTTE
            plugin_data = plugins[plugin].get_bulk_data(ids_correvents)
225
            if plugin_data:
226 cf3c2494 Vincent QUEMENER
                plugins_data[plugin] = plugin_data
227 a2fa6a5b Francois POIROTTE
            else:
228
                plugins_data[plugin] = {}
229 cf3c2494 Vincent QUEMENER
230 4b573169 Francois POIROTTE
        # Ajout des formulaires et préparation
231
        # des données pour ces formulaires.
232
        tmpl_context.last_modification = \
233
            mktime(get_last_modification_timestamp(ids_events).timetuple())
234
235
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
236
            submit_text=_('Apply'), action=url('/update'))
237
238 a2fa6a5b Francois POIROTTE
        if request.response_type == 'text/csv':
239
            # Sans les 2 en-têtes suivants qui désactivent la mise en cache,
240
            # Internet Explorer refuse de télécharger le fichier CSV (cf. #961).
241
            response.headers['Pragma'] = 'public'           # Nécessaire pour IE.
242
            response.headers['Cache-Control'] = 'max-age=0' # Nécessaire pour IE.
243
244 377a9c23 Francois POIROTTE
            response.headers["Content-Type"] = "text/csv"
245 a2fa6a5b Francois POIROTTE
            response.headers['Content-Disposition'] = \
246
                            'attachment;filename="alerts.csv"'
247
            return export_csv.export(page, plugins_data)
248
249 19e88cb8 Thomas ANDREJAK
        return dict(
250 73f3220e Vincent QUEMENER
            hostname = None,
251
            servicename = None,
252 cf3c2494 Vincent QUEMENER
            plugins_data = plugins_data,
253 1101e03e Francois POIROTTE
            page = page,
254 5a845c93 Vincent QUEMENER
            sort = sort,
255
            order = order,
256 1101e03e Francois POIROTTE
            event_edit_status_options = edit_event_status_options,
257 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
258 1101e03e Francois POIROTTE
            search = search,
259 adb0e63f Francois POIROTTE
            fixed_search = fixed_search,
260 1101e03e Francois POIROTTE
        )
261 19e88cb8 Thomas ANDREJAK
262 e307e626 Francois POIROTTE
263 b8700112 Francois POIROTTE
    @expose(content_type=CUSTOM_CONTENT_TYPE)
264 db6fbc92 Aurelien BOMPARD
    def i18n(self):
265
        import gettext
266
        import pylons
267
        import os.path
268
269
        # Repris de pylons.i18n.translation:_get_translator.
270
        conf = pylons.config.current_conf()
271
        try:
272
            rootdir = conf['pylons.paths']['root']
273
        except KeyError:
274
            rootdir = conf['pylons.paths'].get('root_path')
275
        localedir = os.path.join(rootdir, 'i18n')
276
277
        lang = get_lang()
278
279
        # Localise le fichier *.mo actuellement chargé
280
        # et génère le chemin jusqu'au *.js correspondant.
281
        filename = gettext.find(conf['pylons.package'], localedir,
282
            languages=lang)
283
        js = filename[:-3] + '.js'
284
285
        themes_filename = gettext.find(
286
            'vigilo-themes',
287
            resource_filename('vigilo.themes.i18n', ''),
288
            languages=lang)
289
        themes_js = themes_filename[:-3] + '.js'
290
291
        # Récupère et envoie le contenu du fichier de traduction *.js.
292
        fhandle = open(js, 'r')
293
        translations = fhandle.read()
294
        fhandle.close()
295
296
        fhandle = open(themes_js, 'r')
297
        translations += fhandle.read()
298
        fhandle.close()
299 b8700112 Francois POIROTTE
300
        response.headers['Content-Type'] = 'text/javascript; charset=utf-8'
301 db6fbc92 Aurelien BOMPARD
        return translations
302
303
304 e307e626 Francois POIROTTE
    class MaskedEventsSchema(schema.Schema):
305 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode masked_events."""
306 e307e626 Francois POIROTTE
        idcorrevent = validators.Int(not_empty=True)
307
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
308
309
    @validate(
310
        validators=MaskedEventsSchema(),
311
        error_handler = process_form_errors)
312 00d2e1d1 Vincent QUEMENER
    @expose('raw_events_table.html')
313 f2e30877 Francois POIROTTE
    @require(access_restriction)
314 e307e626 Francois POIROTTE
    def masked_events(self, idcorrevent, page):
315 54644278 Francois POIROTTE
        """
316 e181e86c Francois POIROTTE
        Affichage de la liste des événements bruts masqués d'un événement
317
        corrélé (événements agrégés dans l'événement corrélé).
318 54644278 Francois POIROTTE

319 5a845c93 Vincent QUEMENER
        @param page: numéro de la page à afficher.
320
        @type  page: C{int}
321 e181e86c Francois POIROTTE
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
322 5a845c93 Vincent QUEMENER
        @type  idcorrevent: C{int}
323 54644278 Francois POIROTTE
        """
324 f1886725 Vincent QUEMENER
325
        # Auto-supervision
326
        self.get_failures()
327
328 195aa50d Francois POIROTTE
        user = get_current_user()
329 a05b9a37 Francois POIROTTE
330
        # Récupère la liste des événements masqués de l'événement
331
        # corrélé donné par idcorrevent.
332 195aa50d Francois POIROTTE
        events = VigiboardRequest(user, False)
333 54644278 Francois POIROTTE
        events.add_table(
334
            Event,
335
            events.items.c.hostname,
336
            events.items.c.servicename,
337
        )
338 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
339
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
340
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
341
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
342 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
343 54644278 Francois POIROTTE
            Event.idsupitem == events.items.c.idsupitem))
344
        events.add_filter(Event.idevent != CorrEvent.idcause)
345
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
346 bcf87133 Francois POIROTTE
347 a05b9a37 Francois POIROTTE
        # Récupère l'instance de SupItem associé à la cause de
348
        # l'événement corrélé. Cette instance est utilisé pour
349
        # obtenir le nom d'hôte/service auquel la cause est
350
        # rattachée (afin de fournir un contexte à l'utilisateur).
351
        hostname = None
352
        servicename = None
353
        cause_supitem = DBSession.query(
354
                SupItem,
355
            ).join(
356
                (Event, Event.idsupitem == SupItem.idsupitem),
357 cf3c2494 Vincent QUEMENER
                (CorrEvent, Event.idevent == CorrEvent.idcause),
358 a05b9a37 Francois POIROTTE
            ).filter(CorrEvent.idcorrevent == idcorrevent
359
            ).one()
360
361
        if isinstance(cause_supitem, LowLevelService):
362
            hostname = cause_supitem.host.name
363
            servicename = cause_supitem.servicename
364
        elif isinstance(cause_supitem, Host):
365
            hostname = cause_supitem.name
366
367 4b573169 Francois POIROTTE
        # Pagination des résultats
368
        events.generate_request()
369 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
370 27140946 Francois POIROTTE
        page = paginate.Page(events.req, page=page,
371
            items_per_page=items_per_page)
372 4b573169 Francois POIROTTE
373 54644278 Francois POIROTTE
        # Vérification que l'événement existe
374 4b573169 Francois POIROTTE
        if not page.item_count:
375 54644278 Francois POIROTTE
            flash(_('No masked event or access denied'), 'error')
376
            redirect('/')
377
378
        return dict(
379 0c8b0e15 Francois POIROTTE
            idcorrevent = idcorrevent,
380 a05b9a37 Francois POIROTTE
            hostname = hostname,
381
            servicename = servicename,
382 cf3c2494 Vincent QUEMENER
            plugins_data = {},
383 54644278 Francois POIROTTE
            page = page,
384 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
385
            search = {},
386 adb0e63f Francois POIROTTE
            fixed_search = {},
387 54644278 Francois POIROTTE
        )
388
389 e307e626 Francois POIROTTE
390
    class EventSchema(schema.Schema):
391 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode event."""
392 e307e626 Francois POIROTTE
        idevent = validators.Int(not_empty=True)
393
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
394
395
    @validate(
396
        validators=EventSchema(),
397
        error_handler = process_form_errors)
398 00d2e1d1 Vincent QUEMENER
    @expose('history_table.html')
399 f2e30877 Francois POIROTTE
    @require(access_restriction)
400 e307e626 Francois POIROTTE
    def event(self, idevent, page):
401 19e88cb8 Thomas ANDREJAK
        """
402 94f31908 Francois POIROTTE
        Affichage de l'historique d'un événement brut.
403 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
404

405 94f31908 Francois POIROTTE
        @param idevent: identifiant de l'événement brut souhaité.
406
        @type idevent: C{int}
407 e181e86c Francois POIROTTE
        @param page: numéro de la page à afficher.
408
        @type page: C{int}
409 c9245ffc Vincent QUEMENER

410 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
411
        VIGILO_EXIG_VIGILO_BAC_0080.
412 19e88cb8 Thomas ANDREJAK
        """
413 f1886725 Vincent QUEMENER
414
        # Auto-supervision
415
        self.get_failures()
416
417 195aa50d Francois POIROTTE
        user = get_current_user()
418
        events = VigiboardRequest(user, False)
419 911069bc Francois POIROTTE
        events.add_table(
420 539f69fc Francois POIROTTE
            Event,
421 72ec8dbf Francois POIROTTE
            events.items.c.hostname.label('hostname'),
422
            events.items.c.servicename.label('servicename'),
423 911069bc Francois POIROTTE
        )
424 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
425
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
426
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
427
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
428 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
429 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
430 94f31908 Francois POIROTTE
        events.add_filter(Event.idevent == idevent)
431 539f69fc Francois POIROTTE
432
        if events.num_rows() != 1:
433
            flash(_('No such event or access denied'), 'error')
434 19e88cb8 Thomas ANDREJAK
            redirect('/')
435 539f69fc Francois POIROTTE
436 19e88cb8 Thomas ANDREJAK
        events.format_events(0, 1)
437 539f69fc Francois POIROTTE
        events.generate_tmpl_context()
438
        history = events.format_history()
439
440 4b573169 Francois POIROTTE
        # Pagination des résultats
441 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
442 4b573169 Francois POIROTTE
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
443 72ec8dbf Francois POIROTTE
        event = events.req[0]
444
445 19e88cb8 Thomas ANDREJAK
        return dict(
446 0c8b0e15 Francois POIROTTE
            idevent = idevent,
447 72ec8dbf Francois POIROTTE
            hostname = event.hostname,
448
            servicename = event.servicename,
449 cf3c2494 Vincent QUEMENER
            plugins_data = {},
450 539f69fc Francois POIROTTE
            page = page,
451 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
452
            search = {},
453 adb0e63f Francois POIROTTE
            fixed_search = {},
454 911069bc Francois POIROTTE
        )
455 19e88cb8 Thomas ANDREJAK
456 e307e626 Francois POIROTTE
457
    class ItemSchema(schema.Schema):
458 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode item."""
459 5a845c93 Vincent QUEMENER
        # Si on ne passe pas le paramètre "page" ou qu'on passe une valeur
460
        # invalide ou pas de valeur du tout, alors on affiche la 1ère page.
461 e307e626 Francois POIROTTE
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
462 5a845c93 Vincent QUEMENER
463
        # Paramètres de tri
464
        sort = validators.String(if_missing=None)
465
        order = validators.OneOf(['asc', 'desc'], if_missing='asc')
466
467
        # L'hôte / service dont on doit afficher les évènements
468 e307e626 Francois POIROTTE
        host = validators.String(not_empty=True)
469
        service = validators.String(if_missing=None)
470
471 24334b4b Vincent QUEMENER
    @validate(
472 e307e626 Francois POIROTTE
        validators=ItemSchema(),
473 6ab72614 Vincent QUEMENER
        error_handler = process_form_errors)
474 eab949e2 Vincent QUEMENER
    @expose('events_table.html')
475 f2e30877 Francois POIROTTE
    @require(access_restriction)
476 5a845c93 Vincent QUEMENER
    def item(self, page, host, service, sort=None, order=None):
477 19e88cb8 Thomas ANDREJAK
        """
478 539f69fc Francois POIROTTE
        Affichage de l'historique de l'ensemble des événements corrélés
479
        jamais ouverts sur l'hôte / service demandé.
480 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
481

482 e181e86c Francois POIROTTE
        @param page: Numéro de la page à afficher.
483 5a845c93 Vincent QUEMENER
        @type: C{int}
484 19e88cb8 Thomas ANDREJAK
        @param host: Nom de l'hôte souhaité.
485 5a845c93 Vincent QUEMENER
        @type: C{str}
486 19e88cb8 Thomas ANDREJAK
        @param service: Nom du service souhaité
487 5a845c93 Vincent QUEMENER
        @type: C{str}
488
        @param sort: Colonne de tri
489
        @type: C{str} or C{None}
490
        @param order: Ordre du tri (asc ou desc)
491
        @type: C{str} or C{None}
492 c9245ffc Vincent QUEMENER

493 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
494
        VIGILO_EXIG_VIGILO_BAC_0080.
495 19e88cb8 Thomas ANDREJAK
        """
496 f1886725 Vincent QUEMENER
497
        # Auto-supervision
498
        self.get_failures()
499
500 24334b4b Vincent QUEMENER
        idsupitem = SupItem.get_supitem(host, service)
501 1a1e8c17 Francois POIROTTE
        if not idsupitem:
502
            flash(_('No such host/service'), 'error')
503
            redirect('/')
504 24334b4b Vincent QUEMENER
505 195aa50d Francois POIROTTE
        user = get_current_user()
506 5a845c93 Vincent QUEMENER
        aggregates = VigiboardRequest(user, False, sort=sort, order=order)
507 539f69fc Francois POIROTTE
        aggregates.add_table(
508 911069bc Francois POIROTTE
            CorrEvent,
509 539f69fc Francois POIROTTE
            aggregates.items.c.hostname,
510
            aggregates.items.c.servicename,
511 911069bc Francois POIROTTE
        )
512 539f69fc Francois POIROTTE
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
513 0842bb2c Francois POIROTTE
        aggregates.add_join((aggregates.items,
514 539f69fc Francois POIROTTE
            Event.idsupitem == aggregates.items.c.idsupitem))
515
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
516 baedcd0f Francois POIROTTE
517 4b573169 Francois POIROTTE
        # Pagination des résultats
518
        aggregates.generate_request()
519 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
520 27140946 Francois POIROTTE
        page = paginate.Page(aggregates.req, page=page,
521
            items_per_page=items_per_page)
522 4b573169 Francois POIROTTE
523 a2a22ade Francois POIROTTE
        # Vérification qu'il y a au moins 1 événement qui correspond
524 4b573169 Francois POIROTTE
        if not page.item_count:
525 911069bc Francois POIROTTE
            flash(_('No access to this host/service or no event yet'), 'error')
526 19e88cb8 Thomas ANDREJAK
            redirect('/')
527 ee3ae8c8 Francois POIROTTE
528 4b573169 Francois POIROTTE
        # Ajout des formulaires et préparation
529
        # des données pour ces formulaires.
530
        ids_events = [event[0].idcause for event in page.items]
531
        tmpl_context.last_modification = \
532
            mktime(get_last_modification_timestamp(ids_events).timetuple())
533 539f69fc Francois POIROTTE
534 4b573169 Francois POIROTTE
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
535
            submit_text=_('Apply'), action=url('/update'))
536 e3c52cfd Aurelien BOMPARD
537 a2fa6a5b Francois POIROTTE
        plugins_data = {}
538
        for plugin in dict(config['columns_plugins']):
539
            plugins_data[plugin] = {}
540
541 19e88cb8 Thomas ANDREJAK
        return dict(
542 0c8b0e15 Francois POIROTTE
            hostname = host,
543
            servicename = service,
544 a2fa6a5b Francois POIROTTE
            plugins_data = plugins_data,
545 539f69fc Francois POIROTTE
            page = page,
546 5a845c93 Vincent QUEMENER
            sort = sort,
547
            order = order,
548 54644278 Francois POIROTTE
            event_edit_status_options = edit_event_status_options,
549 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
550
            search = {},
551 adb0e63f Francois POIROTTE
            fixed_search = {},
552 54644278 Francois POIROTTE
        )
553 19e88cb8 Thomas ANDREJAK
554 e307e626 Francois POIROTTE
555
    class UpdateSchema(schema.Schema):
556 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode update."""
557 e307e626 Francois POIROTTE
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
558
        last_modification = validators.Number(not_empty=True)
559
        trouble_ticket = validators.String(if_missing='')
560 9e0ea30e Francois POIROTTE
        ack = validators.OneOf(
561
            [unicode(s[0]) for s in edit_event_status_options],
562
            not_empty=True)
563 e307e626 Francois POIROTTE
564
    @validate(
565
        validators=UpdateSchema(),
566
        error_handler = process_form_errors)
567 9e0ea30e Francois POIROTTE
    @require(
568
        All(
569
            not_anonymous(msg=l_("You need to be authenticated")),
570 73119f8a Francois POIROTTE
            Any(config.is_manager,
571 a5f99051 Francois POIROTTE
                has_permission('vigiboard-update'),
572 9e0ea30e Francois POIROTTE
                msg=l_("You don't have write access to VigiBoard"))
573
        ))
574 dcd79358 Francois POIROTTE
    @expose()
575 a9a4679d Francois POIROTTE
    def update(self, id, last_modification, trouble_ticket, ack):
576 19e88cb8 Thomas ANDREJAK
        """
577 a2a22ade Francois POIROTTE
        Mise à jour d'un événement suivant les arguments passés.
578 8484b8bd Francois POIROTTE
        Cela peut être un changement de ticket ou un changement de statut.
579 e3c52cfd Aurelien BOMPARD

580 a9a4679d Francois POIROTTE
        @param id: Le ou les identifiants des événements à traiter
581
        @param last_modification: La date de la dernière modification
582 57387640 Francois POIROTTE
            dont l'utilisateur est au courant.
583 a9a4679d Francois POIROTTE
        @param trouble_ticket: Nouveau numéro du ticket associé.
584
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
585 c9245ffc Vincent QUEMENER

586 e3c52cfd Aurelien BOMPARD
        Cette méthode permet de satisfaire les exigences suivantes :
587 e181e86c Francois POIROTTE
            - VIGILO_EXIG_VIGILO_BAC_0020,
588
            - VIGILO_EXIG_VIGILO_BAC_0060,
589
            - VIGILO_EXIG_VIGILO_BAC_0110.
590 19e88cb8 Thomas ANDREJAK
        """
591
592 97f6d842 Vincent QUEMENER
        # On vérifie que des identifiants ont bien été transmis via
593
        # le formulaire, et on informe l'utilisateur le cas échéant.
594 a9a4679d Francois POIROTTE
        if id is None:
595 10848680 Francois POIROTTE
            flash(_('No event has been selected'), 'warning')
596 a9a4679d Francois POIROTTE
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
597 5d20c2c5 Francois POIROTTE
598 2b740fc8 Francois POIROTTE
        # On récupère la liste de tous les identifiants des événements
599
        # à mettre à jour.
600 8b2edebe Aurelien BOMPARD
        ids = [ int(i) for i in id.strip(',').split(',') ]
601 195aa50d Francois POIROTTE
602
        user = get_current_user()
603 303419a6 Francois POIROTTE
        events = VigiboardRequest(user)
604 915f3245 Francois POIROTTE
        events.add_table(
605
            CorrEvent,
606
            Event,
607
            events.items.c.hostname,
608
            events.items.c.servicename,
609
        )
610 911069bc Francois POIROTTE
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
611 0842bb2c Francois POIROTTE
        events.add_join((events.items,
612 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
613 780ca169 Francois POIROTTE
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
614 e3c52cfd Aurelien BOMPARD
615 5edd29ac Vincent QUEMENER
        events.generate_request()
616 915f3245 Francois POIROTTE
        idevents = [event[0].idcause for event in events.req]
617 a9a4679d Francois POIROTTE
618 0842bb2c Francois POIROTTE
        # Si des changements sont survenus depuis que la
619 5edd29ac Vincent QUEMENER
        # page est affichée, on en informe l'utilisateur.
620 a9a4679d Francois POIROTTE
        last_modification = datetime.fromtimestamp(last_modification)
621
        cur_last_modification = get_last_modification_timestamp(idevents, None)
622
        if cur_last_modification and last_modification < cur_last_modification:
623 5edd29ac Vincent QUEMENER
            flash(_('Changes have occurred since the page was last displayed, '
624
                    'your changes HAVE NOT been saved.'), 'warning')
625 a9a4679d Francois POIROTTE
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
626 f744bc14 Francois POIROTTE
627 19e88cb8 Thomas ANDREJAK
        # Vérification que au moins un des identifiants existe et est éditable
628 94f31908 Francois POIROTTE
        if not events.num_rows():
629 19e88cb8 Thomas ANDREJAK
            flash(_('No access to this event'), 'error')
630
            redirect('/')
631 8484b8bd Francois POIROTTE
632 2b740fc8 Francois POIROTTE
        if ack == u'Forced':
633
            condition = Any(
634 73119f8a Francois POIROTTE
                config.is_manager,
635 2b740fc8 Francois POIROTTE
                has_permission('vigiboard-admin'),
636
                msg=l_("You don't have administrative access "
637
                        "to VigiBoard"))
638
            try:
639
                condition.check_authorization(request.environ)
640
            except NotAuthorizedError, e:
641
                reason = unicode(e)
642
                flash(reason, 'error')
643
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
644
645 915f3245 Francois POIROTTE
        # Si un module de gestion de ticket est utilisé,
646
        # il a la possibilité de changer à la volée le libellé du ticket.
647
        if self._tickets:
648
            trouble_ticket = self._tickets.createTicket(events.req, trouble_ticket)
649
650 8ba2de75 Francois POIROTTE
        # Définit 2 mappings dont les ensembles sont disjoincts
651
        # pour basculer entre la représentation en base de données
652
        # et la représentation "humaine" du bac à événements.
653
        ack_mapping = {
654
            # Permet d'associer la valeur dans le widget ToscaWidgets
655
            # (cf. vigiboard.widgets.edit_event.edit_event_status_options)
656
            # avec la valeur dans la base de données.
657
            u'None': CorrEvent.ACK_NONE,
658
            u'Acknowledged': CorrEvent.ACK_KNOWN,
659
            u'AAClosed': CorrEvent.ACK_CLOSED,
660
661
            # Permet d'afficher un libellé plus sympathique pour l'utilisateur
662
            # représentant l'état d'acquittement stocké en base de données.
663
            CorrEvent.ACK_NONE: l_('None'),
664
            CorrEvent.ACK_KNOWN: l_('Acknowledged'),
665
            CorrEvent.ACK_CLOSED: l_('Acknowledged and closed'),
666
        }
667
668 2b740fc8 Francois POIROTTE
        # Modification des événements et création d'un historique
669
        # chaque fois que cela est nécessaire.
670 915f3245 Francois POIROTTE
        for data in events.req:
671
            event = data[0]
672 f744bc14 Francois POIROTTE
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
673 8484b8bd Francois POIROTTE
                history = EventHistory(
674 3f7736d0 Francois POIROTTE
                        type_action=u"Ticket change",
675 8484b8bd Francois POIROTTE
                        idevent=event.idcause,
676 a9a4679d Francois POIROTTE
                        value=unicode(trouble_ticket),
677 2b740fc8 Francois POIROTTE
                        text="Changed trouble ticket from '%(from)s' "
678
                             "to '%(to)s'" % {
679
                            'from': event.trouble_ticket,
680
                            'to': trouble_ticket,
681
                        },
682 e1133e5a Francois POIROTTE
                        username=user.user_name,
683 ee3ae8c8 Francois POIROTTE
                        timestamp=datetime.now(),
684 8484b8bd Francois POIROTTE
                    )
685 0842bb2c Francois POIROTTE
                DBSession.add(history)
686 7f26a756 Francois POIROTTE
                LOGGER.info(_('User "%(user)s" (%(address)s) changed the '
687
                            'trouble ticket from "%(previous)s" to "%(new)s" '
688
                            'on event #%(idevent)d') % {
689
                                'user': request.identity['repoze.who.userid'],
690
                                'address': request.remote_addr,
691
                                'previous': event.trouble_ticket,
692
                                'new': trouble_ticket,
693
                                'idevent': event.idcause,
694
                            })
695 a9a4679d Francois POIROTTE
                event.trouble_ticket = trouble_ticket
696 8484b8bd Francois POIROTTE
697 9e0ea30e Francois POIROTTE
            # Changement du statut d'acquittement.
698
            if ack != u'NoChange':
699 2b740fc8 Francois POIROTTE
                changed_ack = ack
700 9e0ea30e Francois POIROTTE
                # Pour forcer l'acquittement d'un événement,
701
                # il faut en plus avoir la permission
702
                # "vigiboard-admin".
703
                if ack == u'Forced':
704 2b740fc8 Francois POIROTTE
                    changed_ack = u'AAClosed'
705 7e0c4383 Francois POIROTTE
                    cause = event.cause
706 f8020955 Francois POIROTTE
                    # On met systématiquement l'événement à l'état "OK",
707
                    # même s'il s'agit d'un hôte.
708
                    # Techniquement, c'est incorrect, mais on fait ça
709
                    # pour masquer l'événement de toutes façons...
710 7e0c4383 Francois POIROTTE
                    cause.current_state = \
711 2b740fc8 Francois POIROTTE
                        StateName.statename_to_value(u'OK')
712
713 f8020955 Francois POIROTTE
                    # Mise à jour de l'état dans State, pour que
714
                    # VigiMap soit également mis à jour.
715
                    DBSession.query(State).filter(
716 7e0c4383 Francois POIROTTE
                            State.idsupitem == cause.idsupitem,
717 f8020955 Francois POIROTTE
                        ).update({
718
                            'state': StateName.statename_to_value(u'OK'),
719
                        })
720
721 2b740fc8 Francois POIROTTE
                    history = EventHistory(
722 3f7736d0 Francois POIROTTE
                            type_action=u"Forced change state",
723 2b740fc8 Francois POIROTTE
                            idevent=event.idcause,
724
                            value=u'OK',
725
                            text="Forced state to 'OK'",
726
                            username=user.user_name,
727
                            timestamp=datetime.now(),
728 caa4b302 Francois POIROTTE
                            state=StateName.statename_to_value(u'OK'),
729 2b740fc8 Francois POIROTTE
                        )
730
                    DBSession.add(history)
731 7f26a756 Francois POIROTTE
                    LOGGER.info(_('User "%(user)s" (%(address)s) forcefully '
732
                                'closed event #%(idevent)d') % {
733
                                    'user': request. \
734
                                            identity['repoze.who.userid'],
735
                                    'address': request.remote_addr,
736
                                    'idevent': event.idcause,
737
                                })
738 ddbaec88 Francois POIROTTE
739 8ba2de75 Francois POIROTTE
                # Convertit la valeur du widget ToscaWidgets
740
                # vers le code interne puis vers un libellé
741
                # "humain".
742
                ack_label = ack_mapping[ack_mapping[changed_ack]]
743
744
                # Si le changement a été forcé,
745
                # on veut le mettre en évidence.
746
                if ack == u'Forced':
747 3f7736d0 Francois POIROTTE
                    history_label = u'Forced'
748 8ba2de75 Francois POIROTTE
                else:
749
                    history_label = ack_label
750
751 19e88cb8 Thomas ANDREJAK
                history = EventHistory(
752 3f7736d0 Francois POIROTTE
                        type_action=u"Acknowledgement change state",
753 8484b8bd Francois POIROTTE
                        idevent=event.idcause,
754 8ba2de75 Francois POIROTTE
                        value=unicode(history_label),
755 2cf703a5 Francois POIROTTE
                        text=u"Changed acknowledgement status "
756
                            u"from '%s' to '%s'" % (
757 8ba2de75 Francois POIROTTE
                            ack_mapping[event.ack],
758
                            ack_label,
759 ee3ae8c8 Francois POIROTTE
                        ),
760 e1133e5a Francois POIROTTE
                        username=user.user_name,
761 ee3ae8c8 Francois POIROTTE
                        timestamp=datetime.now(),
762 8484b8bd Francois POIROTTE
                    )
763 19e88cb8 Thomas ANDREJAK
                DBSession.add(history)
764 7f26a756 Francois POIROTTE
                LOGGER.info(_('User "%(user)s" (%(address)s) changed the state '
765
                            'from "%(previous)s" to "%(new)s" on event '
766
                            '#%(idevent)d') % {
767
                                'user': request.identity['repoze.who.userid'],
768
                                'address': request.remote_addr,
769 8ba2de75 Francois POIROTTE
                                'previous': _(ack_mapping[event.ack]),
770
                                'new': _(ack_label),
771 7f26a756 Francois POIROTTE
                                'idevent': event.idcause,
772
                            })
773 8ba2de75 Francois POIROTTE
                event.ack = ack_mapping[changed_ack]
774 3d0d254c Francois POIROTTE
775 10848680 Francois POIROTTE
        DBSession.flush()
776 19e88cb8 Thomas ANDREJAK
        flash(_('Updated successfully'))
777 a9a4679d Francois POIROTTE
        redirect(request.environ.get('HTTP_REFERER', '/'))
778 19e88cb8 Thomas ANDREJAK
779 e307e626 Francois POIROTTE
780
    class GetPluginValueSchema(schema.Schema):
781 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode get_plugin_value."""
782 e307e626 Francois POIROTTE
        idcorrevent = validators.Int(not_empty=True)
783 65383903 Francois POIROTTE
        plugin_name = validators.String(not_empty=True)
784 e307e626 Francois POIROTTE
        # Permet de passer des paramètres supplémentaires au plugin.
785
        allow_extra_fields = True
786
787
    @validate(
788
        validators=GetPluginValueSchema(),
789 4c08cd96 Francois POIROTTE
        error_handler = handle_validation_errors_json)
790 8ad24667 Thomas ANDREJAK
    @expose('json')
791 f2e30877 Francois POIROTTE
    @require(access_restriction)
792 cf3c2494 Vincent QUEMENER
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
793 8ad24667 Thomas ANDREJAK
        """
794 4dd2035e Francois POIROTTE
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
795
        donné via JSON.
796 8ad24667 Thomas ANDREJAK
        """
797 6520dbc0 Vincent QUEMENER
798
        # Vérification de l'existence du plugin
799 65383903 Francois POIROTTE
        plugins = dict(config['columns_plugins'])
800
        if plugin_name not in plugins:
801
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
802
803 f8f519ac Vincent QUEMENER
        # Récupération de la liste des évènements corrélés
804
        events = DBSession.query(CorrEvent.idcorrevent)
805
806
        # Filtrage des évènements en fonction des permissions de
807
        # l'utilisateur (s'il n'appartient pas au groupe 'managers')
808 73119f8a Francois POIROTTE
        if not config.is_manager.is_met(request.environ):
809 9f441867 Vincent QUEMENER
            user = get_current_user()
810
811 f8f519ac Vincent QUEMENER
            events = events.join(
812 9f441867 Vincent QUEMENER
                (Event, Event.idevent == CorrEvent.idcause),
813
            ).outerjoin(
814
                (LowLevelService, LowLevelService.idservice == Event.idsupitem),
815
            ).join(
816
                (SUPITEM_GROUP_TABLE,
817
                    or_(
818
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
819
                            LowLevelService.idhost,
820
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
821
                            Event.idsupitem,
822
                    )
823
                ),
824
            ).join(
825 f8f519ac Vincent QUEMENER
                (GroupHierarchy,
826
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
827 9f441867 Vincent QUEMENER
            ).join(
828 f8f519ac Vincent QUEMENER
                (DataPermission,
829
                    DataPermission.idgroup == GroupHierarchy.idparent),
830 9f441867 Vincent QUEMENER
            ).join(
831 f8f519ac Vincent QUEMENER
                (USER_GROUP_TABLE,
832
                    USER_GROUP_TABLE.c.idgroup == DataPermission.idusergroup),
833
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
834
835
        # Filtrage des évènements en fonction
836
        # de l'identifiant passé en paramètre
837
        events = events.filter(CorrEvent.idcorrevent == idcorrevent).count()
838
839
        # Pas d'événement ou permission refusée. On ne distingue pas
840
        # les 2 cas afin d'éviter la divulgation d'informations.
841
        if events == 0:
842
            raise HTTPNotFound(_('No such incident or insufficient '
843
                                'permissions'))
844
845
        # L'évènement existe bien, et l'utilisateur dispose
846
        # des permissions appropriées. On fait alors appel au
847
        # plugin pour récupérer les informations à retourner.
848 cf3c2494 Vincent QUEMENER
        return plugins[plugin_name].get_json_data(idcorrevent, *arg, **krgv)
849 1bb369b9 Francois POIROTTE
850
    @validate(validators={
851
        "fontsize": validators.Regex(
852
            r'[0-9]+(pt|px|em|%)',
853 6ab72614 Vincent QUEMENER
            regexOps = ('I',)
854 4c08cd96 Francois POIROTTE
        )}, error_handler = handle_validation_errors_json)
855 693e96f1 Thomas ANDREJAK
    @expose('json')
856 b8500d1a Thomas ANDREJAK
    def set_fontsize(self, fontsize):
857 4dd2035e Francois POIROTTE
        """Enregistre la taille de la police dans les préférences."""
858 b8500d1a Thomas ANDREJAK
        session['fontsize'] = fontsize
859
        session.save()
860 e2e218d7 Francois POIROTTE
        return dict()
861 693e96f1 Thomas ANDREJAK
862 08d86103 Francois POIROTTE
    @validate(validators={"refresh": validators.Int()},
863 4c08cd96 Francois POIROTTE
            error_handler = handle_validation_errors_json)
864 693e96f1 Thomas ANDREJAK
    @expose('json')
865 b8500d1a Thomas ANDREJAK
    def set_refresh(self, refresh):
866 4dd2035e Francois POIROTTE
        """Enregistre le temps de rafraichissement dans les préférences."""
867 8d822583 Francois POIROTTE
        session['refresh'] = bool(refresh)
868 b8500d1a Thomas ANDREJAK
        session.save()
869 e2e218d7 Francois POIROTTE
        return dict()
870
871
    @expose('json')
872
    def set_theme(self, theme):
873 4dd2035e Francois POIROTTE
        """Enregistre le thème à utiliser dans les préférences."""
874
        # On sauvegarde l'ID du thème sans vérifications
875
        # car les thèmes (styles CSS) sont définies dans
876
        # les packages de thèmes (ex: vigilo-themes-default).
877
        # La vérification de la valeur est faite dans les templates.
878 e2e218d7 Francois POIROTTE
        session['theme'] = theme
879
        session.save()
880
        return dict()
881 2b740fc8 Francois POIROTTE
882 36f6910e Francois POIROTTE
    @validate(validators={"items": validators.Int()},
883
            error_handler = handle_validation_errors_json)
884
    @expose('json')
885
    def set_items_per_page(self, items):
886
        """Enregistre le nombre d'alertes par page dans les préférences."""
887
        session['items_per_page'] = items
888
        session.save()
889
        return dict()
890
891 ea0e5dfb Francois POIROTTE
    @require(access_restriction)
892
    @expose('json')
893 aa6e5fe9 Aurelien BOMPARD
    def get_groups(self, parent_id=None, onlytype="", offset=0, noCache=None):
894 48acee1e Francois POIROTTE
        """
895
        Affiche un étage de l'arbre de
896
        sélection des hôtes et groupes d'hôtes.
897

898
        @param parent_id: identifiant du groupe d'hôte parent
899
        @type  parent_id: C{int} or None
900
        """
901
902
        # Si l'identifiant du groupe parent n'est pas
903 70938860 Vincent QUEMENER
        # spécifié, on retourne la liste des groupes
904
        # racines, fournie par la méthode get_root_groups.
905 48acee1e Francois POIROTTE
        if parent_id is None:
906 70938860 Vincent QUEMENER
            return self.get_root_groups()
907 48acee1e Francois POIROTTE
908 0bd9c069 Francois POIROTTE
        # TODO: Utiliser un schéma de validation
909
        parent_id = int(parent_id)
910 aa6e5fe9 Aurelien BOMPARD
        offset = int(offset)
911 ea0e5dfb Francois POIROTTE
912 cf3c2494 Vincent QUEMENER
        # On récupère la liste des groupes de supitems dont
913
        # l'identifiant du parent est passé en paramètre.
914
        supitem_groups = DBSession.query(
915
                SupItemGroup.idgroup,
916
                SupItemGroup.name,
917
            ).join(
918
                (GroupHierarchy,
919
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
920
            ).filter(GroupHierarchy.idparent == parent_id
921 837cb99f Vincent QUEMENER
            ).filter(GroupHierarchy.hops == 1
922
            ).order_by(SupItemGroup.name)
923 cf3c2494 Vincent QUEMENER
924
        # Si l'utilisateur n'appartient pas au groupe 'managers',
925
        # on filtre les résultats en fonction de ses permissions.
926 73119f8a Francois POIROTTE
        if not config.is_manager.is_met(request.environ):
927 0bd9c069 Francois POIROTTE
            user = get_current_user()
928 eec46cb0 Vincent QUEMENER
            GroupHierarchy_aliased = aliased(GroupHierarchy,
929
                name='GroupHierarchy_aliased')
930 70938860 Vincent QUEMENER
            supitem_groups = supitem_groups.join(
931 cf3c2494 Vincent QUEMENER
                (GroupHierarchy_aliased,
932 70938860 Vincent QUEMENER
                    or_(
933
                        GroupHierarchy_aliased.idchild == SupItemGroup.idgroup,
934
                        GroupHierarchy_aliased.idparent == SupItemGroup.idgroup
935
                    )),
936 cf3c2494 Vincent QUEMENER
                (DataPermission,
937 70938860 Vincent QUEMENER
                    or_(
938
                        DataPermission.idgroup == \
939
                            GroupHierarchy_aliased.idparent,
940
                        DataPermission.idgroup == \
941
                            GroupHierarchy_aliased.idchild,
942
                    )),
943 cf3c2494 Vincent QUEMENER
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
944
                    DataPermission.idusergroup),
945
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
946 eec46cb0 Vincent QUEMENER
947 aa6e5fe9 Aurelien BOMPARD
        limit = int(config.get("max_menu_entries", 20))
948
        result = {"groups": [], "items": []}
949
        num_children_left = supitem_groups.distinct().count() - offset
950
        if offset:
951
            result["continued_from"] = offset
952
            result["continued_type"] = "group"
953
        all_grs = supitem_groups.distinct().limit(limit).offset(offset).all()
954
        for group in all_grs:
955
            result["groups"].append({
956 48acee1e Francois POIROTTE
                'id'   : group.idgroup,
957
                'name' : group.name,
958 aa6e5fe9 Aurelien BOMPARD
                'type' : "group",
959
            })
960
        if num_children_left > limit:
961
            result["groups"].append({
962
                'name': _("Next %(limit)s") % {"limit": limit},
963
                'offset': offset + limit,
964
                'parent_id': parent_id,
965
                'type': 'continued',
966
                'for_type': 'group',
967 48acee1e Francois POIROTTE
            })
968
969 aa6e5fe9 Aurelien BOMPARD
        return result
970 48acee1e Francois POIROTTE
971 70938860 Vincent QUEMENER
    def get_root_groups(self):
972 48acee1e Francois POIROTTE
        """
973
        Retourne tous les groupes racines (c'est à dire n'ayant
974
        aucun parent) d'hôtes auquel l'utilisateur a accès.
975

976
        @return: Un dictionnaire contenant la liste de ces groupes.
977
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
978
        """
979 ea0e5dfb Francois POIROTTE
980 0bd9c069 Francois POIROTTE
        # On récupère tous les groupes qui ont un parent.
981
        children = DBSession.query(
982
            SupItemGroup,
983
        ).distinct(
984
        ).join(
985
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
986
        ).filter(GroupHierarchy.hops > 0)
987
988
        # Ensuite on les exclut de la liste des groupes,
989
        # pour ne garder que ceux qui sont au sommet de
990
        # l'arbre et qui constituent nos "root groups".
991
        root_groups = DBSession.query(
992
            SupItemGroup,
993
        ).except_(children
994
        ).order_by(SupItemGroup.name)
995 48acee1e Francois POIROTTE
996
        # On filtre ces groupes racines afin de ne
997
        # retourner que ceux auquels l'utilisateur a accès
998
        user = get_current_user()
999 73119f8a Francois POIROTTE
        if not config.is_manager.is_met(request.environ):
1000 eec46cb0 Vincent QUEMENER
            root_groups = root_groups.join(
1001
                (GroupHierarchy,
1002 70938860 Vincent QUEMENER
                    GroupHierarchy.idparent == SupItemGroup.idgroup),
1003 eec46cb0 Vincent QUEMENER
                (DataPermission,
1004 70938860 Vincent QUEMENER
                    DataPermission.idgroup == GroupHierarchy.idchild),
1005 eec46cb0 Vincent QUEMENER
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
1006
                    DataPermission.idusergroup),
1007
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
1008 48acee1e Francois POIROTTE
1009
        groups = []
1010 0bd9c069 Francois POIROTTE
        for group in root_groups.all():
1011 48acee1e Francois POIROTTE
            groups.append({
1012
                'id'   : group.idgroup,
1013
                'name' : group.name,
1014 aa6e5fe9 Aurelien BOMPARD
                'type' : "group",
1015 48acee1e Francois POIROTTE
            })
1016
1017 aa6e5fe9 Aurelien BOMPARD
        return dict(groups=groups, items=[])
1018 48acee1e Francois POIROTTE
1019 0842bb2c Francois POIROTTE
def get_last_modification_timestamp(event_id_list,
1020 4dd2035e Francois POIROTTE
                                    value_if_none=datetime.now()):
1021 97f6d842 Vincent QUEMENER
    """
1022 0842bb2c Francois POIROTTE
    Récupère le timestamp de la dernière modification
1023 97f6d842 Vincent QUEMENER
    opérée sur l'un des événements dont l'identifiant
1024
    fait partie de la liste passée en paramètre.
1025
    """
1026 5a845c93 Vincent QUEMENER
    if not event_id_list:
1027
        last_modification_timestamp = None
1028
    else:
1029
        last_modification_timestamp = DBSession.query(
1030 97f6d842 Vincent QUEMENER
                                func.max(EventHistory.timestamp),
1031
                         ).filter(EventHistory.idevent.in_(event_id_list)
1032
                         ).scalar()
1033 5a845c93 Vincent QUEMENER
1034 e9ccb711 Francois POIROTTE
    if not last_modification_timestamp:
1035 24334b4b Vincent QUEMENER
        if not value_if_none:
1036
            return None
1037
        else:
1038
            last_modification_timestamp = value_if_none
1039 e9ccb711 Francois POIROTTE
    return datetime.fromtimestamp(mktime(
1040
        last_modification_timestamp.timetuple()))