Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / root.py @ b42cc707

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, page, host, service=None):
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
        idsupitem = SupItem.get_supitem(host, service)
379

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

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

    
398
        items_per_page = int(config['vigiboard_items_per_page'])
399

    
400
        id_first_row = items_per_page * (page-1)
401
        id_last_row = min(id_first_row + items_per_page, total_rows)
402

    
403
        aggregates.format_events(id_first_row, id_last_row)
404
        aggregates.generate_tmpl_context()
405

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

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

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

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

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

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

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

    
534
        DBSession.flush()
535
        flash(_('Updated successfully'))
536
        redirect(request.environ.get('HTTP_REFERER', url('/')))
537

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

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

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

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

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

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

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

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

    
634
def get_plugins_instances():
635
    """
636
    Renvoie une liste d'instances de plugins pour VigiBoard.
637

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