Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigigraph / vigigraph / controllers / rpc.py @ 8089d65a

History | View | Annotate | Download (32.7 KB)

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2006-2013 CS-SI
3
# License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
4

    
5
"""RPC controller for the combobox of vigigraph"""
6

    
7
# pylint: disable-msg=W0613
8
# W0613: Unused argument : les arguments des contrôleurs sont les composants
9
#        de la query-string de l'URL
10

    
11

    
12
import time
13
import urllib2
14
import logging
15

    
16
## La fonction parse_qsl a été déplacée dans Python 2.6.
17
#try:
18
#    from urlparse import parse_qsl
19
#except ImportError:
20
#    from cgi import parse_qsl
21

    
22
from pylons.i18n import ugettext as _, lazy_ugettext as l_, \
23
                        lazy_ungettext as ln_
24
from tg import expose, request, redirect, tmpl_context, \
25
    config, validate, flash, exceptions as http_exc
26

    
27
from repoze.what.predicates import not_anonymous, has_permission, \
28
                                    in_group, Any, All
29
from formencode import schema
30
from tw.forms import validators
31
from sqlalchemy.orm import aliased, lazyload
32
from sqlalchemy.sql import functions
33

    
34
from vigilo.turbogears.controllers import BaseController
35
from vigilo.turbogears.helpers import get_current_user
36
from vigilo.turbogears.controllers.proxy import get_through_proxy
37
from vigilo.turbogears.decorators import paginate
38

    
39
from vigilo.models.session import DBSession
40
from vigilo.models.tables import Host, SupItemGroup, PerfDataSource
41
from vigilo.models.tables import Graph, GraphGroup, Change, UserGroup
42
from vigilo.models.tables import DataPermission
43
from vigilo.models.tables.group import Group
44
from vigilo.models.tables.grouphierarchy import GroupHierarchy
45

    
46
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE
47
from vigilo.models.tables.secondary_tables import GRAPH_GROUP_TABLE
48
from vigilo.models.tables.secondary_tables import GRAPH_PERFDATASOURCE_TABLE
49
from vigilo.models.tables.secondary_tables import USER_GROUP_TABLE
50
from vigilo.models.functions import sql_escape_like
51

    
52
LOGGER = logging.getLogger(__name__)
53

    
54
__all__ = ['RpcController']
55

    
56
def ungettext(singular, plural, n):
57
    return ln_(singular, plural, n) % {
58
        'qtty': n,
59
    }
60

    
61
# pylint: disable-msg=R0201
62
class RpcController(BaseController):
63
    """
64
    Class Controleur TurboGears
65
    """
66

    
67
    # L'accès à ce contrôleur nécessite d'être identifié.
68
    allow_only = All(
69
        not_anonymous(msg=l_("You need to be authenticated")),
70
        Any(
71
            config.is_manager,
72
            has_permission('vigigraph-access',
73
                msg=l_("You don't have access to VigiGraph")),
74
        ),
75
    )
76

    
77
    # Plages de temps affichées sur la page de métrologie complète
78
    # d'un hôte avec la durée associée (en secondes).
79
    # Voir aussi graph.js pour l'équivalent côté JavaScript sur un graphe.
80
    presets = [
81
        {
82
            "caption" :
83
                ungettext("Last %(qtty)d hour", "Last %(qtty)d hours", 12),
84
            "duration" : 43200,
85
        },
86
        {
87
            "caption" :
88
                ungettext("Last %(qtty)d hour", "Last %(qtty)d hours", 24),
89
            "duration" : 86400,
90
        },
91
        {
92
            "caption" :
93
                ungettext("Last %(qtty)d day", "Last %(qtty)d days", 2),
94
            "duration" : 192800,
95
        },
96
        {
97
            "caption" :
98
                ungettext("Last %(qtty)d day", "Last %(qtty)d days", 7),
99
            "duration" : 604800,
100
        },
101
        {
102
            "caption" :
103
                ungettext("Last %(qtty)d day", "Last %(qtty)d days", 14),
104
            "duration" : 1209600,
105
        },
106
        {
107
            "caption" :
108
                ungettext("Last %(qtty)d month", "Last %(qtty)d months", 1),
109
            "duration" : 86400 * 30,
110
        },
111
        {
112
            "caption" :
113
                ungettext("Last %(qtty)d month", "Last %(qtty)d months", 3),
114
            "duration" : 86400 * 30 * 3,
115
        },
116
        {
117
            "caption" :
118
                ungettext("Last %(qtty)d month", "Last %(qtty)d months", 6),
119
            "duration" : 86400 * 30 * 6,
120
        },
121
        {
122
            "caption" :
123
                ungettext("Last %(qtty)d year", "Last %(qtty)d years", 1),
124
            "duration" : 86400 * 365,
125
        },
126
    ]
