Commit 0a61873a authored by Your Name's avatar Your Name

fix: restore dashboard functionality and add response cache page

- Restore Rate Limits and Response Cache links in analytics page for admin users
- Add detailed Optimization Savings section with token and cost breakdown
- Fix JavaScript syntax error in rate limits page
- Add response cache dashboard page with statistics and management
- Update setup.py to include new response cache template
parent 6f748815
......@@ -1169,6 +1169,9 @@ class ResponseCache:
'errors': 0
}
# Per-provider statistics
self.provider_stats = {}
# Initialize backends
self.redis_client = None
self.sqlite_backend = None
......@@ -1358,12 +1361,14 @@ class ResponseCache:
try:
cache_key = self._generate_cache_key(request_data)
provider_id = request_data.get('provider_id', 'unknown')
if self.backend == 'redis' and self.redis_client:
# Try Redis first
data = self.redis_client.get(cache_key)
if data:
self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits')
logger.debug(f"Cache hit (Redis): {cache_key}")
return self._deserialize_response(data)
elif self.backend == 'sqlite' and self.sqlite_backend:
......@@ -1371,6 +1376,7 @@ class ResponseCache:
data = self.sqlite_backend.get(cache_key)
if data:
self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits')
logger.debug(f"Cache hit (SQLite): {cache_key}")
return data
elif self.backend == 'mysql' and self.mysql_backend:
......@@ -1378,6 +1384,7 @@ class ResponseCache:
data = self.mysql_backend.get(cache_key)
if data:
self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits')
logger.debug(f"Cache hit (MySQL): {cache_key}")
return data
elif self.backend == 'memory':
......@@ -1399,10 +1406,12 @@ class ResponseCache:
self._memory_access_order.append(cache_key)
self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits')
logger.debug(f"Cache hit (Memory): {cache_key}")
return self._memory_cache[cache_key]
self.stats['misses'] += 1
self._update_provider_stats(provider_id, 'misses')
logger.debug(f"Cache miss: {cache_key}")
return None
......
# Backend Bulk Operations API Design
## Overview
Add POST /dashboard/users/bulk endpoint to handle bulk user management operations (enable, disable, delete, tier change) with proper validation, error handling, and partial failure support.
## Architecture
- **New Endpoint**: POST /dashboard/users/bulk in main.py
- **Authentication**: Admin-only access using existing require_admin middleware
- **Input Validation**: JSON body with action, user_ids, extra_data (for tier operations)
- **Processing**: Sequential database operations with individual success/failure tracking
- **Response**: JSON with success status and operation summary message
## Components
### Input Validation
- Require admin authentication
- Parse JSON: `{"action": "enable|disable|delete|tier", "user_ids": [1,2,3], "extra_data": {"tier_id": 1}}`
- Validate action in allowed set: `["enable", "disable", "delete", "tier"]`
- Validate user_ids as non-empty list of integers
- For tier action: validate extra_data.tier_id exists and references valid tier
### Action Processing
- **enable**: Call `db.update_user(user_id, None, None, None, True)` for each user
- **disable**: Call `db.update_user(user_id, None, None, None, False)` for each user
- **delete**: Call `db.delete_user(user_id)` for each user
- **tier**: Call `db.set_user_tier(user_id, tier_id)` for each user (after tier validation)
### Error Handling
- Individual operation failures don't stop processing others
- Track successful vs failed operations per user
- Return 500 for unexpected exceptions with error details
- Log errors for debugging
### Response Format
- Success: `{"success": true, "message": "Enabled 3 of 3 users"}`
- Partial failure: `{"success": true, "message": "Enabled 2 of 3 users (1 failed)"}`
- Total failure: `{"success": false, "error": "All operations failed"}`
- Validation error: `{"success": false, "error": "Invalid action"}`
## Data Flow
1. Request → require_admin auth check
2. Parse/validate JSON body
3. Validate tier existence (if tier action)
4. Process each user_id sequentially:
- Execute appropriate db operation
- Track success/failure
5. Generate summary message
6. Return JSON response
## Testing
- Unit test with curl: `curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{"action":"enable","user_ids":[1,2]}' http://localhost:8000/dashboard/users/bulk`
- Verify response format and database state changes
- Test partial failures and error cases
## Security Considerations
- Admin-only endpoint (enforced by require_admin)
- Input validation prevents SQL injection
- No sensitive data in responses
- Operations logged for audit trail
## Implementation Notes
- Follow existing main.py patterns for route definition and error handling
- Use DatabaseRegistry.get_config_database() for db access
- Maintain compatibility with existing individual user operation endpoints
\ No newline at end of file
......@@ -204,6 +204,7 @@ setup(
'templates/dashboard/user_autoselects.html',
'templates/dashboard/user_tokens.html',
'templates/dashboard/rate_limits.html',
'templates/dashboard/response_cache.html',
'templates/dashboard/users.html',
'templates/dashboard/signup.html',
'templates/dashboard/verify.html',
......
......@@ -537,6 +537,59 @@ document.getElementById('timeRangeSelect').addEventListener('change', function()
{% endfor %}
</div>
<h3 style="margin-top: 30px; margin-bottom: 15px;">Optimization Savings</h3>
{% if optimization_savings and optimization_savings.total_tokens_saved > 0 %}
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 20px;">
<div style="background: #27ae60; color: white; padding: 20px; border-radius: 8px;">
<h4 style="font-size: 14px; margin-bottom: 10px;">Total Tokens Saved</h4>
<p style="font-size: 28px; font-weight: bold;">{{ format_tokens(optimization_savings.total_tokens_saved) }}</p>
{% if optimization_savings.date_range %}
<small style="color: rgba(255,255,255,0.8);">
{{ optimization_savings.date_range.start[:10] }} to {{ optimization_savings.date_range.end[:10] }}
</small>
{% endif %}
</div>
<div style="background: #16a085; color: white; padding: 20px; border-radius: 8px;">
<h4 style="font-size: 14px; margin-bottom: 10px;">Total Cost Saved</h4>
<p style="font-size: 28px; font-weight: bold;">${{ "%.2f"|format(optimization_savings.total_cost_saved) }}</p>
<small style="color: rgba(255,255,255,0.8);">From optimizations</small>
</div>
</div>
{% if optimization_savings.savings_by_type %}
<table>
<tr>
<th>Optimization Type</th>
<th>Count</th>
<th>Tokens Saved</th>
<th>Cost Saved</th>
<th>Avg Tokens/Optimization</th>
<th>Max Tokens Saved</th>
</tr>
{% for opt_type, stats in optimization_savings.savings_by_type.items() %}
<tr>
<td><strong>{{ opt_type.title() }}</strong></td>
<td>{{ stats.count }}</td>
<td>{{ format_tokens(stats.tokens_saved) }}</td>
<td>${{ "%.4f"|format(stats.cost_saved) }}</td>
<td>{{ format_tokens(stats.avg_tokens_saved) }}</td>
<td>{{ format_tokens(stats.max_tokens_saved) }}</td>
</tr>
{% endfor %}
<tr style="background: #0f3460; font-weight: bold;">
<td><strong>Total</strong></td>
<td>{{ optimization_savings.savings_by_type.values() | sum(attribute='count') }}</td>
<td>{{ format_tokens(optimization_savings.total_tokens_saved) }}</td>
<td>${{ "%.4f"|format(optimization_savings.total_cost_saved) }}</td>
<td>-</td>
<td>-</td>
</tr>
</table>
{% endif %}
{% else %}
<p style="color: #a0a0a0;">No optimization savings recorded yet. Enable deduplication, condensation, batching, or caching to see savings.</p>
{% endif %}
<script>
// Fetch and display savings estimation
fetch('{{ url_for(request, "/dashboard/response-cache/stats") }}')
......@@ -680,7 +733,11 @@ fetch('{{ url_for(request, "/dashboard/response-cache/stats") }}')
<p style="color: #a0a0a0;">No token usage data available for the selected period.</p>
{% endif %}
<div style="margin-top: 30px; display: flex; gap: 10px;">
<div style="margin-top: 30px; display: flex; gap: 10px; flex-wrap: wrap;">
<a href="/dashboard" class="btn btn-secondary">Back to Dashboard</a>
{% if is_admin %}
<a href="{{ url_for(request, '/dashboard/rate-limits') }}" class="btn">Rate Limits</a>
<a href="{{ url_for(request, '/dashboard/response-cache') }}" class="btn">Response Cache</a>
{% endif %}
</div>
{% endblock %}
\ No newline at end of file
......@@ -196,7 +196,7 @@ async function clearAllRateLimiters() {
// First get the list of providers
try {
const response = await fetch({{ url_for(request, "/dashboard/rate-limits/data") }});
const response = await fetch('{{ url_for(request, "/dashboard/rate-limits/data") }}');
const data = await response.json();
for (const providerId of Object.keys(data)) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment