Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

views.py 17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

23 24 25
# App de gestion des statistiques pour re2o
# Gabriel Détraz
# Gplv2
chirac's avatar
chirac committed
26 27 28 29 30 31 32 33 34 35 36
"""
Vues des logs et statistiques générales.

La vue index générale affiche une selection des dernières actions,
classées selon l'importance, avec date, et user formatés.

Stats_logs renvoie l'ensemble des logs.

Les autres vues sont thématiques, ensemble des statistiques et du
nombre d'objets par models, nombre d'actions par user, etc
"""
37 38 39

from __future__ import unicode_literals

40
from django.urls import reverse
41 42
from django.shortcuts import render, redirect
from django.contrib import messages
43
from django.contrib.auth.decorators import login_required
44
from django.db.models import Count, Max, F
45 46

from reversion.models import Revision
47
from reversion.models import Version, ContentType
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
from users.models import (
    User,
    ServiceUser,
    School,
    ListRight,
    ListShell,
    Ban,
    Whitelist,
    Adherent,
    Club
)
from cotisations.models import (
    Facture,
    Vente,
    Article,
    Banque,
    Paiement,
    Cotisation
)
from machines.models import (
    Machine,
    MachineType,
    IpType,
    Extension,
    Interface,
    Domain,
    IpList,
    OuverturePortList,
    Service,
    Vlan,
    Nas,
    SOA,
    Mx,
    Ns
)
from topologie.models import (
    Switch,
    Port,
    Room,
    Stack,
    ModelSwitch,
90 91
    ConstructorSwitch,
    AccessPoint
92
)
93
from preferences.models import GeneralOption
chirac's avatar
chirac committed
94
from re2o.views import form
95 96 97 98 99
from re2o.utils import (
    all_whitelisted,
    all_baned,
    all_has_access,
    all_adherent,
100
    re2o_paginator,
101 102
)
from re2o.acl import (
103 104 105 106
    can_view_all,
    can_view_app,
    can_edit_history,
)
chirac's avatar
chirac committed
107
from re2o.utils import all_active_assigned_interfaces_count
108
from re2o.utils import all_active_interfaces_count, SortTable
109

110

111
@login_required
112
@can_view_app('logs')
113
def index(request):
chirac's avatar
chirac committed
114 115
    """Affiche les logs affinés, date reformatées, selectionne
    les event importants (ajout de droits, ajout de ban/whitelist)"""
116
    pagination_number = GeneralOption.get_cached_value('pagination_number')
117
    # The types of content kept for display
chirac's avatar
chirac committed
118
    content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
119
    # Select only wanted versions
chirac's avatar
chirac committed
120 121 122 123
    versions = Version.objects.filter(
        content_type__in=ContentType.objects.filter(
            model__in=content_type_filter
        )
124 125 126 127 128 129 130
    ).select_related('revision')
    versions = SortTable.sort(
        versions,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.LOGS_INDEX
    )
131
    versions = re2o_paginator(request, versions, pagination_number)
132 133 134 135 136
    # Force to have a list instead of QuerySet
    versions.count(0)
    # Items to remove later because invalid
    to_remove = []
    # Parse every item (max = pagination_number)
chirac's avatar
chirac committed
137 138 139
    for i in range(len(versions.object_list)):
        if versions.object_list[i].object:
            version = versions.object_list[i]
140
            versions.object_list[i] = {
chirac's avatar
chirac committed
141 142 143 144 145 146 147 148 149 150 151 152
                'rev_id': version.revision.id,
                'comment': version.revision.comment,
                'datetime': version.revision.date_created.strftime(
                    '%d/%m/%y %H:%M:%S'
                    ),
                'username':
                    version.revision.user.get_username()
                    if version.revision.user else '?',
                'user_id': version.revision.user_id,
                'version': version}
        else:
            to_remove.insert(0, i)
153
    # Remove all tagged invalid items
chirac's avatar
chirac committed
154
    for i in to_remove:
155 156
        versions.object_list.pop(i)
    return render(request, 'logs/index.html', {'versions_list': versions})
157

chirac's avatar
chirac committed
158

159
@login_required
160
@can_view_all(GeneralOption)
161
def stats_logs(request):
chirac's avatar
chirac committed
162 163
    """Affiche l'ensemble des logs et des modifications sur les objets,
    classés par date croissante, en vrac"""
164
    pagination_number = GeneralOption.get_cached_value('pagination_number')
165
    revisions = Revision.objects.all().select_related('user')\
