Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 011743be

History | View | Annotate | Download (39.4 KB)

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

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

161 e3c52cfd Aurelien BOMPARD
        Cette méthode permet de satisfaire les exigences suivantes :
162
            - VIGILO_EXIG_VIGILO_BAC_0040,
163 e181e86c Francois POIROTTE
            - VIGILO_EXIG_VIGILO_BAC_0070,
164
            - VIGILO_EXIG_VIGILO_BAC_0100,
165 19e88cb8 Thomas ANDREJAK
        """
166 f1886725 Vincent QUEMENER
167
        # Auto-supervision
168
        self.get_failures()
169
170 195aa50d Francois POIROTTE
        user = get_current_user()
171 5a845c93 Vincent QUEMENER
        aggregates = VigiboardRequest(user, search=search, sort=sort, order=order)
172 cf3c2494 Vincent QUEMENER
173 911069bc Francois POIROTTE
        aggregates.add_table(
174
            CorrEvent,
175 b2668166 Francois POIROTTE
            aggregates.items.c.address,
176 911069bc Francois POIROTTE
            aggregates.items.c.hostname,
177
            aggregates.items.c.servicename
178
        )
179
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
180 cf3c2494 Vincent QUEMENER
        aggregates.add_contains_eager(CorrEvent.cause)
181
        aggregates.add_group_by(Event)
182 e3c52cfd Aurelien BOMPARD
        aggregates.add_join((aggregates.items,
183 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == aggregates.items.c.idsupitem))
184 5d20c2c5 Francois POIROTTE
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
185 e3c52cfd Aurelien BOMPARD
186 adb0e63f Francois POIROTTE
        # On ne garde que les champs effectivement renseignés.
187
        for column in search.copy():
188
            if not search[column]:
189
                del search[column]
190
191
        # On sérialise les champs de type dict.
192
        def serialize_dict(dct, key):
193
            if isinstance(dct[key], dict):
194
                for subkey in dct[key]:
195
                    serialize_dict(dct[key], subkey)
196 00ece25a Francois POIROTTE
                    dct['%s.%s' % (key, subkey)] = dct[key][subkey]
197 adb0e63f Francois POIROTTE
                del dct[key]
198 00ece25a Francois POIROTTE
            elif isinstance(dct[key], datetime):
199
                dct[key] = dct[key].strftime(dateformat.get_date_format())
200 adb0e63f Francois POIROTTE
        fixed_search = search.copy()
201
        for column in fixed_search.copy():
202
            serialize_dict(fixed_search, column)
203
204 4b573169 Francois POIROTTE
        # Pagination des résultats
205
        aggregates.generate_request()
206 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
207 27140946 Francois POIROTTE
        page = paginate.Page(aggregates.req, page=page,
208
            items_per_page=items_per_page)
209 3d0d254c Francois POIROTTE
210 cf3c2494 Vincent QUEMENER
        # Récupération des données des plugins
211
        plugins_data = {}
212
        plugins = dict(config['columns_plugins'])
213 4b573169 Francois POIROTTE
214
        ids_events = [event[0].idcause for event in page.items]
215
        ids_correvents = [event[0].idcorrevent for event in page.items]
216 cf3c2494 Vincent QUEMENER
        for plugin in plugins:
217 4b573169 Francois POIROTTE
            plugin_data = plugins[plugin].get_bulk_data(ids_correvents)
218
            if plugin_data:
219 cf3c2494 Vincent QUEMENER
                plugins_data[plugin] = plugin_data
220 a2fa6a5b Francois POIROTTE
            else:
221
                plugins_data[plugin] = {}
222 cf3c2494 Vincent QUEMENER
223 4b573169 Francois POIROTTE
        # Ajout des formulaires et préparation
224
        # des données pour ces formulaires.
225 1c5486c7 Francois POIROTTE
        tmpl_context.last_modification = calendar.timegm(
226
            get_last_modification_timestamp(ids_events).timetuple())
227 4b573169 Francois POIROTTE
228
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
229
            submit_text=_('Apply'), action=url('/update'))
230
231 a2fa6a5b Francois POIROTTE
        if request.response_type == 'text/csv':
232
            # Sans les 2 en-têtes suivants qui désactivent la mise en cache,
233
            # Internet Explorer refuse de télécharger le fichier CSV (cf. #961).
234
            response.headers['Pragma'] = 'public'           # Nécessaire pour IE.
235
            response.headers['Cache-Control'] = 'max-age=0' # Nécessaire pour IE.
236
237 377a9c23 Francois POIROTTE
            response.headers["Content-Type"] = "text/csv"
238 a2fa6a5b Francois POIROTTE
            response.headers['Content-Disposition'] = \
239
                            'attachment;filename="alerts.csv"'
240
            return export_csv.export(page, plugins_data)
241
242 19e88cb8 Thomas ANDREJAK
        return dict(
243 73f3220e Vincent QUEMENER
            hostname = None,
244
            servicename = None,
245 cf3c2494 Vincent QUEMENER
            plugins_data = plugins_data,
246 1101e03e Francois POIROTTE
            page = page,
247 5a845c93 Vincent QUEMENER
            sort = sort,
248
            order = order,
249 1101e03e Francois POIROTTE
            event_edit_status_options = edit_event_status_options,
250 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
251 1101e03e Francois POIROTTE
            search = search,
252 adb0e63f Francois POIROTTE
            fixed_search = fixed_search,
253 1101e03e Francois POIROTTE
        )
254 19e88cb8 Thomas ANDREJAK
255 e307e626 Francois POIROTTE
256
    class MaskedEventsSchema(schema.Schema):
257 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode masked_events."""
258 e307e626 Francois POIROTTE
        idcorrevent = validators.Int(not_empty=True)
