# 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. # App de gestion des statistiques pour re2o # Gabriel Détraz # Gplv2 """ 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 """ from __future__ import unicode_literals from django.urls import reverse 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 from django.db.models import Count from reversion.models import Revision from reversion.models import Version, ContentType 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 ) from preferences.models import GeneralOption from re2o.views import form from re2o.utils import ( all_whitelisted, all_baned, all_has_access, all_adherent, can_view_all, can_view_app, can_edit_history, ) from re2o.utils import all_active_assigned_interfaces_count from re2o.utils import all_active_interfaces_count, SortTable STATS_DICT = { 0: ["Tout", 36], 1: ["1 mois", 1], 2: ["2 mois", 2], 3: ["6 mois", 6], 4: ["1 an", 12], 5: ["2 an", 24], } @login_required @can_view_app('logs') def index(request): """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() pagination_number = options.pagination_number # The types of content kept for display content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user'] # Select only wanted versions versions = Version.objects.filter( content_type__in=ContentType.objects.filter( model__in=content_type_filter ) ).select_related('revision') versions = SortTable.sort( versions, request.GET.get('col'), request.GET.get('order'), SortTable.LOGS_INDEX ) paginator = Paginator(versions, pagination_number) page = request.GET.get('page') try: versions = paginator.page(page) except PageNotAnInteger: # If page is not an integer, deliver first page. versions = paginator.page(1) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. 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) for i in range(len(versions.object_list)): if versions.object_list[i].object: version = versions.object_list[i] versions.object_list[i] = { '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) # Remove all tagged invalid items for i in to_remove: versions.object_list.pop(i) return render(request, 'logs/index.html', {'versions_list': versions}) @login_required @can_view_all(GeneralOption) def stats_logs(request): """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() pagination_number = options.pagination_number revisions = Revision.objects.all().select_related('user')\ .prefetch_related('version_set__object') revisions = SortTable.sort( revisions, request.GET.get('col'), request.GET.get('order'), SortTable.LOGS_STATS_LOGS ) 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: # If page is out of range (e.g. 9999), deliver last page of results. revisions = paginator.page(paginator.num_pages) return render(request, 'logs/stats_logs.html', { 'revisions_list': revisions }) @login_required @can_edit_history def revert_action(request, revision_id): """ Annule l'action en question """ try: revision = Revision.objects.get(id=revision_id) except Revision.DoesNotExist: messages.error(request, u"Revision inexistante") if request.method == "POST": revision.revert() messages.success(request, "L'action a été supprimée") return redirect(reverse('logs:index')) return form({ 'objet': revision, 'objet_name': revision.__class__.__name__ }, 'logs/delete.html', request) @login_required @can_view_all(IpList) @can_view_all(Interface) @can_view_all(User) def stats_general(request): """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() for ip_range in IpType.objects.select_related('vlan').all(): all_ip = IpList.objects.filter(ip_type=ip_range) used_ip = Interface.objects.filter(ipv4__in=all_ip).count() active_ip = all_active_assigned_interfaces_count().filter( ipv4__in=IpList.objects.filter(ip_type=ip_range) ).count() ip_dict[ip_range] = [ip_range, ip_range.vlan, all_ip.count(), used_ip, active_ip, all_ip.count()-used_ip] _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() stats = [ [["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], { '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()] }], [["Range d'ip", "Vlan", "Nombre d'ip totales", "Ip assignées", "Ip assignées à une machine active", "Ip non assignées"], ip_dict] ] return render(request, 'logs/stats_general.html', {'stats_list': stats}) @login_required @can_view_app('users') @can_view_app('cotisations') @can_view_app('machines') @can_view_app('topologie') def stats_models(request): """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""" stats = { 'Users': { 'users': [User.PRETTY_NAME, User.objects.count()], 'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()], 'clubs': [Club.PRETTY_NAME, Club.objects.count()], '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()], 'service': [Service.PRETTY_NAME, Service.objects.count()], 'ouvertureportlist': [ OuverturePortList.PRETTY_NAME, OuverturePortList.objects.count() ], 'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()], 'SOA': [SOA.PRETTY_NAME, SOA.objects.count()], 'Mx': [Mx.PRETTY_NAME, Mx.objects.count()], 'Ns': [Ns.PRETTY_NAME, Ns.objects.count()], 'nas': [Nas.PRETTY_NAME, Nas.objects.count()], }, 'Topologie': { 'switch': [Switch.PRETTY_NAME, Switch.objects.count()], 'port': [Port.PRETTY_NAME, Port.objects.count()], 'chambre': [Room.PRETTY_NAME, Room.objects.count()], 'stack': [Stack.PRETTY_NAME, Stack.objects.count()], 'modelswitch': [ ModelSwitch.PRETTY_NAME, ModelSwitch.objects.count() ], 'constructorswitch': [ ConstructorSwitch.PRETTY_NAME, ConstructorSwitch.objects.count() ], }, 'Actions effectuées sur la base': { 'revision': ["Nombre d'actions", Revision.objects.count()], }, } return render(request, 'logs/stats_models.html', {'stats_list': stats}) @login_required @can_view_app('users') def stats_users(request): """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""" onglet = request.GET.get('onglet') try: _search_field = STATS_DICT[onglet] except KeyError: _search_field = STATS_DICT[0] onglet = 0 stats = { '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], }, } return render(request, 'logs/stats_users.html', { 'stats_list': stats, 'stats_dict': STATS_DICT, 'active_field': onglet }) @login_required @can_view_app('users') def stats_actions(request): """Vue qui affiche les statistiques de modifications d'objets par utilisateurs. Affiche le nombre de modifications aggrégées par utilisateurs""" stats = { 'Utilisateur': { 'Action': User.objects.annotate( num=Count('revision') ).order_by('-num')[:40], }, } return render(request, 'logs/stats_users.html', {'stats_list': stats})