Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ cf3c2494

History | View | Annotate | Download (34 KB)

1 19e88cb8 Thomas ANDREJAK
# -*- coding: utf-8 -*-
2 e3c52cfd Aurelien BOMPARD
# vim:set expandtab tabstop=4 shiftwidth=4:
3 a77de887 Francois POIROTTE
################################################################################
4
#
5 3b537383 Francois POIROTTE
# Copyright (C) 2007-2011 CS-SI
6 a77de887 Francois POIROTTE
#
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License version 2 as
9
# published by the Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
################################################################################
20
21 e181e86c Francois POIROTTE
"""VigiBoard Controller"""
22 19e88cb8 Thomas ANDREJAK
23 7365fb51 Francois POIROTTE
from datetime import datetime
24
from time import mktime
25
import math
26
27 55759768 Aurelien BOMPARD
from tg.exceptions import HTTPNotFound, HTTPInternalServerError
28 aa0788a2 Francois POIROTTE
from tg import expose, validate, require, flash, \
29 df25ac35 Francois POIROTTE
    tmpl_context, request, config, session, redirect
30 3e6ee4db Francois POIROTTE
from tw.forms import validators
31 88cac5bb Francois POIROTTE
from pylons.i18n import ugettext as _, lazy_ugettext as l_
32 6ab72614 Vincent QUEMENER
from sqlalchemy import asc
33 97f6d842 Vincent QUEMENER
from sqlalchemy.sql import func
34 eec46cb0 Vincent QUEMENER
from sqlalchemy.orm import aliased
35 cf3c2494 Vincent QUEMENER
from sqlalchemy.orm import contains_eager
36 9e0ea30e Francois POIROTTE
from repoze.what.predicates import Any, All, in_group, \
37 55759768 Aurelien BOMPARD
                                    has_permission, not_anonymous, \
38
                                    NotAuthorizedError
39 1a1e8c17 Francois POIROTTE
from formencode import schema
40 65383903 Francois POIROTTE
from pkg_resources import working_set
41 ee3ae8c8 Francois POIROTTE
42 e7e3d45e Francois POIROTTE
from vigilo.models.session import DBSession
43 a05b9a37 Francois POIROTTE
from vigilo.models.tables import Event, EventHistory, CorrEvent, Host, \
44 9e0ea30e Francois POIROTTE
                                    SupItem, SupItemGroup, LowLevelService, \
45 eec46cb0 Vincent QUEMENER
                                    StateName, State, DataPermission
46 0bd9c069 Francois POIROTTE
from vigilo.models.tables.grouphierarchy import GroupHierarchy
47 080abea3 Francois POIROTTE
from vigilo.models.functions import sql_escape_like
48 eec46cb0 Vincent QUEMENER
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE, \
49
        USER_GROUP_TABLE
50 7365fb51 Francois POIROTTE
51 e307e626 Francois POIROTTE
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
52 ea0e5dfb Francois POIROTTE
from vigilo.turbogears.controllers.proxy import ProxyController
53 98a40b9f Aurelien BOMPARD
from vigilo.turbogears.controllers.api.root import ApiRootController
54 195aa50d Francois POIROTTE
from vigilo.turbogears.helpers import get_current_user
55
56 7365fb51 Francois POIROTTE
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
57
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
58 2dbc5942 Francois POIROTTE
59 7365fb51 Francois POIROTTE
from vigiboard.widgets.edit_event import edit_event_status_options
60 2dbc5942 Francois POIROTTE
from vigiboard.widgets.search_form import create_search_form, get_calendar_lang
61 19e88cb8 Thomas ANDREJAK
62 e3c52cfd Aurelien BOMPARD
__all__ = ('RootController', 'get_last_modification_timestamp',
63 97f6d842 Vincent QUEMENER
           'date_to_timestamp')
64 19e88cb8 Thomas ANDREJAK
65 4d783824 Francois POIROTTE
# pylint: disable-msg=R0201
66 b8500d1a Thomas ANDREJAK
class RootController(VigiboardRootController):
67 19e88cb8 Thomas ANDREJAK
    """
68
    Le controller général de vigiboard
69
    """
70 e307e626 Francois POIROTTE
    autocomplete = AutoCompleteController()
71 ea0e5dfb Francois POIROTTE
    nagios = ProxyController('nagios', '/nagios/',
72
        not_anonymous(l_('You need to be authenticated')))
73 98a40b9f Aurelien BOMPARD
    api = ApiRootController("/api")
74 ea0e5dfb Francois POIROTTE
75 ef31cc13 Francois POIROTTE
76 f2e30877 Francois POIROTTE
    # Prédicat pour la restriction de l'accès aux interfaces.
77 a5f99051 Francois POIROTTE
    # L'utilisateur doit avoir la permission "vigiboard-access"
78 f2e30877 Francois POIROTTE
    # ou appartenir au groupe "managers" pour accéder à VigiBoard.
79
    access_restriction = All(
80
        not_anonymous(msg=l_("You need to be authenticated")),
81
        Any(in_group('managers'),
82 a5f99051 Francois POIROTTE
            has_permission('vigiboard-access'),
83
            msg=l_("You don't have access to VigiBoard"))
84 f2e30877 Francois POIROTTE
    )
85
86 aa0788a2 Francois POIROTTE
    def process_form_errors(self, *argv, **kwargv):
87 19e88cb8 Thomas ANDREJAK
        """
88
        Gestion des erreurs de validation : On affiche les erreurs
89
        puis on redirige vers la dernière page accédée.
90
        """
91 aa0788a2 Francois POIROTTE
        for k in tmpl_context.form_errors:
92
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
93 a9a4679d Francois POIROTTE
        redirect(request.environ.get('HTTP_REFERER', '/'))
94 19e88cb8 Thomas ANDREJAK
95 4c08cd96 Francois POIROTTE
    @expose('json')
96
    def handle_validation_errors_json(self, *args, **kwargs):
97
        kwargs['errors'] = tmpl_context.form_errors