259
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
260
261
    @validate(
262
        validators=MaskedEventsSchema(),
263
        error_handler = process_form_errors)
264 00d2e1d1 Vincent QUEMENER
    @expose('raw_events_table.html')
265 f2e30877 Francois POIROTTE
    @require(access_restriction)
266 02c4a1e7 Francois POIROTTE
    def masked_events(self, idcorrevent, page=1):
267 54644278 Francois POIROTTE
        """
268 e181e86c Francois POIROTTE
        Affichage de la liste des événements bruts masqués d'un événement
269
        corrélé (événements agrégés dans l'événement corrélé).
270 54644278 Francois POIROTTE

271 5a845c93 Vincent QUEMENER
        @param page: numéro de la page à afficher.
272
        @type  page: C{int}
273 e181e86c Francois POIROTTE
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
274 5a845c93 Vincent QUEMENER
        @type  idcorrevent: C{int}
275 54644278 Francois POIROTTE
        """
276 f1886725 Vincent QUEMENER
277
        # Auto-supervision
278
        self.get_failures()
279
280 195aa50d Francois POIROTTE
        user = get_current_user()
281 a05b9a37 Francois POIROTTE
282
        # Récupère la liste des événements masqués de l'événement
283
        # corrélé donné par idcorrevent.
284 195aa50d Francois POIROTTE
        events = VigiboardRequest(user, False)
285 54644278 Francois POIROTTE
        events.add_table(
286
            Event,
287
            events.items.c.hostname,
288
            events.items.c.servicename,
289
        )
290 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
291
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
292
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
293
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
294 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
295 54644278 Francois POIROTTE
            Event.idsupitem == events.items.c.idsupitem))
296
        events.add_filter(Event.idevent != CorrEvent.idcause)
297
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
298 bcf87133 Francois POIROTTE
299 a05b9a37 Francois POIROTTE
        # Récupère l'instance de SupItem associé à la cause de
300
        # l'événement corrélé. Cette instance est utilisé pour
301
        # obtenir le nom d'hôte/service auquel la cause est
302
        # rattachée (afin de fournir un contexte à l'utilisateur).
303
        hostname = None
304
        servicename = None
