search on table
This commit is contained in:
@@ -21,7 +21,6 @@ def activity_log():
|
|||||||
end_date,
|
end_date,
|
||||||
user_name
|
user_name
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"activity_log.html",
|
"activity_log.html",
|
||||||
logs=filtered_logs,
|
logs=filtered_logs,
|
||||||
|
|||||||
@@ -27,13 +27,27 @@ class FolderAndFile:
|
|||||||
os.makedirs(folder, exist_ok=True)
|
os.makedirs(folder, exist_ok=True)
|
||||||
return folder
|
return folder
|
||||||
|
|
||||||
# -----------------------------
|
@staticmethod
|
||||||
# FILE PATH METHODS
|
def get_logs_folder():
|
||||||
# -----------------------------
|
folder = os.path.join(current_app.root_path, "logs")
|
||||||
|
|
||||||
|
if not os.path.exists(folder):
|
||||||
|
os.makedirs(folder)
|
||||||
|
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
return folder
|
||||||
|
|
||||||
|
# FILE PATH METHODS - download
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_download_path(filename):
|
def get_download_path(filename):
|
||||||
return os.path.join(FolderAndFile.get_download_folder(), filename)
|
return os.path.join(FolderAndFile.get_download_folder(), filename)
|
||||||
|
|
||||||
|
# FILE PATH METHODS - upload file
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_upload_path(filename):
|
def get_upload_path(filename):
|
||||||
return os.path.join(FolderAndFile.get_upload_folder(), filename)
|
return os.path.join(FolderAndFile.get_upload_folder(), filename)
|
||||||
|
|
||||||
|
# FILE PATH METHODS - activity log file
|
||||||
|
@staticmethod
|
||||||
|
def get_activity_log_path(filename):
|
||||||
|
return os.path.join(FolderAndFile.get_logs_folder(), filename)
|
||||||
121
model/Log.py
121
model/Log.py
@@ -1,29 +1,39 @@
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from model.FolderAndFile import FolderAndFile
|
||||||
|
|
||||||
class LogHelper:
|
class LogHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log_action(action, details=""):
|
def log_action(action, details=""):
|
||||||
"""Add a log entry."""
|
"""Log user actions with timestamp, user, action, and details."""
|
||||||
log_data = LogData()
|
logData = LogData()
|
||||||
log_data.add_log(action, details)
|
logData.WriteLog(action, details="")
|
||||||
|
|
||||||
|
|
||||||
class LogData:
|
class LogData:
|
||||||
|
|
||||||
|
filepath = ""
|
||||||
|
timestamp = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.filepath = os.path.join(current_app.root_path, 'activity.log')
|
self.filepath = FolderAndFile.get_activity_log_path('activity.log')
|
||||||
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
self.user = getattr(current_user, "cn", None) \
|
|
||||||
or getattr(current_user, "username", None) \
|
|
||||||
or getattr(current_user, "sAMAccountName", "Unknown")
|
|
||||||
|
|
||||||
|
if hasattr(current_user, "cn") and current_user.cn:
|
||||||
|
self.user = current_user.cn
|
||||||
|
elif hasattr(current_user, "username") and current_user.username:
|
||||||
|
self.user = current_user.username
|
||||||
|
elif hasattr(current_user, "sAMAccountName") and current_user.sAMAccountName:
|
||||||
|
self.user = current_user.sAMAccountName
|
||||||
|
else:
|
||||||
|
self.user = "Unknown"
|
||||||
|
|
||||||
def add_log(self, action, details=""):
|
def WriteLog(self, action, details=""):
|
||||||
"""Create/Add a log entry."""
|
"""Log user actions with timestamp, user, action, and details."""
|
||||||
|
|
||||||
with open(self.filepath, "a", encoding="utf-8") as f:
|
with open(self.filepath, "a", encoding="utf-8") as f:
|
||||||
f.write(
|
f.write(
|
||||||
f"Timestamp: {self.timestamp} | "
|
f"Timestamp: {self.timestamp} | "
|
||||||
@@ -32,73 +42,46 @@ class LogData:
|
|||||||
f"Details: {details}\n"
|
f"Details: {details}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_all_logs(self):
|
|
||||||
"""Read all logs."""
|
def GetActivitiesLog(self):
|
||||||
logs = []
|
logs = []
|
||||||
|
|
||||||
if os.path.exists(self.filepath):
|
if os.path.exists(self.filepath):
|
||||||
with open(self.filepath, 'r', encoding="utf-8") as f:
|
with open(self.filepath, 'r') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
parts = line.strip().split(" | ")
|
parts = line.strip().split(" | ")
|
||||||
if len(parts) == 4:
|
if len(parts) == 4:
|
||||||
logs.append({
|
logs.append({
|
||||||
"timestamp": parts[0].split(":", 1)[1].strip(),
|
"timestamp": parts[0].replace("Timestamp:", "").strip(),
|
||||||
"user": parts[1].split(":", 1)[1].strip(),
|
"user": parts[1].replace("User:", "").strip(),
|
||||||
"action": parts[2].split(":", 1)[1].strip(),
|
"action": parts[2].replace("Action:", "").strip(),
|
||||||
"details": parts[3].split(":", 1)[1].strip()
|
"details": parts[3].replace("Details:", "").strip()
|
||||||
})
|
})
|
||||||
return logs
|
return logs
|
||||||
|
|
||||||
def get_filtered_logs(self, start_date=None, end_date=None, user_name=None):
|
def GetFilteredActivitiesLog(self, startDate, endDate, userName):
|
||||||
"""Filter logs by date and/or user."""
|
|
||||||
logs = self.get_all_logs()
|
|
||||||
|
|
||||||
# Filter by date
|
filtered_logs = self.GetActivitiesLog()
|
||||||
if start_date or end_date:
|
|
||||||
start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min
|
|
||||||
end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max
|
|
||||||
logs = [
|
|
||||||
log for log in logs
|
|
||||||
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
|
|
||||||
]
|
|
||||||
|
|
||||||
# Filter by username
|
# Date filter
|
||||||
if user_name:
|
if startDate or endDate:
|
||||||
logs = [log for log in logs if user_name.lower() in log.get("user", "").lower()]
|
try:
|
||||||
|
start_dt = datetime.strptime(startDate, "%Y-%m-%d") if startDate else datetime.min
|
||||||
return logs
|
end_dt = datetime.strptime(endDate, "%Y-%m-%d") if endDate else datetime.max
|
||||||
|
|
||||||
def update_log(self, index, action=None, details=None):
|
|
||||||
"""Update a specific log entry by index (0-based)."""
|
|
||||||
logs = self.get_all_logs()
|
|
||||||
if 0 <= index < len(logs):
|
|
||||||
if action:
|
|
||||||
logs[index]["action"] = action
|
|
||||||
if details:
|
|
||||||
logs[index]["details"] = details
|
|
||||||
self._rewrite_logs_file(logs)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def delete_log(self, index):
|
|
||||||
"""Delete a specific log entry by index (0-based)."""
|
|
||||||
logs = self.get_all_logs()
|
|
||||||
if 0 <= index < len(logs):
|
|
||||||
logs.pop(index)
|
|
||||||
self._rewrite_logs_file(logs)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
# ------------------- INTERNAL HELPER -------------------
|
|
||||||
|
|
||||||
def _rewrite_logs_file(self, logs):
|
|
||||||
"""Overwrite the log file with current logs."""
|
|
||||||
with open(self.filepath, "w", encoding="utf-8") as f:
|
|
||||||
for log in logs:
|
|
||||||
f.write(
|
|
||||||
f"Timestamp: {log['timestamp']} | "
|
|
||||||
f"User: {log['user']} | "
|
|
||||||
f"Action: {log['action']} | "
|
|
||||||
f"Details: {log['details']}\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
filtered_logs = [
|
||||||
|
log for log in filtered_logs
|
||||||
|
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Date filter error:", e)
|
||||||
|
#Why catching all exceptions? Need to handle specific exceptions
|
||||||
|
|
||||||
|
# Username filter
|
||||||
|
if userName:
|
||||||
|
filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()]
|
||||||
|
|
||||||
|
return filtered_logs
|
||||||
@@ -1,26 +1,23 @@
|
|||||||
|
|
||||||
// Search on table using search inpute options
|
// Search on table using search inpute options
|
||||||
function searchTable() {
|
function searchTable() {
|
||||||
let input = document.getElementById("searchBar").value.toLowerCase();
|
let input = document.getElementById("searchBar").value.toLowerCase();
|
||||||
let rows = document.querySelectorAll("table tbody tr");
|
let tables = document.querySelectorAll("table");
|
||||||
|
|
||||||
rows.forEach(row => {
|
tables.forEach(table => {
|
||||||
let blockName = row.cells[1].textContent.toLowerCase();
|
let rows = table.querySelectorAll("tr");
|
||||||
let districtName = row.cells[2].textContent.toLowerCase();
|
|
||||||
let villageName = row.cells[3].textContent.toLowerCase();
|
|
||||||
|
|
||||||
if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) {
|
rows.forEach((row, index) => {
|
||||||
row.style.display = "";
|
if (index === 0) return; // header skip
|
||||||
} else {
|
|
||||||
row.style.display = "none";
|
let text = row.textContent.toLowerCase();
|
||||||
}
|
|
||||||
|
row.style.display = text.includes(input) ? "" : "none";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Common Sorting Script for Tables
|
// Common Sorting Script for Tables
|
||||||
function sortTable(n, dir) {
|
function sortTable(n, dir) {
|
||||||
var table, rows, switching, i, x, y, shouldSwitch;
|
var table, rows, switching, i, x, y, shouldSwitch;
|
||||||
@@ -57,14 +54,14 @@ function sortTable(n, dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach sorting functionality to all sortable tables
|
// Attach sorting functionality to all sortable tables
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Find all elements with the class "sortable-header"
|
// Find all elements with the class "sortable-header"
|
||||||
var sortableHeaders = document.querySelectorAll(".sortable-header");
|
var sortableHeaders = document.querySelectorAll(".sortable-header");
|
||||||
|
|
||||||
sortableHeaders.forEach(function(header) {
|
sortableHeaders.forEach(function (header) {
|
||||||
// Attach click event for ascending sort
|
// Attach click event for ascending sort
|
||||||
if (header.querySelector(".sort-asc")) {
|
if (header.querySelector(".sort-asc")) {
|
||||||
header.querySelector(".sort-asc").addEventListener("click", function() {
|
header.querySelector(".sort-asc").addEventListener("click", function () {
|
||||||
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
|
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
|
||||||
sortTable(columnIndex, "asc");
|
sortTable(columnIndex, "asc");
|
||||||
});
|
});
|
||||||
@@ -72,7 +69,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
|
|
||||||
// Attach click event for descending sort
|
// Attach click event for descending sort
|
||||||
if (header.querySelector(".sort-desc")) {
|
if (header.querySelector(".sort-desc")) {
|
||||||
header.querySelector(".sort-desc").addEventListener("click", function() {
|
header.querySelector(".sort-desc").addEventListener("click", function () {
|
||||||
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
|
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
|
||||||
sortTable(columnIndex, "desc");
|
sortTable(columnIndex, "desc");
|
||||||
});
|
});
|
||||||
@@ -105,4 +102,31 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
displayButton.classList.add("active-button");
|
displayButton.classList.add("active-button");
|
||||||
addButton.classList.remove("active-button");
|
addButton.classList.remove("active-button");
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|
||||||
|
let tables = document.querySelectorAll("table");
|
||||||
|
|
||||||
|
tables.forEach(table => {
|
||||||
|
let header = table.querySelector("tr:first-child");
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
header.style.position = "sticky";
|
||||||
|
header.style.top = "0";
|
||||||
|
header.style.background = "#fff";
|
||||||
|
header.style.zIndex = "2";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.parentElement.classList.contains("table-wrapper")) {
|
||||||
|
let wrapper = document.createElement("div");
|
||||||
|
wrapper.classList.add("table-wrapper");
|
||||||
|
wrapper.style.maxHeight = "65vh"
|
||||||
|
wrapper.style.overflowY = "auto";
|
||||||
|
|
||||||
|
table.parentNode.insertBefore(wrapper, table);
|
||||||
|
wrapper.appendChild(table);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user