98
        return dict(kwargs)
99 e3c52cfd Aurelien BOMPARD
100 e307e626 Francois POIROTTE
    class DefaultSchema(schema.Schema):
101 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode default."""
102 e307e626 Francois POIROTTE
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
103 2dbc5942 Francois POIROTTE
        supitemgroup = validators.Int(if_missing=None, if_invalid=None)
104 e307e626 Francois POIROTTE
        host = validators.String(if_missing=None)
105
        service = validators.String(if_missing=None)
106
        output = validators.String(if_missing=None)
107
        trouble_ticket = validators.String(if_missing=None)
108
        from_date = validators.String(if_missing=None)
109
        to_date = validators.String(if_missing=None)
110
111
    @validate(
112
        validators=DefaultSchema(),
113
        error_handler = process_form_errors)
114 d5ffcf1f Vincent QUEMENER
    @expose('events_table.html')
115 f2e30877 Francois POIROTTE
    @require(access_restriction)
116 e307e626 Francois POIROTTE
    def default(self, page, supitemgroup, host, service,
117
                output, trouble_ticket, from_date, to_date):
118 19e88cb8 Thomas ANDREJAK
        """
119 bc94248f Francois POIROTTE
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
120 a2a22ade Francois POIROTTE
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
121 bc94248f Francois POIROTTE
        en compte, puis de sévérité.
122 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
123

124 bc94248f Francois POIROTTE
        @param page: Numéro de la page souhaitée, commence à 1
125 19e88cb8 Thomas ANDREJAK
        @param host: Si l'utilisateur souhaite sélectionner seulement certains
126 a2a22ade Francois POIROTTE
                     événements suivant leur hôte, il peut placer une expression
127 19e88cb8 Thomas ANDREJAK
                     ici en suivant la structure du LIKE en SQL
128
        @param service: Idem que host mais sur les services
129
        @param output: Idem que host mais sur le text explicatif
130
        @param trouble_ticket: Idem que host mais sur les tickets attribués
131 c9245ffc Vincent QUEMENER

132 e3c52cfd Aurelien BOMPARD
        Cette méthode permet de satisfaire les exigences suivantes :
133
            - VIGILO_EXIG_VIGILO_BAC_0040,
134 e181e86c Francois POIROTTE
            - VIGILO_EXIG_VIGILO_BAC_0070,
135
            - VIGILO_EXIG_VIGILO_BAC_0100,