305
        cause_supitem = DBSession.query(
306
                SupItem,
307
            ).join(
308
                (Event, Event.idsupitem == SupItem.idsupitem),
309 cf3c2494 Vincent QUEMENER
                (CorrEvent, Event.idevent == CorrEvent.idcause),
310 a05b9a37 Francois POIROTTE
            ).filter(CorrEvent.idcorrevent == idcorrevent
311
            ).one()
312
313
        if isinstance(cause_supitem, LowLevelService):
314
            hostname = cause_supitem.host.name
315
            servicename = cause_supitem.servicename
316
        elif isinstance(cause_supitem, Host):
317
            hostname = cause_supitem.name
318
319 4b573169 Francois POIROTTE
        # Pagination des résultats
320
        events.generate_request()
321 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
322 27140946 Francois POIROTTE
        page = paginate.Page(events.req, page=page,
323
            items_per_page=items_per_page)
324 4b573169 Francois POIROTTE
325 54644278 Francois POIROTTE
        # Vérification que l'événement existe
326 4b573169 Francois POIROTTE
        if not page.item_count:
327 54644278 Francois POIROTTE
            flash(_('No masked event or access denied'), 'error')
328
            redirect('/')
329
330
        return dict(
331 0c8b0e15 Francois POIROTTE
            idcorrevent = idcorrevent,
332 a05b9a37 Francois POIROTTE
            hostname = hostname,
333
            servicename = servicename,
334 cf3c2494 Vincent QUEMENER
            plugins_data = {},
335 54644278 Francois POIROTTE
            page = page,
336 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
337
            search = {},
338 adb0e63f Francois POIROTTE
            fixed_search = {},
339 54644278 Francois POIROTTE
        )
340
341 e307e626 Francois POIROTTE
342
    class EventSchema(schema.Schema):
343 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode event."""
344 e307e626 Francois POIROTTE
        idevent = validators.Int(not_empty=True)
345
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
346
347
    @validate(
348
        validators=EventSchema(),
349
        error_handler = process_form_errors)
350 00d2e1d1 Vincent QUEMENER
    @expose('history_table.html')
351 f2e30877 Francois POIROTTE
    @require(access_restriction)
352 02c4a1e7 Francois POIROTTE
    def event(self, idevent, page=1):
353 19e88cb8 Thomas ANDREJAK
        """
354 94f31908 Francois POIROTTE
        Affichage de l'historique d'un événement brut.
355 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
356

357 94f31908 Francois POIROTTE
        @param idevent: identifiant de l'événement brut souhaité.
358
        @type idevent: C{int}
359 e181e86c Francois POIROTTE
        @param page: numéro de la page à afficher.
360
        @type page: C{int}
361 c9245ffc Vincent QUEMENER

362 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
363
        VIGILO_EXIG_VIGILO_BAC_0080.
