Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ ea0e5dfb

History | View | Annotate | Download (31.1 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.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
from vigiboard.widgets.edit_event import edit_event_status_options
36

    
37
__all__ = ('RootController', 'get_last_modification_timestamp', 
38
           'date_to_timestamp')
39

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

    
49

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

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

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

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

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

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

    
133
        # Application des filtres si nécessaire
134
        if supitemgroup:
135
            search['supitemgroup'] = supitemgroup
136
            supitemgroup = sql_escape_like(supitemgroup)
137
            aggregates.add_join((SupItemGroup, SupItemGroup.idgroup == \
138
                aggregates.items.c.idsupitemgroup))
139
            aggregates.add_filter(
140
                SupItemGroup.name.ilike('%s' % supitemgroup))
141

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

    
148
        if service:
149
            search['service'] = service
150
            service = sql_escape_like(service)
151
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
152
                '%s' % service))
153

    
154
        if output:
155
            search['output'] = output
156
            output = sql_escape_like(output)
157
            aggregates.add_filter(Event.message.ilike('%s' % output))
158

    
159
        if trouble_ticket:
160
            search['tt'] = trouble_ticket
161
            trouble_ticket = sql_escape_like(trouble_ticket)
162
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
163
                '%s' % trouble_ticket))
164

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

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

    
193
        # Calcul des éléments à afficher et du nombre de pages possibles
194
        total_rows = aggregates.num_rows()
195
        items_per_page = int(config['vigiboard_items_per_page'])
196

    
197
        # Si le numéro de page dépasse le nombre de pages existantes,
198
        # on redirige automatiquement vers la 1ère page.
199
        if total_rows and items_per_page * (page-1) > total_rows:
200
            redirect('/', page=1, **search)
201

    
202
        id_first_row = items_per_page * (page-1)
203
        id_last_row = min(id_first_row + items_per_page, total_rows)
204

    
205
        aggregates.format_events(id_first_row, id_last_row)
206
        aggregates.generate_tmpl_context()
207

    
208
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
209
        if not total_rows:
210
            id_first_row = 0
211
        else:
212
            id_first_row += 1
213

    
214
        return dict(
215
            hostname = None,
216
            servicename = None,
217
            events = aggregates.events,
218
            plugins = get_plugins_instances(),
219
            rows_info = {
220
                'id_first_row': id_first_row,
221
                'id_last_row': id_last_row,
222
                'total_rows': total_rows,
223
            },
224
            nb_pages = nb_pages,
225
            page = page,
226
            event_edit_status_options = edit_event_status_options,
227
            search = search,
228
            refresh_times = config['vigiboard_refresh_times'],
229
        )
230

    
231

    
232
    class MaskedEventsSchema(schema.Schema):
233
        """Schéma de validation de la méthode masked_events."""
234
        idcorrevent = validators.Int(not_empty=True)
235
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
236

    
237
    @validate(
238
        validators=MaskedEventsSchema(),
239
        error_handler = process_form_errors)
240
    @expose('raw_events_table.html')
241
    @require(access_restriction)
242
    def masked_events(self, idcorrevent, page):
243
        """
244
        Affichage de la liste des événements bruts masqués d'un événement
245
        corrélé (événements agrégés dans l'événement corrélé).
246

247
        @param idcorrevent: identifiant de l'événement corrélé souhaité.
248
        @type idcorrevent: C{int}
249
        """
250
        user = get_current_user()
251

    
252
        # Récupère la liste des événements masqués de l'événement
253
        # corrélé donné par idcorrevent.
254
        events = VigiboardRequest(user, False)
255
        events.add_table(
256
            Event,
257
            events.items.c.hostname,
258
            events.items.c.servicename,
259
        )
