Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 88c9eb8e

History | View | Annotate | Download (28.8 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 validators, 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.functions import sql_escape_like
26
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE
27

    
28
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController
29
from vigilo.turbogears.helpers import get_current_user
30

    
31
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
32
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
33
from vigiboard.widgets.edit_event import edit_event_status_options
34

    
35
__all__ = ('RootController', 'get_last_modification_timestamp', 
36
           'date_to_timestamp')
37

    
38
# pylint: disable-msg=R0201
39
class RootController(VigiboardRootController):
40
    """
41
    Le controller général de vigiboard
42
    """
43
    autocomplete = AutoCompleteController()
44

    
45
    # Prédicat pour la restriction de l'accès aux interfaces.
46
    # L'utilisateur doit avoir la permission "vigiboard-read"
47
    # ou appartenir au groupe "managers" pour accéder à VigiBoard.
48
    access_restriction = All(
49
        not_anonymous(msg=l_("You need to be authenticated")),
50
        Any(in_group('managers'),
51
            has_permission('vigiboard-read'),
52
            msg=l_("You don't have read access to VigiBoard"))
53
    )
54

    
55
    def process_form_errors(self, *argv, **kwargv):
56
        """
57
        Gestion des erreurs de validation : On affiche les erreurs
58
        puis on redirige vers la dernière page accédée.
59
        """
60
        for k in tmpl_context.form_errors:
61
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
62
        redirect(request.environ.get('HTTP_REFERER', '/'))
63

    
64
    @expose('json')
65
    def handle_validation_errors_json(self, *args, **kwargs):
66
        kwargs['errors'] = tmpl_context.form_errors
67
        return dict(kwargs)
68
    
69
    class DefaultSchema(schema.Schema):
70
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
71
        supitemgroup = validators.String(if_missing=None)
72
        host = validators.String(if_missing=None)
73
        service = validators.String(if_missing=None)
74
        output = validators.String(if_missing=None)
75
        trouble_ticket = validators.String(if_missing=None)
76
        from_date = validators.String(if_missing=None)
77
        to_date = validators.String(if_missing=None)
78

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

92
        @param page: Numéro de la page souhaitée, commence à 1
93
        @param host: Si l'utilisateur souhaite sélectionner seulement certains
94
                     événements suivant leur hôte, il peut placer une expression
95
                     ici en suivant la structure du LIKE en SQL
96
        @param service: Idem que host mais sur les services
97
        @param output: Idem que host mais sur le text explicatif
98
        @param trouble_ticket: Idem que host mais sur les tickets attribués
99

100
        Cette méthode permet de satisfaire les exigences suivantes : 
101
        - VIGILO_EXIG_VIGILO_BAC_0040, 
102
        - VIGILO_EXIG_VIGILO_BAC_0070,
103
        - VIGILO_EXIG_VIGILO_BAC_0100,
104
        """
105
        user = get_current_user()
106
        aggregates = VigiboardRequest(user)
107
        aggregates.add_table(
108
            CorrEvent,
109
            aggregates.items.c.hostname,
110
            aggregates.items.c.servicename
111
        )
112
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
113
        aggregates.add_join((aggregates.items, 
114
            Event.idsupitem == aggregates.items.c.idsupitem))
115
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
116
        
117
        search = {
118
            'host': '',
119
            'service': '',
120
            'output': '',
121
            'tt': '',
122
            'from_date': '',
123
            'to_date': '',
124
            'supitemgroup': '',
125
        }
126

    
127
        # Application des filtres si nécessaire
128
        if supitemgroup:
129
            search['supitemgroup'] = supitemgroup
130
            supitemgroup = sql_escape_like(supitemgroup)
131
            aggregates.add_join((SupItemGroup, SupItemGroup.idgroup == \
132
                aggregates.items.c.idsupitemgroup))
133
            aggregates.add_filter(
134
                SupItemGroup.name.ilike('%%%s%%' % supitemgroup))
135

    
136
        if host:
137
            search['host'] = host
138
            host = sql_escape_like(host)
139
            aggregates.add_filter(aggregates.items.c.hostname.ilike(
140
                '%%%s%%' % host))
141

    
142
        if service:
143
            search['service'] = service
144
            service = sql_escape_like(service)
145
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
146
                '%%%s%%' % service))
147

    
148
        if output:
149
            search['output'] = output
150
            output = sql_escape_like(output)
151
            aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
152

    
153
        if trouble_ticket:
154
            search['tt'] = trouble_ticket
155
            trouble_ticket = sql_escape_like(trouble_ticket)
156
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
157
                '%%%s%%' % trouble_ticket))
158

    
159
        if from_date:
160
            search['from_date'] = from_date.lower()
161
            try:
162
                # TRANSLATORS: Format de date et heure.
163
                from_date = datetime.strptime(
164
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
165
            except ValueError:
166
                # On ignore silencieusement la date invalide reçue.
167
                pass
168
            else:
169
                aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
170

    
171
        if to_date:
172
            search['to_date'] = to_date.lower()
173
            try:
174
                # TRANSLATORS: Format de date et heure.
175
                to_date = datetime.strptime(
176
                    to_date, _('%Y-%m-%d %I:%M:%S %p'))
177
            except ValueError:
178
                # On ignore silencieusement la date invalide reçue.
179
                pass
180
            else:
181
                aggregates.add_filter(CorrEvent.timestamp_active <= to_date)
182

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

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

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

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

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

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

    
221

    
222
    class MaskedEventsSchema(schema.Schema):
223
        idcorrevent = validators.Int(not_empty=True)
224
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
225

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

236
        @param idevent: identifiant de l'événement souhaité
237
        """
238
        user = get_current_user()
239

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

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

    
275
        if isinstance(cause_supitem, LowLevelService):
276
            hostname = cause_supitem.host.name
277
            servicename = cause_supitem.servicename
278
        elif isinstance(cause_supitem, Host):
279
            hostname = cause_supitem.name
280

    
281
        # Vérification que l'événement existe
282
        total_rows = events.num_rows()
283
        if total_rows < 1:
284
            flash(_('No masked event or access denied'), 'error')
285
            redirect('/')
286

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

    
290
        id_first_row = items_per_page * (page-1)
291
        id_last_row = min(id_first_row + items_per_page, total_rows)
292

    
293
        events.format_events(id_first_row, id_last_row)
294
        events.generate_tmpl_context()
295

    
296
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
297
        if not total_rows:
298
            id_first_row = 0
299
        else:
300
            id_first_row += 1
301

    
302
        return dict(
303
            idcorrevent = idcorrevent,
304
            hostname = hostname,
305
            servicename = servicename,
306
            events = events.events,
307
            plugins = get_plugins_instances(),
308
            rows_info = {
309
                'id_first_row': id_first_row,
310
                'id_last_row': id_last_row,
311
                'total_rows': total_rows,
312
            },
313
            nb_pages = nb_pages,
314
            page = page,
315
            search = {
316
                'host': '',
317
                'service': '',
318
                'output': '',
319
                'tt': '',
320
                'from_date': '',
321
                'to_date': '',
322
                'supitemgroup': '',
323
            },
324
           refresh_times=config['vigiboard_refresh_times'],
325
        )
326

    
327

    
328
    class EventSchema(schema.Schema):
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

345
        Cette méthode permet de satisfaire
346
        l'exigence VIGILO_EXIG_VIGILO_BAC_0080.
347
        """
348
        if not page:
349
            page = 1
350

    
351
        user = get_current_user()
352
        events = VigiboardRequest(user, False)
353
        events.add_table(
354
            Event,
355
            events.items.c.hostname.label('hostname'),
356
            events.items.c.servicename.label('servicename'),
357
        )
358
        events.add_join((EVENTSAGGREGATE_TABLE, \
359
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
360
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
361
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
362
        events.add_join((events.items, 
363
            Event.idsupitem == events.items.c.idsupitem))
364
        events.add_filter(Event.idevent == idevent)
365

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

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

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

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

    
380
        history_entries = history[id_first_row : id_last_row]
381

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

    
388
        event = events.req[0]
389

    
390
        return dict(
391
            idevent = idevent,
392
            hostname = event.hostname,
393
            servicename = event.servicename,
394
            plugins = get_plugins_instances(),
395
            rows_info = {
396
                'id_first_row': id_first_row,
397
                'id_last_row': id_last_row,
398
                'total_rows': total_rows,
399
            },
400
            nb_pages = nb_pages,
401
            page = page,
402
            history = history_entries,
403
            search = {
404
                'host': '',
405
                'service': '',
406
                'output': '',
407
                'tt': '',
408
                'from_date': '',
409
                'to_date': '',
410
                'supitemgroup': '',
411
            },
412
           refresh_times=config['vigiboard_refresh_times'],
413
        )
414

    
415

    
416
    class ItemSchema(schema.Schema):
417
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
418
        host = validators.String(not_empty=True)
419
        service = validators.String(if_missing=None)
420

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

432
        @param host: Nom de l'hôte souhaité.
433
        @param service: Nom du service souhaité
434

435
        Cette méthode permet de satisfaire
436
        l'exigence VIGILO_EXIG_VIGILO_BAC_0080.
437
        """
438
        idsupitem = SupItem.get_supitem(host, service)
439

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

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

    
458
        items_per_page = int(config['vigiboard_items_per_page'])
459

    
460
        id_first_row = items_per_page * (page-1)
461
        id_last_row = min(id_first_row + items_per_page, total_rows)
462

    
463
        aggregates.format_events(id_first_row, id_last_row)
464
        aggregates.generate_tmpl_context()
465

    
466
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
467
        if not total_rows:
468
            id_first_row = 0
469
        else:
470
            id_first_row += 1
471
        
472
        return dict(
473
            hostname = host,
474
            servicename = service,
475
            events = aggregates.events,
476
            plugins = get_plugins_instances(),
477
            rows_info = {
478
                'id_first_row': id_first_row,
479
                'id_last_row': id_last_row,
480
                'total_rows': total_rows,
481
            },
482
            nb_pages = nb_pages,
483
            page = page,
484
            event_edit_status_options = edit_event_status_options,
485
            search = {
486
                'host': '',
487
                'service': '',
488
                'output': '',
489
                'tt': '',
490
                'from_date': '',
491
                'to_date': '',
492
                'supitemgroup': '',
493
            },
494
            refresh_times=config['vigiboard_refresh_times'],
495
        )
496

    
497

    
498
    class UpdateSchema(schema.Schema):
499
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
500
        last_modification = validators.Number(not_empty=True)
501
        trouble_ticket = validators.String(if_missing='')
502
        ack = validators.OneOf(
503
            [unicode(s[0]) for s in edit_event_status_options],
504
            not_empty=True)
505

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

528
        Cette méthode permet de satisfaire les exigences suivantes : 
529
        - VIGILO_EXIG_VIGILO_BAC_0020,
530
        - VIGILO_EXIG_VIGILO_BAC_0060,
531
        - VIGILO_EXIG_VIGILO_BAC_0110.
532
        """
533

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

    
540
        # Le filtre permet d'éliminer les chaines vides contenues dans le
541
        # tableau ('a,b,' -> split -> ['a','b',''] -> filter -> ['a','b']).
542
        ids = map(int, filter(len, id.split(',')))
543

    
544
        # Si l'utilisateur édite plusieurs événements à la fois,
545
        # il nous faut chacun des identifiants
546

    
547
        user = get_current_user()
548
        events = VigiboardRequest(user)
549
        events.add_table(CorrEvent)
550
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
551
        events.add_join((events.items, 
552
            Event.idsupitem == events.items.c.idsupitem))
553
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
554
        
555
        events.generate_request()
556
        idevents = [cause.idcause for cause in events.req]
557

    
558
        # Si des changements sont survenus depuis que la 
559
        # page est affichée, on en informe l'utilisateur.
560
        last_modification = datetime.fromtimestamp(last_modification)
561
        cur_last_modification = get_last_modification_timestamp(idevents, None)
562
        if cur_last_modification and last_modification < cur_last_modification:
563
            flash(_('Changes have occurred since the page was last displayed, '
564
                    'your changes HAVE NOT been saved.'), 'warning')
565
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
566

    
567
        # Vérification que au moins un des identifiants existe et est éditable
568
        if not events.num_rows():
569
            flash(_('No access to this event'), 'error')
570
            redirect('/')
571
        
572
        # Modification des événements et création d'un historique
573
        # pour chacun d'eux.
574
        for req in events.req:
575
            event = req
576

    
577
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
578
                history = EventHistory(
579
                        type_action="Ticket change",
580
                        idevent=event.idcause,
581
                        value=unicode(trouble_ticket),
582
                        text="Changed trouble ticket from '%s' to '%s'" % (
583
                            event.trouble_ticket, trouble_ticket
584
                        ),
585
                        username=user.user_name,
586
                        timestamp=datetime.now(),
587
                    )
588
                DBSession.add(history)   
589
                event.trouble_ticket = trouble_ticket
590

    
591
            # Changement du statut d'acquittement.
592
            if ack != u'NoChange':
593
                # Pour forcer l'acquittement d'un événement,
594
                # il faut en plus avoir la permission
595
                # "vigiboard-admin".
596
                if ack == u'Forced':
597
                    condition = Any(
598
                        in_group('managers'),
599
                        has_permission('vigiboard-admin'),
600
                        msg=l_("You don't have administrative access "
601
                                "to VigiBoard"))
602
                    try:
603
                        condition.check_authorization(request.environ)
604
                    except NotAuthorizedError, e:
605
                        reason = unicode(e)
606
                        flash(reason, 'error')
607
                        raise redirect(request.environ.get('HTTP_REFERER', '/'))
608
                    else:
609
                        ack = u'AAClosed'
610
                        # On met systématiquement l'état à "OK", même s'il
611
                        # s'agit d'un hôte. Techniquement, c'est incorrect,
612
                        # mais comme on fait ça pour masquer l'événement...
613
                        event.cause.current_state = \
614
                            StateName.statename_to_value(u'OK')
615

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

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

    
634

    
635
    class GetPluginValueSchema(schema.Schema):
636
        idcorrevent = validators.Int(not_empty=True)
637
        plugin_name = validators.OneOf(
638
            [unicode(i[0]) for i in config.get('vigiboard_plugins', [])],
639
            not_empty=True, hideList=True)
640
        # Permet de passer des paramètres supplémentaires au plugin.
641
        allow_extra_fields = True
642

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

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

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

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

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

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

    
707
    @expose('json')
708
    def set_theme(self, theme):
709
        """Enregistre le thème à utiliser dans les préférences."""
710
        # On sauvegarde l'ID du thème sans vérifications
711
        # car les thèmes (styles CSS) sont définies dans
712
        # les packages de thèmes (ex: vigilo-themes-default).
713
        # La vérification de la valeur est faite dans les templates.
714
        session['theme'] = theme
715
        session.save()
716
        return dict()
717
    
718
def get_last_modification_timestamp(event_id_list, 
719
                                    value_if_none=datetime.now()):
720
    """
721
    Récupère le timestamp de la dernière modification 
722
    opérée sur l'un des événements dont l'identifiant
723
    fait partie de la liste passée en paramètre.
724
    """
725
    last_modification_timestamp = DBSession.query(
726
                                func.max(EventHistory.timestamp),
727
                         ).filter(EventHistory.idevent.in_(event_id_list)
728
                         ).scalar()
729
    if not last_modification_timestamp:
730
        if not value_if_none:
731
            return None
732
        else:
733
            last_modification_timestamp = value_if_none
734
    return datetime.fromtimestamp(mktime(
735
        last_modification_timestamp.timetuple()))
736

    
737
def get_plugins_instances():
738
    """
739
    Renvoie une liste d'instances de plugins pour VigiBoard.
740

741
    @return: Liste de tuples contenant le nom du plugin
742
        et l'instance associée.
743
    @rtype: C{list} of C{tuple}
744
    """
745
    plugins = config.get('vigiboard_plugins', [])
746
    plugins_instances = []
747
    for (plugin_name, plugin_class) in plugins:
748
        try:
749
            mypac = __import__(
750
                'vigiboard.controllers.plugins.' + plugin_name,
751
                globals(), locals(), [plugin_class], -1)
752
            plugin = getattr(mypac, plugin_class)
753
            if callable(plugin):
754
                plugins_instances.append((plugin_name, plugin()))
755
        except ImportError:
756
            pass
757
    return plugins_instances
758