364 19e88cb8 Thomas ANDREJAK
        """
365 f1886725 Vincent QUEMENER
366
        # Auto-supervision
367
        self.get_failures()
368
369 195aa50d Francois POIROTTE
        user = get_current_user()
370
        events = VigiboardRequest(user, False)
371 911069bc Francois POIROTTE
        events.add_table(
372 539f69fc Francois POIROTTE
            Event,
373 72ec8dbf Francois POIROTTE
            events.items.c.hostname.label('hostname'),
374
            events.items.c.servicename.label('servicename'),
375 911069bc Francois POIROTTE
        )
376 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
377
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
378
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
379
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
380 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
381 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
382 94f31908 Francois POIROTTE
        events.add_filter(Event.idevent == idevent)
383 539f69fc Francois POIROTTE
384
        if events.num_rows() != 1:
385
            flash(_('No such event or access denied'), 'error')
386 19e88cb8 Thomas ANDREJAK
            redirect('/')
387 539f69fc Francois POIROTTE
388 19e88cb8 Thomas ANDREJAK
        events.format_events(0, 1)
389 539f69fc Francois POIROTTE
        events.generate_tmpl_context()
390
        history = events.format_history()
391
392 4b573169 Francois POIROTTE
        # Pagination des résultats
393 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
394 4b573169 Francois POIROTTE
        page = paginate.Page(history, page=page, items_per_page=items_per_page)
395 72ec8dbf Francois POIROTTE
        event = events.req[0]
396
397 19e88cb8 Thomas ANDREJAK
        return dict(
398 0c8b0e15 Francois POIROTTE
            idevent = idevent,
399 72ec8dbf Francois POIROTTE
            hostname = event.hostname,
400
            servicename = event.servicename,
401 cf3c2494 Vincent QUEMENER
            plugins_data = {},
402 539f69fc Francois POIROTTE
            page = page,
403 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
404
            search = {},
405 adb0e63f Francois POIROTTE
            fixed_search = {},
406 911069bc Francois POIROTTE
        )
407 19e88cb8 Thomas ANDREJAK
408 e307e626 Francois POIROTTE
409
    class ItemSchema(schema.Schema):
410 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode item."""
411 5a845c93 Vincent QUEMENER
        # Si on ne passe pas le paramètre "page" ou qu'on passe une valeur
412
        # invalide ou pas de valeur du tout, alors on affiche la 1ère page.
413 e307e626 Francois POIROTTE
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
414 5a845c93 Vincent QUEMENER
415
        # Paramètres de tri
416
        sort = validators.String(if_missing=None)
417
        order = validators.OneOf(['asc', 'desc'], if_missing='asc')
418
419
        # L'hôte / service dont on doit afficher les évènements
420 e307e626 Francois POIROTTE
        host = validators.String(not_empty=True)
421
        service = validators.String(if_missing=None)
422
423 24334b4b Vincent QUEMENER
    @validate(
424 e307e626 Francois POIROTTE
        validators=ItemSchema(),
425 6ab72614 Vincent QUEMENER
        error_handler = process_form_errors)
426 eab949e2 Vincent QUEMENER
    @expose('events_table.html')
427 f2e30877 Francois POIROTTE
    @require(access_restriction)
428 02c4a1e7 Francois POIROTTE
    def item(self, page=1, host=None, service=None, sort=None, order=None):
429 19e88cb8 Thomas ANDREJAK
        """
430 539f69fc Francois POIROTTE
        Affichage de l'historique de l'ensemble des événements corrélés
431
        jamais ouverts sur l'hôte / service demandé.
432 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
433

434 e181e86c Francois POIROTTE
        @param page: Numéro de la page à afficher.
435 5a845c93 Vincent QUEMENER
        @type: C{int}
436 19e88cb8 Thomas ANDREJAK
        @param host: Nom de l'hôte souhaité.
437 5a845c93 Vincent QUEMENER
        @type: C{str}
438 19e88cb8 Thomas ANDREJAK
        @param service: Nom du service souhaité
439 5a845c93 Vincent QUEMENER
        @type: C{str}
440
        @param sort: Colonne de tri
441
        @type: C{str} or C{None}
442
        @param order: Ordre du tri (asc ou desc)
443
        @type: C{str} or C{None}
444 c9245ffc Vincent QUEMENER

445 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
446
        VIGILO_EXIG_VIGILO_BAC_0080.
447 19e88cb8 Thomas ANDREJAK
        """
448 f1886725 Vincent QUEMENER
449
        # Auto-supervision
450
        self.get_failures()
451
452 24334b4b Vincent QUEMENER
        idsupitem = SupItem.get_supitem(host, service)
453 1a1e8c17 Francois POIROTTE
        if not idsupitem:
454
            flash(_('No such host/service'), 'error')
455
            redirect('/')
456 24334b4b Vincent QUEMENER
457 195aa50d Francois POIROTTE
        user = get_current_user()
458 5a845c93 Vincent QUEMENER
        aggregates = VigiboardRequest(user, False, sort=sort, order=order)