chirac's avatar
chirac committed
166
        .prefetch_related('version_set__object')
167 168 169 170 171 172
    revisions = SortTable.sort(
        revisions,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.LOGS_STATS_LOGS
    )
173
    revisions = re2o_paginator(request, revisions, pagination_number)
chirac's avatar
chirac committed
174 175 176 177
    return render(request, 'logs/stats_logs.html', {
        'revisions_list': revisions
        })

178

179
@login_required
180
@can_edit_history
181 182 183 184 185
def revert_action(request, revision_id):
    """ Annule l'action en question """
    try:
        revision = Revision.objects.get(id=revision_id)
    except Revision.DoesNotExist:
chirac's avatar
chirac committed
186
        messages.error(request, u"Revision inexistante")
187 188 189
    if request.method == "POST":
        revision.revert()
        messages.success(request, "L'action a été supprimée")
190
        return redirect(reverse('logs:index'))
chirac's avatar
chirac committed
191 192 193 194 195
    return form({
        'objet': revision,
        'objet_name': revision.__class__.__name__
        }, 'logs/delete.html', request)

196

Gabriel Detraz's avatar
Gabriel Detraz committed
197
@login_required
198
@can_view_all(IpList, Interface, User)
Gabriel Detraz's avatar
Gabriel Detraz committed
199
def stats_general(request):
chirac's avatar
chirac committed
200 201 202 203
    """Statistiques générales affinées sur les ip, activées, utilisées par
    range, et les statistiques générales sur les users : users actifs,
    cotisants, activés, archivés, etc"""
    ip_dict = dict()
204
    for ip_range in IpType.objects.select_related('vlan').all():
Gabriel Detraz's avatar
Gabriel Detraz committed
205 206
        all_ip = IpList.objects.filter(ip_type=ip_range)
        used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
chirac's avatar
chirac committed
207 208 209
        active_ip = all_active_assigned_interfaces_count().filter(
            ipv4__in=IpList.objects.filter(ip_type=ip_range)
        ).count()
210
        ip_dict[ip_range] = [ip_range, ip_range.vlan, all_ip.count(),
chirac's avatar
chirac committed
211
                             used_ip, active_ip, all_ip.count()-used_ip]
212 213 214 215 216
    _all_adherent = all_adherent()
    _all_has_access = all_has_access()
    _all_baned = all_baned()
    _all_whitelisted = all_whitelisted()
    _all_active_interfaces_count = all_active_interfaces_count()
Maël Kervella's avatar
Maël Kervella committed
217 218
    _all_active_assigned_interfaces_count = \
        all_active_assigned_interfaces_count()
Gabriel Detraz's avatar
Gabriel Detraz committed
219
    stats = [
Maël Kervella's avatar
Maël Kervella committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
        [   # First set of data (about users)
            [   # Headers
                "Categorie",
                "Nombre d'utilisateurs (total club et adhérents)",
                "Nombre d'adhérents",
                "Nombre de clubs"
            ],
            {   # Data
                'active_users': [
                    "Users actifs",
                    User.objects.filter(state=User.STATE_ACTIVE).count(),
                    (Adherent.objects
                     .filter(state=Adherent.STATE_ACTIVE)
                     .count()),
                    Club.objects.filter(state=Club.STATE_ACTIVE).count()
                ],
                'inactive_users': [
                    "Users désactivés",
                    User.objects.filter(state=User.STATE_DISABLED).count(),
                    (Adherent.objects
                     .filter(state=Adherent.STATE_DISABLED)
                     .count()),
                    Club.objects.filter(state=Club.STATE_DISABLED).count()
                ],
                'archive_users': [
                    "Users archivés",
                    User.objects.filter(state=User.STATE_ARCHIVE).count(),
                    (Adherent.objects
                     .filter(state=Adherent.STATE_ARCHIVE)
                     .count()),
                    Club.objects.filter(state=Club.STATE_ARCHIVE).count()
                ],
                'adherent_users': [
                    "Cotisant à l'association",
                    _all_adherent.count(),
                    _all_adherent.exclude(adherent__isnull=True).count(),
                    _all_adherent.exclude(club__isnull=True).count()
                ],
                'connexion_users': [
                    "Utilisateurs bénéficiant d'une connexion",
                    _all_has_access.count(),
                    _all_has_access.exclude(adherent__isnull=True).count(),
                    _all_has_access.exclude(club__isnull=True).count()
                ],
                'ban_users': [
                    "Utilisateurs bannis",
                    _all_baned.count(),
                    _all_baned.exclude(adherent__isnull=True).count(),
                    _all_baned.exclude(club__isnull=True).count()
                ],
                'whitelisted_user': [
                    "Utilisateurs bénéficiant d'une connexion gracieuse",
                    _all_whitelisted.count(),
                    _all_whitelisted.exclude(adherent__isnull=True).count(),
                    _all_whitelisted.exclude(club__isnull=True).count()
                ],
                'actives_interfaces': [
                    "Interfaces actives (ayant accès au reseau)",
                    _all_active_interfaces_count.count(),
                    (_all_active_interfaces_count
                     .exclude(machine__user__adherent__isnull=True)
                     .count()),
                    (_all_active_interfaces_count
                     .exclude(machine__user__club__isnull=True)
                     .count())
                ],
                'actives_assigned_interfaces': [
                    "Interfaces actives et assignées ipv4",
                    _all_active_assigned_interfaces_count.count(),
                    (_all_active_assigned_interfaces_count
                     .exclude(machine__user__adherent__isnull=True)
                     .count()),
                    (_all_active_assigned_interfaces_count
                     .exclude(machine__user__club__isnull=True)
                     .count())
                ]
            }
        ],
        [   # Second set of data (about ip adresses)
            [   # Headers
                "Range d'ip",
                "Vlan",
                "Nombre d'ip totales",
                "Ip assignées",
                "Ip assignées à une machine active",
                "Ip non assignées"
            ],
            ip_dict  # Data already prepared
chirac's avatar
chirac committed
308
        ]
Maël Kervella's avatar
Maël Kervella committed
309
    ]
