improved pagination

This commit is contained in:
2025-04-08 08:58:47 -04:00
parent 01ecccc928
commit de13c8b2f9
2 changed files with 214 additions and 147 deletions

View File

@@ -18,6 +18,14 @@
<option value="last_30_days" {% if time_range == 'last_30_days' %}selected{% endif %}>Last 30 Days</option> <option value="last_30_days" {% if time_range == 'last_30_days' %}selected{% endif %}>Last 30 Days</option>
<option value="all" {% if time_range == 'all' %}selected{% endif %}>All Time</option> <option value="all" {% if time_range == 'all' %}selected{% endif %}>All Time</option>
</select> </select>
<label for="per_page">Entries per page:</label>
<select name="per_page" id="per_page">
{% for option in [5,10, 25, 50, 100] %}
<option value="{{ option }}" {% if per_page == option %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
<button type="submit">Update</button> <button type="submit">Update</button>
</form> </form>
@@ -56,15 +64,29 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if total_pages_accept > 1 %} {% if pagination_accept.pages|length > 1 %}
<div class="pagination"> <div class="pagination">
{% for page in range(1, total_pages_accept + 1) %} {% if pagination_accept.show_first %}
<a href="{{ url_for('stats.stats_page', page_accept=pagination_accept.first_page, page_reject=page_reject, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&laquo;</a>
{% endif %}
{% if pagination_accept.show_prev %}
<a href="{{ url_for('stats.stats_page', page_accept=pagination_accept.prev_page, page_reject=page_reject, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&lsaquo;</a>
{% endif %}
{% for page in pagination_accept.pages %}
{% if page == page_accept %} {% if page == page_accept %}
<span class="current-page">{{ page }}</span> <span class="current-page">{{ page }}</span>
{% else %} {% else %}
<a href="{{ url_for('stats.stats_page', page_accept=page, page_reject=page_reject, page_fallback=page_fallback, time_range=time_range) }}">{{ page }}</a> <a href="{{ url_for('stats.stats_page', page_accept=page, page_reject=page_reject, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">{{ page }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if pagination_accept.show_next %}
<a href="{{ url_for('stats.stats_page', page_accept=pagination_accept.next_page, page_reject=page_reject, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&rsaquo;</a>
{% endif %}
{% if pagination_accept.show_last %}
<a href="{{ url_for('stats.stats_page', page_accept=pagination_accept.last_page, page_reject=page_reject, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&raquo;</a>
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
@@ -92,15 +114,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if total_pages_reject > 1 %} {% if pagination_reject.pages|length > 1 %}
<div class="pagination"> <div class="pagination">
{% for page in range(1, total_pages_reject + 1) %} {% if pagination_reject.show_first %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=pagination_reject.first_page, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&laquo;</a>
{% endif %}
{% if pagination_reject.show_prev %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=pagination_reject.prev_page, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&lsaquo;</a>
{% endif %}
{% for page in pagination_reject.pages %}
{% if page == page_reject %} {% if page == page_reject %}
<span class="current-page">{{ page }}</span> <span class="current-page">{{ page }}</span>
{% else %} {% else %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page, page_fallback=page_fallback, time_range=time_range) }}">{{ page }}</a> <a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">{{ page }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if pagination_reject.show_next %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=pagination_reject.next_page, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&rsaquo;</a>
{% endif %}
{% if pagination_reject.show_last %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=pagination_reject.last_page, page_fallback=page_fallback, per_page=per_page, time_range=time_range) }}">&raquo;</a>
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
@@ -153,24 +187,36 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if total_pages_fallback > 1 %} {% if pagination_fallback.pages|length > 1 %}
<div class="pagination"> <div class="pagination">
{% for page in range(1, total_pages_fallback + 1) %} {% if pagination_fallback.show_first %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=pagination_fallback.first_page, per_page=per_page, time_range=time_range) }}">&laquo;</a>
{% endif %}
{% if pagination_fallback.show_prev %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=pagination_fallback.prev_page, per_page=per_page, time_range=time_range) }}">&lsaquo;</a>
{% endif %}
{% for page in pagination_fallback.pages %}
{% if page == page_fallback %} {% if page == page_fallback %}
<span class="current-page">{{ page }}</span> <span class="current-page">{{ page }}</span>
{% else %} {% else %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=page, time_range=time_range) }}">{{ page }}</a> <a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=page, per_page=per_page, time_range=time_range) }}">{{ page }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if pagination_fallback.show_next %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=pagination_fallback.next_page, per_page=per_page, time_range=time_range) }}">&rsaquo;</a>
{% endif %}
{% if pagination_fallback.show_last %}
<a href="{{ url_for('stats.stats_page', page_accept=page_accept, page_reject=page_reject, page_fallback=pagination_fallback.last_page, per_page=per_page, time_range=time_range) }}">&raquo;</a>
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// MAC vendor lookup
const queriedPrefixes = new Set(); const queriedPrefixes = new Set();
document.querySelectorAll('.vendor-cell').forEach(cell => { document.querySelectorAll('.vendor-cell').forEach(cell => {
const mac = cell.getAttribute('data-mac'); const mac = cell.getAttribute('data-mac');
@@ -200,7 +246,6 @@
} }
}); });
// Auto-refresh toggle logic
const refreshCheckbox = document.getElementById('auto-refresh-checkbox'); const refreshCheckbox = document.getElementById('auto-refresh-checkbox');
const refreshStatus = document.getElementById('refresh-status'); const refreshStatus = document.getElementById('refresh-status');
let intervalId = null; let intervalId = null;
@@ -209,7 +254,7 @@
refreshStatus.textContent = "Auto-refresh enabled"; refreshStatus.textContent = "Auto-refresh enabled";
intervalId = setInterval(() => { intervalId = setInterval(() => {
document.querySelector('form').submit(); document.querySelector('form').submit();
}, 30000); // 30 seconds }, 30000);
} }
function stopAutoRefresh() { function stopAutoRefresh() {
@@ -225,7 +270,6 @@
} }
}); });
// Default: start disabled
stopAutoRefresh(); stopAutoRefresh();
}); });
</script> </script>