136 19e88cb8 Thomas ANDREJAK
        """
137 195aa50d Francois POIROTTE
        user = get_current_user()
138 97f6d842 Vincent QUEMENER
        aggregates = VigiboardRequest(user)
139 cf3c2494 Vincent QUEMENER
140 911069bc Francois POIROTTE
        aggregates.add_table(
141
            CorrEvent,
142
            aggregates.items.c.hostname,
143
            aggregates.items.c.servicename
144
        )
145
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
146 cf3c2494 Vincent QUEMENER
        aggregates.add_contains_eager(CorrEvent.cause)
147
        aggregates.add_group_by(Event)
148 e3c52cfd Aurelien BOMPARD
        aggregates.add_join((aggregates.items,
149 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == aggregates.items.c.idsupitem))
150 5d20c2c5 Francois POIROTTE
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
151 e3c52cfd Aurelien BOMPARD
152 2dbc5942 Francois POIROTTE
        search = {}
153 398c727b Francois POIROTTE
154 19e88cb8 Thomas ANDREJAK
        # Application des filtres si nécessaire
155 24d74687 Francois POIROTTE
        if supitemgroup:
156 0bd9c069 Francois POIROTTE
            search['supitemgroup'] = supitemgroup
157
            aggregates.add_join((GroupHierarchy, GroupHierarchy.idchild ==
158
                aggregates.items.c.idsupitemgroup))
159
            aggregates.add_filter(GroupHierarchy.idparent == supitemgroup)
160 398c727b Francois POIROTTE
161 08d86103 Francois POIROTTE
        if host:
162 88cac5bb Francois POIROTTE
            search['host_'] = host
163 08d86103 Francois POIROTTE
            host = sql_escape_like(host)
164 0bd9c069 Francois POIROTTE
            aggregates.add_filter(aggregates.items.c.hostname.ilike(
165
                '%s' % host))
166 bc94248f Francois POIROTTE
167 08d86103 Francois POIROTTE
        if service:
168 693e96f1 Thomas ANDREJAK
            search['service'] = service
169 08d86103 Francois POIROTTE
            service = sql_escape_like(service)
170 0bd9c069 Francois POIROTTE
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
171
                '%s' % service))
172 bc94248f Francois POIROTTE
173 08d86103 Francois POIROTTE
        if output:
174 693e96f1 Thomas ANDREJAK
            search['output'] = output
175 08d86103 Francois POIROTTE
            output = sql_escape_like(output)
176 0bd9c069 Francois POIROTTE
            aggregates.add_filter(Event.message.ilike('%s' % output))
177 bc94248f Francois POIROTTE
178 08d86103 Francois POIROTTE
        if trouble_ticket:
179 693e96f1 Thomas ANDREJAK
            search['tt'] = trouble_ticket
180 08d86103 Francois POIROTTE
            trouble_ticket = sql_escape_like(trouble_ticket)
181 0bd9c069 Francois POIROTTE
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
182
                '%s' % trouble_ticket))
183 19e88cb8 Thomas ANDREJAK
184 1101e03e Francois POIROTTE
        if from_date:
185 88c9eb8e Francois POIROTTE
            search['from_date'] = from_date.lower()
186 1101e03e Francois POIROTTE
            try:
187 5bb827fd Francois POIROTTE
                # TRANSLATORS: Format de date et heure Python/JavaScript.
188
                # TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5
189
                # TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html
190 1101e03e Francois POIROTTE
                from_date = datetime.strptime(
191 0842bb2c Francois POIROTTE
                    from_date.encode('utf8'),
192
                    _('%Y-%m-%d %I:%M:%S %p').encode('utf8'))
193 1101e03e Francois POIROTTE
            except ValueError:
194 9211ef65 Francois POIROTTE
                # On ignore silencieusement la date invalide reçue.
195
                pass
196
            else:
197
                aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
198 1101e03e Francois POIROTTE
199
        if to_date:
200 88c9eb8e Francois POIROTTE
            search['to_date'] = to_date.lower()
201 1101e03e Francois POIROTTE
            try:
202 3ba83d8b Francois POIROTTE
                # TRANSLATORS: Format de date et heure.
203 5bb827fd Francois POIROTTE
                # TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5
204
                # TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html
205 1101e03e Francois POIROTTE
                to_date = datetime.strptime(
206 0842bb2c Francois POIROTTE
                    to_date.encode('utf8'),
207
                    _('%Y-%m-%d %I:%M:%S %p').encode('utf8'))
208 1101e03e Francois POIROTTE
            except ValueError:
209 9211ef65 Francois POIROTTE
                # On ignore silencieusement la date invalide reçue.
210
                pass
211
            else:
212
                aggregates.add_filter(CorrEvent.timestamp_active <= to_date)
213 1101e03e Francois POIROTTE
214 19e88cb8 Thomas ANDREJAK
        # Calcul des éléments à afficher et du nombre de pages possibles
215 3d0d254c Francois POIROTTE
        total_rows = aggregates.num_rows()
216 ee3ae8c8 Francois POIROTTE
        items_per_page = int(config['vigiboard_items_per_page'])
217 19e88cb8 Thomas ANDREJAK
218 ee3ae8c8 Francois POIROTTE
        id_first_row = items_per_page * (page-1)
219
        id_last_row = min(id_first_row + items_per_page, total_rows)
220 19e88cb8 Thomas ANDREJAK
221 88cac5bb Francois POIROTTE
        # Si le numéro de page dépasse le nombre de pages existantes,
222
        # on redirige automatiquement vers la 1ère page.
223
        if total_rows and id_first_row >= total_rows:
224
            redirect('/', page=total_rows / items_per_page, **search)
225
226 3d0d254c Francois POIROTTE
        aggregates.format_events(id_first_row, id_last_row)
227
        aggregates.generate_tmpl_context()
228 baedcd0f Francois POIROTTE
229 ee3ae8c8 Francois POIROTTE
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
230 baedcd0f Francois POIROTTE
        if not total_rows:
231
            id_first_row = 0
232
        else:
233
            id_first_row += 1
234 3d0d254c Francois POIROTTE
235 cf3c2494 Vincent QUEMENER
        # Récupération des données des plugins
236
        plugins_data = {}
237
        plugins = dict(config['columns_plugins'])
238
        for plugin in plugins:
239
            plugin_data = plugins[plugin].get_bulk_data(
240
                [event[0].idcorrevent for event in aggregates.events]
241
            )
242
            if plugin_data :
243
                plugins_data[plugin] = plugin_data
244
245 19e88cb8 Thomas ANDREJAK
        return dict(
246 73f3220e Vincent QUEMENER
            hostname = None,
247
            servicename = None,
248 1101e03e Francois POIROTTE
            events = aggregates.events,
249 cf3c2494 Vincent QUEMENER
            plugins_data = plugins_data,
250 1101e03e Francois POIROTTE
            rows_info = {
251
                'id_first_row': id_first_row,
252
                'id_last_row': id_last_row,
253
                'total_rows': total_rows,
254
            },
255
            nb_pages = nb_pages,
256
            page = page,
257
            event_edit_status_options = edit_event_status_options,
258 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
259 1101e03e Francois POIROTTE
            search = search,
260 2dbc5942 Francois POIROTTE
            get_calendar_lang = get_calendar_lang,
261 1101e03e Francois POIROTTE
        )
262 19e88cb8 Thomas ANDREJAK
263 e307e626 Francois POIROTTE
264
    class MaskedEventsSchema(schema.Schema):
265 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode masked_events."""
266 e307e626 Francois POIROTTE
        idcorrevent = validators.Int(not_empty=True)
267
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
268
269
    @validate(
270
        validators=MaskedEventsSchema(),
271
        error_handler = process_form_errors)
272 00d2e1d1 Vincent QUEMENER
    @expose('raw_events_table.html')
273 f2e30877 Francois POIROTTE
    @require(access_restriction)
274 e307e626 Francois POIROTTE
    def masked_events(self, idcorrevent, page):
275 54644278 Francois POIROTTE
        """
276 e181e86c Francois POIROTTE
        Affichage de la liste des événements bruts masqués d'un événement
277
        corrélé (événements agrégés dans l'événement corrélé).
278 54644278 Francois POIROTTE

279 e181e86c Francois POIROTTE
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
280
        @type idcorrevent: C{int}
281 54644278 Francois POIROTTE
        """
282 195aa50d Francois POIROTTE
        user = get_current_user()
283 a05b9a37 Francois POIROTTE
284
        # Récupère la liste des événements masqués de l'événement
285
        # corrélé donné par idcorrevent.
286 195aa50d Francois POIROTTE
        events = VigiboardRequest(user, False)
287 54644278 Francois POIROTTE
        events.add_table(
288
            Event,
289
            events.items.c.hostname,
290
            events.items.c.servicename,
291
        )
292 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
293
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
294
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
295
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
296 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
297 54644278 Francois POIROTTE
            Event.idsupitem == events.items.c.idsupitem))
298
        events.add_filter(Event.idevent != CorrEvent.idcause)
299
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
300 bcf87133 Francois POIROTTE
301 a05b9a37 Francois POIROTTE
        # Récupère l'instance de SupItem associé à la cause de
302
        # l'événement corrélé. Cette instance est utilisé pour
303
        # obtenir le nom d'hôte/service auquel la cause est
304
        # rattachée (afin de fournir un contexte à l'utilisateur).
305
        hostname = None
306
        servicename = None
