Features Added: 🔥 Modern Chat System: - Real-time messaging with modern Tailwind CSS design - Post-linked discussions for adventure sharing - Chat categories (general, technical-support, adventure-planning) - Mobile-responsive interface with gradient backgrounds - JavaScript polling for live message updates 🎯 Comprehensive Admin Panel: - Chat room management with merge capabilities - Password reset system with email templates - User management with admin controls - Chat statistics and analytics dashboard - Room binding to posts and categorization �� Mobile API Integration: - RESTful API endpoints at /api/v1/chat - Session-based authentication for mobile apps - Comprehensive endpoints for rooms, messages, users - Mobile app compatibility (React Native, Flutter) 🛠️ Technical Improvements: - Enhanced database models with ChatRoom categories - Password reset token system with email verification - Template synchronization fixes for Docker deployment - Migration scripts for database schema updates - Improved error handling and validation 🎨 UI/UX Enhancements: - Modern card-based layouts matching app design - Consistent styling across chat and admin interfaces - Mobile-optimized touch interactions - Professional gradient designs and glass morphism effects 📚 Documentation: - Updated README with comprehensive API documentation - Added deployment instructions for Docker (port 8100) - Configuration guide for production environments - Mobile integration examples and endpoints This update transforms the platform into a comprehensive motorcycle adventure community with modern chat capabilities and professional admin management tools.
326 lines
14 KiB
HTML
326 lines
14 KiB
HTML
{% extends "admin/base.html" %}
|
|
|
|
{% block title %}Admin Dashboard - Moto Adventure{% endblock %}
|
|
|
|
{% block admin_content %}
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Dashboard</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="location.reload()">
|
|
<i class="fas fa-sync-alt"></i> Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
<div class="card border-left-primary h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-primary text-uppercase mb-1">Total Users</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">{{ total_users }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-users fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
<div class="card border-left-success h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-success text-uppercase mb-1">Published Posts</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">{{ published_posts }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-check fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
<div class="card border-left-warning h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-warning text-uppercase mb-1">Pending Posts</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">
|
|
<a href="{{ url_for('admin.posts', status='pending') }}" class="text-decoration-none text-dark">
|
|
{{ pending_posts }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-clock fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
<div class="card border-left-info h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-info text-uppercase mb-1">Active Users (30d)</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">{{ active_users }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-user-check fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Views Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-4 col-md-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 fw-bold text-primary">Views Today</h6>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
<div class="h3 mb-0 text-gray-800">{{ views_today }}</div>
|
|
<small class="text-muted">Yesterday: {{ views_yesterday }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4 col-md-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 fw-bold text-primary">Views This Week</h6>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
<div class="h3 mb-0 text-gray-800">{{ views_this_week }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4 col-md-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 fw-bold text-primary">Total Engagement</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="small mb-1">Comments: <span class="float-end">{{ total_comments }}</span></div>
|
|
<div class="small mb-1">Likes: <span class="float-end">{{ total_likes }}</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password Reset Management -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-6 col-md-6 mb-4">
|
|
<div class="card border-left-danger h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-danger text-uppercase mb-1">Password Reset Requests</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">
|
|
<a href="{{ url_for('admin.password_reset_requests') }}" class="text-decoration-none text-dark">
|
|
{{ pending_password_requests or 0 }}
|
|
</a>
|
|
</div>
|
|
<div class="small text-muted">Pending requests need attention</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-key fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<a href="{{ url_for('admin.password_reset_requests') }}" class="btn btn-danger btn-sm">
|
|
<i class="fas fa-cogs me-1"></i>Manage Requests
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-md-6 mb-4">
|
|
<div class="card border-left-secondary h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs fw-bold text-secondary text-uppercase mb-1">Active Reset Tokens</div>
|
|
<div class="h5 mb-0 fw-bold text-gray-800">{{ active_reset_tokens or 0 }}</div>
|
|
<div class="small text-muted">Unused tokens (24h expiry)</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-link fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<a href="{{ url_for('admin.password_reset_tokens') }}" class="btn btn-secondary btn-sm">
|
|
<i class="fas fa-list me-1"></i>View Tokens
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Overview -->
|
|
<div class="row">
|
|
<!-- Chat Management -->
|
|
<div class="col-lg-4 mb-4">
|
|
<div class="card border-left-info h-100">
|
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
|
<h6 class="m-0 fw-bold text-info">
|
|
<i class="fas fa-comments me-2"></i>Chat Management
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="text-center mb-3">
|
|
<div class="h4 mb-0 text-gray-800">{{ total_chat_rooms or 0 }}</div>
|
|
<small class="text-muted">Total Chat Rooms</small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="small text-muted mb-1">Active Rooms: {{ active_chat_rooms or 0 }}</div>
|
|
<div class="small text-muted mb-1">Linked to Posts: {{ linked_chat_rooms or 0 }}</div>
|
|
<div class="small text-muted">Recent Messages: {{ recent_chat_messages or 0 }}</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<a href="{{ url_for('admin.manage_chats') }}" class="btn btn-info btn-block">
|
|
<i class="fas fa-cogs me-1"></i>Manage Chats
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Posts -->
|
|
<div class="col-lg-4 mb-4">
|
|
<div class="card">
|
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
|
<h6 class="m-0 fw-bold text-primary">Recent Posts</h6>
|
|
<a href="{{ url_for('admin.posts') }}" class="btn btn-sm btn-primary">View All</a>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_posts %}
|
|
{% for post in recent_posts %}
|
|
<div class="d-flex align-items-center mb-3 border-bottom pb-2">
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">
|
|
<a href="{{ url_for('admin.post_detail', post_id=post.id) }}" class="text-decoration-none">
|
|
{{ post.title[:50] }}{% if post.title|length > 50 %}...{% endif %}
|
|
</a>
|
|
</h6>
|
|
<small class="text-muted">
|
|
by {{ post.author.nickname }} • {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
</small>
|
|
</div>
|
|
<div class="ms-2">
|
|
{% if post.published %}
|
|
<span class="badge bg-success">Published</span>
|
|
{% else %}
|
|
<span class="badge bg-warning text-dark">Pending</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="text-muted">No recent posts</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Most Viewed Posts -->
|
|
<div class="col-lg-4 mb-4">
|
|
<div class="card">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 fw-bold text-primary">Most Viewed Posts</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if most_viewed_posts %}
|
|
{% for post_id, title, view_count in most_viewed_posts %}
|
|
<div class="d-flex justify-content-between align-items-center mb-2 border-bottom pb-1">
|
|
<div class="flex-grow-1">
|
|
<a href="{{ url_for('admin.post_detail', post_id=post_id) }}" class="text-decoration-none">
|
|
{{ title[:40] }}{% if title|length > 40 %}...{% endif %}
|
|
</a>
|
|
</div>
|
|
<span class="badge bg-info">{{ view_count }} views</span>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="text-muted">No view data available</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Row -->
|
|
<div class="row">
|
|
<!-- Most Viewed Pages -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 fw-bold text-primary">Most Viewed Pages</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if most_viewed_pages %}
|
|
{% for path, view_count in most_viewed_pages %}
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<code class="small">{{ path }}</code>
|
|
<span class="badge bg-secondary">{{ view_count }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="text-muted">No page view data available</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Users -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
|
<h6 class="m-0 fw-bold text-primary">Recent Users</h6>
|
|
<a href="{{ url_for('admin.users') }}" class="btn btn-sm btn-primary">View All</a>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_users %}
|
|
{% for user in recent_users %}
|
|
<div class="d-flex align-items-center mb-3 border-bottom pb-2">
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">
|
|
<a href="{{ url_for('admin.user_detail', user_id=user.id) }}" class="text-decoration-none">
|
|
{{ user.nickname }}
|
|
</a>
|
|
</h6>
|
|
<small class="text-muted">{{ user.email }} • {{ user.created_at.strftime('%Y-%m-%d') }}</small>
|
|
</div>
|
|
<div class="ms-2">
|
|
{% if user.is_admin %}
|
|
<span class="badge bg-danger">Admin</span>
|
|
{% else %}
|
|
<span class="badge bg-primary">User</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="text-muted">No recent users</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|