views.py 16.2 KB
Newer Older
lhark's avatar
lhark committed
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.

chirac's avatar
chirac committed
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

chirac's avatar
chirac committed
40 41 42 43
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
chirac's avatar
chirac committed
44
from django.db.models import Count
chirac's avatar
chirac committed
45 46

from reversion.models import Revision
47
from reversion.models import Version, ContentType
chirac's avatar
chirac committed
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 90 91 92
from users.models import (
    User,
    ServiceUser,
    Right,
    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,
    ConstructorSwitch
)
93
from preferences.models import GeneralOption
chirac's avatar
chirac committed
94
from re2o.views import form
chirac's avatar
Menage  
chirac committed
95 96
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
from re2o.utils import all_active_assigned_interfaces_count
97
from re2o.utils import all_active_interfaces_count, SortTable
guimoz's avatar
guimoz committed
98 99

STATS_DICT = {
chirac's avatar
chirac committed
100 101 102 103 104 105
    0: ["Tout", 36],
    1: ["1 mois", 1],
    2: ["2 mois", 2],
    3: ["6 mois", 6],
    4: ["1 an", 12],
    5: ["2 an", 24],
guimoz's avatar
guimoz committed
106 107
}

108

chirac's avatar
chirac committed
109 110 111
@login_required
@permission_required('cableur')
def index(request):
chirac's avatar
chirac committed
112 113 114
    """Affiche les logs affinés, date reformatées, selectionne
    les event importants (ajout de droits, ajout de ban/whitelist)"""
    options, _created = GeneralOption.objects.get_or_create()
115
    pagination_number = options.pagination_number
116
    # The types of content kept for display
chirac's avatar
chirac committed
117
    content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
118
    # Select only wanted versions
chirac's avatar
chirac committed
119 120 121 122
    versions = Version.objects.filter(
        content_type__in=ContentType.objects.filter(
            model__in=content_type_filter
        )
123 124 125 126 127 128 129
    ).select_related('revision')
    versions = SortTable.sort(
        versions,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.LOGS_INDEX
    )
130
    paginator = Paginator(versions, pagination_number)
chirac's avatar
chirac committed
131 132
    page = request.GET.get('page')
    try:
133
        versions = paginator.page(page)
chirac's avatar
chirac committed
134 135
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
136
        versions = paginator.page(1)
chirac's avatar
chirac committed
137
    except EmptyPage:
chirac's avatar
chirac committed
138
        # If page is out of range (e.g. 9999), deliver last page of results.
139 140 141 142 143 144 145
        versions = paginator.page(paginator.num_pages)

    # 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
146 147 148
    for i in range(len(versions.object_list)):
        if versions.object_list[i].object:
            version = versions.object_list[i]