459 539f69fc Francois POIROTTE
        aggregates.add_table(
460 911069bc Francois POIROTTE
            CorrEvent,
461 539f69fc Francois POIROTTE
            aggregates.items.c.hostname,
462
            aggregates.items.c.servicename,
463 911069bc Francois POIROTTE
        )
464 539f69fc Francois POIROTTE
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
465 0842bb2c Francois POIROTTE
        aggregates.add_join((aggregates.items,
466 539f69fc Francois POIROTTE
            Event.idsupitem == aggregates.items.c.idsupitem))
467
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
468 baedcd0f Francois POIROTTE
469 4b573169 Francois POIROTTE
        # Pagination des résultats
470
        aggregates.generate_request()
471 36f6910e Francois POIROTTE
        items_per_page = int(session.get('items_per_page', config['vigiboard_items_per_page']))
472 27140946 Francois POIROTTE
        page = paginate.Page(aggregates.req, page=page,
473
            items_per_page=items_per_page)
474 4b573169 Francois POIROTTE
475 a2a22ade Francois POIROTTE
        # Vérification qu'il y a au moins 1 événement qui correspond
476 4b573169 Francois POIROTTE
        if not page.item_count:
477 911069bc Francois POIROTTE
            flash(_('No access to this host/service or no event yet'), 'error')
478 19e88cb8 Thomas ANDREJAK
            redirect('/')
479 ee3ae8c8 Francois POIROTTE
480 4b573169 Francois POIROTTE
        # Ajout des formulaires et préparation
481
        # des données pour ces formulaires.
482
        ids_events = [event[0].idcause for event in page.items]
483 1c5486c7 Francois POIROTTE
        tmpl_context.last_modification = calendar.timegm(
484
            get_last_modification_timestamp(ids_events).timetuple())
485 539f69fc Francois POIROTTE
486 4b573169 Francois POIROTTE
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
487
            submit_text=_('Apply'), action=url('/update'))
488 e3c52cfd Aurelien BOMPARD
489 a2fa6a5b Francois POIROTTE
        plugins_data = {}
490
        for plugin in dict(config['columns_plugins']):
491
            plugins_data[plugin] = {}
492
493 19e88cb8 Thomas ANDREJAK
        return dict(
494 0c8b0e15 Francois POIROTTE
            hostname = host,
495
            servicename = service,
496 a2fa6a5b Francois POIROTTE
            plugins_data = plugins_data,
497 539f69fc Francois POIROTTE
            page = page,
498 5a845c93 Vincent QUEMENER
            sort = sort,
499
            order = order,
500 54644278 Francois POIROTTE
            event_edit_status_options = edit_event_status_options,
501 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
502
            search = {},
503 adb0e63f Francois POIROTTE
            fixed_search = {},
504 54644278 Francois POIROTTE
        )
505 19e88cb8 Thomas ANDREJAK
506 e307e626 Francois POIROTTE
507
    class UpdateSchema(schema.Schema):
508 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode update."""
509 e307e626 Francois POIROTTE
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
510
        last_modification = validators.Number(not_empty=True)
511
        trouble_ticket = validators.String(if_missing='')
512 9e0ea30e Francois POIROTTE
        ack = validators.OneOf(
513
            [unicode(s[0]) for s in edit_event_status_options],
514
            not_empty=True)
515 e307e626 Francois POIROTTE
516
    @validate(
517
        validators=UpdateSchema(),
518
        error_handler = process_form_errors)
519 9e0ea30e Francois POIROTTE
    @require(
520
        All(
521
            not_anonymous(msg=l_("You need to be authenticated")),
522 73119f8a Francois POIROTTE
            Any(config.is_manager,
523 a5f99051 Francois POIROTTE
                has_permission('vigiboard-update'),
524 9e0ea30e Francois POIROTTE
                msg=l_("You don't have write access to VigiBoard"))
525
        ))
526 dcd79358 Francois POIROTTE
    @expose()
527 a9a4679d Francois POIROTTE
    def update(self, id, last_modification, trouble_ticket, ack):
528 19e88cb8 Thomas ANDREJAK
        """