Gabriel Detraz's avatar
Gabriel Detraz committed
310 311 312
    return render(request, 'logs/stats_general.html', {'stats_list': stats})


313
@login_required
314
@can_view_app('users', 'cotisations', 'machines', 'topologie')
315
def stats_models(request):
chirac's avatar
chirac committed
316 317 318
    """Statistiques générales, affiche les comptages par models:
    nombre d'users, d'écoles, de droits, de bannissements,
    de factures, de ventes, de banque, de machines, etc"""
319
    stats = {
chirac's avatar
chirac committed
320 321
        'Users': {
            'users': [User.PRETTY_NAME, User.objects.count()],
322 323
            'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()],
            'clubs': [Club.PRETTY_NAME, Club.objects.count()],
chirac's avatar
chirac committed
324 325 326 327 328 329 330 331 332
            'serviceuser': [ServiceUser.PRETTY_NAME,
                            ServiceUser.objects.count()],
            'school': [School.PRETTY_NAME, School.objects.count()],
            'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
            'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
            'ban': [Ban.PRETTY_NAME, Ban.objects.count()],
            'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
        },
        'Cotisations': {
Maël Kervella's avatar
Maël Kervella committed
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
            'factures': [
                Facture._meta.verbose_name.title(),
                Facture.objects.count()
            ],
            'vente': [
                Vente._meta.verbose_name.title(),
                Vente.objects.count()
            ],
            'cotisation': [
                Cotisation._meta.verbose_name.title(),
                Cotisation.objects.count()
            ],
            'article': [
                Article._meta.verbose_name.title(),
                Article.objects.count()
            ],
            'banque': [
                Banque._meta.verbose_name.title(),
                Banque.objects.count()
            ],
chirac's avatar
chirac committed
353 354 355 356 357 358 359 360 361 362 363
        },
        'Machines': {
            'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
            'typemachine': [MachineType.PRETTY_NAME,
                            MachineType.objects.count()],
            'typeip': [IpType.PRETTY_NAME, IpType.objects.count()],
            'extension': [Extension.PRETTY_NAME, Extension.objects.count()],
            'interface': [Interface.PRETTY_NAME, Interface.objects.count()],
            'alias': [Domain.PRETTY_NAME,
                      Domain.objects.exclude(cname=None).count()],
            'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
364 365 366 367 368 369
            'service': [Service.PRETTY_NAME, Service.objects.count()],
            'ouvertureportlist': [
                OuverturePortList.PRETTY_NAME,
                OuverturePortList.objects.count()
            ],
            'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
370
            'SOA': [SOA.PRETTY_NAME, SOA.objects.count()],
371 372 373
            'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
            'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
            'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
chirac's avatar
chirac committed
374 375 376
        },
        'Topologie': {
            'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
377
            'bornes': [AccessPoint.PRETTY_NAME, AccessPoint.objects.count()],
chirac's avatar
chirac committed
378 379
            'port': [Port.PRETTY_NAME, Port.objects.count()],
            'chambre': [Room.PRETTY_NAME, Room.objects.count()],
380 381 382 383 384 385 386 387 388
            'stack': [Stack.PRETTY_NAME, Stack.objects.count()],
            'modelswitch': [
                ModelSwitch.PRETTY_NAME,
                ModelSwitch.objects.count()
            ],
            'constructorswitch': [
                ConstructorSwitch.PRETTY_NAME,
                ConstructorSwitch.objects.count()
            ],
chirac's avatar
chirac committed
389 390 391 392 393
        },
        'Actions effectuées sur la base':
        {
            'revision': ["Nombre d'actions", Revision.objects.count()],
        },
394
    }