307
        cause_supitem = DBSession.query(
308
                SupItem,
309
            ).join(
310
                (Event, Event.idsupitem == SupItem.idsupitem),
311 cf3c2494 Vincent QUEMENER
                (CorrEvent, Event.idevent == CorrEvent.idcause),
312 a05b9a37 Francois POIROTTE
            ).filter(CorrEvent.idcorrevent == idcorrevent
313
            ).one()
314
315
        if isinstance(cause_supitem, LowLevelService):
316
            hostname = cause_supitem.host.name
317
            servicename = cause_supitem.servicename
318
        elif isinstance(cause_supitem, Host):
319
            hostname = cause_supitem.name
320
321 54644278 Francois POIROTTE
        # Vérification que l'événement existe
322 bcf87133 Francois POIROTTE
        total_rows = events.num_rows()
323
        if total_rows < 1:
324 54644278 Francois POIROTTE
            flash(_('No masked event or access denied'), 'error')
325
            redirect('/')
326
327 88cac5bb Francois POIROTTE
         # Calcul des éléments à afficher et du nombre de pages possibles
328
        total_rows = events.num_rows()
329 54644278 Francois POIROTTE
        items_per_page = int(config['vigiboard_items_per_page'])
330
331
        id_first_row = items_per_page * (page-1)
332
        id_last_row = min(id_first_row + items_per_page, total_rows)
333
334
        events.format_events(id_first_row, id_last_row)
335
        events.generate_tmpl_context()
336
337
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
338
        if not total_rows:
339
            id_first_row = 0
340
        else:
341
            id_first_row += 1
342
343
        return dict(
344 0c8b0e15 Francois POIROTTE
            idcorrevent = idcorrevent,
345 a05b9a37 Francois POIROTTE
            hostname = hostname,
346
            servicename = servicename,
347 54644278 Francois POIROTTE
            events = events.events,
348 cf3c2494 Vincent QUEMENER
            plugins_data = {},
349 54644278 Francois POIROTTE
            rows_info = {
350
                'id_first_row': id_first_row,
351
                'id_last_row': id_last_row,
352
                'total_rows': total_rows,
353
            },
354
            nb_pages = nb_pages,
355
            page = page,
356 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
357
            search = {},
358
            get_calendar_lang = get_calendar_lang,
359 54644278 Francois POIROTTE
        )
360
361 e307e626 Francois POIROTTE
362
    class EventSchema(schema.Schema):
363 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode event."""
364 e307e626 Francois POIROTTE
        idevent = validators.Int(not_empty=True)
365
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
366
367
    @validate(
368
        validators=EventSchema(),
369
        error_handler = process_form_errors)
370 00d2e1d1 Vincent QUEMENER
    @expose('history_table.html')
371 f2e30877 Francois POIROTTE
    @require(access_restriction)
372 e307e626 Francois POIROTTE
    def event(self, idevent, page):
373 19e88cb8 Thomas ANDREJAK
        """
374 94f31908 Francois POIROTTE
        Affichage de l'historique d'un événement brut.
375 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
376

377 94f31908 Francois POIROTTE
        @param idevent: identifiant de l'événement brut souhaité.
378
        @type idevent: C{int}
379 e181e86c Francois POIROTTE
        @param page: numéro de la page à afficher.
380
        @type page: C{int}
381 c9245ffc Vincent QUEMENER

382 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
383
        VIGILO_EXIG_VIGILO_BAC_0080.
384 19e88cb8 Thomas ANDREJAK
        """
385 195aa50d Francois POIROTTE
        user = get_current_user()
386
        events = VigiboardRequest(user, False)
387 911069bc Francois POIROTTE
        events.add_table(
388 539f69fc Francois POIROTTE
            Event,
389 72ec8dbf Francois POIROTTE
            events.items.c.hostname.label('hostname'),
390
            events.items.c.servicename.label('servicename'),
391 911069bc Francois POIROTTE
        )
392 072f2a16 Francois POIROTTE
        events.add_join((EVENTSAGGREGATE_TABLE, \
393
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
394
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
395
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
396 e3c52cfd Aurelien BOMPARD
        events.add_join((events.items,
397 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
398 94f31908 Francois POIROTTE
        events.add_filter(Event.idevent == idevent)
399 539f69fc Francois POIROTTE
400
        if events.num_rows() != 1:
401
            flash(_('No such event or access denied'), 'error')
402 19e88cb8 Thomas ANDREJAK
            redirect('/')
403 539f69fc Francois POIROTTE
404 19e88cb8 Thomas ANDREJAK
        events.format_events(0, 1)
405 539f69fc Francois POIROTTE
        events.generate_tmpl_context()
406
        history = events.format_history()
407
408
        total_rows = history.count()
409
        items_per_page = int(config['vigiboard_items_per_page'])
410
411
        id_first_row = items_per_page * (page-1)
412
        id_last_row = min(id_first_row + items_per_page, total_rows)
413
414
        history_entries = history[id_first_row : id_last_row]
415
416
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
417
        if not total_rows:
418
            id_first_row = 0
419
        else:
420
            id_first_row += 1
421 19e88cb8 Thomas ANDREJAK
422 72ec8dbf Francois POIROTTE
        event = events.req[0]
423
424 19e88cb8 Thomas ANDREJAK
        return dict(
425 0c8b0e15 Francois POIROTTE
            idevent = idevent,
426 72ec8dbf Francois POIROTTE
            hostname = event.hostname,
427
            servicename = event.servicename,
428 cf3c2494 Vincent QUEMENER
            plugins_data = {},
429 911069bc Francois POIROTTE
            rows_info = {
430 539f69fc Francois POIROTTE
                'id_first_row': id_first_row,
431
                'id_last_row': id_last_row,
432
                'total_rows': total_rows,
433 911069bc Francois POIROTTE
            },
434 539f69fc Francois POIROTTE
            nb_pages = nb_pages,
435
            page = page,
436 15b98053 Francois POIROTTE
            history = history_entries,
437 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
438
            search = {},
439
            get_calendar_lang = get_calendar_lang,
440 911069bc Francois POIROTTE
        )
441 19e88cb8 Thomas ANDREJAK
442 e307e626 Francois POIROTTE
443
    class ItemSchema(schema.Schema):
444 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode item."""
445 e307e626 Francois POIROTTE
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
446
        host = validators.String(not_empty=True)
