Initial commit - LEGO Instructions Manager v1.5.0
This commit is contained in:
211
app/templates/admin/users.html
Normal file
211
app/templates/admin/users.html
Normal file
@@ -0,0 +1,211 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}User Management - Admin - {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>
|
||||
<i class="bi bi-people"></i> User Management
|
||||
</h1>
|
||||
<p class="text-muted">Manage users and permissions</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" action="{{ url_for('admin.users') }}">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="search"
|
||||
value="{{ search }}" placeholder="Search by username or email...">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="bi bi-search"></i> Search
|
||||
</button>
|
||||
{% if search %}
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-x"></i> Clear
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-list"></i> Users ({{ pagination.total }})
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Joined</th>
|
||||
<th>Sets</th>
|
||||
<th>Instructions</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<strong>{{ user.username }}</strong>
|
||||
{% if user.id == current_user.id %}
|
||||
<span class="badge bg-info">You</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>
|
||||
<span class="badge bg-success">{{ user_stats[user.id]['sets'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ user_stats[user.id]['instructions'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if user.is_admin %}
|
||||
<span class="badge bg-danger">
|
||||
<i class="bi bi-shield-lock"></i> Admin
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">User</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
{% if user.id != current_user.id %}
|
||||
<button class="btn btn-outline-primary toggle-admin-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-is-admin="{{ user.is_admin|lower }}">
|
||||
<i class="bi bi-shield"></i>
|
||||
{% if user.is_admin %}Revoke{% else %}Grant{% endif %} Admin
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal{{ user.id }}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="text-muted small">Cannot modify yourself</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal{{ user.id }}" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Delete User?</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('admin.delete_user', user_id=user.id) }}">
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete <strong>{{ user.username }}</strong>?</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="delete_data{{ user.id }}" name="delete_data">
|
||||
<label class="form-check-label" for="delete_data{{ user.id }}">
|
||||
Also delete all their sets and instructions
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
If unchecked, their content will be reassigned to you.
|
||||
</small>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger">Delete User</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination.pages > 1 %}
|
||||
<div class="card-footer">
|
||||
<nav>
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
<li class="page-item {% if not pagination.has_prev %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('admin.users', page=pagination.prev_num, search=search) }}">Previous</a>
|
||||
</li>
|
||||
{% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
|
||||
{% if page_num %}
|
||||
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('admin.users', page=page_num, search=search) }}">{{ page_num }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('admin.users', page=pagination.next_num, search=search) }}">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox display-1 text-muted"></i>
|
||||
<p class="mt-3 text-muted">No users found</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Toggle admin status with AJAX
|
||||
document.querySelectorAll('.toggle-admin-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
const isAdmin = this.dataset.isAdmin === 'true';
|
||||
|
||||
if (confirm(`${isAdmin ? 'Revoke' : 'Grant'} admin access for ${username}?`)) {
|
||||
fetch(`/admin/users/${userId}/toggle-admin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error updating admin status');
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user