529 a2a22ade Francois POIROTTE
        Mise à jour d'un événement suivant les arguments passés.
530 8484b8bd Francois POIROTTE
        Cela peut être un changement de ticket ou un changement de statut.
531 e3c52cfd Aurelien BOMPARD

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

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

850
        @param parent_id: identifiant du groupe d'hôte parent
851
        @type  parent_id: C{int} or None
852
        """
853
854
        # Si l'identifiant du groupe parent n'est pas
855 70938860 Vincent QUEMENER
        # spécifié, on retourne la liste des groupes
856
        # racines, fournie par la méthode get_root_groups.
857 48acee1e Francois POIROTTE
        if parent_id is None:
858 70938860 Vincent QUEMENER
            return self.get_root_groups()
859 48acee1e Francois POIROTTE
860 0bd9c069 Francois POIROTTE
        # TODO: Utiliser un schéma de validation
861
        parent_id = int(parent_id)
862 aa6e5fe9 Aurelien BOMPARD
        offset = int(offset)
863 ea0e5dfb Francois POIROTTE
864 cf3c2494 Vincent QUEMENER
        # On récupère la liste des groupes de supitems dont
865
        # l'identifiant du parent est passé en paramètre.
866
        supitem_groups = DBSession.query(
867
                SupItemGroup.idgroup,
868
                SupItemGroup.name,
869
            ).join(
870
                (GroupHierarchy,
871
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
872
            ).filter(GroupHierarchy.idparent == parent_id
873 837cb99f Vincent QUEMENER
            ).filter(GroupHierarchy.hops == 1
874
            ).order_by(SupItemGroup.name)
875 cf3c2494 Vincent QUEMENER
876
        # Si l'utilisateur n'appartient pas au groupe 'managers',
877
        # on filtre les résultats en fonction de ses permissions.
878 73119f8a Francois POIROTTE
        if not config.is_manager.is_met(request.environ):
879 0bd9c069 Francois POIROTTE
            user = get_current_user()
880 eec46cb0 Vincent QUEMENER
            GroupHierarchy_aliased = aliased(GroupHierarchy,
881
                name='GroupHierarchy_aliased')
882 70938860 Vincent QUEMENER
            supitem_groups = supitem_groups.join(
883 cf3c2494 Vincent QUEMENER
                (GroupHierarchy_aliased,
884 70938860 Vincent QUEMENER
                    or_(
885
                        GroupHierarchy_aliased.idchild == SupItemGroup.idgroup,
886
                        GroupHierarchy_aliased.idparent == SupItemGroup.idgroup
887
                    )),
888 cf3c2494 Vincent QUEMENER
                (DataPermission,
889 70938860 Vincent QUEMENER
                    or_(
890
                        DataPermission.idgroup == \
891
                            GroupHierarchy_aliased.idparent,
892
                        DataPermission.idgroup == \
893
                            GroupHierarchy_aliased.idchild,
894
                    )),
895 cf3c2494 Vincent QUEMENER
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
896
                    DataPermission.idusergroup),
897
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
898 eec46cb0 Vincent QUEMENER
899 aa6e5fe9 Aurelien BOMPARD
        limit = int(config.get("max_menu_entries", 20))
900
        result = {"groups": [], "items": []}
901
        num_children_left = supitem_groups.distinct().count() - offset
902
        if offset:
903
            result["continued_from"] = offset
904
            result["continued_type"] = "group"
905
        all_grs = supitem_groups.distinct().limit(limit).offset(offset).all()
906
        for group in all_grs:
907
            result["groups"].append({
908 48acee1e Francois POIROTTE
                'id'   : group.idgroup,
909
                'name' : group.name,
910 aa6e5fe9 Aurelien BOMPARD
                'type' : "group",
911
            })
912
        if num_children_left > limit:
913
            result["groups"].append({
914
                'name': _("Next %(limit)s") % {"limit": limit},
915
                'offset': offset + limit,
916
                'parent_id': parent_id,
917
                'type': 'continued',
918
                'for_type': 'group',
919 48acee1e Francois POIROTTE
            })
920
921 aa6e5fe9 Aurelien BOMPARD
        return result
922 48acee1e Francois POIROTTE
923 70938860 Vincent QUEMENER
    def get_root_groups(self):
924 48acee1e Francois POIROTTE
        """
