Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -0,0 +1,303 @@
{% extends "base/base.html" %}
{% load static %}
{% block title %}Performance Dashboard - ThrillWiki Admin{% endblock %}
{% block content %}
<div class="space-y-6">
<!-- Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Performance Dashboard</h1>
<p class="text-muted-foreground">Monitor application performance and cache statistics</p>
</div>
<button
type="button"
onclick="refreshMetrics()"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-primary rounded-md hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Refresh
</button>
</div>
<!-- Quick Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" id="quick-stats">
<!-- Database Status -->
<div class="bg-card rounded-lg border border-border p-4">
<div class="flex items-center gap-3">
<div class="p-2 bg-success-100 text-success-700 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
</div>
<div>
<p class="text-sm text-muted-foreground">Database</p>
<p class="text-lg font-semibold">{{ database_stats.engine }}</p>
<p class="text-xs text-muted-foreground">{{ database_stats.active_connections|default:"N/A" }} connections</p>
</div>
</div>
</div>
<!-- Cache Hit Rate -->
<div class="bg-card rounded-lg border border-border p-4">
<div class="flex items-center gap-3">
<div class="p-2 bg-info-100 text-info-700 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<p class="text-sm text-muted-foreground">Cache Hit Rate</p>
<p class="text-lg font-semibold" id="cache-hit-rate">
{% if cache_stats.default.hit_rate %}
{{ cache_stats.default.hit_rate }}
{% else %}
N/A
{% endif %}
</p>
<p class="text-xs text-muted-foreground">Default cache</p>
</div>
</div>
</div>
<!-- Middleware -->
<div class="bg-card rounded-lg border border-border p-4">
<div class="flex items-center gap-3">
<div class="p-2 bg-warning-100 text-warning-700 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</div>
<div>
<p class="text-sm text-muted-foreground">Middleware</p>
<p class="text-lg font-semibold">{{ middleware_config.count }} active</p>
<p class="text-xs text-muted-foreground">
{% if middleware_config.has_gzip %}GZip{% endif %}
{% if middleware_config.has_performance %}+ Perf{% endif %}
</p>
</div>
</div>
</div>
<!-- Connection Pool -->
<div class="bg-card rounded-lg border border-border p-4">
<div class="flex items-center gap-3">
<div class="p-2 bg-primary-100 text-primary-700 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
<div>
<p class="text-sm text-muted-foreground">DB Conn Max Age</p>
<p class="text-lg font-semibold">{{ database_stats.conn_max_age }}s</p>
<p class="text-xs text-muted-foreground">Connection pooling</p>
</div>
</div>
</div>
</div>
<!-- Cache Statistics -->
<div class="bg-card rounded-lg border border-border">
<div class="px-6 py-4 border-b border-border">
<h2 class="text-lg font-semibold">Cache Statistics</h2>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for cache_name, stats in cache_stats.items %}
<div class="p-4 bg-muted/50 rounded-lg">
<h3 class="font-medium mb-3 capitalize">{{ cache_name }}</h3>
<dl class="space-y-2 text-sm">
<div class="flex justify-between">
<dt class="text-muted-foreground">Backend</dt>
<dd class="font-medium">{{ stats.backend|default:"N/A" }}</dd>
</div>
{% if stats.hit_rate %}
<div class="flex justify-between">
<dt class="text-muted-foreground">Hit Rate</dt>
<dd class="font-medium text-success-600">{{ stats.hit_rate }}</dd>
</div>
{% endif %}
{% if stats.used_memory_human %}
<div class="flex justify-between">
<dt class="text-muted-foreground">Memory Used</dt>
<dd class="font-medium">{{ stats.used_memory_human }}</dd>
</div>
{% endif %}
{% if stats.connected_clients %}
<div class="flex justify-between">
<dt class="text-muted-foreground">Connections</dt>
<dd class="font-medium">{{ stats.connected_clients }}</dd>
</div>
{% endif %}
{% if stats.keyspace_hits %}
<div class="flex justify-between">
<dt class="text-muted-foreground">Hits / Misses</dt>
<dd class="font-medium">{{ stats.keyspace_hits|floatformat:0 }} / {{ stats.keyspace_misses|floatformat:0 }}</dd>
</div>
{% endif %}
{% if stats.error %}
<div class="text-error-600">
Error: {{ stats.error }}
</div>
{% endif %}
</dl>
</div>
{% empty %}
<p class="text-muted-foreground">No cache statistics available.</p>
{% endfor %}
</div>
</div>
</div>
<!-- Cache Configuration -->
<div class="bg-card rounded-lg border border-border">
<div class="px-6 py-4 border-b border-border">
<h2 class="text-lg font-semibold">Cache Configuration</h2>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="px-4 py-2 text-left font-medium text-muted-foreground">Cache</th>
<th class="px-4 py-2 text-left font-medium text-muted-foreground">Backend</th>
<th class="px-4 py-2 text-left font-medium text-muted-foreground">Location</th>
<th class="px-4 py-2 text-left font-medium text-muted-foreground">Key Prefix</th>
<th class="px-4 py-2 text-left font-medium text-muted-foreground">Max Connections</th>
</tr>
</thead>
<tbody>
{% for cache_name, config in cache_config.items %}
<tr class="border-b border-border last:border-0">
<td class="px-4 py-3 font-medium capitalize">{{ cache_name }}</td>
<td class="px-4 py-3">{{ config.backend }}</td>
<td class="px-4 py-3 font-mono text-xs">{{ config.location }}</td>
<td class="px-4 py-3">{{ config.key_prefix|default:"None" }}</td>
<td class="px-4 py-3">{{ config.max_connections|default:"N/A" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Middleware Configuration -->
<div class="bg-card rounded-lg border border-border">
<div class="px-6 py-4 border-b border-border">
<h2 class="text-lg font-semibold">Middleware Stack</h2>
</div>
<div class="p-6">
<div class="flex flex-wrap gap-2">
{% for middleware in middleware_config.middleware_list %}
<span class="inline-flex items-center px-3 py-1 text-xs font-medium rounded-full
{% if 'cache' in middleware|lower %}bg-info-100 text-info-800
{% elif 'gzip' in middleware|lower %}bg-success-100 text-success-800
{% elif 'performance' in middleware|lower %}bg-warning-100 text-warning-800
{% elif 'security' in middleware|lower %}bg-error-100 text-error-800
{% else %}bg-muted text-muted-foreground{% endif %}">
{{ middleware|truncatechars:50 }}
</span>
{% endfor %}
</div>
</div>
</div>
<!-- Performance Tips -->
<div class="bg-card rounded-lg border border-border">
<div class="px-6 py-4 border-b border-border">
<h2 class="text-lg font-semibold">Performance Optimization Status</h2>
</div>
<div class="p-6">
<ul class="space-y-3">
<li class="flex items-center gap-3">
{% if middleware_config.has_gzip %}
<span class="w-5 h-5 rounded-full bg-success-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-success-700">GZip compression enabled</span>
{% else %}
<span class="w-5 h-5 rounded-full bg-warning-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-warning-700">GZip compression not enabled</span>
{% endif %}
</li>
<li class="flex items-center gap-3">
{% if middleware_config.has_cache_update and middleware_config.has_cache_fetch %}
<span class="w-5 h-5 rounded-full bg-success-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-success-700">Page-level caching middleware enabled</span>
{% else %}
<span class="w-5 h-5 rounded-full bg-warning-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-warning-700">Page-level caching middleware not complete</span>
{% endif %}
</li>
<li class="flex items-center gap-3">
{% if middleware_config.has_performance %}
<span class="w-5 h-5 rounded-full bg-success-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-success-700">Performance monitoring middleware enabled</span>
{% else %}
<span class="w-5 h-5 rounded-full bg-warning-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-warning-700">Performance monitoring middleware not enabled</span>
{% endif %}
</li>
<li class="flex items-center gap-3">
{% if database_stats.conn_max_age > 0 %}
<span class="w-5 h-5 rounded-full bg-success-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-success-700">Database connection pooling enabled ({{ database_stats.conn_max_age }}s)</span>
{% else %}
<span class="w-5 h-5 rounded-full bg-warning-500 flex items-center justify-center">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</span>
<span class="text-warning-700">Database connection pooling not enabled</span>
{% endif %}
</li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function refreshMetrics() {
// Reload the page to get fresh metrics
window.location.reload();
}
// Auto-refresh every 30 seconds
// setInterval(refreshMetrics, 30000);
</script>
{% endblock %}

View File

@@ -1,6 +1,10 @@
<div class="divide-y">
<div class="divide-y" role="listbox" aria-label="Search results">
{% for object in object_list %}
<div class="p-4">
<div id="result-option-{{ forloop.counter0 }}"
role="option"
aria-selected="false"
tabindex="-1"
class="p-4 cursor-pointer">
<h3 class="text-lg font-semibold">
<a href="{{ object.get_absolute_url }}" class="hover:text-blue-600">
{{ object }}
@@ -36,11 +40,11 @@
{% endblock %}
</div>
{% empty %}
<div class="p-8 text-center text-gray-500">
<div class="p-8 text-center text-gray-500" role="status" aria-live="polite">
<p>No {{ view.model|model_name_plural }} found matching your criteria.</p>
{% if applied_filters %}
<p class="mt-2">
<a href="{{ request.path }}"
<a href="{{ request.path }}"
class="text-blue-600 hover:text-blue-500"
hx-get="{{ request.path }}"
hx-target="#results-container"
@@ -93,7 +97,7 @@
hx-target="#results-container"
hx-push-url="true">
<span class="sr-only">Previous</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</a>
@@ -101,7 +105,7 @@
{% for i in page_obj.paginator.page_range %}
{% if i == page_obj.number %}
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600">
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600" aria-current="page">
{{ i }}
</span>
{% elif i > page_obj.number|add:"-3" and i < page_obj.number|add:"3" %}
@@ -122,7 +126,7 @@
hx-target="#results-container"
hx-push-url="true">
<span class="sr-only">Next</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</a>
@@ -131,4 +135,4 @@
</div>
</div>
</div>
{% endif %}
{% endif %}

View File

@@ -1,45 +1,49 @@
{% load static %}
<div id="ride-search-results" class="mt-4">
<div id="ride-search-results" class="mt-4" role="listbox" aria-label="Search results">
{% if rides %}
<div class="space-y-4">
<div class="text-sm text-gray-600 dark:text-gray-400">
<div class="text-sm text-gray-600 dark:text-gray-400" aria-live="polite">
Found {{ rides|length }} ride{{ rides|length|pluralize }}
</div>
{% for ride in rides %}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xs border border-gray-200 dark:border-gray-700 p-4 hover:shadow-md transition-shadow">
<div id="ride-option-{{ forloop.counter0 }}"
role="option"
aria-selected="false"
tabindex="-1"
class="bg-white dark:bg-gray-800 rounded-lg shadow-xs border border-gray-200 dark:border-gray-700 p-4 hover:shadow-md transition-shadow cursor-pointer">
<div class="flex items-start justify-between">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
<a href="{% url 'parks:rides:ride_detail' park_slug=ride.park.slug ride_slug=ride.slug %}"
<a href="{% url 'parks:rides:ride_detail' park_slug=ride.park.slug ride_slug=ride.slug %}"
class="hover:text-blue-600 dark:hover:text-blue-400">
{{ ride.name }}
</a>
</h3>
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400">
at <a href="{% url 'parks:park_detail' slug=ride.park.slug %}"
at <a href="{% url 'parks:park_detail' slug=ride.park.slug %}"
class="text-blue-600 dark:text-blue-400 hover:underline">
{{ ride.park.name }}
</a>
</div>
{% if ride.description %}
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300 line-clamp-2">
{{ ride.description|truncatewords:20 }}
</p>
{% endif %}
<div class="mt-2 flex flex-wrap gap-2">
{% if ride.get_category_display %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ ride.get_category_display }}
</span>
{% endif %}
{% if ride.status %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{% if ride.status == 'OPERATING' %}bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
{% elif ride.status == 'CLOSED_TEMP' %}bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200
{% else %}bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200{% endif %}">
@@ -48,11 +52,11 @@
{% endif %}
</div>
</div>
{% if ride.photos.exists %}
<div class="ml-4 shrink-0">
{% with ride.photos.first as photo %}
<img src="{{ photo.image.url }}"
<img src="{{ photo.image.url }}"
alt="{{ ride.name }}"
class="w-16 h-16 rounded-lg object-cover">
{% endwith %}
@@ -63,9 +67,9 @@
{% endfor %}
</div>
{% else %}
<div class="text-center py-8">
<div class="text-center py-8" role="status" aria-live="polite">
<div class="text-gray-500 dark:text-gray-400">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No rides found</h3>
@@ -75,4 +79,4 @@
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div id="search-dropdown" class="search-dropdown">
<div id="search-dropdown" class="search-dropdown" role="listbox" aria-label="Search results">
{% include "core/search/partials/search_suggestions.html" %}
<div id="search-results">
{% for item in results %}

View File

@@ -1,4 +1,8 @@
<div class="search-result-item">
<div id="search-option-{{ forloop.counter0|default:item.id }}"
role="option"
aria-selected="false"
tabindex="-1"
class="search-result-item cursor-pointer">
<a href="{{ item.url }}">{{ item.title }}</a>
<div class="muted">{{ item.subtitle }}</div>
</div>