Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ 9211ef65

History | View | Annotate | Download (24.9 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
import urllib
9

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

    
21
from vigilo.models.configure import DBSession
22
from vigilo.models import Event, EventHistory, CorrEvent, SupItem, \
23
                            HostGroup, ServiceGroup, StateName, User
24
from vigilo.models.functions import sql_escape_like
25
from vigilo.models.secondary_tables import EVENTSAGGREGATE_TABLE
26

    
27
from vigilo.turbogears.controllers.autocomplete \
28
    import make_autocomplete_controller
29
from vigiboard.controllers.vigiboardrequest import VigiboardRequest
30
from vigiboard.controllers.vigiboard_controller import VigiboardRootController
31
from vigiboard.widgets.edit_event import edit_event_status_options
32
from vigiboard.lib.base import BaseController
33

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

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

    
44
    def process_form_errors(self, *argv, **kwargv):
45
        """
46
        Gestion des erreurs de validation : On affiche les erreurs
47
        puis on redirige vers la dernière page accédée.
48
        """
49
        for k in tmpl_context.form_errors:
50
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
51
        if request.environ.get('HTTP_REFERER') :
52
            redirect(request.environ.get('HTTP_REFERER'
53
                ).split(request.environ.get('HTTP_HOST'))[1])
54
        else :
55
            redirect('/')
56

    
57
    @validate(validators={
58
            'page': validators.Int(min=1),
59
        }, error_handler=process_form_errors)
60
    @expose('events_table.html')
61
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
62
    def default(self, page=1, hostgroup=None, servicegroup=None,
63
            host=None, service=None, output=None, trouble_ticket=None,
64
            from_date=None, to_date=None, *argv, **krgv):
65
        """
66
        Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
67
        (page 1 par defaut), la liste des événements, rangés par ordre de prise
68
        en compte, puis de sévérité.
69
        Pour accéder à cette page, l'utilisateur doit être authentifié.
70

71
        @param page: Numéro de la page souhaitée, commence à 1
72
        @param host: Si l'utilisateur souhaite sélectionner seulement certains
73
                     événements suivant leur hôte, il peut placer une expression
74
                     ici en suivant la structure du LIKE en SQL
75
        @param service: Idem que host mais sur les services
76
        @param output: Idem que host mais sur le text explicatif
77
        @param trouble_ticket: Idem que host mais sur les tickets attribués
78
        """
79
        if not page:
80
            page = 1
81

    
82
        username = request.environ['repoze.who.identity']['repoze.who.userid']
83
        user = User.by_user_name(username)
84

    
85
        aggregates = VigiboardRequest(user)
86
        aggregates.add_table(
87
            CorrEvent,
88
            aggregates.items.c.hostname,
89
            aggregates.items.c.servicename
90
        )
91
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
92
        aggregates.add_join((aggregates.items, 
93
            Event.idsupitem == aggregates.items.c.idsupitem))
94
        aggregates.add_order_by(asc(aggregates.items.c.hostname))
95
        
96
        search = {
97
            'host': '',
98
            'service': '',
99
            'output': '',
100
            'tt': '',
101
            'from_date': '',
102
            'to_date': '',
103
            'hostgroup': '',
104
            'servicegroup': '',
105
        }
106

    
107
        # Application des filtres si nécessaire
108
        if hostgroup:
109
            search['hostgroup'] = hostgroup
110
            hostgroup = sql_escape_like(hostgroup)
111
            aggregates.add_join((HostGroup, HostGroup.idgroup == \
112
                aggregates.items.c.idhostgroup))
113
            aggregates.add_filter(HostGroup.name.ilike('%%%s%%' % hostgroup))
114

    
115
        if servicegroup:
116
            search['servicegroup'] = servicegroup
117
            servicegroup = sql_escape_like(servicegroup)
118
            aggregates.add_join((ServiceGroup, ServiceGroup.idgroup == \
119
                aggregates.items.c.idservicegroup))
120
            aggregates.add_filter(
121
                ServiceGroup.name.ilike('%%%s%%' % servicegroup))
122

    
123
        if host:
124
            search['host'] = host
125
            host = sql_escape_like(host)
126
            aggregates.add_filter(aggregates.items.c.hostname.ilike(
127
                '%%%s%%' % host))
128

    
129
        if service:
130
            search['service'] = service
131
            service = sql_escape_like(service)
132
            aggregates.add_filter(aggregates.items.c.servicename.ilike(
133
                '%%%s%%' % service))
134

    
135
        if output:
136
            search['output'] = output
137
            output = sql_escape_like(output)
138
            aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
139

    
140
        if trouble_ticket:
141
            search['tt'] = trouble_ticket
142
            trouble_ticket = sql_escape_like(trouble_ticket)
143
            aggregates.add_filter(CorrEvent.trouble_ticket.ilike(
144
                '%%%s%%' % trouble_ticket))
145

    
146
        if from_date:
147
            search['from_date'] = from_date
148
            try:
149
                # TRANSLATORS: Format de date et heure.
150
                from_date = datetime.strptime(
151
                    from_date, _('%Y-%m-%d %I:%M:%S %p'))
152
            except ValueError:
153
                # On ignore silencieusement la date invalide reçue.
154
                pass
155
            else:
156
                aggregates.add_filter(CorrEvent.timestamp_active >= from_date)
157

    
158
        if to_date:
159
            search['to_date'] = to_date
160
            try:
161
                # TRANSLATORS: Format de date et heure.
162
                to_date = datetime.strptime(
163
                    to_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 <= to_date)
169

    
170
        # Calcul des éléments à afficher et du nombre de pages possibles
171
        total_rows = aggregates.num_rows()
172
        items_per_page = int(config['vigiboard_items_per_page'])
173

    
174
        id_first_row = items_per_page * (page-1)
175
        id_last_row = min(id_first_row + items_per_page, total_rows)
176

    
177
        aggregates.format_events(id_first_row, id_last_row)
178
        aggregates.generate_tmpl_context()
179

    
180
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
181
        if not total_rows:
182
            id_first_row = 0
183
        else:
184
            id_first_row += 1
185

    
186
        return dict(
187
            hostname = None,
188
            servicename = None,
189
            events = aggregates.events,
190
            plugins = get_plugins_instances(),
191
            rows_info = {
192
                'id_first_row': id_first_row,
193
                'id_last_row': id_last_row,
194
                'total_rows': total_rows,
195
            },
196
            nb_pages = nb_pages,
197
            page = page,
198
            event_edit_status_options = edit_event_status_options,
199
            search = search,
200
            refresh_times = config['vigiboard_refresh_times'],
201
        )
202

    
203
    @validate(validators={
204
            'idcorrevent': validators.Int(not_empty=True),
205
            'page': validators.Int(min=1),
206
        }, error_handler=process_form_errors)
207
    @expose('raw_events_table.html')
208
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
209
    def masked_events(self, idcorrevent, page=1):
210
        """
211
        Affichage de la liste des événements bruts masqués dans un
212
        événement corrélé (agrégés).
213

214
        @param idevent: identifiant de l'événement souhaité
215
        """
216
        if not page:
217
            page = 1
218

    
219
        username = request.environ['repoze.who.identity']['repoze.who.userid']
220
        events = VigiboardRequest(User.by_user_name(username), False)
221
        events.add_table(
222
            Event,
223
            events.items.c.hostname,
224
            events.items.c.servicename,
225
        )
226
        events.add_join((EVENTSAGGREGATE_TABLE, \
227
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
228
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
229
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
230
        events.add_join((events.items, 
231
            Event.idsupitem == events.items.c.idsupitem))
232
        events.add_filter(Event.idevent != CorrEvent.idcause)
233
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
234

    
235
        # Vérification que l'événement existe
236
        total_rows = events.num_rows()
237
        if total_rows < 1:
238
            flash(_('No masked event or access denied'), 'error')
239
            redirect('/')
240

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

    
244
        id_first_row = items_per_page * (page-1)
245
        id_last_row = min(id_first_row + items_per_page, total_rows)
246

    
247
        events.format_events(id_first_row, id_last_row)
248
        events.generate_tmpl_context()
249

    
250
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
251
        if not total_rows:
252
            id_first_row = 0
253
        else:
254
            id_first_row += 1
255

    
256
        return dict(
257
            idcorrevent = idcorrevent,
258
            hostname = None,
259
            servicename = None,
260
            events = events.events,
261
            plugins = get_plugins_instances(),
262
            rows_info = {
263
                'id_first_row': id_first_row,
264
                'id_last_row': id_last_row,
265
                'total_rows': total_rows,
266
            },
267
            nb_pages = nb_pages,
268
            page = page,
269
            search = {
270
                'host': '',
271
                'service': '',
272
                'output': '',
273
                'tt': '',
274
                'from_date': '',
275
                'to_date': '',
276
                'hostgroup': '',
277
                'servicegroup': '',
278
            },
279
           refresh_times=config['vigiboard_refresh_times'],
280
        )
281

    
282
    @validate(validators={
283
            'idevent': validators.Int(not_empty=True),
284
            'page': validators.Int(min=1),
285
        }, error_handler=process_form_errors)
286
    @expose('history_table.html')
287
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
288
    def event(self, idevent, page=1):
289
        """
290
        Affichage de l'historique d'un événement brut.
291
        Pour accéder à cette page, l'utilisateur doit être authentifié.
292

293
        @param idevent: identifiant de l'événement brut souhaité.
294
        @type idevent: C{int}
295
        """
296
        if not page:
297
            page = 1
298

    
299
        username = request.environ['repoze.who.identity']['repoze.who.userid']
300
        events = VigiboardRequest(User.by_user_name(username), False)
301
        events.add_table(
302
            Event,
303
            events.items.c.hostname,
304
            events.items.c.servicename,
305
        )
306
        events.add_join((EVENTSAGGREGATE_TABLE, \
307
            EVENTSAGGREGATE_TABLE.c.idevent == Event.idevent))
308
        events.add_join((CorrEvent, CorrEvent.idcorrevent == \
309
            EVENTSAGGREGATE_TABLE.c.idcorrevent))
310
        events.add_join((events.items, 
311
            Event.idsupitem == events.items.c.idsupitem))
312
        events.add_filter(Event.idevent == idevent)
313

    
314
        if events.num_rows() != 1:
315
            flash(_('No such event or access denied'), 'error')
316
            redirect('/')
317

    
318
        events.format_events(0, 1)
319
        events.generate_tmpl_context()
320
        history = events.format_history()
321

    
322
        total_rows = history.count()
323
        items_per_page = int(config['vigiboard_items_per_page'])
324

    
325
        id_first_row = items_per_page * (page-1)
326
        id_last_row = min(id_first_row + items_per_page, total_rows)
327

    
328
        history_entries = history[id_first_row : id_last_row]
329

    
330
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
331
        if not total_rows:
332
            id_first_row = 0
333
        else:
334
            id_first_row += 1
335

    
336
        return dict(
337
            idevent = idevent,
338
            plugins = get_plugins_instances(),
339
            rows_info = {
340
                'id_first_row': id_first_row,
341
                'id_last_row': id_last_row,
342
                'total_rows': total_rows,
343
            },
344
            nb_pages = nb_pages,
345
            page = page,
346
            history = history_entries,
347
            search = {
348
                'host': '',
349
                'service': '',
350
                'output': '',
351
                'tt': '',
352
                'from_date': '',
353
                'to_date': '',
354
                'hostgroup': '',
355
                'servicegroup': '',
356
            },
357
           refresh_times=config['vigiboard_refresh_times'],
358
        )
359

    
360
    @validate(
361
        validators={
362
            'host': validators.NotEmpty(),
363
#            'service': validators.NotEmpty(),
364
            'page': validators.Int(min=1),
365
        }, 
366
        error_handler = process_form_errors)
367
    @expose('events_table.html')
368
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
369
    def item(self, host, service=None, page=1):
370
        """
371
        Affichage de l'historique de l'ensemble des événements corrélés
372
        jamais ouverts sur l'hôte / service demandé.
373
        Pour accéder à cette page, l'utilisateur doit être authentifié.
374

375
        @param host: Nom de l'hôte souhaité.
376
        @param service: Nom du service souhaité
377
        """
378
        if not page:
379
            page = 1
380
            
381
        idsupitem = SupItem.get_supitem(host, service)
382

    
383
        username = request.environ['repoze.who.identity']['repoze.who.userid']
384
        aggregates = VigiboardRequest(User.by_user_name(username), False)
385
        aggregates.add_table(
386
            CorrEvent,
387
            aggregates.items.c.hostname,
388
            aggregates.items.c.servicename,
389
        )
390
        aggregates.add_join((Event, CorrEvent.idcause == Event.idevent))
391
        aggregates.add_join((aggregates.items, 
392
            Event.idsupitem == aggregates.items.c.idsupitem))
393
        aggregates.add_filter(aggregates.items.c.idsupitem == idsupitem)
394

    
395
        # Vérification qu'il y a au moins 1 événement qui correspond
396
        total_rows = aggregates.num_rows()
397
        if not total_rows:
398
            flash(_('No access to this host/service or no event yet'), 'error')
399
            redirect('/')
400

    
401
        items_per_page = int(config['vigiboard_items_per_page'])
402

    
403
        id_first_row = items_per_page * (page-1)
404
        id_last_row = min(id_first_row + items_per_page, total_rows)
405

    
406
        aggregates.format_events(id_first_row, id_last_row)
407
        aggregates.generate_tmpl_context()
408

    
409
        nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0)))
