Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigigraph / vigigraph / controllers / rpc.py @ 77b7746d

History | View | Annotate | Download (32.6 KB)

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2006-2016 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
from tg.i18n import ugettext as _, lazy_ugettext as l_, lazy_ungettext as ln_
17
from tg import expose, request, redirect, tmpl_context, \
18
    config, validate, flash, exceptions as http_exc
19

    
20
from repoze.what.predicates import not_anonymous, has_permission, \
21
                                    in_group, Any, All
22
from formencode import schema
23
from tw.forms import validators
24
from sqlalchemy.orm import aliased, lazyload
25
from sqlalchemy.sql import functions
26

    
27
from vigilo.turbogears.controllers import BaseController
28
from vigilo.turbogears.helpers import get_current_user
29
from vigilo.turbogears.controllers.proxy import get_through_proxy
30
from tg.decorators import paginate
31

    
32
from vigilo.models.session import DBSession
33
from vigilo.models.tables import Host, SupItemGroup, PerfDataSource
34
from vigilo.models.tables import Graph, GraphGroup, Change, UserGroup
35
from vigilo.models.tables import DataPermission
36
from vigilo.models.tables.group import Group
37
from vigilo.models.tables.grouphierarchy import GroupHierarchy
38

    
39
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE
40
from vigilo.models.tables.secondary_tables import GRAPH_GROUP_TABLE
41
from vigilo.models.tables.secondary_tables import GRAPH_PERFDATASOURCE_TABLE
42
from vigilo.models.tables.secondary_tables import USER_GROUP_TABLE
43
from vigilo.models.functions import sql_escape_like
44

    
45
LOGGER = logging.getLogger(__name__)
46

    
47
__all__ = ['RpcController']
48

    
49
def ungettext(singular, plural, n):
50
    return ln_(singular, plural, n) % {
51
        'qtty': n,
52
    }
53

    
54
# pylint: disable-msg=R0201
55
class RpcController(BaseController):
56
    """
57
    Class Controleur TurboGears
58
    """
59

    
60
    # L'accès à ce contrôleur nécessite d'être identifié.
61
    allow_only = All(
62
        not_anonymous(msg=l_("You need to be authenticated")),
63
        Any(
64
            config.is_manager,
65
            has_permission('vigigraph-access',
66
                msg=l_("You don't have access to VigiGraph")),
67
        ),
68
    )
69

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

    
121
    def process_form_errors(self, *args, **kwargs):
122
        """
123
        Gestion des erreurs de validation : On affiche les erreurs
124
        puis on redirige vers la dernière page accédée.
125
        """
126
        for k in tmpl_context.form_errors:
127
            flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error')
128
        redirect(request.environ.get('HTTP_REFERER', '/'))
129

    
130
    class SearchHostAndGraphSchema(schema.Schema):
131
        """Schéma de validation pour la méthode L{searchHostAndGraph}."""
132
        search_form_host = validators.UnicodeString(if_missing=None)
133
        search_form_graph = validators.UnicodeString(if_missing=None)
134

    
135
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
136
    @validate(
137
        validators = SearchHostAndGraphSchema(),
138
        error_handler = process_form_errors)
139
    @expose('json')
140
    def searchHostAndGraph(self, search_form_host=None, search_form_graph=None):
141
        """
142
        Determination des couples (hote-graphe) repondant aux criteres de
143
        recherche sur hote et/ou graphe.
144

145
        Un critere peut correspondre a un intitule complet hote ou graphe
146
        ou a un extrait.
147

148
        @return: couples hote-graphe
149
        @rtype: document json (sous forme de dict)
150
        """
151
        limit = 100
152
        user = get_current_user()
153
        ids = []
154
        labels = []
155

    
156
        if user is None:
157
            return dict(items=[])
158

    
159
        # On a un nom d'indicateur, mais pas de nom d'hôte,
160
        # on considère que l'utilisateur veut tous les indicateurs
161
        # correspondant au motif, quel que soit l'hôte.
162
        if search_form_graph:
163
            if not search_form_host:
164
                search_form_host = u'*'
165

    
166
            search_form_host = sql_escape_like(search_form_host)
167
            search_form_graph = sql_escape_like(search_form_graph)
