Add Activity log Service and model

This commit is contained in:
2026-08-01 15:41:26 +05:30
parent 2d7d146ec3
commit e98e1422a0
6 changed files with 350 additions and 43 deletions

View File

@@ -0,0 +1,208 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid py-4">
<!-- HEADER -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center">
<div class="mb-3 mb-lg-0">
<h3 class="fw-bold text-primary mb-1"> <i class="bi bi-clock-history me-2"></i> Activity Logs </h3>
<small class="text-muted"> View application logs, search records and download log files. </small>
</div>
<div class="d-flex flex-wrap gap-2">
<a id="downloadLogBtn" href="{{ url_for('activity.download_log') }}?file={{ file_name }}" class="btn btn-success">
<i class="bi bi-download me-1"></i>
Download Log
</a>
<a href="{{ url_for('activity.activity') }}" class="btn btn-outline-primary">
<i class="bi bi-arrow-clockwise me-1"></i>
Refresh
</a>
</div>
</div>
</div>
</div>
<!-- FILTERS -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="bi bi-funnel-fill me-2"></i> Filters
</h5>
</div>
<div class="card-body">
<form method="GET">
<div class="row g-3">
<!-- Log File select -->
<div class="col-xl-2 col-lg-4 col-md-6">
<label class="form-label fw-bold">Log File</label>
<select class="form-select" name="file">
<option value="app.log" {% if file_name=="app.log" %}selected{% endif %}> Application </option>
<option value="debug.log" {% if file_name=="debug.log" %}selected{% endif %}> Debug </option>
<option value="error.log" {% if file_name=="error.log" %}selected{% endif %}> Error </option>
</select>
</div>
<!-- User search -->
<div class="col-xl-2 col-lg-4 col-md-6">
<label class="form-label fw-bold">User</label>
<input type="text" class="form-control" name="user" value="{{ user }}" placeholder="Username">
</div>
<!-- Level -->
<div class="col-xl-2 col-lg-4 col-md-6">
<label class="form-label fw-bold">Level</label>
<select class="form-select" name="level">
<option value="">All</option>
<option value="INFO"
{% if level=="INFO" %}selected{% endif %}>
INFO
</option>
<option value="WARNING"
{% if level=="WARNING" %}selected{% endif %}>
WARNING
</option>
<option value="ERROR"
{% if level=="ERROR" %}selected{% endif %}>
ERROR
</option>
</select>
</div>
<!-- From Date -->
<div class="col-xl-2 col-lg-4 col-md-6">
<label class="form-label fw-bold">From Date</label>
<input type="date" class="form-control" name="from_date" value="{{ from_date }}">
</div>
<!-- To Date -->
<div class="col-xl-2 col-lg-4 col-md-6">
<label class="form-label fw-bold">To Date</label>
<input type="date" class="form-control" name="to_date" value="{{ to_date }}">
</div>
<!-- Search button -->
<div class="col-xl-2 col-lg-12">
<label class="form-label fw-bold">Search</label>
<input type="text" class="form-control" name="search" value="{{ search }}" placeholder="Search">
</div>
</div>
<hr>
<!-- Search and Reset button -->
<div class="d-flex gap-2">
<button class="btn btn-primary">
<i class="bi bi-search"></i> Search
</button>
<button type="reset" class="btn btn-secondary">
<i class="bi bi-arrow-clockwise"></i> Reset
</button>
</div>
</form>
</div>
</div>
<!-- LOG TABLE -->
<div class="card shadow-sm">
<!-- Log Entries -->
<div class="card-header bg-dark text-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-list-ul me-2"></i>
Log Entries
</h5>
<span class="badge bg-light text-dark">
{{ logs|length }} Records
</span>
</div>
</div>
<!-- Show table Entries -->
<div class="card-body p-0">
<div class="table-responsive" style="max-height:600px; overflow-y:auto; font-size:12px;">
<table class="table table-striped table-hover table-bordered align-middle mb-0 small">
<thead class="table-dark sticky-top">
<tr>
<th>Sr No</th>
<th>Date & Time</th>
<th>User</th>
<th>Level</th>
<th>Module</th>
<th>Activity</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ log.date }}</td>
<td>{{ log.user }}</td>
<td>
{% if log.level=="INFO" %}
<span class="badge bg-success">INFO</span>
{% elif log.level=="WARNING" %}
<span class="badge bg-warning text-dark">WARNING</span>
{% else %}
<span class="badge bg-danger">ERROR</span>
{% endif %}
</td>
<td>{{ log.module }}</td>
<td>{{ log.message }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const fileSelect = document.querySelector('select[name="file"]');
const downloadBtn = document.getElementById("downloadLogBtn");
function updateDownloadLink() {
downloadBtn.href =
"{{ url_for('activity.download_log') }}?file=" +
encodeURIComponent(fileSelect.value);
}
updateDownloadLink();
fileSelect.addEventListener("change", updateDownloadLink);
});
</script>
{% endblock %}

View File

@@ -168,6 +168,12 @@
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
</li>
<li>
<a class="dropdown-item" href="{{ url_for('activity.activity') }}">
<i class="bi bi-clock-history me-2"></i>
Activity Log
</a>
</li>
<li>
<a class="dropdown-item text-warning" href="/logout">
@@ -188,8 +194,7 @@
<!-- PAGE CONTENT -->
<div class="container-fluid vh-100 pt-5 overflow-hidden">
<!-- FLASH MESSAGES -->
<div class="position-fixed top-0 end-0 p-2 p-md-3 mt-5"
style="z-index:1080; width:min(95vw,500px);">
<div class="position-fixed top-0 end-0 p-2 p-md-3 mt-5" style="z-index:1080; width:min(95vw,500px);">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
@@ -230,17 +235,13 @@
<!-- Timeline -->
<div class="progress mt-2" style="height:5px;">
<div class="progress-bar progress-bar-striped progress-bar-animated timer-bar"
role="progressbar"
style="width:100%">
<div class="progress-bar progress-bar-striped progress-bar-animated timer-bar" role="progressbar" style="width:100%">
</div>
</div>
</div>
</div>
<button type="button"
class="btn-close"
data-bs-dismiss="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert">
</button>
</div>
@@ -298,80 +299,54 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap-multiselect@1.1.2/dist/js/bootstrap-multiselect.min.js"></script>
<script>
// show alert msg
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".notification-alert").forEach(function(alert){
const progressBar = alert.querySelector(".timer-bar");
let width = 100;
const interval = setInterval(function() {
width -= 2;
progressBar.style.width = width + "%";
if(width <= 0){
clearInterval(interval);
const bsAlert = bootstrap.Alert.getOrCreateInstance(alert);
bsAlert.close();
}
}, 100);
});
});
// New
// show msg Processing your request
function showLoader(message = "Processing your request...") {
document.getElementById("loaderMessage").innerHTML = message;
document.getElementById("globalLoader").classList.remove("d-none");
}
// Automatically hide
function hideLoader(){
document.getElementById("globalLoader").classList.add("d-none");
}
// Automatically apply to all forms having class="loading-form"
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".loading-form").forEach(function(form){
form.addEventListener("submit", function(){
if(!this.checkValidity())
return;
showLoader();
const btn = this.querySelector("button[type='submit']");
if(btn){
btn.disabled = true;
btn.innerHTML = `
<span class="spinner-border spinner-border-sm me-2"></span>
Processing...
`;
}
});
});
});
</script>