chirac's avatar
chirac committed
395 396
    return render(request, 'logs/stats_models.html', {'stats_list': stats})

chirac's avatar
chirac committed
397 398

@login_required
399
@can_view_app('users')
chirac's avatar
chirac committed
400
def stats_users(request):
chirac's avatar
chirac committed
401 402 403 404
    """Affiche les statistiques base de données aggrégées par user :
    nombre de machines par user, d'etablissements par user,
    de moyens de paiements par user, de banque par user,
    de bannissement par user, etc"""
chirac's avatar
chirac committed
405
    stats = {
chirac's avatar
chirac committed
406 407 408 409 410 411 412 413 414 415 416 417 418 419
        'Utilisateur': {
            'Machines': User.objects.annotate(
                num=Count('machine')
            ).order_by('-num')[:10],
            'Facture': User.objects.annotate(
                num=Count('facture')
            ).order_by('-num')[:10],
            'Bannissement': User.objects.annotate(
                num=Count('ban')
            ).order_by('-num')[:10],
            'Accès gracieux': User.objects.annotate(
                num=Count('whitelist')
            ).order_by('-num')[:10],
            'Droits': User.objects.annotate(
420
                num=Count('groups')
chirac's avatar
chirac committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
            ).order_by('-num')[:10],
        },
        'Etablissement': {
            'Utilisateur': School.objects.annotate(
                num=Count('user')
            ).order_by('-num')[:10],
        },
        'Moyen de paiement': {
            'Utilisateur': Paiement.objects.annotate(
                num=Count('facture')
            ).order_by('-num')[:10],
        },
        'Banque': {
            'Utilisateur': Banque.objects.annotate(
                num=Count('facture')
            ).order_by('-num')[:10],
        },
chirac's avatar
chirac committed
438
    }
439
    return render(request, 'logs/stats_users.html', {'stats_list': stats})
chirac's avatar
chirac committed
440

chirac's avatar
chirac committed
441 442

@login_required
443
@can_view_app('users')
chirac's avatar
chirac committed
444
def stats_actions(request):
chirac's avatar
chirac committed
445 446 447
    """Vue qui affiche les statistiques de modifications d'objets par
    utilisateurs.
    Affiche le nombre de modifications aggrégées par utilisateurs"""
chirac's avatar
chirac committed
448
    stats = {
chirac's avatar
chirac committed
449 450 451 452 453
        'Utilisateur': {
            'Action': User.objects.annotate(
                num=Count('revision')
            ).order_by('-num')[:40],
        },
chirac's avatar
chirac committed
454 455
    }
    return render(request, 'logs/stats_users.html', {'stats_list': stats})
456

Maël Kervella's avatar
Maël Kervella committed
457

458 459 460
@login_required
@can_view_app('users')
def stats_droits(request):
461
    """Affiche la liste des droits et les users ayant chaque droit"""
Maël Kervella's avatar
Maël Kervella committed
462 463
    stats_list = {}

Gabriel Detraz's avatar
Gabriel Detraz committed
464
    for droit in ListRight.objects.all().select_related('group_ptr'):
Maël Kervella's avatar
Maël Kervella committed
465 466
        stats_list[droit] = droit.user_set.all().annotate(
            num=Count('revision'),
467
            last=Max('revision__date_created'),
Maël Kervella's avatar
Maël Kervella committed
468
        )
469

470 471 472 473 474
    stats_list['Superuser'] = User.objects.filter(is_superuser=True).annotate(
            num=Count('revision'),
            last=Max('revision__date_created'),
    )

Maël Kervella's avatar
Maël Kervella committed
475 476 477 478 479
    return render(
        request,
        'logs/stats_droits.html',
        {'stats_list': stats_list}
    )