168

    
169
            items = DBSession.query(
170
                    Host.idhost.label('idhost'),
171
                    Host.name.label('hostname'),
172
                    Graph.idgraph.label('idgraph'),
173
                    Graph.name.label('graphname'),
174
                ).distinct().join(
175
                    (PerfDataSource, PerfDataSource.idhost == Host.idhost),
176
                    (GRAPH_PERFDATASOURCE_TABLE, \
177
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
178
                        PerfDataSource.idperfdatasource),
179
                    (Graph, Graph.idgraph == \
180
                        GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
181
                    (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
182
                        Host.idhost),
183
                ).filter(Host.name.ilike(search_form_host)
184
                ).filter(Graph.name.ilike(search_form_graph)
185
                ).order_by(
186
                    Host.name.asc(),
187
                    Graph.name.asc(),
188
                )
189

    
190
        # On a ni hôte, ni indicateur. On renvoie une liste vide.
191
        # Si l'utilisateur voulait vraiment quelque chose,
192
        # il n'avait qu'à le demander.
193
        elif not search_form_host:
194
            return []
195

    
196
        # Sinon, on a juste un motif pour un hôte.
197
        # On renvoie la liste des hôtes correspondant.
198
        else:
199
            search_form_host = sql_escape_like(search_form_host)
200
            items = DBSession.query(
201
                    Host.idhost.label('idhost'),
202
                    Host.name.label('hostname'),
203
                ).distinct().join(
204
                    (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
205
                        Host.idhost),
206
                ).filter(Host.name.ilike(search_form_host)
207
                ).order_by(Host.name.asc())
208

    
209
        # Les managers ont accès à tout.
210
        # Les autres ont un accès restreint.
211
        if not config.is_manager.is_met(request.environ):
212
            supitemgroups = [sig[0] for sig in user.supitemgroups() if sig[1]]
213
            # pylint: disable-msg=E1103
214
            items = items.join(
215
                (GroupHierarchy, GroupHierarchy.idchild == \
216
                    SUPITEM_GROUP_TABLE.c.idgroup)
217
            ).filter(GroupHierarchy.idparent.in_(supitemgroups))
218

    
219
        items = items.limit(limit + 1).all() # pylint: disable-msg=E1103
220
        more_results = len(items) > limit
221

    
222
        if not search_form_graph:
223
            for i in xrange(min(limit, len(items))):
224
                ids.append((items[i].idhost, None))
225
                labels.append((items[i].hostname, None))
226
        else:
227
            for i in xrange(min(limit, len(items))):
228
                ids.append((items[i].idhost, items[i].idgraph))
229
                labels.append((items[i].hostname, items[i].graphname))
230

    
231
        return dict(labels=labels, ids=ids, more=more_results)
232

    
233
    @expose('graphslist.html')
234
    def graphsList(self, nocache=None, **kwargs):
235
        """
236
        Generation document avec url des graphes affiches
237
        (pour l impression )
238

239
        @param kwargs : arguments nommes
240
        @type kwargs  : dict
241

242
        @return: url de graphes
243
        @rtype: document html
244
        """
245
        # @TODO: le must serait de hot-patcher mootools pour que son serializer
246
        # d'URL utilise directement le format attendu par TurboGears
247
        # (notation pointée plutôt qu'avec des crochets)
248

    
249
        if not kwargs:
250
            return dict(graphslist=[])
251

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

    
259
        while True:
260
            try:
261
                host = kwargs['graphs[%d][host]' % i]
262
                graph = kwargs['graphs[%d][graph]' % i]
263
                start = int(kwargs.get('graphs[%d][start]' % i,
264
                            time.time() - 86400))
265
                duration = int(kwargs.get('graphs[%d][duration]' % i))
266
                nocache = kwargs['graphs[%d][nocache]' % i]
267
            except KeyError:
268
                break
269

    
270
            if not (host and graph and duration and nocache):
271
                break
272

    
273
            graphslist.append({
274
                'host': host,
275
                'graph': graph,
276
                'start': start,
277
                'duration': duration,
278
                'nocache': nocache,
279
                'start_date': time.strftime(format,
280
                    time.localtime(start)).decode('utf8'),
281
                'end_date': time.strftime(format,
282
                    time.localtime(start + duration)).decode('utf8')
283
            })
284
            i += 1
285
        return dict(graphslist=graphslist)
286

    
287
    @expose('json')
288
    def tempoDelayRefresh(self, nocache=None):
289
        """
290
        Determination valeur temporisation pour le rafraichissement automatique
291
        d un graphe
292

293
        @return: valeur de temporisation
294
        @rtype: C{str}
295
        """
296

    
297
        try:
298
            delay = int(config['refresh_delay'])
299
        except (ValueError, KeyError):
300
            delay = 30
301
        return {'delay': delay}
302

    
303
    class GetIndicatorsSchema(schema.Schema):
304
        """Schéma de validation pour la méthode L{getIndicators}."""
305
        host = validators.UnicodeString(not_empty=True)
306
        graph = validators.UnicodeString(not_empty=True)
307
        nocache = validators.UnicodeString(if_missing=None)
308

    
309
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
310
    @validate(
311
        validators = GetIndicatorsSchema(),
312
        error_handler = process_form_errors)
313
    @expose('json')
314
    def getIndicators(self, host, graph, nocache=None):
315
        """
316
        Liste d indicateurs associes a un graphe
317

318
        @param graph : graphe
319
        @type graph  : C{str}
320

321
        @return: dictionnaire des indicateurs d un graphe
322
        @rtype: document json (sous forme de dict)
323
        """
324

    
325
        indicators = self.getListIndicators(host, graph)
326
        indicators = [(ind.name, ind.label) for ind in indicators]
327
        return dict(items=indicators)
328

    
329
    class StartTimeSchema(schema.Schema):
330
        """Schéma de validation pour la méthode L{getIndicators}."""
331
        host = validators.UnicodeString(not_empty=True)
332
        nocache = validators.UnicodeString(if_missing=None)
333

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

    
349
    class FullHostPageSchema(schema.Schema):
350
        """Schéma de validation pour la méthode L{fullHostPage}."""
351
        host = validators.UnicodeString(not_empty=True)
352
        start = validators.Int(if_missing=None)
353
        duration = validators.Int(if_missing=86400)
354

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

369
        @param host : hôte
370
        @type host : C{str}
371
        @param start : date-heure de debut des donnees
372
        @type start : C{str}
373
        @param duration : plage de temps des données. Parametre optionnel,
374
            initialisé a 86400 = plage de 1 jour.
375
        @type duration : C{str}
376

377
        @return: page avec les images des graphes et boutons de deplacement
378
            dans le temps
379
        @rtype: page html
380
        """
381
        if start is None:
382
            start = int(time.time()) - int(duration)
383
        else:
384
            start = int(start)
385
        duration = int(duration)
386

    
387
        user = get_current_user()
388
        if user is None:
389
            return dict(host=host, start=start, duration=duration,
390
                        presets=self.presets, graphs=[])
391

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

    
397
            # On vérifie que l'hôte en question existe bel et bien.
398
            host_obj = Host.by_host_name(host)
399
            if not host_obj:
400
                message = _('No such host "%s"') % host
401
                LOGGER.warning(message)
402
                raise http_exc.HTTPNotFound(message)
403

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

    
413
        # Récupération de la liste des noms des graphes associés à l'hôte.
414
        graphs = DBSession.query(
415
                Graph.name
416
            ).distinct(
417
            ).join(
418
                (GRAPH_PERFDATASOURCE_TABLE,
419
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
420
                (PerfDataSource, PerfDataSource.idperfdatasource ==
421
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
422
                (Host, Host.idhost == PerfDataSource.idhost),
423
            ).filter(Host.name == host)
424

    
425
        graphs = graphs.all()
426
        return dict(host=host, start=start, duration=duration,
427
                    presets=self.presets, graphs=graphs)
428

    
429

    
430
    class SearchHostSchema(schema.Schema):
431
        """Schéma de validation pour la méthode L{getIndicators}."""
432
        allow_extra_fields = True
433
        filter_extra_fields = True
434
        query = validators.UnicodeString(not_empty=True)
435

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

449
        @keyword query: Prefixe de recherche sur les noms d'hôtes
450
        @type    query: C{unicode}
451
        """
452
        if not query:
453
            redirect("searchHostForm")
454
            return
455

    
456
        query = sql_escape_like(query.strip())
457
        user = get_current_user()
458
        if user is None:
459
            return dict(items=[])
460

    
461
        # Récupère les hôtes auxquels l'utilisateur a réellement accès.
462
        hosts = DBSession.query(
463
                Host.name,
464
            ).distinct(
465
            ).join(
466
                (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
467
                    Host.idhost),
468
            ).filter(Host.name.like(query + u'%')
469
            ).order_by(Host.name.asc(),)
470

    
471
        # Les managers ont accès à tout.
472
        # Les autres ont un accès restreint.
473
        if not config.is_manager.is_met(request.environ):
474
            supitemgroups = [sig[0] for sig in user.supitemgroups() if sig[1]]
475
            hosts = hosts.join(
476
                    (GroupHierarchy, GroupHierarchy.idchild == \
477
                        SUPITEM_GROUP_TABLE.c.idgroup)
478
                ).filter(GroupHierarchy.idparent.in_(supitemgroups))
479

    
480
        return dict(hosts=[h.name for h in hosts])
481

    
482
    # VIGILO_EXIG_VIGILO_PERF_0030:Moteur de recherche des graphes
483
    @expose('getopensearch.xml', content_type='text/xml')
484
    def getOpenSearch(self):
485
        """
486
        Moteur de recherche des graphes
487

488
        @return: document
489
        @rtype: document xml
490
        """
491
        return dict()
492

    
493
    @expose('json')
494
    def hosttree(self, parent_id=None, onlytype="", offset=0, noCache=None):
495
        """
496
        Affiche un étage de l'arbre de
497
        sélection des hôtes et groupes d'hôtes.
498

499
        @param parent_id: identifiant du groupe d'hôte parent
500
        @type  parent_id: C{int} or None
501
        """
502

    
503
        # Si l'identifiant du groupe parent n'est pas
504
        # spécifié, on retourne la liste des groupes racines,
505
        # fournie par la méthode get_root_hosts_groups.
506
        if parent_id is None:
507
            return self.get_root_host_groups()
508

    
509
        # TODO: Utiliser un schéma de validation
510
        parent_id = int(parent_id)
511
        offset = int(offset)
512

    
513
        # On vérifie si le groupe parent fait partie des
514
        # groupes auxquel l'utilisateur a accès, et on
515
        # retourne une liste vide dans le cas contraire
516
        is_manager = config.is_manager.is_met(request.environ)
517
        if not is_manager:
518
            direct_access = False
519
            user = get_current_user()
520

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

    
533
            # Distance positive.
534
            distance = DBSession.query(
535
                    functions.max(GroupHierarchy.hops)
536
                ).join(
537
                    (Group, Group.idgroup == GroupHierarchy.idparent),
538
                    (DataPermission,
539
                        DataPermission.idgroup == Group.idgroup),
540
                    (UserGroup,
541
                        UserGroup.idgroup == DataPermission.idusergroup),
542
                    (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
543
                        UserGroup.idgroup),
544
                ).filter(USER_GROUP_TABLE.c.username == user.user_name
545
                ).filter(Group.grouptype == u'supitemgroup'
546
                ).filter(GroupHierarchy.idchild == parent_id
547
                ).scalar()
548

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

    
568
            if distance is None:
569
                # Pas d'accès à ce groupe.
570
                return dict(groups = [], items = [])
571

    
572
            direct_access = distance >= 0
573

    
574
        limit = int(config.get("max_menu_entries", 20))
575
        result = {"groups": [], "items": []}
576

    
577
        if not onlytype or onlytype == "group":
578
            # On récupère la liste des groupes dont
579
            # l'identifiant du parent est passé en paramètre
580
            gh1 = aliased(GroupHierarchy, name='gh1')
581
            gh2 = aliased(GroupHierarchy, name='gh2')
582

    
583
            db_groups = DBSession.query(
584
                SupItemGroup
585
            ).options(lazyload('_path_obj')
586
            ).distinct(
587
            ).join(
588
                (gh1, gh1.idchild == SupItemGroup.idgroup),
589
            ).filter(gh1.hops == 1
590
            ).filter(gh1.idparent == parent_id
591
            ).order_by(SupItemGroup.name.asc())
592

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

    
607
            num_children_left = db_groups.count() - offset
608
            if offset:
609
                result["continued_from"] = offset
610
                result["continued_type"] = "group"
611
            all_groups = db_groups.limit(limit).offset(offset).all()
612
            for group in all_groups:
613
                result["groups"].append({
614
                    'id'   : group.idgroup,
615
                    'name' : group.name,
616
                    'type' : "group",
617
                })
618
            if num_children_left > limit:
619
                result["groups"].append({
620
                    'name': _("Next %(limit)s") % {"limit": limit},
621
                    'offset': offset + limit,
622
                    'parent_id': parent_id,
623
                    'type': 'continued',
624
                    'for_type': 'group',
625
                })
626

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

    
660
        return result
661

    
662
    @expose('json')
663
    def graphtree(self, host_id=None, parent_id=None, offset=0, noCache=None):
664
        """
665
        Affiche un étage de l'arbre de sélection
666
        des graphes et groupes de graphes.
667

668
        @param parent_id: identifiant du groupe de graphes parent
669
        @type  parent_id: C{int} or None
670
        """
671

    
672
        # Si l'identifiant de l'hôte n'est pas spécifié, on
673
        # retourne un dictionnaire contenant deux listes vides
674
        if host_id is None:
675
            return dict(groups = [], graphs=[])
676

    
677
        # On vérifie les permissions sur l'hôte
678
        # TODO: Utiliser un schéma de validation
679
        host_id = int(host_id)
680
        host = DBSession.query(Host
681
            ).filter(Host.idhost == host_id
682
            ).first()
683
        if host is None:
684
            return dict(groups = [], graphs=[])
685
        user = get_current_user()
686
        if not host.is_allowed_for(user):
687
            return dict(groups = [], graphs=[])
688

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

    
707
        # Si l'identifiant du groupe parent n'est pas spécifié,
708
        # on récupère la liste des groupes de graphes racines.
709
        if parent_id is None:
710
            graph_groups = GraphGroup.get_top_groups()
711

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

    
727
        # On réalise l'intersection des deux listes
728
        groups = []
729
        for gg in graph_groups:
730
            if gg in host_graph_groups:
731
                groups.append({
732
                    'id'   : gg.idgroup,
733
                    'name' : gg.name,
734
                    'type' : "group",
735
                })
736

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

    
763
        return dict(groups=groups, items=graphs)
764

    
765
    def get_root_host_groups(self):
766
        """
767
        Retourne tous les groupes racines (c'est à dire n'ayant
768
        aucun parent) d'hôtes auquel l'utilisateur a accès.
769

770
        @return: Un dictionnaire contenant la liste de ces groupes.
771
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
772
        """
773

    
774
        # On récupère tous les groupes qui ont un parent.
775
        children = DBSession.query(
776
            SupItemGroup,
777
        ).distinct(
778
        ).join(
779
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
780
        ).filter(GroupHierarchy.hops > 0)
781

    
782
        # Ensuite on les exclut de la liste des groupes,
783
        # pour ne garder que ceux qui sont au sommet de
784
        # l'arbre et qui constituent nos "root groups".
785
        root_groups = DBSession.query(
786
            SupItemGroup,
787
        ).except_(children
788
        ).order_by(SupItemGroup.name)
789

    
790
        # On filtre ces groupes racines afin de ne
791
        # retourner que ceux auquels l'utilisateur a accès
792
        user = get_current_user()
793
        if not config.is_manager.is_met(request.environ):
794
            user_groups = [ug[0] for ug in user.supitemgroups()]
795
            root_groups = root_groups.filter(
796
                SupItemGroup.idgroup.in_(user_groups))
797

    
798
        groups = []
799
        for group in root_groups.all():
800
            groups.append({
801
                'id'   : group.idgroup,
802
                'name' : group.name,
803
                'type' : "group",
804
            })
805

    
806
        return dict(groups=groups, items=[])
807

    
808
    def getListIndicators(self, host, graph):
809
        """
810
        Liste d indicateurs associes a un graphe
811

812
        @param graph : graphe
813
        @type graph  : C{str}
814

815
        @return: liste d indicateurs
816
        @rtype  : list
817
        """
818

    
819
        indicators = []
820
        if graph is not None:
821
            indicators = DBSession.query(
822
                    PerfDataSource.name, PerfDataSource.label
823
                ).distinct(
824
                ).join(
825
                    (GRAPH_PERFDATASOURCE_TABLE, \
826
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
827
                        PerfDataSource.idperfdatasource),
828
                    (Graph, Graph.idgraph == \
829
                        GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
830
                    (Host, Host.idhost == PerfDataSource.idhost),
831
                ).filter(Graph.name == graph
832
                ).filter(Host.name == host
833
                ).all()
834
        return indicators
835

    
836
    @expose('json')
837
    def dbmtime(self):
838
        change = Change.by_table_name(u"Graph")
839
        if change is None:
840
            return {"mtime": None}
841
        mtime = change.last_modified.replace(microsecond=0)
842
        return {"mtime": mtime}
843

    
844
    @expose('json')
845
    def selectHostAndGraph(self, host, graph):
846
        # @TODO: vérifier les permissions
847
        ids = DBSession.query(
848
                Host.idhost, Graph.idgraph
849
            ).join(
850
                (PerfDataSource, PerfDataSource.idhost == Host.idhost),
851
                (GRAPH_PERFDATASOURCE_TABLE, \
852
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource == \
853
                    PerfDataSource.idperfdatasource),
854
                (Graph, Graph.idgraph == \
855
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph),
856
            ).filter(Graph.name == unicode(graph)
857
            ).filter(Host.name == unicode(host)
858
            ).first()
859

    
860
        return {
861
            'idhost': ids and ids.idhost or None,
862
            'idgraph': ids and ids.idgraph or None,
863
        }
864

    
865
    @expose('json')
866
    def external_links(self):
867
        return dict(links=config['external_links'])