447
        service = validators.String(if_missing=None)
448
449 24334b4b Vincent QUEMENER
    @validate(
450 e307e626 Francois POIROTTE
        validators=ItemSchema(),
451 6ab72614 Vincent QUEMENER
        error_handler = process_form_errors)
452 eab949e2 Vincent QUEMENER
    @expose('events_table.html')
453 f2e30877 Francois POIROTTE
    @require(access_restriction)
454 e307e626 Francois POIROTTE
    def item(self, page, host, service):
455 19e88cb8 Thomas ANDREJAK
        """
456 539f69fc Francois POIROTTE
        Affichage de l'historique de l'ensemble des événements corrélés
457
        jamais ouverts sur l'hôte / service demandé.
458 19e88cb8 Thomas ANDREJAK
        Pour accéder à cette page, l'utilisateur doit être authentifié.
459

460 e181e86c Francois POIROTTE
        @param page: Numéro de la page à afficher.
461 19e88cb8 Thomas ANDREJAK
        @param host: Nom de l'hôte souhaité.
462
        @param service: Nom du service souhaité
463 c9245ffc Vincent QUEMENER

464 e181e86c Francois POIROTTE
        Cette méthode permet de satisfaire l'exigence
465
        VIGILO_EXIG_VIGILO_BAC_0080.
466 19e88cb8 Thomas ANDREJAK
        """
467 24334b4b Vincent QUEMENER
        idsupitem = SupItem.get_supitem(host, service)
468 1a1e8c17 Francois POIROTTE
        if not idsupitem:
469
            flash(_('No such host/service'), 'error')
470
            redirect('/')
471 24334b4b Vincent QUEMENER
472 195aa50d Francois POIROTTE
        user = get_current_user()
473
        aggregates = VigiboardRequest(user, False)
474 539f69fc Francois POIROTTE
        aggregates.add_table(
475 911069bc Francois POIROTTE
            CorrEvent,
476 539f69fc Francois POIROTTE
            aggregates.items.c.hostname,
477
            aggregates.items.c.servicename,
478 911069bc Francois POIROTTE
        )
479 539f69fc Francois POIROTTE
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
480 0842bb2c Francois POIROTTE
        aggregates.add_join((aggregates.items,
481 539f69fc Francois POIROTTE
            Event.idsupitem == aggregates.items.c.idsupitem))
482
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
483 baedcd0f Francois POIROTTE
484 a2a22ade Francois POIROTTE
        # Vérification qu'il y a au moins 1 événement qui correspond
485 539f69fc Francois POIROTTE
        total_rows = aggregates.num_rows()
486 94f31908 Francois POIROTTE
        if not total_rows:
487 911069bc Francois POIROTTE
            flash(_('No access to this host/service or no event yet'), 'error')
488 19e88cb8 Thomas ANDREJAK
            redirect('/')
489 ee3ae8c8 Francois POIROTTE
490 539f69fc Francois POIROTTE
        items_per_page = int(config['vigiboard_items_per_page'])
491
492
        id_first_row = items_per_page * (page-1)
493
        id_last_row = min(id_first_row + items_per_page, total_rows)
494
495
        aggregates.format_events(id_first_row, id_last_row)
496
        aggregates.generate_tmpl_context()
497
498
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
499
        if not total_rows:
500
            id_first_row = 0
501
        else:
502
            id_first_row += 1
503 e3c52cfd Aurelien BOMPARD
504 19e88cb8 Thomas ANDREJAK
        return dict(
505 0c8b0e15 Francois POIROTTE
            hostname = host,
506
            servicename = service,
507 539f69fc Francois POIROTTE
            events = aggregates.events,
508 cf3c2494 Vincent QUEMENER
            plugins_data = {},
509 54644278 Francois POIROTTE
            rows_info = {
510 539f69fc Francois POIROTTE
                'id_first_row': id_first_row,
511
                'id_last_row': id_last_row,
512
                'total_rows': total_rows,
513 54644278 Francois POIROTTE
            },
514 539f69fc Francois POIROTTE
            nb_pages = nb_pages,
515
            page = page,
516 54644278 Francois POIROTTE
            event_edit_status_options = edit_event_status_options,
517 2dbc5942 Francois POIROTTE
            search_form = create_search_form,
518
            search = {},
519
            get_calendar_lang = get_calendar_lang,
520 54644278 Francois POIROTTE
        )
521 19e88cb8 Thomas ANDREJAK
522 e307e626 Francois POIROTTE
523
    class UpdateSchema(schema.Schema):
524 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode update."""
525 e307e626 Francois POIROTTE
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
526
        last_modification = validators.Number(not_empty=True)
527
        trouble_ticket = validators.String(if_missing='')
528 9e0ea30e Francois POIROTTE
        ack = validators.OneOf(
529
            [unicode(s[0]) for s in edit_event_status_options],
530
            not_empty=True)
531 e307e626 Francois POIROTTE
532
    @validate(
533
        validators=UpdateSchema(),
534
        error_handler = process_form_errors)
535 9e0ea30e Francois POIROTTE
    @require(
536
        All(
537
            not_anonymous(msg=l_("You need to be authenticated")),
538
            Any(in_group('managers'),
539 a5f99051 Francois POIROTTE
                has_permission('vigiboard-update'),
540 9e0ea30e Francois POIROTTE
                msg=l_("You don't have write access to VigiBoard"))
541
        ))
542 dcd79358 Francois POIROTTE
    @expose()
543 a9a4679d Francois POIROTTE
    def update(self, id, last_modification, trouble_ticket, ack):
544 19e88cb8 Thomas ANDREJAK
        """