View File

@@ -1,11 +1,11 @@
from flask import Blueprint, render_template, request, current_app, redirect, url_for, jsonify from flask import Blueprint, render_template, request, current_app, redirect, url_for, jsonify
from db_interface import get_latest_auth_logs, count_auth_logs, get_all_groups, get_vendor_info, get_user_by_mac, add_user, get_known_mac_vendors from db_interface import get_latest_auth_logs, count_auth_logs, get_all_groups, get_vendor_info, get_user_by_mac, add_user, get_known_mac_vendors
from math import ceil from math import ceil
import re
import pytz import pytz
import humanize import humanize
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from time import sleep from time import sleep
import threading
stats = Blueprint('stats', __name__) stats = Blueprint('stats', __name__)
@@ -21,33 +21,53 @@ def get_time_filter_delta(time_range):
"last_30_days": timedelta(days=30), "last_30_days": timedelta(days=30),
}.get(time_range) }.get(time_range)
def get_pagination_data(current_page, total_pages, max_display=7):
pagination = []
if total_pages <= max_display:
pagination = list(range(1, total_pages + 1))
else:
half = max_display // 2
if current_page <= half:
pagination = list(range(1, max_display + 1))
elif current_page >= total_pages - half:
pagination = list(range(total_pages - max_display + 1, total_pages + 1))
else:
pagination = list(range(current_page - half, current_page + half + 1))
return {
"pages": pagination,
"show_first": current_page > 1,
"show_last": current_page < total_pages,
"show_prev": current_page > 1,
"show_next": current_page < total_pages,
"prev_page": max(current_page - 1, 1),
"next_page": min(current_page + 1, total_pages),
"first_page": 1,
"last_page": total_pages
}
@stats.route('/stats', methods=['GET', 'POST']) @stats.route('/stats', methods=['GET', 'POST'])
def stats_page(): def stats_page():
time_range = request.form.get('time_range') or request.args.get('time_range') or 'last_minute' time_range = request.form.get('time_range') or request.args.get('time_range') or 'last_minute'
per_page = int(request.form.get('per_page') or request.args.get('per_page') or 25)
# Per-card pagination values
per_page = 25
page_accept = int(request.args.get('page_accept', 1)) page_accept = int(request.args.get('page_accept', 1))
page_reject = int(request.args.get('page_reject', 1)) page_reject = int(request.args.get('page_reject', 1))
page_fallback = int(request.args.get('page_fallback', 1)) page_fallback = int(request.args.get('page_fallback', 1))
# Timezone setup
tz_name = current_app.config.get('APP_TIMEZONE', 'UTC') tz_name = current_app.config.get('APP_TIMEZONE', 'UTC')
local_tz = pytz.timezone(tz_name) local_tz = pytz.timezone(tz_name)
# Accept pagination
total_accept = count_auth_logs('Access-Accept', time_range) total_accept = count_auth_logs('Access-Accept', time_range)
total_pages_accept = ceil(total_accept / per_page) total_pages_accept = ceil(total_accept / per_page)
offset_accept = (page_accept - 1) * per_page offset_accept = (page_accept - 1) * per_page
accept_entries = get_latest_auth_logs('Access-Accept', per_page, time_range, offset_accept) accept_entries = get_latest_auth_logs('Access-Accept', per_page, time_range, offset_accept)
# Reject pagination
total_reject = count_auth_logs('Access-Reject', time_range) total_reject = count_auth_logs('Access-Reject', time_range)
total_pages_reject = ceil(total_reject / per_page) total_pages_reject = ceil(total_reject / per_page)
offset_reject = (page_reject - 1) * per_page offset_reject = (page_reject - 1) * per_page
reject_entries = get_latest_auth_logs('Access-Reject', per_page, time_range, offset_reject) reject_entries = get_latest_auth_logs('Access-Reject', per_page, time_range, offset_reject)
# Fallback pagination
total_fallback = count_auth_logs('Accept-Fallback', time_range) total_fallback = count_auth_logs('Accept-Fallback', time_range)
total_pages_fallback = ceil(total_fallback / per_page) total_pages_fallback = ceil(total_fallback / per_page)
offset_fallback = (page_fallback - 1) * per_page offset_fallback = (page_fallback - 1) * per_page
@@ -64,40 +84,43 @@ def stats_page():
entry['ago'] = 'unknown' entry['ago'] = 'unknown'
vendor_info = get_vendor_info(entry['mac_address'], insert_if_found=False) vendor_info = get_vendor_info(entry['mac_address'], insert_if_found=False)
entry['vendor'] = vendor_info['vendor'] if vendor_info else None # placeholder entry['vendor'] = vendor_info['vendor'] if vendor_info else None
user = get_user_by_mac(entry['mac_address']) user = get_user_by_mac(entry['mac_address'])
entry['already_exists'] = user is not None entry['already_exists'] = user is not None
entry['existing_vlan'] = user['vlan_id'] if user else None entry['existing_vlan'] = user['vlan_id'] if user else None
entry['description'] = user['description'] if user else None entry['description'] = user['description'] if user else None
match = re.search(r'VLAN\s+(\d+)', entry.get('result', ''))
entry['vlan_id'] = match.group(1) if match else None
return entry return entry
# Enrich entries
accept_entries = [enrich(e) for e in accept_entries] accept_entries = [enrich(e) for e in accept_entries]
reject_entries = [enrich(e) for e in reject_entries] reject_entries = [enrich(e) for e in reject_entries]
fallback_entries = [enrich(e) for e in fallback_entries] fallback_entries = [enrich(e) for e in fallback_entries]
available_groups = get_all_groups() available_groups = get_all_groups()
return render_template( return render_template(
"stats.html", "stats.html",
time_range=time_range, time_range=time_range,
per_page=per_page,
accept_entries=accept_entries, accept_entries=accept_entries,
reject_entries=reject_entries, reject_entries=reject_entries,
fallback_entries=fallback_entries, fallback_entries=fallback_entries,
available_groups=available_groups, available_groups=available_groups,
page_accept=page_accept, page_accept=page_accept,
total_pages_accept=total_pages_accept, pagination_accept=get_pagination_data(page_accept, total_pages_accept),
page_reject=page_reject, page_reject=page_reject,
total_pages_reject=total_pages_reject, pagination_reject=get_pagination_data(page_reject, total_pages_reject),
page_fallback=page_fallback, page_fallback=page_fallback,
total_pages_fallback=total_pages_fallback pagination_fallback=get_pagination_data(page_fallback, total_pages_fallback)
) )
@stats.route('/add', methods=['POST']) @stats.route('/add', methods=['POST'])
def add(): def add():
mac = request.form['mac_address'] mac = request.form['mac_address']