410
        if not total_rows:
411
            id_first_row = 0
412
        else:
413
            id_first_row += 1
414
        
415
        return dict(
416
            hostname = host,
417
            servicename = service,
418
            events = aggregates.events,
419
            plugins = get_plugins_instances(),
420
            rows_info = {
421
                'id_first_row': id_first_row,
422
                'id_last_row': id_last_row,
423
                'total_rows': total_rows,
424
            },
425
            nb_pages = nb_pages,
426
            page = page,
427
            event_edit_status_options = edit_event_status_options,
428
            search = {
429
                'host': '',
430
                'service': '',
431
                'output': '',
432
                'tt': '',
433
                'from_date': '',
434
                'to_date': '',
435
                'hostgroup': '',
436
                'servicegroup': '',
437
            },
438
            refresh_times=config['vigiboard_refresh_times'],
439
        )
440

    
441
    @validate(validators={
442
        "id": validators.Regex(r'^[0-9]+(,[0-9]+)*,?$'),
443
#        "trouble_ticket": validators.Regex(r'^[0-9]*$'),
444
        "ack": validators.OneOf([
445
            u'NoChange',
446
            u'None',
447
            u'Acknowledged',
448
            u'AAClosed'
449
        ])}, error_handler=process_form_errors)
450
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
451
    def update(self, **krgv):
