Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 1a1e8c17

History | View | Annotate | Download (31.6 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4: 
3
"""VigiBoard Controller"""
4

    
5
from datetime import datetime
6
from time import mktime
7
import math
8

    
9
from tg.exceptions import HTTPNotFound, HTTPInternalServerError
10
from tg import expose, validate, require, flash, \
11
    tmpl_context, request, config, session, redirect
12
from tw.forms import validators
13
from pylons.i18n import ugettext as _
14
from pylons.i18n import lazy_ugettext as l_
15
from sqlalchemy import asc
16
from sqlalchemy.sql import func
17
from repoze.what.predicates import Any, All, in_group, \
18
                                    has_permission, not_anonymous
19
from formencode import schema
20

    
21
from vigilo.models.session import DBSession
22
from vigilo.models.tables import Event, EventHistory, CorrEvent, Host, \
23
                                    SupItem, SupItemGroup, LowLevelService, \
24
                                    StateName
25
from vigilo.models.tables.grouphierarchy import GroupHierarchy
26
from vigilo.models.functions import sql_escape_like
27
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE
28

    
29
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
30
from vigilo.turbogears.controllers.proxy import ProxyController
31
from vigilo.turbogears.helpers import get_current_user
32

    
33
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
34
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
35

    
36
from vigiboard.widgets.edit_event import edit_event_status_options
37
from vigiboard.widgets.search_form import create_search_form, get_calendar_lang
38

    
39
__all__ = ('RootController', 'get_last_modification_timestamp', 
40
           'date_to_timestamp')
41

    
42
# pylint: disable-msg=R0201
43
class RootController(VigiboardRootController):
44
    """
45
    Le controller général de vigiboard
46
    """
47
    autocomplete = AutoCompleteController()
48
    nagios = ProxyController('nagios', '/nagios/',
49
        not_anonymous(l_('You need to be authenticated')))
50

    
51

    
52
    # Prédicat pour la restriction de l'accès aux interfaces.
53
    # L'utilisateur doit avoir la permission "vigiboard-access"
54
    # ou appartenir au groupe "managers" pour accéder à VigiBoard.
55
    access_restriction = All(
56
        not_anonymous(msg=l_("You need to be authenticated")),
57
        Any(in_group('managers'),
58
            has_permission('vigiboard-access'),
59
            msg=l_("You don't have access to VigiBoard"))
60
    )
61

    
62
    def process_form_errors(self, *argv, **kwargv):
63
        """
64
        Gestion des erreurs de validation : On affiche les erreurs
65
        puis on redirige vers la dernière page accédée.
66
        """
67
        for k in tmpl_context.form_errors:
68
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
69
        redirect(request.environ.get('HTTP_REFERER', '/'))
70

    
71
    @expose('json')
72
    def handle_validation_errors_json(self, *args, **kwargs):
73
        kwargs['errors'] = tmpl_context.form_errors
74
        return dict(kwargs)
75
    
76
    class DefaultSchema(schema.Schema):
77
        """Schéma de validation de la méthode default."""
78
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
79
        supitemgroup = validators.Int(if_missing=None, if_invalid=None)
80
        host = validators.String(if_missing=None)
81
        service = validators.String(if_missing=None)
82
        output = validators.String(if_missing=None)
83
        trouble_ticket = validators.String(if_missing=None)
84
        from_date = validators.String(if_missing=None)
85
        to_date = validators.String(if_missing=None)
86

    
87
    @validate(
88
        validators=DefaultSchema(),
89
        error_handler = process_form_errors)
90
    @expose('events_table.html')
91
    @require(access_restriction)
92
    def default(self, page, supitemgroup, host, service,
93
                output, trouble_ticket, from_date, to_date):
94
        """
95
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
96
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
97
        en compte, puis de sévérité.
98
        Pour accéder à cette page, l'utilisateur doit être authentifié.
99

100
        @param page: Numéro de la page souhaitée, commence à 1
101
        @param host: Si l'utilisateur souhaite sélectionner seulement certains
102
                     événements suivant leur hôte, il peut placer une expression
103
                     ici en suivant la structure du LIKE en SQL
104
        @param service: Idem que host mais sur les services
105
        @param output: Idem que host mais sur le text explicatif
106
        @param trouble_ticket: Idem que host mais sur les tickets attribués
107

108
        Cette méthode permet de satisfaire les exigences suivantes : 
109
            - VIGILO_EXIG_VIGILO_BAC_0040, 
110
            - VIGILO_EXIG_VIGILO_BAC_0070,
111
            - VIGILO_EXIG_VIGILO_BAC_0100,
112
        """
113
        user = get_current_user()
114
        aggregates = VigiboardRequest(user)
115
        aggregates.add_table(
116
            CorrEvent,
117
            aggregates.items.c.hostname,
118
            aggregates.items.c.servicename
119
        )
120
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
121
        aggregates.add_join((aggregates.items, 
122
            Event.idsupitem == aggregates.items.c.idsupitem))
123
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
124
        
125
        search = {}
126

    
127
        # Application des filtres si nécessaire
128
        if supitemgroup:
129
            search['supitemgroup'] = supitemgroup
130
            aggregates.add_filter(aggregates.items.c.idsupitemgroup == \
131
                supitemgroup)
132

    
133
        if host:
134
            search['host'] = host
135
            host = sql_escape_like(host)
136
            aggregates.add_filter(aggregates.items.c.hostname.ilike(
137
                '%s' % host))
138

    
139
        if service:
140
            search['service'] = service
141
            service = sql_escape_like(service)
142
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
143
                '%s' % service))
