easy audit added for activity log
This commit is contained in:
parent
4467a56d67
commit
21135ae821
Binary file not shown.
Binary file not shown.
@ -109,5 +109,6 @@ urlpatterns = [
|
||||
# users urls
|
||||
path('user/<int:pk>',views.user_detail,name='user_detail'),
|
||||
path('user/user_profile_image_update/<int:pk>',views.user_profile_image_update,name='user_profile_image_update'),
|
||||
path('easy_logs/',views.easy_logs,name='easy_logs')
|
||||
|
||||
]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import json
|
||||
import requests
|
||||
from django.utils.translation import gettext as _
|
||||
from django.contrib.auth.models import User
|
||||
from rich import print
|
||||
from django.template.loader import render_to_string
|
||||
@ -13,7 +14,7 @@ from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from .forms import (
|
||||
CandidateExamDateForm,
|
||||
CandidateExamDateForm,
|
||||
ZoomMeetingForm,
|
||||
JobPostingForm,
|
||||
FormTemplateForm,
|
||||
@ -23,9 +24,10 @@ from .forms import (
|
||||
ProfileImageUploadForm,
|
||||
|
||||
)
|
||||
from easyaudit.models import CRUDEvent, LoginEvent, RequestEvent
|
||||
from rest_framework import viewsets
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import Paginator
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from .linkedin_service import LinkedInService
|
||||
from .serializers import JobPostingSerializer, CandidateSerializer
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
@ -2394,3 +2396,50 @@ def user_detail(request, pk):
|
||||
|
||||
}
|
||||
return render(request, 'user/profile.html', context)
|
||||
|
||||
|
||||
|
||||
|
||||
def easy_logs(request):
|
||||
"""
|
||||
Function-based view to display Django Easy Audit logs with tab switching and pagination.
|
||||
"""
|
||||
logs_per_page = 20
|
||||
|
||||
# 1. Determine the active tab and the corresponding model/queryset
|
||||
active_tab = request.GET.get('tab', 'crud')
|
||||
|
||||
if active_tab == 'login':
|
||||
queryset = LoginEvent.objects.order_by('-datetime')
|
||||
tab_title = _("User Authentication")
|
||||
elif active_tab == 'request':
|
||||
queryset = RequestEvent.objects.order_by('-datetime')
|
||||
tab_title = _("HTTP Requests")
|
||||
else: # Default is 'crud'
|
||||
queryset = CRUDEvent.objects.order_by('-datetime')
|
||||
tab_title = _("Model Changes (CRUD)")
|
||||
active_tab = 'crud'
|
||||
|
||||
# 2. Apply Pagination
|
||||
paginator = Paginator(queryset, logs_per_page)
|
||||
page = request.GET.get('page')
|
||||
|
||||
try:
|
||||
# Get the page object for the requested page number
|
||||
logs_page = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
# If page is not an integer, deliver first page.
|
||||
logs_page = paginator.page(1)
|
||||
except EmptyPage:
|
||||
# If page is out of range, deliver last page of results.
|
||||
logs_page = paginator.page(paginator.num_pages)
|
||||
|
||||
context = {
|
||||
'logs': logs_page,
|
||||
'total_count': queryset.count(),
|
||||
'active_tab': active_tab,
|
||||
'tab_title': tab_title,
|
||||
}
|
||||
|
||||
return render(request, "includes/easy_logs.html", context)
|
||||
|
||||
|
||||
@ -143,7 +143,9 @@
|
||||
</div>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="{% url 'user_detail' request.user.pk %}"><i class="fas fa-user-circle me-3 text-primary fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-cog me-3 text-primary fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-history me-3 text-primary fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li>
|
||||
@ -164,6 +166,7 @@
|
||||
{% endif %}
|
||||
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li>
|
||||
<form method="post" action="{% url 'account_logout'%}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
@ -177,6 +180,7 @@
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
337
templates/includes/easy_logs.html
Normal file
337
templates/includes/easy_logs.html
Normal file
@ -0,0 +1,337 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Audit Dashboard" %}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* ---------------------------------------------------- */
|
||||
/* 1. Theme Variables (Teal Focus) */
|
||||
/* ---------------------------------------------------- */
|
||||
:root {
|
||||
--color-primary: #007a88; /* Main Teal */
|
||||
--color-primary-dark: #004d55; /* Dark Teal for Headings, Login Status, and Strong Text */
|
||||
|
||||
/* Standard Dark Text (for max visibility) */
|
||||
/* Muted text */
|
||||
--color-text-on-dark: #f0f0f0; /* Light text for badges (guaranteed contrast) */
|
||||
|
||||
/* Adjusted Status Colors for contrast */
|
||||
--color-success: #157347;
|
||||
--color-danger: #bb2d3b;
|
||||
--color-warning: #ffc107;
|
||||
--color-info: #0dcaf0;
|
||||
--color-light: #f8f9fa;
|
||||
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* 2. Layout, Header, and Summary */
|
||||
/* ---------------------------------------------------- */
|
||||
.container-fluid {
|
||||
background-color: var(--color-background-light);
|
||||
}
|
||||
.audit-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #e9ecef;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
min-height: 60vh;
|
||||
}
|
||||
.dashboard-header {
|
||||
color: var(--color-primary-dark);
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.summary-alert {
|
||||
border-color: var(--color-primary) !important;
|
||||
background-color: var(--color-primary-light) !important;
|
||||
}
|
||||
.summary-alert h6, .summary-alert strong {
|
||||
color: var(--color-primary-dark) !important;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* 3. Tabs Styling */
|
||||
/* ---------------------------------------------------- */
|
||||
.nav-tabs {
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
background-color: #ffffff;
|
||||
padding-top: 1rem;
|
||||
border-radius: 0.75rem 0.75rem 0 0;
|
||||
}
|
||||
.nav-link-es {
|
||||
color: var(--color-text-secondary);
|
||||
border: none;
|
||||
}
|
||||
.nav-link-es.active {
|
||||
color: var(--color-primary) !important;
|
||||
font-weight: 600;
|
||||
border-bottom: 3px solid var(--color-primary) !important;
|
||||
}
|
||||
.nav-link:hover:not(.active) {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* 4. Table and Text Contrast */
|
||||
/* ---------------------------------------------------- */
|
||||
.table th {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.table td {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--color-light) !important;
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
code {
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* 5. BADGE VISIBILITY FIXES (Guaranteed Colors) */
|
||||
/* ---------------------------------------------------- */
|
||||
.badge {
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
padding: 0.4em 0.6em;
|
||||
min-width: 65px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
/* Ensure z-index doesn't cause issues if elements overlap */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Dark Badges (CRUD Create/Delete & Login/Logout Type) - Use light text */
|
||||
.badge-crud-create {
|
||||
background-color: var(--color-success) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
.badge-crud-delete {
|
||||
background-color: var(--color-danger) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
.badge-login-status {
|
||||
background-color: var(--color-primary-dark) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
|
||||
/* Light Badges (CRUD Update & Request Method) - Use dark text */
|
||||
.badge-crud-update {
|
||||
background-color: var(--color-warning) !important;
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
.badge-request-method {
|
||||
background-color: var(--color-info) !important;
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
|
||||
|
||||
/* Pagination - Fully Teal Themed */
|
||||
.pagination .page-item.active .page-link {
|
||||
background-color: var(--color-primary) !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
.pagination .page-link {
|
||||
color: var(--color-primary) !important; /* FIX: Added !important here */
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.pagination .page-item.disabled .page-link {
|
||||
color: var(--color-text-secondary) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);">
|
||||
<h1 class="h3 fw-bold dashboard-header mb-4 px-3">
|
||||
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Audit Logs" %}
|
||||
</h1>
|
||||
|
||||
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert">
|
||||
<h6 class="mb-1">{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong></h6>
|
||||
<p class="mb-0 small">
|
||||
{% trans "Displaying" %}: **{{ logs.start_index }}-{{ logs.end_index }}** {% trans "of" %}
|
||||
**{{ total_count }}** {% trans "total records." %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="audit-card mx-3">
|
||||
|
||||
<ul class="nav nav-tabs px-3" id="auditTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es{% if active_tab == 'crud' %}active{% endif %}"
|
||||
id="crud-tab" href="?tab=crud" aria-controls="crud">
|
||||
<i class="fas fa-database me-2"></i>{% trans "Model Changes (CRUD)" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es {% if active_tab == 'login' %}active{% endif %}"
|
||||
id="login-tab" href="?tab=login" aria-controls="login">
|
||||
<i class="fas fa-user-lock me-2"></i>{% trans "User Authentication" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es {% if active_tab == 'request' %}active{% endif %}"
|
||||
id="request-tab" href="?tab=request" aria-controls="request">
|
||||
<i class="fas fa-globe me-2"></i>{% trans "HTTP Requests" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content p-4" id="auditTabsContent">
|
||||
|
||||
<div class="tab-pane fade show active" role="tabpanel">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover small">
|
||||
|
||||
<thead>
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Action" %}</th>
|
||||
<th scope="col" style="width: 20%;">{% trans "Model" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Object PK" %}</th>
|
||||
<th scope="col" style="width: 30%;">{% trans "Changes" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 20%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 25%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "Type" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Status" %}</th>
|
||||
<th scope="col" style="width: 30%;">{% trans "IP Address" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Method" %}</th>
|
||||
<th scope="col" style="width: 45%;">{% trans "Path" %}</th>
|
||||
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for log in logs.object_list %}
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.get_full_name|default:log.user.username|default:"N/A" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill
|
||||
{% if log.event_type == 1 %}badge-crud-create
|
||||
{% elif log.event_type == 2 %}badge-crud-update
|
||||
{% else %}badge-crud-delete{% endif %}">
|
||||
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %}
|
||||
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %}
|
||||
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td><code style="color: var(--color-text-dark) !important;">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
||||
<td>{{ log.object_id }}</td>
|
||||
<td>
|
||||
<pre class="p-2 m-0" style="font-size: 0.65rem; max-height: 80px; overflow-y: auto;">{{ log.changed_fields }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>
|
||||
{% with user_obj=log.user %}
|
||||
{% if user_obj %}
|
||||
{{ user_obj.get_full_name|default:user_obj.username }}
|
||||
{% else %}
|
||||
<span class="text-danger fw-bold">{{ log.username|default:"N/A" }}</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badge-login-status">
|
||||
{% if log.login_type == 0 %}{% trans "Login" %}
|
||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if log.login_type == 2 %}
|
||||
<i class="fas fa-times-circle me-1" style="color: var(--color-danger);"></i>{% trans "Failed" %}
|
||||
{% else %}
|
||||
<i class="fas fa-check-circle me-1" style="color: var(--color-success);"></i>{% trans "Success" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ log.remote_ip|default:"Unknown" }}</td>
|
||||
</tr>
|
||||
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.get_full_name|default:log.user.username|default:"Anonymous" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badge-request-method">{{ log.method }}</span>
|
||||
</td>
|
||||
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td>
|
||||
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center text-muted py-5">
|
||||
<i class="fas fa-info-circle me-2"></i>{% trans "No logs found for this section or the database is empty." %}
|
||||
</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if logs.has_other_pages %}
|
||||
<nav aria-label="Audit Log Pagination" class="pt-3">
|
||||
<ul class="pagination justify-content-end">
|
||||
|
||||
<li class="page-item {% if not logs.has_previous %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
|
||||
aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% for i in logs.paginator.page_range %}
|
||||
{% comment %} Limiting pages displayed around the current page {% endcomment %}
|
||||
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
||||
<li class="page-item {% if logs.number == i %}active{% endif %}">
|
||||
<a class="page-link"
|
||||
href="?tab={{ active_tab }}&page={{ i }}">
|
||||
{{ i }}
|
||||
</a>
|
||||
</li>
|
||||
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
||||
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if not logs.has_next %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
||||
aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user