452
        """
453
        Mise à jour d'un événement suivant les arguments passés.
454
        Cela peut être un changement de ticket ou un changement de statut.
455
        
456
        @param krgv['id']: Le ou les identifiants des événements à traiter
457
        @param krgv['last_modification']: La date de la dernière modification
458
        dont l'utilisateur est au courant.
459
        @param krgv['tt']: Nouveau numéro du ticket associé.
460
        @param krgv['status']: Nouveau status de/des événements.
461
        """
462

    
463
        # On vérifie que des identifiants ont bien été transmis via
464
        # le formulaire, et on informe l'utilisateur le cas échéant.
465
        if krgv['id'] is None:
466
            flash(_('No event has been selected'), 'warning')
467
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
468

    
469
        # Le filtre permet d'éliminer les chaines vides contenues dans le
470
        # tableau ('a,b,' -> split -> ['a','b',''] -> filter -> ['a','b']).
471
        ids = map(int, filter(len, krgv['id'].split(',')))
472

    
473
        # Si l'utilisateur édite plusieurs événements à la fois,
474
        # il nous faut chacun des identifiants
475
       
476
        username = request.environ['repoze.who.identity']['repoze.who.userid']
477
        events = VigiboardRequest(User.by_user_name(username))
478
        events.add_table(CorrEvent)