545 a2a22ade Francois POIROTTE
        Mise à jour d'un événement suivant les arguments passés.
546 8484b8bd Francois POIROTTE
        Cela peut être un changement de ticket ou un changement de statut.
547 e3c52cfd Aurelien BOMPARD

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

554 e3c52cfd Aurelien BOMPARD
        Cette méthode permet de satisfaire les exigences suivantes :
555 e181e86c Francois POIROTTE
            - VIGILO_EXIG_VIGILO_BAC_0020,
556
            - VIGILO_EXIG_VIGILO_BAC_0060,
557
            - VIGILO_EXIG_VIGILO_BAC_0110.
558 19e88cb8 Thomas ANDREJAK
        """
559
560 97f6d842 Vincent QUEMENER
        # On vérifie que des identifiants ont bien été transmis via
561
        # le formulaire, et on informe l'utilisateur le cas échéant.
562 a9a4679d Francois POIROTTE
        if id is None:
563 10848680 Francois POIROTTE
            flash(_('No event has been selected'), 'warning')
564 a9a4679d Francois POIROTTE
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
565 5d20c2c5 Francois POIROTTE
566 2b740fc8 Francois POIROTTE
        # On récupère la liste de tous les identifiants des événements
567
        # à mettre à jour.
568
        ids = map(int, id.strip(',').split(','))
569 195aa50d Francois POIROTTE
570
        user = get_current_user()
571 303419a6 Francois POIROTTE
        events = VigiboardRequest(user)
572 911069bc Francois POIROTTE
        events.add_table(CorrEvent)
573
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
574 0842bb2c Francois POIROTTE
        events.add_join((events.items,
575 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
576 780ca169 Francois POIROTTE
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
577 e3c52cfd Aurelien BOMPARD
578 5edd29ac Vincent QUEMENER
        events.generate_request()
579
        idevents = [cause.idcause for cause in events.req]
580 a9a4679d Francois POIROTTE
581 0842bb2c Francois POIROTTE
        # Si des changements sont survenus depuis que la
582 5edd29ac Vincent QUEMENER
        # page est affichée, on en informe l'utilisateur.
583 a9a4679d Francois POIROTTE
        last_modification = datetime.fromtimestamp(last_modification)
584
        cur_last_modification = get_last_modification_timestamp(idevents, None)
585
        if cur_last_modification and last_modification < cur_last_modification:
586 5edd29ac Vincent QUEMENER
            flash(_('Changes have occurred since the page was last displayed, '
587
                    'your changes HAVE NOT been saved.'), 'warning')
588 a9a4679d Francois POIROTTE
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
589 f744bc14 Francois POIROTTE
590 19e88cb8 Thomas ANDREJAK
        # Vérification que au moins un des identifiants existe et est éditable
591 94f31908 Francois POIROTTE
        if not events.num_rows():
592 19e88cb8 Thomas ANDREJAK
            flash(_('No access to this event'), 'error')
593
            redirect('/')
594 8484b8bd Francois POIROTTE
595 2b740fc8 Francois POIROTTE
        if ack == u'Forced':
596
            condition = Any(
597
                in_group('managers'),
598
                has_permission('vigiboard-admin'),
599
                msg=l_("You don't have administrative access "
600
                        "to VigiBoard"))
601
            try:
602
                condition.check_authorization(request.environ)
603
            except NotAuthorizedError, e:
604
                reason = unicode(e)
605
                flash(reason, 'error')
606
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
607
608
        # Modification des événements et création d'un historique
609
        # chaque fois que cela est nécessaire.
610
        for event in events.req:
611 f744bc14 Francois POIROTTE
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
612 8484b8bd Francois POIROTTE
                history = EventHistory(
613
                        type_action="Ticket change",
614
                        idevent=event.idcause,
615 a9a4679d Francois POIROTTE
                        value=unicode(trouble_ticket),
616 2b740fc8 Francois POIROTTE
                        text="Changed trouble ticket from '%(from)s' "
617
                             "to '%(to)s'" % {
618
                            'from': event.trouble_ticket,
619
                            'to': trouble_ticket,
620
                        },
621 e1133e5a Francois POIROTTE
                        username=user.user_name,
622 ee3ae8c8 Francois POIROTTE
                        timestamp=datetime.now(),
623 8484b8bd Francois POIROTTE
                    )
624 0842bb2c Francois POIROTTE
                DBSession.add(history)
625 a9a4679d Francois POIROTTE
                event.trouble_ticket = trouble_ticket
626 8484b8bd Francois POIROTTE
627 9e0ea30e Francois POIROTTE
            # Changement du statut d'acquittement.
628
            if ack != u'NoChange':
629 2b740fc8 Francois POIROTTE
                changed_ack = ack
630 9e0ea30e Francois POIROTTE
                # Pour forcer l'acquittement d'un événement,
631
                # il faut en plus avoir la permission
632
                # "vigiboard-admin".
633
                if ack == u'Forced':
634 2b740fc8 Francois POIROTTE
                    changed_ack = u'AAClosed'
635 7e0c4383 Francois POIROTTE
                    cause = event.cause
636 f8020955 Francois POIROTTE
                    # On met systématiquement l'événement à l'état "OK",
637
                    # même s'il s'agit d'un hôte.
638
                    # Techniquement, c'est incorrect, mais on fait ça
639
                    # pour masquer l'événement de toutes façons...
640 7e0c4383 Francois POIROTTE
                    cause.current_state = \
641 2b740fc8 Francois POIROTTE
                        StateName.statename_to_value(u'OK')
642
643 f8020955 Francois POIROTTE
                    # Mise à jour de l'état dans State, pour que
644
                    # VigiMap soit également mis à jour.
645
                    DBSession.query(State).filter(
646 7e0c4383 Francois POIROTTE
                            State.idsupitem == cause.idsupitem,
647 f8020955 Francois POIROTTE
                        ).update({
648
                            'state': StateName.statename_to_value(u'OK'),
649
                        })
650
651 2b740fc8 Francois POIROTTE
                    history = EventHistory(
652
                            type_action="Forced change state",
653
                            idevent=event.idcause,
654
                            value=u'OK',
655
                            text="Forced state to 'OK'",
656
                            username=user.user_name,
657
                            timestamp=datetime.now(),
658
                        )
659
                    DBSession.add(history)
660 9e0ea30e Francois POIROTTE
661 19e88cb8 Thomas ANDREJAK
                history = EventHistory(
662 88cac5bb Francois POIROTTE
                        type_action=u"Acknowledgement change state",
663 8484b8bd Francois POIROTTE
                        idevent=event.idcause,
664 9e0ea30e Francois POIROTTE
                        value=ack,
665 9494bbb3 Vincent QUEMENER
                        text="Changed acknowledgement status "
666
                            "from '%s' to '%s'" % (
667 2b740fc8 Francois POIROTTE
                            event.status, changed_ack
668 ee3ae8c8 Francois POIROTTE
                        ),
669 e1133e5a Francois POIROTTE
                        username=user.user_name,
670 ee3ae8c8 Francois POIROTTE
                        timestamp=datetime.now(),
671 8484b8bd Francois POIROTTE
                    )
672 19e88cb8 Thomas ANDREJAK
                DBSession.add(history)
673 2b740fc8 Francois POIROTTE
                event.status = changed_ack
674 3d0d254c Francois POIROTTE
675 10848680 Francois POIROTTE
        DBSession.flush()
676 19e88cb8 Thomas ANDREJAK
        flash(_('Updated successfully'))
677 a9a4679d Francois POIROTTE
        redirect(request.environ.get('HTTP_REFERER', '/'))
678 19e88cb8 Thomas ANDREJAK
679 e307e626 Francois POIROTTE
680
    class GetPluginValueSchema(schema.Schema):
681 e181e86c Francois POIROTTE
        """Schéma de validation de la méthode get_plugin_value."""
682 e307e626 Francois POIROTTE
        idcorrevent = validators.Int(not_empty=True)
683 65383903 Francois POIROTTE
        plugin_name = validators.String(not_empty=True)
684 e307e626 Francois POIROTTE
        # Permet de passer des paramètres supplémentaires au plugin.
685
        allow_extra_fields = True
686
687
    @validate(
688
        validators=GetPluginValueSchema(),
689 4c08cd96 Francois POIROTTE
        error_handler = handle_validation_errors_json)
690 8ad24667 Thomas ANDREJAK
    @expose('json')
691 f2e30877 Francois POIROTTE
    @require(access_restriction)
692 cf3c2494 Vincent QUEMENER
    def plugin_json(self, idcorrevent, plugin_name, *arg, **krgv):
693 8ad24667 Thomas ANDREJAK
        """