925
        Retourne tous les groupes racines (c'est à dire n'ayant
926
        aucun parent) d'hôtes auquel l'utilisateur a accès.
927

928
        @return: Un dictionnaire contenant la liste de ces groupes.
929
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
930
        """
931 ea0e5dfb Francois POIROTTE
932 0bd9c069 Francois POIROTTE
        # On récupère tous les groupes qui ont un parent.
933
        children = DBSession.query(
934
            SupItemGroup,
935
        ).distinct(
936
        ).join(
937
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
938
        ).filter(GroupHierarchy.hops > 0)
939
940
        # Ensuite on les exclut de la liste des groupes,
941
        # pour ne garder que ceux qui sont au sommet de
942
        # l'arbre et qui constituent nos "root groups".
943
        root_groups = DBSession.query(
944
            SupItemGroup,
945
        ).except_(children
946
        ).order_by(SupItemGroup.name)
947 48acee1e Francois POIROTTE
948
        # On filtre ces groupes racines afin de ne
949
        # retourner que ceux auquels l'utilisateur a accès
950
        user = get_current_user()
951 73119f8a Francois POIROTTE
        if not config.is_manager.is_met(request.environ):
952 eec46cb0 Vincent QUEMENER
            root_groups = root_groups.join(
953
                (GroupHierarchy,
954 70938860 Vincent QUEMENER
                    GroupHierarchy.idparent == SupItemGroup.idgroup),
955 eec46cb0 Vincent QUEMENER
                (DataPermission,
956 70938860 Vincent QUEMENER
                    DataPermission.idgroup == GroupHierarchy.idchild),
957 eec46cb0 Vincent QUEMENER
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
958
                    DataPermission.idusergroup),
959
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
960 48acee1e Francois POIROTTE
961
        groups = []
962 0bd9c069 Francois POIROTTE
        for group in root_groups.all():
963 48acee1e Francois POIROTTE
            groups.append({
964
                'id'   : group.idgroup,
965
                'name' : group.name,
966 aa6e5fe9 Aurelien BOMPARD
                'type' : "group",
967 48acee1e Francois POIROTTE
            })
968
969 aa6e5fe9 Aurelien BOMPARD
        return dict(groups=groups, items=[])
970 48acee1e Francois POIROTTE
971 0842bb2c Francois POIROTTE
def get_last_modification_timestamp(event_id_list,
972 c1f83918 Francois POIROTTE
                                    value_if_none=datetime.utcnow):
973 97f6d842 Vincent QUEMENER
    """
974 0842bb2c Francois POIROTTE
    Récupère le timestamp de la dernière modification
975 97f6d842 Vincent QUEMENER
    opérée sur l'un des événements dont l'identifiant
976
    fait partie de la liste passée en paramètre.
977
    """
978 5a845c93 Vincent QUEMENER
    if not event_id_list:
979
        last_modification_timestamp = None
980
    else:
981
        last_modification_timestamp = DBSession.query(
982 97f6d842 Vincent QUEMENER
                                func.max(EventHistory.timestamp),
983
                         ).filter(EventHistory.idevent.in_(event_id_list)
984
                         ).scalar()
985 5a845c93 Vincent QUEMENER
986 e9ccb711 Francois POIROTTE
    if not last_modification_timestamp:
987 c1f83918 Francois POIROTTE
        if not callable(value_if_none):
988
            return value_if_none
989 24334b4b Vincent QUEMENER
        else:
990 c1f83918 Francois POIROTTE
            last_modification_timestamp = value_if_none()
991
    return last_modification_timestamp