260
        events.add_join((EVENTSAGGREGATE_TABLE, \
261
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
262
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
263
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
264
        events.add_join((events.items, 
265
            Event.idsupitem == events.items.c.idsupitem))
266
        events.add_filter(Event.idevent != CorrEvent.idcause)
267
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
268

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

    
287
        if isinstance(cause_supitem, LowLevelService):
288
            hostname = cause_supitem.host.name
289
            servicename = cause_supitem.servicename
290
        elif isinstance(cause_supitem, Host):
291
            hostname = cause_supitem.name
292

    
293
        # Vérification que l'événement existe
294
        total_rows = events.num_rows()
295
        if total_rows < 1:
296
            flash(_('No masked event or access denied'), 'error')
297
            redirect('/')
298

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

    
302
        id_first_row = items_per_page * (page-1)
303
        id_last_row = min(id_first_row + items_per_page, total_rows)
304

    
305
        events.format_events(id_first_row, id_last_row)
306
        events.generate_tmpl_context()
307

    
308
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
309
        if not total_rows:
310
            id_first_row = 0
311
        else:
312
            id_first_row += 1
313

    
314
        return dict(
315
            idcorrevent = idcorrevent,
316
            hostname = hostname,
317
            servicename = servicename,
318
            events = events.events,
319
            plugins = get_plugins_instances(),
320
            rows_info = {
321
                'id_first_row': id_first_row,
322
                'id_last_row': id_last_row,
323
                'total_rows': total_rows,
324
            },
325
            nb_pages = nb_pages,
326
            page = page,
327
            search = {
328
                'host': '',
329
                'service': '',
330
                'output': '',
331
                'tt': '',
332
                'from_date': '',
333
                'to_date': '',
334
                'supitemgroup': '',
335
            },
336
           refresh_times=config['vigiboard_refresh_times'],
337
        )
338

    
339

    
340
    class EventSchema(schema.Schema):
341
        """Schéma de validation de la méthode event."""
342
        idevent = validators.Int(not_empty=True)
343
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
344

    
345
    @validate(
346
        validators=EventSchema(),
347
        error_handler = process_form_errors)
348
    @expose('history_table.html')
349
    @require(access_restriction)
350
    def event(self, idevent, page):
351
        """
352
        Affichage de l'historique d'un événement brut.
353
        Pour accéder à cette page, l'utilisateur doit être authentifié.
354

355
        @param idevent: identifiant de l'événement brut souhaité.
356
        @type idevent: C{int}
357
        @param page: numéro de la page à afficher.
358
        @type page: C{int}
359

360
        Cette méthode permet de satisfaire l'exigence
361
        VIGILO_EXIG_VIGILO_BAC_0080.
362
        """
363
        user = get_current_user()
364
        events = VigiboardRequest(user, False)
365
        events.add_table(
366
            Event,
367
            events.items.c.hostname.label('hostname'),
368
            events.items.c.servicename.label('servicename'),
369
        )
370
        events.add_join((EVENTSAGGREGATE_TABLE, \
371
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
372
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
373
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
374
        events.add_join((events.items, 
375
            Event.idsupitem == events.items.c.idsupitem))
376
        events.add_filter(Event.idevent == idevent)
377

    
378
        if events.num_rows() != 1:
379
            flash(_('No such event or access denied'), 'error')
380
            redirect('/')
381

    
382
        events.format_events(0, 1)
383
        events.generate_tmpl_context()
384
        history = events.format_history()
385

    
386
        total_rows = history.count()
387
        items_per_page = int(config['vigiboard_items_per_page'])
388

    
389
        id_first_row = items_per_page * (page-1)
390
        id_last_row = min(id_first_row + items_per_page, total_rows)
391

    
392
        history_entries = history[id_first_row : id_last_row]
393

    
394
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
395
        if not total_rows:
396
            id_first_row = 0
397
        else:
398
            id_first_row += 1
399

    
400
        event = events.req[0]
401

    
402
        return dict(
403
            idevent = idevent,
404
            hostname = event.hostname,
405
            servicename = event.servicename,
406
            plugins = get_plugins_instances(),
407
            rows_info = {
408
                'id_first_row': id_first_row,
409
                'id_last_row': id_last_row,
410
                'total_rows': total_rows,
411
            },
412
            nb_pages = nb_pages,
413
            page = page,
414
            history = history_entries,
415
            search = {
416
                'host': '',
417
                'service': '',
418
                'output': '',
419
                'tt': '',
420
                'from_date': '',
421
                'to_date': '',
422
                'supitemgroup': '',
423
            },
424
           refresh_times=config['vigiboard_refresh_times'],
425
        )
426

    
427

    
428
    class ItemSchema(schema.Schema):
429
        """Schéma de validation de la méthode item."""
430
        page = validators.Int(min=1, if_missing=1, if_invalid=1)
431
        host = validators.String(not_empty=True)
432
        service = validators.String(if_missing=None)
433

    
434
    @validate(
435
        validators=ItemSchema(),
436
        error_handler = process_form_errors)
437
    @expose('events_table.html')
438
    @require(access_restriction)
439
    def item(self, page, host, service):
440
        """
441
        Affichage de l'historique de l'ensemble des événements corrélés
442
        jamais ouverts sur l'hôte / service demandé.
443
        Pour accéder à cette page, l'utilisateur doit être authentifié.
444

445
        @param page: Numéro de la page à afficher.
446
        @param host: Nom de l'hôte souhaité.
447
        @param service: Nom du service souhaité
448

449
        Cette méthode permet de satisfaire l'exigence
450
        VIGILO_EXIG_VIGILO_BAC_0080.
451
        """
452
        idsupitem = SupItem.get_supitem(host, service)
453

    
454
        user = get_current_user()
455
        aggregates = VigiboardRequest(user, False)
456
        aggregates.add_table(
457
            CorrEvent,
458
            aggregates.items.c.hostname,
459
            aggregates.items.c.servicename,
460
        )
461
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
462
        aggregates.add_join((aggregates.items, 
463
            Event.idsupitem == aggregates.items.c.idsupitem))
464
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
465

    
466
        # Vérification qu'il y a au moins 1 événement qui correspond
467
        total_rows = aggregates.num_rows()
468
        if not total_rows:
469
            flash(_('No access to this host/service or no event yet'), 'error')
470
            redirect('/')
471

    
472
        items_per_page = int(config['vigiboard_items_per_page'])
473

    
474
        id_first_row = items_per_page * (page-1)
475
        id_last_row = min(id_first_row + items_per_page, total_rows)
476

    
477
        aggregates.format_events(id_first_row, id_last_row)
478
        aggregates.generate_tmpl_context()
479

    
480
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
481
        if not total_rows:
482
            id_first_row = 0
483
        else:
484
            id_first_row += 1
485
        
486
        return dict(
487
            hostname = host,
488
            servicename = service,
489
            events = aggregates.events,
490
            plugins = get_plugins_instances(),
491
            rows_info = {
492
                'id_first_row': id_first_row,
493
                'id_last_row': id_last_row,
494
                'total_rows': total_rows,
495
            },
496
            nb_pages = nb_pages,
497
            page = page,
498
            event_edit_status_options = edit_event_status_options,
499
            search = {
500
                'host': '',
501
                'service': '',
502
                'output': '',
503
                'tt': '',
504
                'from_date': '',
505
                'to_date': '',
506
                'supitemgroup': '',
507
            },
508
            refresh_times=config['vigiboard_refresh_times'],
509
        )
510

    
511

    
512
    class UpdateSchema(schema.Schema):
513
        """Schéma de validation de la méthode update."""
514
        id = validators.Regex(r'^[0-9]+(,[0-9]+)*,?$')
515
        last_modification = validators.Number(not_empty=True)
516
        trouble_ticket = validators.String(if_missing='')
517
        ack = validators.OneOf(
518
            [unicode(s[0]) for s in edit_event_status_options],
519
            not_empty=True)
520

    
521
    @validate(
522
        validators=UpdateSchema(),
523
        error_handler = process_form_errors)
524
    @require(
525
        All(
526
            not_anonymous(msg=l_("You need to be authenticated")),
527
            Any(in_group('managers'),
528
                has_permission('vigiboard-update'),
529
                msg=l_("You don't have write access to VigiBoard"))
530
        ))
531
    @expose()
532
    def update(self, id, last_modification, trouble_ticket, ack):
533
        """
534
        Mise à jour d'un événement suivant les arguments passés.
535
        Cela peut être un changement de ticket ou un changement de statut.
536
        
537
        @param id: Le ou les identifiants des événements à traiter
538
        @param last_modification: La date de la dernière modification
539
            dont l'utilisateur est au courant.
540
        @param trouble_ticket: Nouveau numéro du ticket associé.
541
        @param ack: Nouvel état d'acquittement des événements sélectionnés.
542

543
        Cette méthode permet de satisfaire les exigences suivantes : 
544
            - VIGILO_EXIG_VIGILO_BAC_0020,
545
            - VIGILO_EXIG_VIGILO_BAC_0060,
546
            - VIGILO_EXIG_VIGILO_BAC_0110.
547
        """
548

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

    
555
        # On récupère la liste de tous les identifiants des événements
556
        # à mettre à jour.
557
        ids = map(int, id.strip(',').split(','))
558

    
559
        user = get_current_user()
560
        events = VigiboardRequest(user)
561
        events.add_table(CorrEvent)
562
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
563
        events.add_join((events.items, 
564
            Event.idsupitem == events.items.c.idsupitem))
565
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
566
        
567
        events.generate_request()
568
        idevents = [cause.idcause for cause in events.req]
569

    
570
        # Si des changements sont survenus depuis que la 
571
        # page est affichée, on en informe l'utilisateur.
572
        last_modification = datetime.fromtimestamp(last_modification)
573
        cur_last_modification = get_last_modification_timestamp(idevents, None)
574
        if cur_last_modification and last_modification < cur_last_modification:
575
            flash(_('Changes have occurred since the page was last displayed, '
576
                    'your changes HAVE NOT been saved.'), 'warning')
577
            raise redirect(request.environ.get('HTTP_REFERER', '/'))
578

    
579
        # Vérification que au moins un des identifiants existe et est éditable
580
        if not events.num_rows():
581
            flash(_('No access to this event'), 'error')
582
            redirect('/')
583

    
584
        if ack == u'Forced':
585
            condition = Any(
586
                in_group('managers'),
587
                has_permission('vigiboard-admin'),
588
                msg=l_("You don't have administrative access "
589
                        "to VigiBoard"))
590
            try:
591
                condition.check_authorization(request.environ)
592
            except NotAuthorizedError, e:
593
                reason = unicode(e)
594
                flash(reason, 'error')
595
                raise redirect(request.environ.get('HTTP_REFERER', '/'))
596

    
597
        # Modification des événements et création d'un historique
598
        # chaque fois que cela est nécessaire.
599
        for event in events.req:
600
            if trouble_ticket and trouble_ticket != event.trouble_ticket:
601
                history = EventHistory(
602
                        type_action="Ticket change",
603
                        idevent=event.idcause,
604
                        value=unicode(trouble_ticket),
605
                        text="Changed trouble ticket from '%(from)s' "
606
                             "to '%(to)s'" % {
607
                            'from': event.trouble_ticket,
608
                            'to': trouble_ticket,
609
                        },
610
                        username=user.user_name,
611
                        timestamp=datetime.now(),
612
                    )
613
                DBSession.add(history)   
614
                event.trouble_ticket = trouble_ticket
615

    
616
            # Changement du statut d'acquittement.
617
            if ack != u'NoChange':
618
                changed_ack = ack
619
                # Pour forcer l'acquittement d'un événement,
620
                # il faut en plus avoir la permission
621
                # "vigiboard-admin".
622
                if ack == u'Forced':
623
                    changed_ack = u'AAClosed'
624
                    # On met systématiquement l'état à "OK", même s'il
625
                    # s'agit d'un hôte. Techniquement, c'est incorrect,
626
                    # mais comme on fait ça pour masquer l'événement...
627
                    event.cause.current_state = \
628
                        StateName.statename_to_value(u'OK')
629

    
630
                    history = EventHistory(
631
                            type_action="Forced change state",
632
                            idevent=event.idcause,
633
                            value=u'OK',
634
                            text="Forced state to 'OK'",
635
                            username=user.user_name,
636
                            timestamp=datetime.now(),
637
                        )
638
                    DBSession.add(history)
639

    
640
                history = EventHistory(
641
                        type_action="Acknowledgement change state",
642
                        idevent=event.idcause,
643
                        value=ack,
644
                        text="Changed acknowledgement status "
645
                            "from '%s' to '%s'" % (
646
                            event.status, changed_ack
647
                        ),
648
                        username=user.user_name,
649
                        timestamp=datetime.now(),
650
                    )
651
                DBSession.add(history)
652
                event.status = changed_ack
653

    
654
        DBSession.flush()
655
        flash(_('Updated successfully'))
656
        redirect(request.environ.get('HTTP_REFERER', '/'))
657

    
658

    
659
    class GetPluginValueSchema(schema.Schema):
660
        """Schéma de validation de la méthode get_plugin_value."""
661
        idcorrevent = validators.Int(not_empty=True)
662
        plugin_name = validators.OneOf(
663
            [unicode(i[0]) for i in config.get('vigiboard_plugins', [])],
664
            not_empty=True, hideList=True)
665
        # Permet de passer des paramètres supplémentaires au plugin.
666
        allow_extra_fields = True
667

    
668
    @validate(
669
        validators=GetPluginValueSchema(),
670
        error_handler = handle_validation_errors_json)
671
    @expose('json')
672
    @require(access_restriction)
673
    def get_plugin_value(self, idcorrevent, plugin_name, *arg, **krgv):
674
        """
675
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
676
        donné via JSON.
677
        """
678
        plugins = config.get('vigiboard_plugins', {})
679
        # Permet de vérifier si l'utilisateur a bien les permissions
680
        # pour accéder à cet événement et si l'événement existe.
681
        user = get_current_user()
682
        events = VigiboardRequest(user, False)
683
        events.add_table(CorrEvent.idcorrevent)
684
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
685
        events.add_join((events.items, 
686
            Event.idsupitem == events.items.c.idsupitem))
687
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
688

    
689
        # Pas d'événement ou permission refusée. On ne distingue pas
690
        # les 2 cas afin d'éviter la divulgation d'informations.
691
        if not events.num_rows():
692
            raise HTTPNotFound(_('No such incident or insufficient '
693
                                'permissions'))
694

    
695
        plugin_class = [p[1] for p in plugins if p[0] == plugin_name]
696
        if not plugin_class:
697
            raise HTTPNotFound(_('No such plugin'))
698

    
699
        plugin_class = plugin_class[0]
700
        try:
701
            mypac = __import__(
702
                'vigiboard.controllers.plugins.' + plugin_name,
703
                globals(), locals(), [plugin_class], -1)
704
            plugin = getattr(mypac, plugin_class)
705
            if callable(plugin):
706
                return plugin().get_value(idcorrevent, *arg, **krgv)
707
            raise HTTPInternalServerError(_('Not a valid plugin'))
708
        except ImportError:
709
            raise HTTPInternalServerError(_('Plugin could not be loaded'))
710

    
711
    @validate(validators={
712
        "fontsize": validators.Regex(
713
            r'[0-9]+(pt|px|em|%)',
714
            regexOps = ('I',)
715
        )}, error_handler = handle_validation_errors_json)
716
    @expose('json')
717
    def set_fontsize(self, fontsize):
718
        """Enregistre la taille de la police dans les préférences."""
719
        session['fontsize'] = fontsize
720
        session.save()
721
        return dict()
722

    
723
    @validate(validators={"refresh": validators.Int()},
724
            error_handler = handle_validation_errors_json)
725
    @expose('json')
726
    def set_refresh(self, refresh):
727
        """Enregistre le temps de rafraichissement dans les préférences."""
728
        session['refresh'] = refresh
729
        session.save()
730
        return dict()
731

    
732
    @expose('json')
733
    def set_theme(self, theme):
734
        """Enregistre le thème à utiliser dans les préférences."""
735
        # On sauvegarde l'ID du thème sans vérifications
736
        # car les thèmes (styles CSS) sont définies dans
737
        # les packages de thèmes (ex: vigilo-themes-default).
738
        # La vérification de la valeur est faite dans les templates.
739
        session['theme'] = theme
740
        session.save()
741
        return dict()
742

    
743
    @require(access_restriction)
744
    @expose('json')
745
    def get_groups(self, idgroup=None):
746
        user = get_current_user()
747
        groups = DBSession.query(
748
                    SupItemGroup.idgroup,
749
                    SupItemGroup.name,
750
                ).join(
751
                    (GroupHierarchy, GroupHierarchy.idchild == \
752
                        SupItemGroup.idgroup),
753
                )
754

    
755
        is_manager = in_group('managers').is_met(request.environ)
756
        if not is_manager:
757
            user_groups = [ug[0] for ug in user.supitemgroups() if ug[1]]
758
            groups = groups.filter(SupItemGroup.idgroup.in_(user_groups))
759

    
760
        # Cas des groupes racines (parents les plus élevés dans l'arbre).
761
        if idgroup:
762
            groups = groups.filter(GroupHierarchy.idparent == idgroup)
763
        else:
764
            groups = groups
765

    
766
        groups = [(g.idgroup, g.name) for g in groups.all()]
767
        return dict(groups=groups)
768

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

    
788
def get_plugins_instances():
789
    """
790
    Renvoie une liste d'instances de plugins pour VigiBoard.
791

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