Add search for activity logs
This commit is contained in:
@@ -19,9 +19,27 @@ class Activity:
|
||||
# We don't want logging to break the main application flow
|
||||
current_app.logger.error(f"Error logging activity: {e}")
|
||||
|
||||
def get_recent_logs(self, limit=50, offset=0):
|
||||
"""Fetches recent activity logs with person names, supporting pagination."""
|
||||
query = """
|
||||
def get_recent_logs(self, limit=50, offset=0, search_query=None):
|
||||
"""Fetches recent activity logs with person names, supporting pagination and search."""
|
||||
params = [limit, offset]
|
||||
search_clause = ""
|
||||
|
||||
if search_query:
|
||||
# Add wildcard percentages for partial matching
|
||||
term = f"%{search_query}%"
|
||||
search_clause = """
|
||||
WHERE
|
||||
p.name ILIKE %s OR
|
||||
al.action ILIKE %s OR
|
||||
al.entity_type ILIKE %s OR
|
||||
al.details ILIKE %s
|
||||
"""
|
||||
# Prepend search terms to params list (limit/offset must change position if we were using ? placeholders
|
||||
# but with %s list, order matters. Let's reconstruct consistent order).
|
||||
# Actually, LIMIT/OFFSET are at the end. Search params come before.
|
||||
params = [term, term, term, term, limit, offset]
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
al.id,
|
||||
al.person_id,
|
||||
@@ -35,7 +53,8 @@ class Activity:
|
||||
al.timestamp
|
||||
FROM activity_log al
|
||||
LEFT JOIN person p ON al.person_id = p.person_id
|
||||
{search_clause}
|
||||
ORDER BY al.timestamp DESC
|
||||
LIMIT %s OFFSET %s
|
||||
"""
|
||||
return self.execute(query, [limit, offset])
|
||||
return self.execute(query, params)
|
||||
|
||||
@@ -53,7 +53,9 @@ def settings_activity():
|
||||
def settings_activity_logs():
|
||||
limit = 50
|
||||
offset = request.args.get('offset', 0, type=int)
|
||||
logs = db.activityRequest.get_recent_logs(limit=limit, offset=offset)
|
||||
search_query = request.args.get('search_query', '')
|
||||
|
||||
logs = db.activityRequest.get_recent_logs(limit=limit, offset=offset, search_query=search_query)
|
||||
|
||||
# Check if there are more logs to load
|
||||
has_more = len(logs) == limit
|
||||
@@ -61,4 +63,6 @@ def settings_activity_logs():
|
||||
return render_template('partials/activity_logs.html',
|
||||
logs=logs,
|
||||
offset=offset,
|
||||
has_more=has_more)
|
||||
has_more=has_more,
|
||||
search_query=search_query,
|
||||
limit=limit)
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
{% if has_more %}
|
||||
<tr id="load-more-row">
|
||||
<td colspan="5" class="p-4 text-center">
|
||||
<button hx-get="{{ url_for('settings.settings_activity_logs', offset=offset + limit) }}"
|
||||
<button
|
||||
hx-get="{{ url_for('settings.settings_activity_logs', offset=offset + limit, search_query=search_query) }}"
|
||||
hx-target="#load-more-row" hx-swap="outerHTML"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-cyan-700 bg-cyan-100 hover:bg-cyan-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 transition-colors">
|
||||
Load More...
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 lg:p-8 mb-8">
|
||||
<div class="mb-6 border-b border-gray-100 pb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900">Activity Logs</h3>
|
||||
<p class="text-sm text-gray-500">Review recent actions and administrative changes.</p>
|
||||
<div class="mb-6 border-b border-gray-100 pb-4 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900">Activity Logs</h3>
|
||||
<p class="text-sm text-gray-500">Review recent actions and administrative changes.</p>
|
||||
</div>
|
||||
<div class="relative max-w-sm w-full">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<input type="text" name="search_query"
|
||||
class="focus:ring-cyan-500 focus:border-cyan-500 block w-full pl-10 p-2 sm:text-sm border-gray-300 rounded-lg bg-gray-50"
|
||||
placeholder="Search logs by action, user, or details..."
|
||||
hx-get="{{ url_for('settings.settings_activity_logs') }}" hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#activity-logs-container">
|
||||
</div>
|
||||
</div>
|
||||
<div id="activity-logs-container" hx-get="{{ url_for('settings.settings_activity_logs') }}" hx-trigger="load">
|
||||
<div class="flex justify-center p-12">
|
||||
|
||||
@@ -54,17 +54,26 @@
|
||||
render #notification-template with (message: 'User added') then append it to #notifications-container
|
||||
then call _hyperscript.processNode(#notifications-container)
|
||||
then reset() me">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-end">
|
||||
<div class="grow w-full sm:w-auto">
|
||||
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="person-name">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-end justify-end">
|
||||
<div class="grow w-full sm:w-auto max-w-sm">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1" for="person-name">
|
||||
New user
|
||||
</label>
|
||||
<input id="person-name"
|
||||
class="appearance-none block w-full bg-white text-gray-700 border border-gray-300 rounded-lg py-3 px-4 leading-tight focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-transparent"
|
||||
type="text" name="name" placeholder="Full Name">
|
||||
<div class="relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<input id="person-name"
|
||||
class="focus:ring-cyan-500 focus:border-cyan-500 block w-full pl-10 p-2 sm:text-sm border-gray-300 rounded-lg bg-gray-50"
|
||||
type="text" name="name" placeholder="Full Name">
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="w-full sm:w-auto flex items-center justify-center text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-3 transition-colors shadow-sm"
|
||||
class="w-full sm:w-auto flex items-center justify-center text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 transition-colors shadow-sm"
|
||||
type="submit">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
|
||||
Reference in New Issue
Block a user