525 lines
13 KiB
HTML
525 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}User Management - DigiServer v2{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>👥 User Management</h1>
|
|
<button class="btn btn-primary" onclick="showCreateUserModal()">+ Create New User</button>
|
|
</div>
|
|
|
|
<!-- Users Table -->
|
|
<div class="card">
|
|
<h2>All Users</h2>
|
|
<div class="table-responsive">
|
|
<table class="user-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Role</th>
|
|
<th>Created At</th>
|
|
<th>Last Login</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if users %}
|
|
{% for user in users %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ user.username }}</strong>
|
|
{% if user.id == current_user.id %}
|
|
<span class="badge badge-info">You</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-{{ 'success' if user.role == 'admin' else 'secondary' }}">
|
|
{{ user.role|capitalize }}
|
|
</span>
|
|
</td>
|
|
<td>{{ user.created_at | localtime if user.created_at else 'N/A' }}</td>
|
|
<td>{{ user.last_login | localtime if user.last_login else 'Never' }}</td>
|
|
<td class="actions">
|
|
{% if user.id != current_user.id %}
|
|
<button class="btn btn-sm btn-warning" onclick="showEditUserModal({{ user.id }}, '{{ user.username }}', '{{ user.role }}')">
|
|
Edit Role
|
|
</button>
|
|
<button class="btn btn-sm btn-secondary" onclick="showResetPasswordModal({{ user.id }}, '{{ user.username }}')">
|
|
Reset Password
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="confirmDeleteUser({{ user.id }}, '{{ user.username }}')">
|
|
Delete
|
|
</button>
|
|
{% else %}
|
|
<span class="text-muted">Current User</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="5" class="text-center">No users found</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Role Descriptions -->
|
|
<div class="card">
|
|
<h2>📖 Role Descriptions</h2>
|
|
<div class="role-descriptions">
|
|
<div class="role-item">
|
|
<h3>👤 Normal User</h3>
|
|
<ul>
|
|
<li>Upload media content</li>
|
|
<li>Add/remove media from playlists</li>
|
|
<li>Edit media in playlists</li>
|
|
<li>Set display time for media items</li>
|
|
<li>View players and groups</li>
|
|
</ul>
|
|
</div>
|
|
<div class="role-item">
|
|
<h3>👑 Admin User</h3>
|
|
<ul>
|
|
<li>All normal user permissions</li>
|
|
<li>Create and manage users</li>
|
|
<li>Manage players and groups</li>
|
|
<li>Delete content</li>
|
|
<li>Access system settings</li>
|
|
<li>View system logs</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create User Modal -->
|
|
<div id="createUserModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Create New User</h2>
|
|
<span class="close" onclick="closeModal('createUserModal')">×</span>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('admin.create_user') }}">
|
|
<div class="form-group">
|
|
<label for="username">Username *</label>
|
|
<input type="text" id="username" name="username" required minlength="3"
|
|
placeholder="Enter username" class="form-control">
|
|
<small>Minimum 3 characters</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="password">Password *</label>
|
|
<input type="password" id="password" name="password" required minlength="6"
|
|
placeholder="Enter password" class="form-control">
|
|
<small>Minimum 6 characters</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="role">Role *</label>
|
|
<select id="role" name="role" required class="form-control">
|
|
<option value="user">Normal User</option>
|
|
<option value="admin">Admin User</option>
|
|
</select>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('createUserModal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Create User</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Role Modal -->
|
|
<div id="editRoleModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Edit User Role</h2>
|
|
<span class="close" onclick="closeModal('editRoleModal')">×</span>
|
|
</div>
|
|
<form method="POST" id="editRoleForm">
|
|
<div class="form-group">
|
|
<label>Username</label>
|
|
<input type="text" id="edit_username" readonly class="form-control">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit_role">New Role *</label>
|
|
<select id="edit_role" name="role" required class="form-control">
|
|
<option value="user">Normal User</option>
|
|
<option value="admin">Admin User</option>
|
|
</select>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('editRoleModal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Update Role</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reset Password Modal -->
|
|
<div id="resetPasswordModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Reset User Password</h2>
|
|
<span class="close" onclick="closeModal('resetPasswordModal')">×</span>
|
|
</div>
|
|
<form method="POST" id="resetPasswordForm">
|
|
<div class="form-group">
|
|
<label>Username</label>
|
|
<input type="text" id="reset_username" readonly class="form-control">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="new_password">New Password *</label>
|
|
<input type="password" id="new_password" name="password" required minlength="6"
|
|
placeholder="Enter new password" class="form-control">
|
|
<small>Minimum 6 characters</small>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('resetPasswordModal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Reset Password</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.page-header .btn-primary {
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
}
|
|
|
|
.page-header .btn-primary:hover {
|
|
background: #5568d3;
|
|
}
|
|
|
|
body.dark-mode .page-header .btn-primary {
|
|
background: #7c3aed;
|
|
}
|
|
|
|
body.dark-mode .page-header .btn-primary:hover {
|
|
background: #6d28d9;
|
|
}
|
|
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.user-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.user-table th,
|
|
.user-table td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.user-table th {
|
|
background: #f8f9fa;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
}
|
|
|
|
.user-table tbody tr:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
body.dark-mode .user-table th,
|
|
body.dark-mode .user-table td {
|
|
border-bottom: 1px solid #4a5568;
|
|
}
|
|
|
|
body.dark-mode .user-table th {
|
|
background: #1a202c;
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
body.dark-mode .user-table tbody tr:hover {
|
|
background: #1a202c;
|
|
}
|
|
|
|
.badge {
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
display: inline-block;
|
|
}
|
|
|
|
.badge-success {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.badge-secondary {
|
|
background: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.badge-info {
|
|
background: #17a2b8;
|
|
color: white;
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 6px 12px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-warning {
|
|
background: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background: #e0a800;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #c82333;
|
|
}
|
|
|
|
.role-descriptions {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.role-item {
|
|
padding: 15px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.role-item h3 {
|
|
margin-bottom: 10px;
|
|
color: #495057;
|
|
}
|
|
|
|
.role-item ul {
|
|
list-style-position: inside;
|
|
padding-left: 0;
|
|
}
|
|
|
|
.role-item li {
|
|
padding: 5px 0;
|
|
color: #666;
|
|
}
|
|
|
|
body.dark-mode .role-item {
|
|
background: #1a202c;
|
|
border: 1px solid #4a5568;
|
|
}
|
|
|
|
body.dark-mode .role-item h3 {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
body.dark-mode .role-item li {
|
|
color: #a0aec0;
|
|
}
|
|
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.modal-content {
|
|
background-color: #fff;
|
|
margin: 5% auto;
|
|
padding: 0;
|
|
border-radius: 8px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
body.dark-mode .modal-content {
|
|
background-color: #2d3748;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
body.dark-mode .modal-header {
|
|
border-bottom: 1px solid #4a5568;
|
|
}
|
|
|
|
.modal-header h2 {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
}
|
|
|
|
body.dark-mode .modal-header h2 {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.close {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #aaa;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.close:hover {
|
|
color: #000;
|
|
}
|
|
|
|
.modal form {
|
|
padding: 20px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
}
|
|
|
|
body.dark-mode .form-group label {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.form-control {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ced4da;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
background: white;
|
|
color: #2d3748;
|
|
}
|
|
|
|
.form-control:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
}
|
|
|
|
body.dark-mode .form-control {
|
|
background: #1a202c;
|
|
border-color: #4a5568;
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
body.dark-mode .form-control:focus {
|
|
border-color: #7c3aed;
|
|
}
|
|
|
|
.form-group small {
|
|
display: block;
|
|
margin-top: 5px;
|
|
color: #6c757d;
|
|
font-size: 12px;
|
|
}
|
|
|
|
body.dark-mode .form-group small {
|
|
color: #a0aec0;
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid #e0e0e0;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
body.dark-mode .modal-footer {
|
|
border-top: 1px solid #4a5568;
|
|
}
|
|
|
|
.text-muted {
|
|
color: #6c757d;
|
|
}
|
|
|
|
.text-center {
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
function showCreateUserModal() {
|
|
document.getElementById('createUserModal').style.display = 'block';
|
|
}
|
|
|
|
function showEditUserModal(userId, username, currentRole) {
|
|
document.getElementById('edit_username').value = username;
|
|
document.getElementById('edit_role').value = currentRole;
|
|
document.getElementById('editRoleForm').action = `/admin/user/${userId}/role`;
|
|
document.getElementById('editRoleModal').style.display = 'block';
|
|
}
|
|
|
|
function showResetPasswordModal(userId, username) {
|
|
document.getElementById('reset_username').value = username;
|
|
document.getElementById('resetPasswordForm').action = `/admin/user/${userId}/password`;
|
|
document.getElementById('resetPasswordModal').style.display = 'block';
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'none';
|
|
}
|
|
|
|
function confirmDeleteUser(userId, username) {
|
|
if (confirm(`Are you sure you want to delete user "${username}"? This action cannot be undone.`)) {
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = `/admin/user/${userId}/delete`;
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.onclick = function(event) {
|
|
const modals = document.getElementsByClassName('modal');
|
|
for (let modal of modals) {
|
|
if (event.target === modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|