694 4dd2035e Francois POIROTTE
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
695
        donné via JSON.
696 8ad24667 Thomas ANDREJAK
        """
697 65383903 Francois POIROTTE
        plugins = dict(config['columns_plugins'])
698
        if plugin_name not in plugins:
699
            raise HTTPNotFound(_("No such plugin '%s'") % plugin_name)
700
701 4dd2035e Francois POIROTTE
        # Permet de vérifier si l'utilisateur a bien les permissions
702
        # pour accéder à cet événement et si l'événement existe.
703 195aa50d Francois POIROTTE
        user = get_current_user()
704
        events = VigiboardRequest(user, False)
705 4dd2035e Francois POIROTTE
        events.add_table(CorrEvent.idcorrevent)
706
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
707 0842bb2c Francois POIROTTE
        events.add_join((events.items,
708 bfd8ead8 Vincent QUEMENER
            Event.idsupitem == events.items.c.idsupitem))
709 4dd2035e Francois POIROTTE
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
710
711
        # Pas d'événement ou permission refusée. On ne distingue pas
712
        # les 2 cas afin d'éviter la divulgation d'informations.
713
        if not events.num_rows():
714 df25ac35 Francois POIROTTE
            raise HTTPNotFound(_('No such incident or insufficient '
715
                                'permissions'))
716 0f56fff9 Francois POIROTTE
717 cf3c2494 Vincent QUEMENER
        return plugins[plugin_name].get_json_data(idcorrevent, *arg, **krgv)
718 1bb369b9 Francois POIROTTE
719
    @validate(validators={
720
        "fontsize": validators.Regex(
721
            r'[0-9]+(pt|px|em|%)',
722 6ab72614 Vincent QUEMENER
            regexOps = ('I',)
723 4c08cd96 Francois POIROTTE
        )}, error_handler = handle_validation_errors_json)
724 693e96f1 Thomas ANDREJAK
    @expose('json')
725 b8500d1a Thomas ANDREJAK
    def set_fontsize(self, fontsize):
726 4dd2035e Francois POIROTTE
        """Enregistre la taille de la police dans les préférences."""
727 b8500d1a Thomas ANDREJAK
        session['fontsize'] = fontsize
728
        session.save()
729 e2e218d7 Francois POIROTTE
        return dict()
730 693e96f1 Thomas ANDREJAK
731 08d86103 Francois POIROTTE
    @validate(validators={"refresh": validators.Int()},
732 4c08cd96 Francois POIROTTE
            error_handler = handle_validation_errors_json)
733 693e96f1 Thomas ANDREJAK
    @expose('json')
734 b8500d1a Thomas ANDREJAK
    def set_refresh(self, refresh):
735 4dd2035e Francois POIROTTE
        """Enregistre le temps de rafraichissement dans les préférences."""
736 b8500d1a Thomas ANDREJAK
        session['refresh'] = refresh
737
        session.save()
738 e2e218d7 Francois POIROTTE
        return dict()
739
740
    @expose('json')
741
    def set_theme(self, theme):
742 4dd2035e Francois POIROTTE
        """Enregistre le thème à utiliser dans les préférences."""
743
        # On sauvegarde l'ID du thème sans vérifications
744
        # car les thèmes (styles CSS) sont définies dans
745
        # les packages de thèmes (ex: vigilo-themes-default).
746
        # La vérification de la valeur est faite dans les templates.
747 e2e218d7 Francois POIROTTE
        session['theme'] = theme
748
        session.save()
749
        return dict()
750 2b740fc8 Francois POIROTTE
751 ea0e5dfb Francois POIROTTE
    @require(access_restriction)
752
    @expose('json')
753 48acee1e Francois POIROTTE
    def get_groups(self, parent_id=None):
754
        """