144

    
145
        if output:
146
            search['output'] = output
147
            output = sql_escape_like(output)
148
            aggregates.add_filter(Event.message.ilike('%s' % output))
149

    
150
        if trouble_ticket:
151
            search['tt'] = trouble_ticket
152
            trouble_ticket = sql_escape_like(trouble_ticket)
153
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
154
                '%s' % trouble_ticket))
155

    
156
        if from_date:
157
            search['from_date'] = from_date.lower()
158
            try:
159
                # TRANSLATORS: Format de date et heure Python/JavaScript.
160
                # TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5
161
                # TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html
162
                from_date = datetime.strptime(
163
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
164
            except ValueError:
165
                # On ignore silencieusement la date invalide reçue.
166
                pass
167
            else:
168
                aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
169

    
170
        if to_date:
171
            search['to_date'] = to_date.lower()
172
            try:
173
                # TRANSLATORS: Format de date et heure.
174
                # TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5
175
                # TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html
176
                to_date = datetime.strptime(
177
                    to_date, _('%Y-%m-%d %I:%M:%S %p'))
178
            except ValueError:
179
                # On ignore silencieusement la date invalide reçue.
180
                pass
181
            else:
182
                aggregates.add_filter(CorrEvent.timestamp_active <= to_date)
183

    
184
        # Calcul des éléments à afficher et du nombre de pages possibles
185
        total_rows = aggregates.num_rows()
186
        items_per_page = int(config['vigiboard_items_per_page'])
187

    
188
        # Si le numéro de page dépasse le nombre de pages existantes,
189
        # on redirige automatiquement vers la 1ère page.
190
        if total_rows and items_per_page * (page-1) > total_rows:
191
            redirect('/', page=1, **search)
192

    
193
        id_first_row = items_per_page * (page-1)
194
        id_last_row = min(id_first_row + items_per_page, total_rows)
195

    
196
        aggregates.format_events(id_first_row, id_last_row)
197
        aggregates.generate_tmpl_context()
198

    
199
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
200
        if not total_rows:
201
            id_first_row = 0
202
        else:
203
            id_first_row += 1
204

    
205
        return dict(
206
            hostname = None,
207
            servicename = None,
208
            events = aggregates.events,
209
            plugins = get_plugins_instances(),
210
            rows_info = {
211
                'id_first_row': id_first_row,
212
                'id_last_row': id_last_row,
213
                'total_rows': total_rows,
214
            },
215
            nb_pages = nb_pages,
216
            page = page,
217
            event_edit_status_options = edit_event_status_options,
218
            search_form = create_search_form,
219
            search = search,
220
            get_calendar_lang = get_calendar_lang,
221
            refresh_times = config['vigiboard_refresh_times'],
222
        )
223

    
224

    
225
    class MaskedEventsSchema(schema.Schema):
226
        """Schéma de validation de la méthode masked_events."""
227
        idcorrevent = validators.Int(not_empty=True)
228
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
229

    
230
    @validate(
231
        validators=MaskedEventsSchema(),
232
        error_handler = process_form_errors)
233
    @expose('raw_events_table.html')
234
    @require(access_restriction)
235
    def masked_events(self, idcorrevent, page):
236
        """
237
        Affichage de la liste des événements bruts masqués d'un événement
238
        corrélé (événements agrégés dans l'événement corrélé).
239

240
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
241
        @type idcorrevent: C{int}
242
        """
243
        user = get_current_user()
244

    
245
        # Récupère la liste des événements masqués de l'événement
246
        # corrélé donné par idcorrevent.
247
        events = VigiboardRequest(user, False)
248
        events.add_table(
249
            Event,
250
            events.items.c.hostname,
251
            events.items.c.servicename,
252
        )
253
        events.add_join((EVENTSAGGREGATE_TABLE, \
254
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
255
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
256
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
257
        events.add_join((events.items, 
258
            Event.idsupitem == events.items.c.idsupitem))
259
        events.add_filter(Event.idevent != CorrEvent.idcause)
260
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
261

    
262
        # Récupère l'instance de SupItem associé à la cause de
263
        # l'événement corrélé. Cette instance est utilisé pour
264
        # obtenir le nom d'hôte/service auquel la cause est
265
        # rattachée (afin de fournir un contexte à l'utilisateur).
266
        hostname = None
267
        servicename = None
268
        cause_supitem = DBSession.query(
269
                SupItem,
270
            ).join(
271
                (Event, Event.idsupitem == SupItem.idsupitem),
272
                (EVENTSAGGREGATE_TABLE, EVENTSAGGREGATE_TABLE.c.idevent ==
273
                    Event.idevent),
274
                (CorrEvent, CorrEvent.idcorrevent ==
275
                    EVENTSAGGREGATE_TABLE.c.idcorrevent),
276
            ).filter(CorrEvent.idcorrevent == idcorrevent
277
            ).filter(Event.idevent == CorrEvent.idcause
278
            ).one()
279

    
280
        if isinstance(cause_supitem, LowLevelService):
281
            hostname = cause_supitem.host.name
282
            servicename = cause_supitem.servicename
283
        elif isinstance(cause_supitem, Host):
284
            hostname = cause_supitem.name
285

    
286
        # Vérification que l'événement existe
287
        total_rows = events.num_rows()
288
        if total_rows < 1:
289
            flash(_('No masked event or access denied'), 'error')
290
            redirect('/')
291

    
292
        # Calcul des éléments à afficher et du nombre de pages possibles
293
        items_per_page = int(config['vigiboard_items_per_page'])
294

    
295
        id_first_row = items_per_page * (page-1)
296
        id_last_row = min(id_first_row + items_per_page, total_rows)
297

    
298
        events.format_events(id_first_row, id_last_row)
299
        events.generate_tmpl_context()
300

    
301
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
302
        if not total_rows:
303
            id_first_row = 0
304
        else:
305
            id_first_row += 1
306

    
307
        return dict(
308
            idcorrevent = idcorrevent,
309
            hostname = hostname,
310
            servicename = servicename,
311
            events = events.events,
312
            plugins = get_plugins_instances(),
313
            rows_info = {
314
                'id_first_row': id_first_row,
315
                'id_last_row': id_last_row,
316
                'total_rows': total_rows,
317
            },
318
            nb_pages = nb_pages,
319
            page = page,
320
            search_form = create_search_form,
321
            search = {},
322
            get_calendar_lang = get_calendar_lang,
323
            refresh_times=config['vigiboard_refresh_times'],
324
        )
325

    
326

    
327
    class EventSchema(schema.Schema):
328
        """Schéma de validation de la méthode event."""
329
        idevent = validators.Int(not_empty=True)
330
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
331

    
332
    @validate(
333
        validators=EventSchema(),
334
        error_handler = process_form_errors)
335
    @expose('history_table.html')
336
    @require(access_restriction)
337
    def event(self, idevent, page):
338
        """
339
        Affichage de l'historique d'un événement brut.
340
        Pour accéder à cette page, l'utilisateur doit être authentifié.
341

342
        @param idevent: identifiant de l'événement brut souhaité.
343
        @type idevent: C{int}
344
        @param page: numéro de la page à afficher.
345
        @type page: C{int}
346

347
        Cette méthode permet de satisfaire l'exigence
348
        VIGILO_EXIG_VIGILO_BAC_0080.
349
        """
350
        user = get_current_user()
351
        events = VigiboardRequest(user, False)
352
        events.add_table(
353
            Event,
354
            events.items.c.hostname.label('hostname'),
355
            events.items.c.servicename.label('servicename'),
356
        )
357
        events.add_join((EVENTSAGGREGATE_TABLE, \
358
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
359
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
360
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
361
        events.add_join((events.items, 
362
            Event.idsupitem == events.items.c.idsupitem))
363
        events.add_filter(Event.idevent == idevent)
364

    
365
        if events.num_rows() != 1:
366
            flash(_('No such event or access denied'), 'error')
367
            redirect('/')
368

    
369
        events.format_events(0, 1)
370
        events.generate_tmpl_context()
371
        history = events.format_history()
372

    
373
        total_rows = history.count()
374
        items_per_page = int(config['vigiboard_items_per_page'])
375

    
376
        id_first_row = items_per_page * (page-1)
377
        id_last_row = min(id_first_row + items_per_page, total_rows)
378

    
379
        history_entries = history[id_first_row : id_last_row]
380

    
381
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
382
        if not total_rows:
383
            id_first_row = 0
384
        else:
385
            id_first_row += 1
386

    
387
        event = events.req[0]
388

    
389
        return dict(
390
            idevent = idevent,
391
            hostname = event.hostname,
392
            servicename = event.servicename,
393
            plugins = get_plugins_instances(),
394
            rows_info = {
395
                'id_first_row': id_first_row,
396
                'id_last_row': id_last_row,
397
                'total_rows': total_rows,
398
            },
399
            nb_pages = nb_pages,
400
            page = page,
401
            history = history_entries,
402
            search_form = create_search_form,
403
            search = {},
404
            get_calendar_lang = get_calendar_lang,
405
            refresh_times=config['vigiboard_refresh_times'],
406
        )
407

    
408

    
409
    class ItemSchema(schema.Schema):
410
        """Schéma de validation de la méthode item."""
411
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
412
        host = validators.String(not_empty=True)
413
        service = validators.String(if_missing=None)
414

    
415
    @validate(
416
        validators=ItemSchema(),
417
        error_handler = process_form_errors)
418
    @expose('events_table.html')
419
    @require(access_restriction)
420
    def item(self, page, host, service):
421
        """
422
        Affichage de l'historique de l'ensemble des événements corrélés
423
        jamais ouverts sur l'hôte / service demandé.
424
        Pour accéder à cette page, l'utilisateur doit être authentifié.
425

426
        @param page: Numéro de la page à afficher.
427
        @param host: Nom de l'hôte souhaité.
428
        @param service: Nom du service souhaité
429

430
        Cette méthode permet de satisfaire l'exigence
431
        VIGILO_EXIG_VIGILO_BAC_0080.
432
        """
433
        idsupitem = SupItem.get_supitem(host, service)
434
        if not idsupitem:
435
            flash(_('No such host/service'), 'error')
436
            redirect('/')
437

    
438
        user = get_current_user()
439
        aggregates = VigiboardRequest(user, False)
440
        aggregates.add_table(
441
            CorrEvent,
442
            aggregates.items.c.hostname,
443
            aggregates.items.c.servicename,
444
        )
445
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
446
        aggregates.add_join((aggregates.items, 
447
            Event.idsupitem == aggregates.items.c.idsupitem))
448
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
449

    
450
        # Vérification qu'il y a au moins 1 événement qui correspond
451
        total_rows = aggregates.num_rows()
452
        if not total_rows:
453
            flash(_('No access to this host/service or no event yet'), 'error')
454
            redirect('/')
455

    
456
        items_per_page = int(config['vigiboard_items_per_page'])
457

    
458
        id_first_row = items_per_page * (page-1)
459
        id_last_row = min(id_first_row + items_per_page, total_rows)
460

    
461
        aggregates.format_events(id_first_row, id_last_row)
462
        aggregates.generate_tmpl_context()
463

    
464
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
465
        if not total_rows:
466
            id_first_row = 0
467
        else:
468
            id_first_row += 1
469
        
470
        return dict(
471
            hostname = host,
472
            servicename = service,
473
            events = aggregates.events,
474
            plugins = get_plugins_instances(),
475
            rows_info = {
476
                'id_first_row': id_first_row,
477
                'id_last_row': id_last_row,
478
                'total_rows': total_rows,
479
            },
480
            nb_pages = nb_pages,
481
            page = page,
482
            event_edit_status_options = edit_event_status_options,
483
            search_form = create_search_form,
484
            search = {},
485
            get_calendar_lang = get_calendar_lang,
486
            refresh_times=config['vigiboard_refresh_times'],
487
        )
488

    
489

    
490
    class UpdateSchema(schema.Schema):
491
        """Schéma de validation de la méthode update."""
492
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
493
        last_modification = validators.Number(not_empty=True)
494
        trouble_ticket = validators.String(if_missing='')
495
        ack = validators.OneOf(
496
            [unicode(s[0]) for s in edit_event_status_options],
497
            not_empty=True)
498

    
499
    @validate(
500
        validators=UpdateSchema(),
501
        error_handler = process_form_errors)
502
    @require(
503
        All(
504
            not_anonymous(msg=l_("You need to be authenticated")),
505
            Any(in_group('managers'),
506
                has_permission('vigiboard-update'),
507
                msg=l_("You don't have write access to VigiBoard"))
508
        ))
509
    @expose()
510
    def update(self, id, last_modification, trouble_ticket, ack):
511
        """
512
        Mise à jour d'un événement suivant les arguments passés.
513
        Cela peut être un changement de ticket ou un changement de statut.
514
        
515
        @param id: Le ou les identifiants des événements à traiter
516
        @param last_modification: La date de la dernière modification
517
            dont l'utilisateur est au courant.
518
        @param trouble_ticket: Nouveau numéro du ticket associé.
519
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
520

521
        Cette méthode permet de satisfaire les exigences suivantes : 
522
            - VIGILO_EXIG_VIGILO_BAC_0020,
523
            - VIGILO_EXIG_VIGILO_BAC_0060,
524
            - VIGILO_EXIG_VIGILO_BAC_0110.
525
        """
526

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

    
533
        # On récupère la liste de tous les identifiants des événements
534
        # à mettre à jour.
535
        ids = map(int, id.strip(',').split(','))
536

    
537
        user = get_current_user()
538
        events = VigiboardRequest(user)
539
        events.add_table(CorrEvent)
540
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
541
        events.add_join((events.items, 
542
            Event.idsupitem == events.items.c.idsupitem))
543
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
544
        
545
        events.generate_request()
546
        idevents = [cause.idcause for cause in events.req]
547

    
548
        # Si des changements sont survenus depuis que la 
549
        # page est affichée, on en informe l'utilisateur.
550
        last_modification = datetime.fromtimestamp(last_modification)
551
        cur_last_modification = get_last_modification_timestamp(idevents, None)
552
        if cur_last_modification and last_modification < cur_last_modification:
553
            flash(_('Changes have occurred since the page was last displayed, '
554
                    'your changes HAVE NOT been saved.'), 'warning')
555
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
556

    
557
        # Vérification que au moins un des identifiants existe et est éditable
558
        if not events.num_rows():
559
            flash(_('No access to this event'), 'error')
560
            redirect('/')
561

    
562
        if ack == u'Forced':
563
            condition = Any(
564
                in_group('managers'),
565
                has_permission('vigiboard-admin'),
566
                msg=l_("You don't have administrative access "
567
                        "to VigiBoard"))
568
            try:
569
                condition.check_authorization(request.environ)
570
            except NotAuthorizedError, e:
571
                reason = unicode(e)
572
                flash(reason, 'error')
573
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
574

    
575
        # Modification des événements et création d'un historique
576
        # chaque fois que cela est nécessaire.
577
        for event in events.req:
578
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
579
                history = EventHistory(
580
                        type_action="Ticket change",
581
                        idevent=event.idcause,
582
                        value=unicode(trouble_ticket),
583
                        text="Changed trouble ticket from '%(from)s' "
584
                             "to '%(to)s'" % {
585
                            'from': event.trouble_ticket,
586
                            'to': trouble_ticket,
587
                        },
588
                        username=user.user_name,
589
                        timestamp=datetime.now(),
590
                    )
591
                DBSession.add(history)   
592
                event.trouble_ticket = trouble_ticket
593

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

    
608
                    history = EventHistory(
609
                            type_action="Forced change state",
610
                            idevent=event.idcause,
611
                            value=u'OK',
612
                            text="Forced state to 'OK'",
613
                            username=user.user_name,
614
                            timestamp=datetime.now(),
615
                        )
616
                    DBSession.add(history)
617

    
618
                history = EventHistory(
619
                        type_action="Acknowledgement change state",
620
                        idevent=event.idcause,
621
                        value=ack,
622
                        text="Changed acknowledgement status "
623
                            "from '%s' to '%s'" % (
624
                            event.status, changed_ack
625
                        ),
626
                        username=user.user_name,
627
                        timestamp=datetime.now(),
628
                    )
629
                DBSession.add(history)
630
                event.status = changed_ack
631

    
632
        DBSession.flush()
633
        flash(_('Updated successfully'))
634
        redirect(request.environ.get('HTTP_REFERER', '/'))
635

    
636

    
637
    class GetPluginValueSchema(schema.Schema):
638
        """Schéma de validation de la méthode get_plugin_value."""
639
        idcorrevent = validators.Int(not_empty=True)
640
        plugin_name = validators.OneOf(
641
            [unicode(i[0]) for i in config.get('vigiboard_plugins', [])],
642
            not_empty=True, hideList=True)
643
        # Permet de passer des paramètres supplémentaires au plugin.
644
        allow_extra_fields = True
645

    
646
    @validate(
647
        validators=GetPluginValueSchema(),
648
        error_handler = handle_validation_errors_json)
649
    @expose('json')
650
    @require(access_restriction)
651
    def get_plugin_value(self, idcorrevent, plugin_name, *arg, **krgv):
652
        """
653
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
654
        donné via JSON.
655
        """
656
        plugins = config.get('vigiboard_plugins', {})
657
        # Permet de vérifier si l'utilisateur a bien les permissions
658
        # pour accéder à cet événement et si l'événement existe.
659
        user = get_current_user()
660
        events = VigiboardRequest(user, False)
661
        events.add_table(CorrEvent.idcorrevent)
662
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
663
        events.add_join((events.items, 
664
            Event.idsupitem == events.items.c.idsupitem))
665
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
666

    
667
        # Pas d'événement ou permission refusée. On ne distingue pas
668
        # les 2 cas afin d'éviter la divulgation d'informations.
669
        if not events.num_rows():
670
            raise HTTPNotFound(_('No such incident or insufficient '
671
                                'permissions'))
672

    
673
        plugin_class = [p[1] for p in plugins if p[0] == plugin_name]
674
        if not plugin_class:
675
            raise HTTPNotFound(_('No such plugin'))
676

    
677
        plugin_class = plugin_class[0]
678
        try:
679
            mypac = __import__(
680
                'vigiboard.controllers.plugins.' + plugin_name,
681
                globals(), locals(), [plugin_class], -1)
682
            plugin = getattr(mypac, plugin_class)
683
            if callable(plugin):
684
                return plugin().get_value(idcorrevent, *arg, **krgv)
685
            raise HTTPInternalServerError(_('Not a valid plugin'))
686
        except ImportError:
687
            raise HTTPInternalServerError(_('Plugin could not be loaded'))
688

    
689
    @validate(validators={
690
        "fontsize": validators.Regex(
691
            r'[0-9]+(pt|px|em|%)',
692
            regexOps = ('I',)
693
        )}, error_handler = handle_validation_errors_json)
694
    @expose('json')
695
    def set_fontsize(self, fontsize):
696
        """Enregistre la taille de la police dans les préférences."""
697
        session['fontsize'] = fontsize
698
        session.save()
699
        return dict()
700

    
701
    @validate(validators={"refresh": validators.Int()},
702
            error_handler = handle_validation_errors_json)
703
    @expose('json')
704
    def set_refresh(self, refresh):
705
        """Enregistre le temps de rafraichissement dans les préférences."""
706
        session['refresh'] = refresh
707
        session.save()
708
        return dict()
709

    
710
    @expose('json')
711
    def set_theme(self, theme):
712
        """Enregistre le thème à utiliser dans les préférences."""
713
        # On sauvegarde l'ID du thème sans vérifications
714
        # car les thèmes (styles CSS) sont définies dans
715
        # les packages de thèmes (ex: vigilo-themes-default).
716
        # La vérification de la valeur est faite dans les templates.
717
        session['theme'] = theme
718
        session.save()
719
        return dict()
720

    
721
    @require(access_restriction)
722
    @expose('json')
723
    def get_groups(self):
724
        hierarchy = []
725
        user = get_current_user()
726
        groups = DBSession.query(
727
                    SupItemGroup.idgroup,
728
                    SupItemGroup.name,
729
                    GroupHierarchy.idparent,
730
                ).join(
731
                    (GroupHierarchy, GroupHierarchy.idchild == \
732
                        SupItemGroup.idgroup),
733
                ).filter(GroupHierarchy.hops <= 1
734
                ).order_by(GroupHierarchy.hops.asc()
735
                ).order_by(SupItemGroup.name.asc())
736

    
737
        is_manager = in_group('managers').is_met(request.environ)
738
        if not is_manager:
739
            user_groups = [ug[0] for ug in user.supitemgroups() if ug[1]]
740
            groups = groups.filter(SupItemGroup.idgroup.in_(user_groups))
741

    
742
        def find_parent(idgroup):
743
            def __find_parent(hier, idgroup):
744
                for g in hier:
745
                    if g['idgroup'] == idgroup:
746
                        return g['children']
747
                for g in hier:
748
                    res = __find_parent(g['children'], idgroup)
749
                    if res:
750
                        return res
751
                return None
752
            parent = __find_parent(hierarchy, idgroup)
753
            if parent is None:
754
                return hierarchy
755
            return parent
756

    
757
        for g in groups.all():
758
            parent = find_parent(g.idparent)
759
            for item in hierarchy:
760
                if item['idgroup'] == g.idgroup:
761
                    parent.append(item)
762
                    hierarchy.remove(item)
763
                    break
764
            else:
765
                parent.append({
766
                    'idgroup': g.idgroup,
767
                    'name': g.name,
768
                    'children': [],
769
                })
770

    
771
        return dict(groups=hierarchy)
772

    
773
def get_last_modification_timestamp(event_id_list, 
774
                                    value_if_none=datetime.now()):
775
    """
776
    Récupère le timestamp de la dernière modification 
777
    opérée sur l'un des événements dont l'identifiant
778
    fait partie de la liste passée en paramètre.
779
    """
780
    last_modification_timestamp = DBSession.query(
781
                                func.max(EventHistory.timestamp),
782
                         ).filter(EventHistory.idevent.in_(event_id_list)
783
                         ).scalar()
784
    if not last_modification_timestamp:
785
        if not value_if_none:
786
            return None
787
        else:
788
            last_modification_timestamp = value_if_none
789
    return datetime.fromtimestamp(mktime(
790
        last_modification_timestamp.timetuple()))
791

    
792
def get_plugins_instances():
793
    """
794
    Renvoie une liste d'instances de plugins pour VigiBoard.
795

796
    @return: Liste de tuples contenant le nom du plugin
797
        et l'instance associée.
798
    @rtype: C{list} of C{tuple}
799
    """
800
    plugins = config.get('vigiboard_plugins', [])
801
    plugins_instances = []
802
    for (plugin_name, plugin_class) in plugins:
803
        try:
804
            mypac = __import__(
805
                'vigiboard.controllers.plugins.' + plugin_name,
806
                globals(), locals(), [plugin_class], -1)
807
            plugin = getattr(mypac, plugin_class)
808
            if callable(plugin):
809
                plugins_instances.append((plugin_name, plugin()))
810
        except ImportError:
811
            pass
812
    return plugins_instances
813