127

    
128
    def process_form_errors(self, *args, **kwargs):
129
        """
130
        Gestion des erreurs de validation : On affiche les erreurs
131
        puis on redirige vers la dernière page accédée.
132
        """
133
        for k in tmpl_context.form_errors:
134
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
135
        redirect(request.environ.get('HTTP_REFERER', '/'))
136

    
137
    class SearchHostAndGraphSchema(schema.Schema):
138
        """Schéma de validation pour la méthode L{searchHostAndGraph}."""
139
        search_form_host = validators.UnicodeString(if_missing=None)
140
        search_form_graph = validators.UnicodeString(if_missing=None)
141

    
142
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
143
    @validate(
144
        validators = SearchHostAndGraphSchema(),
145
        error_handler = process_form_errors)
146
    @expose('json')
147
    def searchHostAndGraph(self, search_form_host, search_form_graph):
148
        """
149
        Determination des couples (hote-graphe) repondant aux criteres de
150
        recherche sur hote et/ou graphe.
151

152
        Un critere peut correspondre a un intitule complet hote ou graphe
153
        ou a un extrait.
154

155
        @return: couples hote-graphe
156
        @rtype: document json (sous forme de dict)
157
        """
158
        limit = 100
159
        user = get_current_user()
160
        ids = []
161
        labels = []
162

    
163
        if user is None:
164
            return dict(items=[])
165

    
166
        # On a un nom d'indicateur, mais pas de nom d'hôte,
167
        # on considère que l'utilisateur veut tous les indicateurs
168
        # correspondant au motif, quel que soit l'hôte.
169
        if search_form_graph:
170
            if not search_form_host:
171
                search_form_host = u'*'
172

    
173
            search_form_host = sql_escape_like(search_form_host)
174
            search_form_graph = sql_escape_like(search_form_graph)