755
        Affiche un étage de l'arbre de
756
        sélection des hôtes et groupes d'hôtes.
757

758
        @param parent_id: identifiant du groupe d'hôte parent
759
        @type  parent_id: C{int} or None
760
        """
761
762
        # Si l'identifiant du groupe parent n'est pas
763
        # spécifié, on retourne la liste des groupes racines,
764
        # fournie par la méthode get_root_hosts_groups.
765
        if parent_id is None:
766
            return self.get_root_host_groups()
767
768 0bd9c069 Francois POIROTTE
        # TODO: Utiliser un schéma de validation
769
        parent_id = int(parent_id)
770 ea0e5dfb Francois POIROTTE
771 cf3c2494 Vincent QUEMENER
        # On récupère la liste des groupes de supitems dont
772
        # l'identifiant du parent est passé en paramètre.
773
        supitem_groups = DBSession.query(
774
                SupItemGroup.idgroup,
775
                SupItemGroup.name,
776
            ).join(
777
                (GroupHierarchy,
778
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
779
            ).filter(GroupHierarchy.idparent == parent_id
780
            ).filter(GroupHierarchy.idchild != parent_id)
781
782
        # Si l'utilisateur n'appartient pas au groupe 'managers',
783
        # on filtre les résultats en fonction de ses permissions.
784 ea0e5dfb Francois POIROTTE
        is_manager = in_group('managers').is_met(request.environ)
785 0bd9c069 Francois POIROTTE
        if not is_manager:
786
            user = get_current_user()
787 eec46cb0 Vincent QUEMENER
            GroupHierarchy_aliased = aliased(GroupHierarchy,
788
                name='GroupHierarchy_aliased')
789 cf3c2494 Vincent QUEMENER
            supitem_groups.join(
790
                (GroupHierarchy_aliased,
791
                    GroupHierarchy_aliased.idchild == SupItemGroup.idgroup),
792
                (DataPermission,
793
                    DataPermission.idgroup == GroupHierarchy_aliased.idparent),
794
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
795
                    DataPermission.idusergroup),
796
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
797 eec46cb0 Vincent QUEMENER
798 48acee1e Francois POIROTTE
        groups = []
799 cf3c2494 Vincent QUEMENER
        for group in supitem_groups.distinct().all():
800 48acee1e Francois POIROTTE
            groups.append({
801
                'id'   : group.idgroup,
802
                'name' : group.name,
803
            })
804
805
        return dict(groups = groups, leaves = [])
806
807
    def get_root_host_groups(self):
808
        """
809
        Retourne tous les groupes racines (c'est à dire n'ayant
810
        aucun parent) d'hôtes auquel l'utilisateur a accès.
811

812
        @return: Un dictionnaire contenant la liste de ces groupes.
813
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
814
        """
815 ea0e5dfb Francois POIROTTE
816 0bd9c069 Francois POIROTTE
        # On récupère tous les groupes qui ont un parent.
817
        children = DBSession.query(
818
            SupItemGroup,
819
        ).distinct(
820
        ).join(
821
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
822
        ).filter(GroupHierarchy.hops > 0)
823
824
        # Ensuite on les exclut de la liste des groupes,
825
        # pour ne garder que ceux qui sont au sommet de
826
        # l'arbre et qui constituent nos "root groups".
827
        root_groups = DBSession.query(
828
            SupItemGroup,
829
        ).except_(children
830
        ).order_by(SupItemGroup.name)
831 48acee1e Francois POIROTTE
832
        # On filtre ces groupes racines afin de ne
833
        # retourner que ceux auquels l'utilisateur a accès
834
        user = get_current_user()
835
        is_manager = in_group('managers').is_met(request.environ)
836
        if not is_manager:
837 eec46cb0 Vincent QUEMENER
838
            root_groups = root_groups.join(
839
                (GroupHierarchy,
840
                    GroupHierarchy.idchild == SupItemGroup.idgroup),
841
                (DataPermission,
842
                    DataPermission.idgroup == GroupHierarchy.idparent),
843
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
844
                    DataPermission.idusergroup),
845
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
846 48acee1e Francois POIROTTE
847
        groups = []
848 0bd9c069 Francois POIROTTE
        for group in root_groups.all():
849 48acee1e Francois POIROTTE
            groups.append({
850
                'id'   : group.idgroup,
851
                'name' : group.name,
852
            })
853
854
        return dict(groups = groups, leaves=[])
855
856 0842bb2c Francois POIROTTE
def get_last_modification_timestamp(event_id_list,
857 4dd2035e Francois POIROTTE
                                    value_if_none=datetime.now()):
858 97f6d842 Vincent QUEMENER
    """
859 0842bb2c Francois POIROTTE
    Récupère le timestamp de la dernière modification
860 97f6d842 Vincent QUEMENER
    opérée sur l'un des événements dont l'identifiant
861
    fait partie de la liste passée en paramètre.
862
    """
863
    last_modification_timestamp = DBSession.query(
864
                                func.max(EventHistory.timestamp),
865
                         ).filter(EventHistory.idevent.in_(event_id_list)
866
                         ).scalar()
867 e9ccb711 Francois POIROTTE
    if not last_modification_timestamp:
868 24334b4b Vincent QUEMENER
        if not value_if_none:
869
            return None
870
        else:
871
            last_modification_timestamp = value_if_none
872 e9ccb711 Francois POIROTTE
    return datetime.fromtimestamp(mktime(
873
        last_modification_timestamp.timetuple()))