149
            versions.object_list[i] = {
chirac's avatar
chirac committed
150 151 152 153 154 155 156 157 158 159 160 161
                '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)
162
    # Remove all tagged invalid items
chirac's avatar
chirac committed
163
    for i in to_remove:
164 165
        versions.object_list.pop(i)
    return render(request, 'logs/index.html', {'versions_list': versions})
166

chirac's avatar
chirac committed
167

168 169 170
@login_required
@permission_required('cableur')
def stats_logs(request):
chirac's avatar
chirac committed
171 172 173
    """Affiche l'ensemble des logs et des modifications sur les objets,
    classés par date croissante, en vrac"""
    options, _created = GeneralOption.objects.get_or_create()
174
    pagination_number = options.pagination_number
175
    revisions = Revision.objects.all().select_related('user')\
chirac's avatar
chirac committed
176
        .prefetch_related('version_set__object')
177 178 179 180 181 182
    revisions = SortTable.sort(
        revisions,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.LOGS_STATS_LOGS
    )
183 184 185 186 187 188 189 190
    paginator = Paginator(revisions, pagination_number)
    page = request.GET.get('page')
    try:
        revisions = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        revisions = paginator.page(1)
    except EmptyPage:
chirac's avatar
chirac committed
191
        # If page is out of range (e.g. 9999), deliver last page of results.
192
        revisions = paginator.page(paginator.num_pages)
chirac's avatar
chirac committed
193 194 195 196
    return render(request, 'logs/stats_logs.html', {
        'revisions_list': revisions
        })

197

198 199 200 201 202 203 204
@login_required
@permission_required('bureau')
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
205
        messages.error(request, u"Revision inexistante")
206 207 208 209
    if request.method == "POST":
        revision.revert()
        messages.success(request, "L'action a été supprimée")
        return redirect("/logs/")
chirac's avatar
chirac committed
210 211 212 213 214
    return form({
        'objet': revision,
        'objet_name': revision.__class__.__name__
        }, 'logs/delete.html', request)

215

Gabriel Detraz's avatar
Gabriel Detraz committed
216 217 218
@login_required
@permission_required('cableur')
def stats_general(request):
chirac's avatar
chirac committed
219 220 221 222
    """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()
223
    for ip_range in IpType.objects.select_related('vlan').all():
Gabriel Detraz's avatar
Gabriel Detraz committed
224 225
        all_ip = IpList.objects.filter(ip_type=ip_range)
        used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
chirac's avatar
chirac committed
226 227 228
        active_ip = all_active_assigned_interfaces_count().filter(
            ipv4__in=IpList.objects.filter(ip_type=ip_range)
        ).count()
229
        ip_dict[ip_range] = [ip_range, ip_range.vlan, all_ip.count(),
chirac's avatar
chirac committed
230
                             used_ip, active_ip, all_ip.count()-used_ip]
231 232 233 234 235 236
    _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()
    _all_active_assigned_interfaces_count = all_active_assigned_interfaces_count()
Gabriel Detraz's avatar
Gabriel Detraz committed
237
    stats = [
238
        [["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], {
chirac's avatar
chirac committed
239 240
            'active_users': [
                "Users actifs",
241 242 243 244
                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': [
chirac's avatar
chirac committed
245
                "Users désactivés",
246 247 248
                User.objects.filter(state=User.STATE_DISABLED).count(),
                Adherent.objects.filter(state=Adherent.STATE_DISABLED).count(),
                Club.objects.filter(state=Club.STATE_DISABLED).count()],
chirac's avatar
chirac committed
249 250
            'archive_users': [
                "Users archivés",
251 252 253
                User.objects.filter(state=User.STATE_ARCHIVE).count(),
                Adherent.objects.filter(state=Adherent.STATE_ARCHIVE).count(),
                Club.objects.filter(state=Club.STATE_ARCHIVE).count()],
chirac's avatar
chirac committed
254
            'adherent_users': [
255 256 257 258
                "Cotisant à l'association",
                _all_adherent.count(),
                _all_adherent.exclude(adherent__isnull=True).count(),
                _all_adherent.exclude(club__isnull=True).count()],
chirac's avatar
chirac committed
259 260
            'connexion_users': [
                "Utilisateurs bénéficiant d'une connexion",
261 262 263
                _all_has_access.count(),
                _all_has_access.exclude(adherent__isnull=True).count(),
                _all_has_access.exclude(club__isnull=True).count()],
chirac's avatar
chirac committed
264 265
            'ban_users': [
                "Utilisateurs bannis",
266 267 268
                _all_baned.count(),
                _all_baned.exclude(adherent__isnull=True).count(),
                _all_baned.exclude(club__isnull=True).count()],
chirac's avatar
chirac committed
269 270
            'whitelisted_user': [
                "Utilisateurs bénéficiant d'une connexion gracieuse",
271 272 273
                _all_whitelisted.count(),
                _all_whitelisted.exclude(adherent__isnull=True).count(),
                _all_whitelisted.exclude(club__isnull=True).count()],
chirac's avatar
chirac committed
274 275
            'actives_interfaces': [
                "Interfaces actives (ayant accès au reseau)",
276 277 278 279 280 281 282
                _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()],
chirac's avatar
chirac committed
283 284
            'actives_assigned_interfaces': [
                "Interfaces actives et assignées ipv4",
285 286 287 288 289 290 291
                _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()]
chirac's avatar
chirac committed
292
        }],
293
        [["Range d'ip", "Vlan", "Nombre d'ip totales", "Ip assignées",
chirac's avatar
chirac committed
294 295
          "Ip assignées à une machine active", "Ip non assignées"], ip_dict]
        ]
Gabriel Detraz's avatar
Gabriel Detraz committed
296 297 298
    return render(request, 'logs/stats_general.html', {'stats_list': stats})


299 300 301
@login_required
@permission_required('cableur')
def stats_models(request):
chirac's avatar
chirac committed
302 303 304
    """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"""
305
    stats = {
chirac's avatar
chirac committed
306 307
        'Users': {
            'users': [User.PRETTY_NAME, User.objects.count()],
308 309
            'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()],
            'clubs': [Club.PRETTY_NAME, Club.objects.count()],
chirac's avatar
chirac committed
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
            'serviceuser': [ServiceUser.PRETTY_NAME,
                            ServiceUser.objects.count()],
            'right': [Right.PRETTY_NAME, Right.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': {
            'factures': [Facture.PRETTY_NAME, Facture.objects.count()],
            'vente': [Vente.PRETTY_NAME, Vente.objects.count()],
            'cotisation': [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
            'article': [Article.PRETTY_NAME, Article.objects.count()],
            'banque': [Banque.PRETTY_NAME, Banque.objects.count()],
        },
        '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()],
336 337 338 339 340 341 342 343 344 345
            'service': [Service.PRETTY_NAME, Service.objects.count()],
            'ouvertureportlist': [
                OuverturePortList.PRETTY_NAME,
                OuverturePortList.objects.count()
            ],
            'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
            'SOA': [Mx.PRETTY_NAME, Mx.objects.count()],
            '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
346 347 348 349 350
        },
        'Topologie': {
            'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
            'port': [Port.PRETTY_NAME, Port.objects.count()],
            'chambre': [Room.PRETTY_NAME, Room.objects.count()],
351 352 353 354 355 356 357 358 359
            '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
360 361 362 363 364
        },
        'Actions effectuées sur la base':
        {
            'revision': ["Nombre d'actions", Revision.objects.count()],
        },
365
    }
chirac's avatar
chirac committed
366 367
    return render(request, 'logs/stats_models.html', {'stats_list': stats})

chirac's avatar
chirac committed
368 369 370 371

@login_required
@permission_required('cableur')
def stats_users(request):
chirac's avatar
chirac committed
372 373 374 375
    """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"""
guimoz's avatar
guimoz committed
376 377
    onglet = request.GET.get('onglet')
    try:
chirac's avatar
chirac committed
378 379 380
        _search_field = STATS_DICT[onglet]
    except KeyError:
        _search_field = STATS_DICT[0]
guimoz's avatar
guimoz committed
381
        onglet = 0
chirac's avatar
chirac committed
382
    stats = {
chirac's avatar
chirac committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
        '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(
                num=Count('right')
            ).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
415
    }
chirac's avatar
chirac committed
416 417 418 419 420 421
    return render(request, 'logs/stats_users.html', {
        'stats_list': stats,
        'stats_dict': STATS_DICT,
        'active_field': onglet
        })

chirac's avatar
chirac committed
422 423 424 425

@login_required
@permission_required('cableur')
def stats_actions(request):
chirac's avatar
chirac committed
426 427 428
    """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
429
    stats = {
chirac's avatar
chirac committed
430 431 432 433 434
        'Utilisateur': {
            'Action': User.objects.annotate(
                num=Count('revision')
            ).order_by('-num')[:40],
        },
chirac's avatar
chirac committed
435 436
    }
    return render(request, 'logs/stats_users.html', {'stats_list': stats})