479
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
480
        events.add_join((events.items, 
481
            Event.idsupitem == events.items.c.idsupitem))
482
        events.add_filter(CorrEvent.idcorrevent.in_(ids))
483
        
484
        events.generate_request()
485
        idevents = [cause.idcause for cause in events.req]
486
        # Si des changements sont survenus depuis que la 
487
        # page est affichée, on en informe l'utilisateur.
488
        last_modification = get_last_modification_timestamp(idevents, None)
489
        if last_modification and datetime.fromtimestamp(\
490
            float(krgv['last_modification'])) < last_modification:
491
            flash(_('Changes have occurred since the page was last displayed, '
492
                    'your changes HAVE NOT been saved.'), 'warning')
493
            raise redirect(request.environ.get('HTTP_REFERER', url('/')))
494
        
495
        # Vérification que au moins un des identifiants existe et est éditable
496
        if not events.num_rows():
497
            flash(_('No access to this event'), 'error')
498
            redirect('/')
499
        
500
        # Modification des événements et création d'un historique
501
        # pour chacun d'eux.
502
        for req in events.req:
503
            if isinstance(req, CorrEvent):
504
                event = req
505
            else:
506
                event = req[0]
507

    
508
            if krgv['trouble_ticket'] != '' :
509
                history = EventHistory(
510
                        type_action="Ticket change",
511
                        idevent=event.idcause,
512
                        value=krgv['trouble_ticket'],
513
                        text="Changed trouble ticket from '%s' to '%s'" % (
514
                            event.trouble_ticket, krgv['trouble_ticket']
515
                        ),
516
                        username=username,
517
                        timestamp=datetime.now(),
518
                    )