175

    
176
            items = DBSession.query(
177
                    Host.idhost.label('idhost'),
178
                    Host.name.label('hostname'),
179
                    Graph.idgraph.label('idgraph'),
180
                    Graph.name.label('graphname'),
181
                ).distinct().join(
182
                    (PerfDataSource, PerfDataSource.idhost == Host.idhost),
183
                    (GRAPH_PERFDATASOURCE_TABLE, \
184
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
185
                        PerfDataSource.idperfdatasource),
186
                    (Graph, Graph.idgraph == \
187
                        GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
188
                    (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
189
                        Host.idhost),
190
                ).filter(Host.name.ilike(search_form_host)
191
                ).filter(Graph.name.ilike(search_form_graph)
192
                ).order_by(
193
                    Host.name.asc(),
194
                    Graph.name.asc(),
195
                )
196

    
197
        # On a ni hôte, ni indicateur. On renvoie une liste vide.
198
        # Si l'utilisateur voulait vraiment quelque chose,
199
        # il n'avait qu'à le demander.
200
        elif not search_form_host:
201
            return []
202

    
203
        # Sinon, on a juste un motif pour un hôte.
204
        # On renvoie la liste des hôtes correspondant.
205
        else:
206
            search_form_host = sql_escape_like(search_form_host)
207
            items = DBSession.query(
208
                    Host.idhost.label('idhost'),
209
                    Host.name.label('hostname'),
210
                ).distinct().join(
211
                    (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
212
                        Host.idhost),
213
                ).filter(Host.name.ilike(search_form_host)
214
                ).order_by(Host.name.asc())
215

    
216
        # Les managers ont accès à tout.
217
        # Les autres ont un accès restreint.
218
        if not config.is_manager.is_met(request.environ):
219
            supitemgroups = [sig[0] for sig in user.supitemgroups() if sig[1]]
220
            # pylint: disable-msg=E1103
221
            items = items.join(
222
                (GroupHierarchy, GroupHierarchy.idchild == \
223
                    SUPITEM_GROUP_TABLE.c.idgroup)
224
            ).filter(GroupHierarchy.idparent.in_(supitemgroups))
225

    
226
        items = items.limit(limit + 1).all() # pylint: disable-msg=E1103
227
        more_results = len(items) > limit
228

    
229
        if not search_form_graph:
230
            for i in xrange(min(limit, len(items))):
231
                ids.append((items[i].idhost, None))
232
                labels.append((items[i].hostname, None))
233
        else:
234
            for i in xrange(min(limit, len(items))):
235
                ids.append((items[i].idhost, items[i].idgraph))
236
                labels.append((items[i].hostname, items[i].graphname))
237

    
238
        return dict(labels=labels, ids=ids, more=more_results)
239

    
240
    @expose('graphslist.html')
241
    def graphsList(self, nocache=None, **kwargs):
242
        """
243
        Generation document avec url des graphes affiches
244
        (pour l impression )
245

246
        @param kwargs : arguments nommes
247
        @type kwargs  : dict
248

249
        @return: url de graphes
250
        @rtype: document html
251
        """
252
        # @TODO: le must serait de hot-patcher mootools pour que son serializer
253
        # d'URL utilise directement le format attendu par TurboGears
254
        # (notation pointée plutôt qu'avec des crochets)
255

    
256
        if not kwargs:
257
            return dict(graphslist=[])
258

    
259
        # On est obligé de convertir le format en UTF-8 car strftime
260
        # n'accepte pas les chaînes Unicode en entrée.
261
        # TRANSLATORS: Format Python de date/heure, lisible par un humain.
262
        format = _("%a, %d %b %Y %H:%M:%S").encode('utf8')
263
        i = 0
264
        graphslist = []
265

    
266
        while True:
267
            try:
268
                host = kwargs['graphs[%d][host]' % i]
269
                graph = kwargs['graphs[%d][graph]' % i]
270
                start = int(kwargs.get('graphs[%d][start]' % i,
271
                            time.time() - 86400))
272
                duration = int(kwargs.get('graphs[%d][duration]' % i))
273
                nocache = kwargs['graphs[%d][nocache]' % i]
274
            except KeyError:
275
                break
276

    
277
            if not (host and graph and duration and nocache):
278
                break
279

    
280
            graphslist.append({
281
                'host': host,
282
                'graph': graph,
283
                'start': start,
284
                'duration': duration,
285
                'nocache': nocache,
286
                'start_date': time.strftime(format,
287
                    time.localtime(start)).decode('utf8'),
288
                'end_date': time.strftime(format,
289
                    time.localtime(start + duration)).decode('utf8')
290
            })
291
            i += 1
292
        return dict(graphslist=graphslist)
293

    
294
    @expose('json')
295
    def tempoDelayRefresh(self, nocache=None):
296
        """
297
        Determination valeur temporisation pour le rafraichissement automatique
298
        d un graphe
299

300
        @return: valeur de temporisation
301
        @rtype: C{str}
302
        """
303

    
304
        try:
305
            delay = int(config['refresh_delay'])
306
        except (ValueError, KeyError):
307
            delay = 30
308
        return {'delay': delay}
309

    
310
    class GetIndicatorsSchema(schema.Schema):
311
        """Schéma de validation pour la méthode L{getIndicators}."""
312
        host = validators.UnicodeString(not_empty=True)
313
        graph = validators.UnicodeString(not_empty=True)
314
        nocache = validators.UnicodeString(if_missing=None)
315

    
316
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
317
    @validate(
318
        validators = GetIndicatorsSchema(),
319
        error_handler = process_form_errors)
320
    @expose('json')
321
    def getIndicators(self, host, graph, nocache):
322
        """
323
        Liste d indicateurs associes a un graphe
324

325
        @param graph : graphe
326
        @type graph  : C{str}
327

328
        @return: dictionnaire des indicateurs d un graphe
329
        @rtype: document json (sous forme de dict)
330
        """
331

    
332
        indicators = self.getListIndicators(host, graph)
333
        indicators = [(ind.name, ind.label) for ind in indicators]
334
        return dict(items=indicators)
335

    
336
    class StartTimeSchema(schema.Schema):
337
        """Schéma de validation pour la méthode L{getIndicators}."""
338
        host = validators.UnicodeString(not_empty=True)
339
        nocache = validators.UnicodeString(if_missing=None)
340

    
341
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
342
    @validate(
343
        validators = StartTimeSchema(),
344
        error_handler = process_form_errors)
345
    @expose('json')
346
    def startTime(self, host, nocache):
347
        # urllib2.quote() ne fonctionne pas sur le type unicode.
348
        # On transcode d'abord le nom d'hôte en UTF-8.
349
        quote_host = isinstance(host, unicode) and \
350
                        host.encode('utf-8') or host
351
        return get_through_proxy(
352
            'vigirrd', host,
353
            '/starttime?host=%s' % urllib2.quote(quote_host, '')
354
        )
355

    
356
    class FullHostPageSchema(schema.Schema):
357
        """Schéma de validation pour la méthode L{fullHostPage}."""
358
        host = validators.UnicodeString(not_empty=True)
359
        start = validators.Int(if_missing=None)
360
        duration = validators.Int(if_missing=86400)
361

    
362
    # VIGILO_EXIG_VIGILO_PERF_0010:Visualisation globale des graphes
363
    # VIGILO_EXIG_VIGILO_PERF_0020:Visualisation unitaire des graphes
364
    # On utilise la même page pour les 2 fonctionalités.
365
    @validate(
366
        validators = FullHostPageSchema(),
367
        error_handler = process_form_errors)
368
    @expose('fullhostpage.html')
369
    def fullHostPage(self, host, start=None, duration=86400):
370
        """
371
        Affichage de l'ensemble des graphes associes a un hote
372
        * d apres les donnees RRD
373
        * avec une date-heure de debut
374
        * pour une plage de temps
375

376
        @param host : hôte
377
        @type host : C{str}
378
        @param start : date-heure de debut des donnees
379
        @type start : C{str}
380
        @param duration : plage de temps des données. Parametre optionnel,
381
            initialisé a 86400 = plage de 1 jour.
382
        @type duration : C{str}
383

384
        @return: page avec les images des graphes et boutons de deplacement
385
            dans le temps
386
        @rtype: page html
387
        """
388
        if start is None:
389
            start = int(time.time()) - int(duration)
390
        else:
391
            start = int(start)
392
        duration = int(duration)
393

    
394
        user = get_current_user()
395
        if user is None:
396
            return dict(host=host, start=start, duration=duration,
397
                        presets=self.presets, graphs=[])
398

    
399
        # Vérification des permissions de l'utilisateur sur l'hôte.
400
        if not config.is_manager.is_met(request.environ):
401
            # Récupération des groupes auxquels l'utilisateur a accès.
402
            supitemgroups = [sig[0] for sig in user.supitemgroups() if sig[1]]
403

    
404
            # On vérifie que l'hôte en question existe bel et bien.
405
            host_obj = Host.by_host_name(host)
406
            if not host_obj:
407
                message = _('No such host "%s"') % host
408
                LOGGER.warning(message)
409
                raise http_exc.HTTPNotFound(message)
410

    
411
            # Récupération des groupes dont l'hôte fait partie
412
            hostgroups = [g.idgroup for g in host_obj.groups]
413
            # Si aucun des groupes de l'hôte ne fait partie des groupes
414
            # auxquels l'utilisateur a accès, on affiche une erreur 403.
415
            if len(set(hostgroups).intersection(set(supitemgroups))) < 1:
416
                message = _('Access denied to host "%s"') % host
417
                LOGGER.warning(message)
418
                raise http_exc.HTTPForbidden(message)
419

    
420
        # Récupération de la liste des noms des graphes associés à l'hôte.
421
        graphs = DBSession.query(
422
                Graph.name
423
            ).distinct(
424
            ).join(
425
                (GRAPH_PERFDATASOURCE_TABLE,
426
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
427
                (PerfDataSource, PerfDataSource.idperfdatasource ==
428
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
429
                (Host, Host.idhost == PerfDataSource.idhost),
430
            ).filter(Host.name == host)
431

    
432
        graphs = graphs.all()
433
        return dict(host=host, start=start, duration=duration,
434
                    presets=self.presets, graphs=graphs)
435

    
436

    
437
    class SearchHostSchema(schema.Schema):
438
        """Schéma de validation pour la méthode L{getIndicators}."""
439
        allow_extra_fields = True
440
        filter_extra_fields = True
441
        query = validators.UnicodeString(not_empty=True)
442

    
443
    @expose('searchhost.html')
444
    @validate(
445
        validators = SearchHostSchema(),
446
        error_handler = process_form_errors)
447
    @paginate('hosts', items_per_page=10)
448
    def searchHost(self, query):
449
        """
450
        Affiche les résultats de la recherche par nom d'hôte.
451
        La requête de recherche (C{query}) correspond à un préfixe
452
        qui sera recherché dans le nom d'hôte. Ce préfixe peut
453
        contenir les caractères '*' et '?' qui agissent comme des
454
        "jokers".
455

456
        @keyword query: Prefixe de recherche sur les noms d'hôtes
457
        @type    query: C{unicode}
458
        """
459
        if not query:
460
            redirect("searchHostForm")
461
            return
462

    
463
        query = sql_escape_like(query.strip())
464
        user = get_current_user()
465
        if user is None:
466
            return dict(items=[])
467

    
468
        # Récupère les hôtes auxquels l'utilisateur a réellement accès.
469
        hosts = DBSession.query(
470
                Host.name,
471
            ).distinct(
472
            ).join(
473
                (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
474
                    Host.idhost),
475
            ).filter(Host.name.like(query + u'%')
476
            ).order_by(Host.name.asc(),)
477

    
478
        # Les managers ont accès à tout.
479
        # Les autres ont un accès restreint.
480
        if not config.is_manager.is_met(request.environ):
481
            supitemgroups = [sig[0] for sig in user.supitemgroups() if sig[1]]
482
            hosts = hosts.join(
483
                    (GroupHierarchy, GroupHierarchy.idchild == \
484
                        SUPITEM_GROUP_TABLE.c.idgroup)
485
                ).filter(GroupHierarchy.idparent.in_(supitemgroups))
486

    
487
        return dict(hosts=[h.name for h in hosts])
488

    
489
    # VIGILO_EXIG_VIGILO_PERF_0030:Moteur de recherche des graphes
490
    @expose('getopensearch.xml', content_type='text/xml')
491
    def getOpenSearch(self):
492
        """
493
        Moteur de recherche des graphes
494

495
        @return: document
496
        @rtype: document xml
497
        """
498
        return dict()
499

    
500
    @expose('json')
501
    def hosttree(self, parent_id=None, onlytype="", offset=0, noCache=None):
502
        """
503
        Affiche un étage de l'arbre de
504
        sélection des hôtes et groupes d'hôtes.
505

506
        @param parent_id: identifiant du groupe d'hôte parent
507
        @type  parent_id: C{int} or None
508
        """
509

    
510
        # Si l'identifiant du groupe parent n'est pas
511
        # spécifié, on retourne la liste des groupes racines,
512
        # fournie par la méthode get_root_hosts_groups.
513
        if parent_id is None:
514
            return self.get_root_host_groups()
515

    
516
        # TODO: Utiliser un schéma de validation
517
        parent_id = int(parent_id)
518
        offset = int(offset)
519

    
520
        # On vérifie si le groupe parent fait partie des
521
        # groupes auxquel l'utilisateur a accès, et on
522
        # retourne une liste vide dans le cas contraire
523
        is_manager = config.is_manager.is_met(request.environ)
524
        if not is_manager:
525
            direct_access = False
526
            user = get_current_user()
527

    
528
            # On calcule la distance de ce groupe par rapport aux groupes
529
            # sur lesquels l'utilisateur a explicitement les permissions.
530
            #
531
            # La distance est définie ainsi :
532
            # 0 : l'utilisateur a des droits explicites sur ce groupe.
533
            # > 0 : l'utilisateur a accès implicitement au groupe.
534
            # < 0 : l'utilisateur n'a pas d'accès (il peut juste parcourir
535
            #       ce groupe)
536
            #
537
            # Il faut 2 étapes pour trouver la distance. La 1ère essaye
538
            # de trouver une distance >= 0, la 2ème une distance <= 0.
539

    
540
            # Distance positive.
541
            distance = DBSession.query(
542
                    functions.max(GroupHierarchy.hops)
543
                ).join(
544
                    (Group, Group.idgroup == GroupHierarchy.idparent),
545
                    (DataPermission,
546
                        DataPermission.idgroup == Group.idgroup),
547
                    (UserGroup,
548
                        UserGroup.idgroup == DataPermission.idusergroup),
549
                    (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
550
                        UserGroup.idgroup),
551
                ).filter(USER_GROUP_TABLE.c.username == user.user_name
552
                ).filter(Group.grouptype == u'supitemgroup'
553
                ).filter(GroupHierarchy.idchild == parent_id
554
                ).scalar()
555

    
556
            if distance is None:
557
                # Distance négative.
558
                distance = DBSession.query(
559
                        functions.max(GroupHierarchy.hops)
560
                    ).join(
561
                        (Group, Group.idgroup == GroupHierarchy.idchild),
562
                        (DataPermission,
563
                            DataPermission.idgroup == Group.idgroup),
564
                        (UserGroup,
565
                            UserGroup.idgroup == DataPermission.idusergroup),
566
                        (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
567
                            UserGroup.idgroup),
568
                    ).filter(USER_GROUP_TABLE.c.username == user.user_name
569
                    ).filter(Group.grouptype == u'supitemgroup'
570
                    ).filter(GroupHierarchy.idparent == parent_id
571
                    ).scalar()
572
                if distance is not None:
573
                    distance = -distance
574

    
575
            if distance is None:
576
                # Pas d'accès à ce groupe.
577
                return dict(groups = [], items = [])
578

    
579
            direct_access = distance >= 0
580

    
581
        limit = int(config.get("max_menu_entries", 20))
582
        result = {"groups": [], "items": []}
583

    
584
        if not onlytype or onlytype == "group":
585
            # On récupère la liste des groupes dont
586
            # l'identifiant du parent est passé en paramètre
587
            gh1 = aliased(GroupHierarchy, name='gh1')
588
            gh2 = aliased(GroupHierarchy, name='gh2')
589

    
590
            db_groups = DBSession.query(
591
                SupItemGroup
592
            ).options(lazyload('_path_obj')
593
            ).distinct(
594
            ).join(
595
                (gh1, gh1.idchild == SupItemGroup.idgroup),
596
            ).filter(gh1.hops == 1
597
            ).filter(gh1.idparent == parent_id
598
            ).order_by(SupItemGroup.name.asc())
599

    
600
            if not is_manager and not direct_access:
601
                # On ne doit afficher que les fils du groupe <parent_id>
602
                # tels que l'utilisateur a accès explicitement à l'un
603
                # des fils de l'un de ces groupes.
604
                db_groups = db_groups.join(
605
                        (gh2, gh2.idparent == gh1.idchild),
606
                        (DataPermission,
607
                            DataPermission.idgroup == gh2.idchild),
608
                        (UserGroup,
609
                            UserGroup.idgroup == DataPermission.idusergroup),
610
                        (USER_GROUP_TABLE,
611
                            USER_GROUP_TABLE.c.idgroup == UserGroup.idgroup),
612
                    ).filter(USER_GROUP_TABLE.c.username == user.user_name)
613

    
614
            num_children_left = db_groups.count() - offset
615
            if offset:
616
                result["continued_from"] = offset
617
                result["continued_type"] = "group"
618
            all_groups = db_groups.limit(limit).offset(offset).all()
619
            for group in all_groups:
620
                result["groups"].append({
621
                    'id'   : group.idgroup,
622
                    'name' : group.name,
623
                    'type' : "group",
624
                })
625
            if num_children_left > limit:
626
                result["groups"].append({
627
                    'name': _("Next %(limit)s") % {"limit": limit},
628
                    'offset': offset + limit,
629
                    'parent_id': parent_id,
630
                    'type': 'continued',
631
                    'for_type': 'group',
632
                })
633

    
634
        # On récupère la liste des hôtes appartenant au
635
        # groupe dont l'identifiant est passé en paramètre
636
        if ((not onlytype or onlytype == "item")
637
                and (is_manager or direct_access)):
638
            db_hosts = DBSession.query(
639
                Host.idhost,
640
                Host.name,
641
            ).join(
642
                (SUPITEM_GROUP_TABLE,
643
                    SUPITEM_GROUP_TABLE.c.idsupitem == Host.idhost
644
                    ),
645
            ).filter(SUPITEM_GROUP_TABLE.c.idgroup == parent_id
646
            ).order_by(Host.name.asc())
647
            num_children_left = db_hosts.count() - offset
648
            if offset:
649
                result["continued_from"] = offset
650
                result["continued_type"] = "item"
651
            all_hosts = db_hosts.limit(limit).offset(offset).all()
652
            for host in all_hosts:
653
                result["items"].append({
654
                    'id'   : host.idhost,
655
                    'name' : host.name,
656
                    'type' : "item",
657
                })
658
            if num_children_left > limit:
659
                result["items"].append({
660
                    'name': _("Next %(limit)s") % {"limit": limit},
661
                    'offset': offset + limit,
662
                    'parent_id': parent_id,
663
                    'type': 'continued',
664
                    'for_type': 'item',
665
                })
666

    
667
        return result
668

    
669
    @expose('json')
670
    def graphtree(self, host_id=None, parent_id=None, offset=0, noCache=None):
671
        """
672
        Affiche un étage de l'arbre de sélection
673
        des graphes et groupes de graphes.
674

675
        @param parent_id: identifiant du groupe de graphes parent
676
        @type  parent_id: C{int} or None
677
        """
678

    
679
        # Si l'identifiant de l'hôte n'est pas spécifié, on
680
        # retourne un dictionnaire contenant deux listes vides
681
        if host_id is None:
682
            return dict(groups = [], graphs=[])
683

    
684
        # On vérifie les permissions sur l'hôte
685
        # TODO: Utiliser un schéma de validation
686
        host_id = int(host_id)
687
        host = DBSession.query(Host
688
            ).filter(Host.idhost == host_id
689
            ).first()
690
        if host is None:
691
            return dict(groups = [], graphs=[])
692
        user = get_current_user()
693
        if not host.is_allowed_for(user):
694
            return dict(groups = [], graphs=[])
695

    
696
        # On récupère la liste des groupes de graphes associés à l'hôte
697
        host_graph_groups = DBSession.query(
698
            GraphGroup
699
        ).distinct(
700
        ).join(
701
            (GRAPH_GROUP_TABLE, \
702
                GRAPH_GROUP_TABLE.c.idgroup == GraphGroup.idgroup),
703
            (Graph, Graph.idgraph == GRAPH_GROUP_TABLE.c.idgraph),
704
            (GRAPH_PERFDATASOURCE_TABLE, \
705
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
706
            (PerfDataSource, PerfDataSource.idperfdatasource == \
707
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
708
            (SUPITEM_GROUP_TABLE, \
709
                SUPITEM_GROUP_TABLE.c.idsupitem == PerfDataSource.idhost),
710
        ).filter(PerfDataSource.idhost == host_id
711
        ).order_by(GraphGroup.name.asc()
712
        ).all()
713

    
714
        # Si l'identifiant du groupe parent n'est pas spécifié,
715
        # on récupère la liste des groupes de graphes racines.
716
        if parent_id is None:
717
            graph_groups = GraphGroup.get_top_groups()
718

    
719
        # Sinon on récupère la liste des graphes dont le
720
        # groupe passé en paramètre est le parent direct
721
        else:
722
            # TODO: Utiliser un schéma de validation
723
            parent_id = int(parent_id)
724
            graph_groups = DBSession.query(
725
                GraphGroup
726
            ).join(
727
                (GroupHierarchy, GroupHierarchy.idchild == \
728
                    GraphGroup.idgroup),
729
            ).filter(GroupHierarchy.hops == 1
730
            ).filter(GroupHierarchy.idparent == parent_id
731
            ).order_by(GraphGroup.name.asc()
732
            ).all()
733

    
734
        # On réalise l'intersection des deux listes
735
        groups = []
736
        for gg in graph_groups:
737
            if gg in host_graph_groups:
738
                groups.append({
739
                    'id'   : gg.idgroup,
740
                    'name' : gg.name,
741
                    'type' : "group",
742
                })
743

    
744
        # On récupère la liste des graphes appartenant au
745
        # groupe dont l'identifiant est passé en paramètre
746
        graphs = []
747
        if parent_id:
748
            db_graphs = DBSession.query(
749
                Graph.idgraph,
750
                Graph.name,
751
            ).distinct(
752
            ).join(
753
                (GRAPH_GROUP_TABLE,
754
                    GRAPH_GROUP_TABLE.c.idgraph == Graph.idgraph),
755
                (GRAPH_PERFDATASOURCE_TABLE,
756
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
757
                (PerfDataSource,
758
                    PerfDataSource.idperfdatasource == \
759
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
760
            ).filter(GRAPH_GROUP_TABLE.c.idgroup == parent_id
761
            ).filter(PerfDataSource.idhost == host_id
762
            ).order_by(Graph.name.asc())
763
            for graph in db_graphs.all():
764
                graphs.append({
765
                    'id'   : graph.idgraph,
766
                    'name' : graph.name,
767
                    'type' : "item",
768
                })
769

    
770
        return dict(groups=groups, items=graphs)
771

    
772
    def get_root_host_groups(self):
773
        """
774
        Retourne tous les groupes racines (c'est à dire n'ayant
775
        aucun parent) d'hôtes auquel l'utilisateur a accès.
776

777
        @return: Un dictionnaire contenant la liste de ces groupes.
778
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
779
        """
780

    
781
        # On récupère tous les groupes qui ont un parent.
782
        children = DBSession.query(
783
            SupItemGroup,
784
        ).distinct(
785
        ).join(
786
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
787
        ).filter(GroupHierarchy.hops > 0)
788

    
789
        # Ensuite on les exclut de la liste des groupes,
790
        # pour ne garder que ceux qui sont au sommet de
791
        # l'arbre et qui constituent nos "root groups".
792
        root_groups = DBSession.query(
793
            SupItemGroup,
794
        ).except_(children
795
        ).order_by(SupItemGroup.name)
796

    
797
        # On filtre ces groupes racines afin de ne
798
        # retourner que ceux auquels l'utilisateur a accès
799
        user = get_current_user()
800
        if not config.is_manager.is_met(request.environ):
801
            user_groups = [ug[0] for ug in user.supitemgroups()]
802
            root_groups = root_groups.filter(
803
                SupItemGroup.idgroup.in_(user_groups))
804

    
805
        groups = []
806
        for group in root_groups.all():
807
            groups.append({
808
                'id'   : group.idgroup,
809
                'name' : group.name,
810
                'type' : "group",
811
            })
812

    
813
        return dict(groups=groups, items=[])
814

    
815
    def getListIndicators(self, host, graph):
816
        """
817
        Liste d indicateurs associes a un graphe
818

819
        @param graph : graphe
820
        @type graph  : C{str}
821

822
        @return: liste d indicateurs
823
        @rtype  : list
824
        """
825

    
826
        indicators = []
827
        if graph is not None:
828
            indicators = DBSession.query(
829
                    PerfDataSource.name, PerfDataSource.label
830
                ).distinct(
831
                ).join(
832
                    (GRAPH_PERFDATASOURCE_TABLE, \
833
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
834
                        PerfDataSource.idperfdatasource),
835
                    (Graph, Graph.idgraph == \
836
                        GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
837
                    (Host, Host.idhost == PerfDataSource.idhost),
838
                ).filter(Graph.name == graph
839
                ).filter(Host.name == host
840
                ).all()
841
        return indicators
842

    
843
    @expose('json')
844
    def dbmtime(self):
845
        change = Change.by_table_name(u"Graph")
846
        if change is None:
847
            return {"mtime": None}
848
        mtime = change.last_modified.replace(microsecond=0)
849
        return {"mtime": mtime}
850

    
851
    @expose('json')
852
    def selectHostAndGraph(self, host, graph):
853
        # @TODO: vérifier les permissions
854
        ids = DBSession.query(
855
                Host.idhost, Graph.idgraph
856
            ).join(
857
                (PerfDataSource, PerfDataSource.idhost == Host.idhost),
858
                (GRAPH_PERFDATASOURCE_TABLE, \
859
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
860
                    PerfDataSource.idperfdatasource),
861
                (Graph, Graph.idgraph == \
862
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
863
            ).filter(Graph.name == unicode(graph)
864
            ).filter(Host.name == unicode(host)
865
            ).first()
866

    
867
        return {
868
            'idhost': ids and ids.idhost or None,
869
            'idgraph': ids and ids.idgraph or None,
870
        }
871

    
872
    @expose('json')
873
    def external_links(self):
874
        return dict(links=config['external_links'])