updated settings
This commit is contained in:
@@ -30,6 +30,16 @@ class User(db.Model, UserMixin):
|
|||||||
"""Check if user has admin role"""
|
"""Check if user has admin role"""
|
||||||
return self.role == 'admin'
|
return self.role == 'admin'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_super_admin(self):
|
||||||
|
"""Check if user has super admin role"""
|
||||||
|
return self.role == 'sadmin'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_admin_access(self):
|
||||||
|
"""Check if user has any admin access (admin or super admin)"""
|
||||||
|
return self.role in ['admin', 'sadmin']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
"""Required by Flask-Login"""
|
"""Required by Flask-Login"""
|
||||||
|
|||||||
@@ -13,15 +13,25 @@ import os
|
|||||||
bp = Blueprint('admin', __name__)
|
bp = Blueprint('admin', __name__)
|
||||||
|
|
||||||
def admin_required(f):
|
def admin_required(f):
|
||||||
"""Decorator to require admin role"""
|
"""Decorator to require admin or super admin role"""
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if not current_user.is_authenticated or not current_user.is_admin:
|
if not current_user.is_authenticated or not current_user.has_admin_access:
|
||||||
flash('Admin access required.', 'danger')
|
flash('Admin access required.', 'danger')
|
||||||
return redirect(url_for('dashboard.index'))
|
return redirect(url_for('dashboard.index'))
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
def super_admin_required(f):
|
||||||
|
"""Decorator to require super admin role only"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if not current_user.is_authenticated or not current_user.is_super_admin:
|
||||||
|
flash('Super admin access required.', 'danger')
|
||||||
|
return redirect(url_for('dashboard.index'))
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
@bp.route('/')
|
@bp.route('/')
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
@@ -64,10 +74,15 @@ def create_user():
|
|||||||
flash('Password must be at least 6 characters long.', 'danger')
|
flash('Password must be at least 6 characters long.', 'danger')
|
||||||
return redirect(url_for('admin.index'))
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
if role not in ['user', 'admin']:
|
if role not in ['user', 'admin', 'sadmin']:
|
||||||
flash('Invalid role specified.', 'danger')
|
flash('Invalid role specified.', 'danger')
|
||||||
return redirect(url_for('admin.index'))
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Prevent creating sadmin users - sadmin only exists from deployment
|
||||||
|
if role == 'sadmin':
|
||||||
|
flash('Super admin users cannot be created through the interface.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
# Check if user already exists
|
# Check if user already exists
|
||||||
if User.query.filter_by(username=username).first():
|
if User.query.filter_by(username=username).first():
|
||||||
flash(f'User "{username}" already exists.', 'danger')
|
flash(f'User "{username}" already exists.', 'danger')
|
||||||
@@ -91,7 +106,7 @@ def create_user():
|
|||||||
|
|
||||||
@bp.route('/delete_user', methods=['POST'])
|
@bp.route('/delete_user', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@super_admin_required
|
||||||
def delete_user():
|
def delete_user():
|
||||||
"""Delete a user using POST form data"""
|
"""Delete a user using POST form data"""
|
||||||
user_id = request.form.get('user_id')
|
user_id = request.form.get('user_id')
|
||||||
@@ -107,6 +122,11 @@ def delete_user():
|
|||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
username = user.username
|
username = user.username
|
||||||
|
|
||||||
|
# Prevent deletion of sadmin users - they are permanent
|
||||||
|
if user.role == 'sadmin':
|
||||||
|
flash('Super admin users cannot be deleted.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.delete(user)
|
db.session.delete(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -122,9 +142,9 @@ def delete_user():
|
|||||||
|
|
||||||
@bp.route('/change_role/<int:user_id>', methods=['POST'])
|
@bp.route('/change_role/<int:user_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@super_admin_required
|
||||||
def change_role(user_id):
|
def change_role(user_id):
|
||||||
"""Change user role"""
|
"""Change user role - restricted to super admin"""
|
||||||
# Prevent changing own role
|
# Prevent changing own role
|
||||||
if user_id == current_user.id:
|
if user_id == current_user.id:
|
||||||
flash('You cannot change your own role.', 'danger')
|
flash('You cannot change your own role.', 'danger')
|
||||||
@@ -133,10 +153,20 @@ def change_role(user_id):
|
|||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
new_role = request.form.get('role')
|
new_role = request.form.get('role')
|
||||||
|
|
||||||
if new_role not in ['user', 'admin']:
|
if new_role not in ['user', 'admin', 'sadmin']:
|
||||||
flash('Invalid role specified.', 'danger')
|
flash('Invalid role specified.', 'danger')
|
||||||
return redirect(url_for('admin.index'))
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Prevent any changes to sadmin users - they are permanent
|
||||||
|
if user.role == 'sadmin':
|
||||||
|
flash('Super admin users cannot have their role changed.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Prevent assigning sadmin role - sadmin only exists from deployment
|
||||||
|
if new_role == 'sadmin':
|
||||||
|
flash('Super admin role cannot be assigned through the interface.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
old_role = user.role
|
old_role = user.role
|
||||||
user.role = new_role
|
user.role = new_role
|
||||||
@@ -423,10 +453,20 @@ def edit_user():
|
|||||||
flash('Username cannot be empty.', 'danger')
|
flash('Username cannot be empty.', 'danger')
|
||||||
return redirect(url_for('admin.index'))
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
if role not in ['user', 'admin']:
|
if role not in ['user', 'admin', 'sadmin']:
|
||||||
flash('Invalid role specified.', 'danger')
|
flash('Invalid role specified.', 'danger')
|
||||||
return redirect(url_for('admin.index'))
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Prevent changing sadmin users - they are permanent
|
||||||
|
if user.role == 'sadmin':
|
||||||
|
flash('Super admin users cannot be modified.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Prevent assigning sadmin role - sadmin only exists from deployment
|
||||||
|
if role == 'sadmin':
|
||||||
|
flash('Super admin role cannot be assigned through the interface.', 'danger')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
# Check if username is taken by another user
|
# Check if username is taken by another user
|
||||||
if username != user.username:
|
if username != user.username:
|
||||||
existing_user = User.query.filter_by(username=username).first()
|
existing_user = User.query.filter_by(username=username).first()
|
||||||
|
|||||||
@@ -2,12 +2,47 @@
|
|||||||
|
|
||||||
{% block title %}Admin Panel - SKE Digital Signage{% endblock %}
|
{% block title %}Admin Panel - SKE Digital Signage{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.clickable-row:hover {
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon.rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-row {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-row td {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-row {
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1><i class="bi bi-gear-fill"></i> Admin Panel</h1>
|
<h1><i class="bi bi-gear-fill"></i> Admin Panel
|
||||||
|
{% if current_user.is_super_admin %}
|
||||||
|
<span class="badge bg-danger ms-2">Super Admin</span>
|
||||||
|
{% elif current_user.is_admin %}
|
||||||
|
<span class="badge bg-warning ms-2">Admin</span>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
<p class="text-muted">System administration and user management</p>
|
<p class="text-muted">System administration and user management</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +160,8 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<!-- Main user row (clickable) -->
|
||||||
|
<tr class="user-row {% if user.username != current_user.username and user.role != 'sadmin' %}clickable-row{% endif %}" data-user-id="{{ user.id }}" {% if user.username != current_user.username and user.role != 'sadmin' %}style="cursor: pointer;"{% endif %}>
|
||||||
<td>
|
<td>
|
||||||
<strong>{{ user.username }}</strong>
|
<strong>{{ user.username }}</strong>
|
||||||
{% if user.username == current_user.username %}
|
{% if user.username == current_user.username %}
|
||||||
@@ -133,8 +169,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if user.role == 'admin' %}
|
{% if user.role == 'sadmin' %}
|
||||||
<span class="badge bg-danger">Admin</span>
|
<span class="badge bg-danger">Super Admin</span>
|
||||||
|
{% elif user.role == 'admin' %}
|
||||||
|
<span class="badge bg-warning">Admin</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-primary">User</span>
|
<span class="badge bg-primary">User</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -149,28 +187,86 @@
|
|||||||
<td>{{ user.created_at.strftime('%Y-%m-%d') if user.created_at else 'N/A' }}</td>
|
<td>{{ user.created_at.strftime('%Y-%m-%d') if user.created_at else 'N/A' }}</td>
|
||||||
<td>{{ user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else 'Never' }}</td>
|
<td>{{ user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else 'Never' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group btn-group-sm">
|
{% if user.username != current_user.username and user.role != 'sadmin' %}
|
||||||
{% if user.username != current_user.username %}
|
<i class="bi bi-chevron-down expand-icon"></i>
|
||||||
<button type="button" class="btn btn-outline-warning edit-user-btn"
|
<small class="text-muted">Click to edit</small>
|
||||||
data-user-id="{{ user.id }}"
|
{% elif user.role == 'sadmin' %}
|
||||||
data-username="{{ user.username }}"
|
<span class="badge bg-danger">Protected</span>
|
||||||
data-role="{{ user.role }}"
|
{% else %}
|
||||||
data-active="{{ user.is_active_user|tojson }}">
|
<span class="badge bg-secondary">Current User</span>
|
||||||
<i class="bi bi-pencil"></i>
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Expandable edit row (hidden by default) -->
|
||||||
|
{% if user.username != current_user.username and user.role != 'sadmin' %}
|
||||||
|
<tr class="edit-row" id="edit-row-{{ user.id }}" style="display: none;">
|
||||||
|
<td colspan="6" class="bg-light">
|
||||||
|
<div class="row p-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h6><i class="bi bi-pencil"></i> Edit User: {{ user.username }}</h6>
|
||||||
|
<form method="POST" action="{{ url_for('admin.edit_user') }}" class="row g-3">
|
||||||
|
<input type="hidden" name="user_id" value="{{ user.id }}">
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control form-control-sm" name="username" value="{{ user.username }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">New Password</label>
|
||||||
|
<input type="password" class="form-control form-control-sm" name="password" placeholder="Leave blank to keep current">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Role</label>
|
||||||
|
<select name="role" class="form-select form-select-sm">
|
||||||
|
<option value="user" {% if user.role == 'user' %}selected{% endif %}>User</option>
|
||||||
|
<option value="admin" {% if user.role == 'admin' %}selected{% endif %}>Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Status</label>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" {% if user.is_active_user %}checked{% endif %}>
|
||||||
|
<label class="form-check-label">Active</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm me-2">
|
||||||
|
<i class="bi bi-check-lg"></i> Save Changes
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm collapse-row" data-user-id="{{ user.id }}">
|
||||||
|
<i class="bi bi-x-lg"></i> Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% if current_user.is_super_admin %}
|
||||||
|
<h6 class="text-danger"><i class="bi bi-trash"></i> Delete User</h6>
|
||||||
|
<p class="small text-muted">This action cannot be undone.</p>
|
||||||
|
<form method="POST" action="{{ url_for('admin.delete_user') }}" onsubmit="return confirm('Are you sure you want to delete user {{ user.username }}? This action cannot be undone!');">
|
||||||
|
<input type="hidden" name="user_id" value="{{ user.id }}">
|
||||||
|
<button type="submit" class="btn btn-danger btn-sm">
|
||||||
|
<i class="bi bi-trash"></i> Delete User
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<h6 class="text-muted"><i class="bi bi-shield-lock"></i> Delete User</h6>
|
||||||
|
<p class="small text-muted">Super admin access required.</p>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" disabled>
|
||||||
|
<i class="bi bi-shield-lock"></i> Restricted
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-danger delete-user-btn"
|
{% endif %}
|
||||||
data-user-id="{{ user.id }}"
|
</div>
|
||||||
data-username="{{ user.username }}">
|
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
|
||||||
<i class="bi bi-lock"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -493,73 +589,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Edit User Modal -->
|
|
||||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Edit User</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form method="POST" action="{{ url_for('admin.edit_user') }}" onsubmit="setTimeout(function(){ window.location.reload(); }, 1000);">
|
|
||||||
<input type="hidden" id="editUserId" name="user_id">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editUsername" class="form-label">Username</label>
|
|
||||||
<input type="text" class="form-control" id="editUsername" name="username" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editRole" class="form-label">Role</label>
|
|
||||||
<select class="form-select" id="editRole" name="role">
|
|
||||||
<option value="user">User</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="editIsActive" name="is_active">
|
|
||||||
<label class="form-check-label" for="editIsActive">
|
|
||||||
Active User
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editPassword" class="form-label">New Password (leave blank to keep current)</label>
|
|
||||||
<input type="password" class="form-control" id="editPassword" name="password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Update User</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete User Modal -->
|
|
||||||
<div class="modal fade" id="deleteUserModal" 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>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to delete user <strong id="deleteUsername"></strong>?</p>
|
|
||||||
<p class="text-danger small">This action cannot be undone.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
||||||
<form method="POST" action="{{ url_for('admin.delete_user') }}" style="display:inline;" onsubmit="setTimeout(function(){ window.location.reload(); }, 1000);">
|
|
||||||
<input type="hidden" id="deleteUserId" name="user_id">
|
|
||||||
<button type="submit" class="btn btn-danger">Delete User</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Schedule Task Modal -->
|
<!-- Quick Schedule Task Modal -->
|
||||||
<div class="modal fade" id="quickScheduleModal" tabindex="-1">
|
<div class="modal fade" id="quickScheduleModal" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -614,70 +643,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let currentUserId = null;
|
|
||||||
|
|
||||||
function editUser(userId, username, role, isActive) {
|
|
||||||
console.log('editUser called with:', userId, username, role, isActive);
|
|
||||||
currentUserId = userId;
|
|
||||||
|
|
||||||
const editUserId = document.getElementById('editUserId');
|
|
||||||
const editUsername = document.getElementById('editUsername');
|
|
||||||
const editRole = document.getElementById('editRole');
|
|
||||||
const editIsActive = document.getElementById('editIsActive');
|
|
||||||
|
|
||||||
if (!editUserId || !editUsername || !editRole || !editIsActive) {
|
|
||||||
console.error('Modal elements not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editUserId.value = userId;
|
|
||||||
editUsername.value = username;
|
|
||||||
editRole.value = role;
|
|
||||||
editIsActive.checked = isActive;
|
|
||||||
document.getElementById('editPassword').value = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const modalElement = document.getElementById('editUserModal');
|
|
||||||
if (!modalElement) {
|
|
||||||
console.error('Edit modal not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modal = new bootstrap.Modal(modalElement);
|
|
||||||
modal.show();
|
|
||||||
console.log('Modal should be shown');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing modal:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteUser(userId, username) {
|
|
||||||
console.log('deleteUser called with:', userId, username);
|
|
||||||
|
|
||||||
const deleteUserId = document.getElementById('deleteUserId');
|
|
||||||
const deleteUsername = document.getElementById('deleteUsername');
|
|
||||||
|
|
||||||
if (!deleteUserId || !deleteUsername) {
|
|
||||||
console.error('Delete modal elements not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUserId.value = userId;
|
|
||||||
deleteUsername.textContent = username;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const modalElement = document.getElementById('deleteUserModal');
|
|
||||||
if (!modalElement) {
|
|
||||||
console.error('Delete modal not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modal = new bootstrap.Modal(modalElement);
|
|
||||||
modal.show();
|
|
||||||
console.log('Delete modal should be shown');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing delete modal:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLogs() {
|
function clearLogs() {
|
||||||
if (confirm('Are you sure you want to clear all server logs? This action cannot be undone.')) {
|
if (confirm('Are you sure you want to clear all server logs? This action cannot be undone.')) {
|
||||||
fetch('/admin/clear_logs', {
|
fetch('/admin/clear_logs', {
|
||||||
@@ -916,30 +881,158 @@ function deleteTask(taskId) {
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('Admin page loaded - initializing...');
|
console.log('Admin page loaded - initializing...');
|
||||||
|
|
||||||
// Add event listeners for edit and delete buttons
|
// Debug: Check how many clickable rows we found
|
||||||
document.querySelectorAll('.edit-user-btn').forEach(button => {
|
const clickableRows = document.querySelectorAll('.clickable-row');
|
||||||
button.addEventListener('click', function() {
|
console.log('Found clickable rows:', clickableRows.length);
|
||||||
|
|
||||||
|
if (clickableRows.length === 0) {
|
||||||
|
console.warn('No clickable rows found! Check if users are loaded and rendered.');
|
||||||
|
// Check if we're on the right tab
|
||||||
|
setTimeout(function() {
|
||||||
|
const activeTab = document.querySelector('.nav-link.active');
|
||||||
|
console.log('Active tab:', activeTab ? activeTab.textContent : 'none');
|
||||||
|
const userTable = document.querySelector('#users table');
|
||||||
|
console.log('User table found:', !!userTable);
|
||||||
|
if (userTable) {
|
||||||
|
const rows = userTable.querySelectorAll('tbody tr');
|
||||||
|
console.log('Total table rows found:', rows.length);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners for expandable user rows
|
||||||
|
clickableRows.forEach((row, index) => {
|
||||||
|
console.log(`Setting up click handler for row ${index}:`, row);
|
||||||
|
|
||||||
|
row.addEventListener('click', function(e) {
|
||||||
|
console.log('Row clicked!', e.target);
|
||||||
|
|
||||||
|
// Don't expand if clicking on a form element
|
||||||
|
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
|
||||||
|
console.log('Clicked on form element, ignoring');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = this.getAttribute('data-user-id');
|
const userId = this.getAttribute('data-user-id');
|
||||||
const username = this.getAttribute('data-username');
|
console.log('User ID:', userId);
|
||||||
const role = this.getAttribute('data-role');
|
|
||||||
const isActive = this.getAttribute('data-active') === 'true';
|
const editRow = document.getElementById(`edit-row-${userId}`);
|
||||||
console.log('Edit button clicked:', userId, username, role, isActive);
|
console.log('Edit row found:', !!editRow);
|
||||||
editUser(userId, username, role, isActive);
|
|
||||||
|
const expandIcon = this.querySelector('.expand-icon');
|
||||||
|
console.log('Expand icon found:', !!expandIcon);
|
||||||
|
|
||||||
|
if (editRow) {
|
||||||
|
if (editRow.style.display === 'none' || editRow.style.display === '') {
|
||||||
|
// Close all other edit rows first
|
||||||
|
document.querySelectorAll('.edit-row').forEach(row => {
|
||||||
|
row.style.display = 'none';
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.expand-icon').forEach(icon => {
|
||||||
|
icon.classList.remove('rotated');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open this edit row
|
||||||
|
editRow.style.display = 'table-row';
|
||||||
|
if (expandIcon) expandIcon.classList.add('rotated');
|
||||||
|
console.log('Opened edit row for user:', userId);
|
||||||
|
} else {
|
||||||
|
// Close this edit row
|
||||||
|
editRow.style.display = 'none';
|
||||||
|
if (expandIcon) expandIcon.classList.remove('rotated');
|
||||||
|
console.log('Closed edit row for user:', userId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Edit row not found for user:', userId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.delete-user-btn').forEach(button => {
|
// Add event listeners for collapse buttons
|
||||||
|
document.querySelectorAll('.collapse-row').forEach(button => {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
const userId = this.getAttribute('data-user-id');
|
const userId = this.getAttribute('data-user-id');
|
||||||
const username = this.getAttribute('data-username');
|
const editRow = document.getElementById(`edit-row-${userId}`);
|
||||||
console.log('Delete button clicked:', userId, username);
|
const userRow = document.querySelector(`.clickable-row[data-user-id="${userId}"]`);
|
||||||
deleteUser(userId, username);
|
const expandIcon = userRow ? userRow.querySelector('.expand-icon') : null;
|
||||||
|
|
||||||
|
if (editRow) {
|
||||||
|
editRow.style.display = 'none';
|
||||||
|
if (expandIcon) expandIcon.classList.remove('rotated');
|
||||||
|
console.log('Collapsed edit row for user:', userId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make functions globally accessible
|
// Setup click handlers for user rows (also call this when tabs change)
|
||||||
window.editUser = editUser;
|
function setupUserRowClickHandlers() {
|
||||||
window.deleteUser = deleteUser;
|
console.log('Setting up user row click handlers...');
|
||||||
|
|
||||||
|
// Remove any existing handlers to avoid duplicates
|
||||||
|
document.querySelectorAll('.clickable-row').forEach(row => {
|
||||||
|
row.replaceWith(row.cloneNode(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add fresh handlers
|
||||||
|
document.querySelectorAll('.clickable-row').forEach((row, index) => {
|
||||||
|
console.log(`Setting up click handler for row ${index}:`, row);
|
||||||
|
|
||||||
|
row.addEventListener('click', function(e) {
|
||||||
|
console.log('Row clicked!', e.target, 'Row:', this);
|
||||||
|
|
||||||
|
// Don't expand if clicking on a form element
|
||||||
|
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
|
||||||
|
console.log('Clicked on form element, ignoring');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = this.getAttribute('data-user-id');
|
||||||
|
console.log('User ID:', userId);
|
||||||
|
|
||||||
|
const editRow = document.getElementById(`edit-row-${userId}`);
|
||||||
|
console.log('Edit row found:', !!editRow);
|
||||||
|
|
||||||
|
const expandIcon = this.querySelector('.expand-icon');
|
||||||
|
console.log('Expand icon found:', !!expandIcon);
|
||||||
|
|
||||||
|
if (editRow) {
|
||||||
|
if (editRow.style.display === 'none' || editRow.style.display === '') {
|
||||||
|
// Close all other edit rows first
|
||||||
|
document.querySelectorAll('.edit-row').forEach(row => {
|
||||||
|
row.style.display = 'none';
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.expand-icon').forEach(icon => {
|
||||||
|
icon.classList.remove('rotated');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open this edit row
|
||||||
|
editRow.style.display = 'table-row';
|
||||||
|
if (expandIcon) expandIcon.classList.add('rotated');
|
||||||
|
console.log('Opened edit row for user:', userId);
|
||||||
|
} else {
|
||||||
|
// Close this edit row
|
||||||
|
editRow.style.display = 'none';
|
||||||
|
if (expandIcon) expandIcon.classList.remove('rotated');
|
||||||
|
console.log('Closed edit row for user:', userId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Edit row not found for user:', userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call setup initially
|
||||||
|
setupUserRowClickHandlers();
|
||||||
|
|
||||||
|
// Re-setup handlers when users tab is shown
|
||||||
|
const usersTab = document.getElementById('users-tab');
|
||||||
|
if (usersTab) {
|
||||||
|
usersTab.addEventListener('shown.bs.tab', function() {
|
||||||
|
console.log('Users tab shown, re-setting up click handlers...');
|
||||||
|
setTimeout(setupUserRowClickHandlers, 100); // Small delay to ensure content is rendered
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check for successful user operations and handle refresh
|
// Check for successful user operations and handle refresh
|
||||||
checkForUserOperationSuccess();
|
checkForUserOperationSuccess();
|
||||||
@@ -972,6 +1065,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log('Maintenance tab content found:', !!maintenanceContent);
|
console.log('Maintenance tab content found:', !!maintenanceContent);
|
||||||
console.log('Scheduler tab content found:', !!schedulerContent);
|
console.log('Scheduler tab content found:', !!schedulerContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Frequency change handler for schedule preview
|
||||||
|
document.getElementById('quickFrequency').addEventListener('change', function() {
|
||||||
const frequency = this.value;
|
const frequency = this.value;
|
||||||
const timeInput = document.getElementById('quickTime');
|
const timeInput = document.getElementById('quickTime');
|
||||||
const previewText = document.getElementById('schedulePreview');
|
const previewText = document.getElementById('schedulePreview');
|
||||||
@@ -1081,6 +1177,6 @@ function checkForUserOperationSuccess() {
|
|||||||
}, 2000); // Wait 2 seconds to let user read the success message
|
}, 2000); // Wait 2 seconds to let user read the success message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -2,5 +2,3 @@
|
|||||||
# https://curl.se/docs/http-cookies.html
|
# https://curl.se/docs/http-cookies.html
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
#HttpOnly_localhost FALSE / FALSE 0 session .eJwlzj0OwjAMQOG7ZGZw4jixe5nKfxGsLZ0Qd6cSw1ufvk_Z15Hns2zv48pH2V9RtmKAo6OyUqXUrCBjpNEUCTBpbHVpcmgaGCii3S0koxUzQYJm12bIHqgLPQktrI8ejaP5PSXEMbuzAU83ndFFbWlzl0ia5YZcZx5_TS3fHxMgMKY.aHenRQ.0uMtLODE40iqcA-M96_kh2ZMGTQ
|
|
||||||
#HttpOnly_127.0.0.1 FALSE / FALSE 0 session .eJydUdtqwzAM_ZWg5zAcX2I7_7GntQTJktdAdiF2YKX032fKHgctfRCSQEfnHOkCc16xnKTA9HaBrrYEZU9JSoEeXots3QGqlLq3chZeqvABuv2bsVXd32je1_X8Asdrf3eHbmiWVZ5DN3Da5Enq-eOLl7w8ZoDx8122fxXg2iTwuZOfpdTyIPfd0x379otNygmmuu3SuoVhAlJmtAYDusEJyqDiOAo5HyMrijrQkFECo5AihcZQi2wcucxeVGTnLWoyIbHBbJI4Q0x2tKwD69SWOmNGb1MgFXwi9GwjUkadUmRxvlmZbwZuaga4_gKYh7s4.aHeshg.sXfRf_q0gHSntT7w8BMVXvOARAs
|
|
||||||
|
|||||||
20
main.py
20
main.py
@@ -37,7 +37,7 @@ def create_default_admin():
|
|||||||
admin_username = os.environ.get('ADMIN_USER', 'admin')
|
admin_username = os.environ.get('ADMIN_USER', 'admin')
|
||||||
admin_password = os.environ.get('ADMIN_PASSWORD', 'admin123')
|
admin_password = os.environ.get('ADMIN_PASSWORD', 'admin123')
|
||||||
|
|
||||||
# Check if admin user already exists
|
# Create default admin user
|
||||||
if not User.query.filter_by(username=admin_username).first():
|
if not User.query.filter_by(username=admin_username).first():
|
||||||
hashed_password = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
hashed_password = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
||||||
admin_user = User(
|
admin_user = User(
|
||||||
@@ -51,6 +51,24 @@ def create_default_admin():
|
|||||||
log_action(f"Default admin user '{admin_username}' created")
|
log_action(f"Default admin user '{admin_username}' created")
|
||||||
else:
|
else:
|
||||||
print(f"Admin user '{admin_username}' already exists")
|
print(f"Admin user '{admin_username}' already exists")
|
||||||
|
|
||||||
|
# Create default super admin user
|
||||||
|
sadmin_username = os.environ.get('SADMIN_USER', 'sadmin')
|
||||||
|
sadmin_password = os.environ.get('SADMIN_PASSWORD', 'sadmin123')
|
||||||
|
|
||||||
|
if not User.query.filter_by(username=sadmin_username).first():
|
||||||
|
hashed_password = bcrypt.generate_password_hash(sadmin_password).decode('utf-8')
|
||||||
|
sadmin_user = User(
|
||||||
|
username=sadmin_username,
|
||||||
|
password=hashed_password,
|
||||||
|
role='sadmin'
|
||||||
|
)
|
||||||
|
db.session.add(sadmin_user)
|
||||||
|
db.session.commit()
|
||||||
|
print(f"Default super admin user '{sadmin_username}' created with password '{sadmin_password}'")
|
||||||
|
log_action(f"Default super admin user '{sadmin_username}' created")
|
||||||
|
else:
|
||||||
|
print(f"Super admin user '{sadmin_username}' already exists")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user