519
                DBSession.add(history)   
520
                event.trouble_ticket = krgv['trouble_ticket']
521

    
522
            if krgv['ack'] != 'NoChange' :
523
                history = EventHistory(
524
                        type_action="Acknowledgement change state",
525
                        idevent=event.idcause,
526
                        value=krgv['ack'],
527
                        text="Changed acknowledgement status "
528
                            "from '%s' to '%s'" % (
529
                            event.status, krgv['ack']
530
                        ),
531
                        username=username,
532
                        timestamp=datetime.now(),
533
                    )
534
                DBSession.add(history)
535
                event.status = krgv['ack']
536

    
537
        DBSession.flush()
538
        flash(_('Updated successfully'))
539
        redirect(request.environ.get('HTTP_REFERER', url('/')))
540

    
541
    @validate(validators={
542
        "plugin_name": validators.OneOf([i[0] for i \
543
            in config.get('vigiboard_plugins', [])]),
544
        'idcorrevent': validators.Int(not_empty=True),
545
        }, error_handler=process_form_errors)
546
    @expose('json')
547
    @require(Any(not_anonymous(), msg=l_("You need to be authenticated")))
548
    def get_plugin_value(self, idcorrevent, plugin_name, *arg, **krgv):
549
        """
550
        Permet de récupérer la valeur d'un plugin associée à un CorrEvent
551
        donné via JSON.
552
        """
553
        plugins = config.get('vigiboard_plugins', {})
554

    
555
        # Permet de vérifier si l'utilisateur a bien les permissions
556
        # pour accéder à cet événement et si l'événement existe.
557
        username = request.environ['repoze.who.identity']['repoze.who.userid']
558
        events = VigiboardRequest(User.by_user_name(username))
559
        events.add_table(CorrEvent.idcorrevent)
560
        events.add_join((Event, CorrEvent.idcause == Event.idevent))
561
        events.add_join((events.items, 
562
            Event.idsupitem == events.items.c.idsupitem))
563
        events.add_filter(CorrEvent.idcorrevent == idcorrevent)
564

    
565
        # Pas d'événement ou permission refusée. On ne distingue pas
566
        # les 2 cas afin d'éviter la divulgation d'informations.
567
        if not events.num_rows():
568
            raise HTTPNotFound(_('No such incident or insufficient permissions'))
569

    
570
        plugin_class = [p[1] for p in plugins if p[0] == plugin_name]
571
        if not plugin_class:
572
            raise HTTPNotFound(_('No such plugin'))
573

    
574
        plugin_class = plugin_class[0]
575
        try:
576
            mypac = __import__(
577
                'vigiboard.controllers.plugins.' + plugin_name,
578
                globals(), locals(), [plugin_class], -1)
579
            plugin = getattr(mypac, plugin_class)
580
            if callable(plugin):
581
                return plugin().get_value(idcorrevent, *arg, **krgv)
582
            raise HTTPInternalServerError(_('Not a valid plugin'))
583
        except ImportError:
584
            raise HTTPInternalServerError(_('Plugin could not be loaded'))
585

    
586
    @validate(validators={
587
        "fontsize": validators.Regex(
588
            r'[0-9]+(pt|px|em|%)',
589
            regexOps = ('I',)
590
        )}, error_handler = process_form_errors)
591
    @expose('json')
592
    def set_fontsize(self, fontsize):
593
        """Enregistre la taille de la police dans les préférences."""
594
        session['fontsize'] = fontsize
595
        session.save()
596
        return dict()
597

    
598
    @validate(validators={"refresh": validators.Int()},
599
            error_handler=process_form_errors)
600
    @expose('json')
601
    def set_refresh(self, refresh):
602
        """Enregistre le temps de rafraichissement dans les préférences."""
603
        session['refresh'] = refresh
604
        session.save()
605
        return dict()
606

    
607
    @expose('json')
608
    def set_theme(self, theme):
609
        """Enregistre le thème à utiliser dans les préférences."""
610
        # On sauvegarde l'ID du thème sans vérifications
611
        # car les thèmes (styles CSS) sont définies dans
612
        # les packages de thèmes (ex: vigilo-themes-default).
613
        # La vérification de la valeur est faite dans les templates.
614
        session['theme'] = theme
615
        session.save()
616
        return dict()
617
    
618
def get_last_modification_timestamp(event_id_list, 
619
                                    value_if_none=datetime.now()):
620
    """
621
    Récupère le timestamp de la dernière modification 
622
    opérée sur l'un des événements dont l'identifiant
623
    fait partie de la liste passée en paramètre.
624
    """
625
    last_modification_timestamp = DBSession.query(
626
                                func.max(EventHistory.timestamp),
627
                         ).filter(EventHistory.idevent.in_(event_id_list)
628
                         ).scalar()
629
    if not last_modification_timestamp:
630
        if not value_if_none:
631
            return None
632
        else:
633
            last_modification_timestamp = value_if_none
634
    return datetime.fromtimestamp(mktime(
635
        last_modification_timestamp.timetuple()))
636

    
637
def get_plugins_instances():
638
    """
639
    Renvoie une liste d'instances de plugins pour VigiBoard.
640

641
    @return: Liste de tuples contenant le nom du plugin et l'instance associé.
642
    @rtype: C{list} of C{tuple}
643
    """
644
    plugins = config.get('vigiboard_plugins', [])
645
    plugins_instances = []
646
    for (plugin_name, plugin_class) in plugins:
647
        try:
648
            mypac = __import__(
649
                'vigiboard.controllers.plugins.' + plugin_name,
650
                globals(), locals(), [plugin_class], -1)
651
            plugin = getattr(mypac, plugin_class)
652
            if callable(plugin):
653
                plugins_instances.append((plugin_name, plugin()))
654
        except ImportError:
655
            pass
656
    return plugins_instances
657