updated settings
This commit is contained in:
@@ -30,6 +30,16 @@ class User(db.Model, UserMixin):
|
||||
"""Check if user has admin role"""
|
||||
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
|
||||
def is_active(self):
|
||||
"""Required by Flask-Login"""
|
||||
|
||||
@@ -13,15 +13,25 @@ import os
|
||||
bp = Blueprint('admin', __name__)
|
||||
|
||||
def admin_required(f):
|
||||
"""Decorator to require admin role"""
|
||||
"""Decorator to require admin or super admin role"""
|
||||
@wraps(f)
|
||||
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')
|
||||
return redirect(url_for('dashboard.index'))
|
||||
return f(*args, **kwargs)
|
||||
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('/')
|
||||
@login_required
|
||||
@admin_required
|
||||
@@ -64,10 +74,15 @@ def create_user():
|
||||
flash('Password must be at least 6 characters long.', 'danger')
|
||||
return redirect(url_for('admin.index'))
|
||||
|
||||
if role not in ['user', 'admin']:
|
||||
if role not in ['user', 'admin', 'sadmin']:
|
||||
flash('Invalid role specified.', 'danger')
|
||||
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
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash(f'User "{username}" already exists.', 'danger')
|
||||
@@ -91,7 +106,7 @@ def create_user():
|
||||
|
||||
@bp.route('/delete_user', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@super_admin_required
|
||||
def delete_user():
|
||||
"""Delete a user using POST form data"""
|
||||
user_id = request.form.get('user_id')
|
||||
@@ -107,6 +122,11 @@ def delete_user():
|
||||
user = User.query.get_or_404(user_id)
|
||||
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:
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
@@ -122,9 +142,9 @@ def delete_user():
|
||||
|
||||
@bp.route('/change_role/<int:user_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@super_admin_required
|
||||
def change_role(user_id):
|
||||
"""Change user role"""
|
||||
"""Change user role - restricted to super admin"""
|
||||
# Prevent changing own role
|
||||
if user_id == current_user.id:
|
||||
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)
|
||||
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')
|
||||
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:
|
||||
old_role = user.role
|
||||
user.role = new_role
|
||||
@@ -423,10 +453,20 @@ def edit_user():
|
||||
flash('Username cannot be empty.', 'danger')
|
||||
return redirect(url_for('admin.index'))
|
||||
|
||||
if role not in ['user', 'admin']:
|
||||
if role not in ['user', 'admin', 'sadmin']:
|
||||
flash('Invalid role specified.', 'danger')
|
||||
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
|
||||
if username != user.username:
|
||||
existing_user = User.query.filter_by(username=username).first()
|
||||
|
||||
@@ -2,12 +2,47 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +160,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% 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>
|
||||
<strong>{{ user.username }}</strong>
|
||||
{% if user.username == current_user.username %}
|
||||
@@ -133,8 +169,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if user.role == 'admin' %}
|
||||
<span class="badge bg-danger">Admin</span>
|
||||
{% if user.role == 'sadmin' %}
|
||||
<span class="badge bg-danger">Super Admin</span>
|
||||
{% elif user.role == 'admin' %}
|
||||
<span class="badge bg-warning">Admin</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary">User</span>
|
||||
{% endif %}
|
||||
@@ -149,28 +187,86 @@
|
||||
<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>
|
||||
<div class="btn-group btn-group-sm">
|
||||
{% if user.username != current_user.username %}
|
||||
<button type="button" class="btn btn-outline-warning edit-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-active="{{ user.is_active_user|tojson }}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
{% if user.username != current_user.username and user.role != 'sadmin' %}
|
||||
<i class="bi bi-chevron-down expand-icon"></i>
|
||||
<small class="text-muted">Click to edit</small>
|
||||
{% elif user.role == 'sadmin' %}
|
||||
<span class="badge bg-danger">Protected</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Current User</span>
|
||||
{% 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 type="button" class="btn btn-outline-danger delete-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
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 %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -493,73 +589,6 @@
|
||||
</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 -->
|
||||
<div class="modal fade" id="quickScheduleModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
@@ -614,70 +643,6 @@
|
||||
</div>
|
||||
|
||||
<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() {
|
||||
if (confirm('Are you sure you want to clear all server logs? This action cannot be undone.')) {
|
||||
fetch('/admin/clear_logs', {
|
||||
@@ -916,30 +881,158 @@ function deleteTask(taskId) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Admin page loaded - initializing...');
|
||||
|
||||
// Add event listeners for edit and delete buttons
|
||||
document.querySelectorAll('.edit-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Debug: Check how many clickable rows we found
|
||||
const clickableRows = document.querySelectorAll('.clickable-row');
|
||||
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 username = this.getAttribute('data-username');
|
||||
const role = this.getAttribute('data-role');
|
||||
const isActive = this.getAttribute('data-active') === 'true';
|
||||
console.log('Edit button clicked:', userId, username, role, isActive);
|
||||
editUser(userId, username, role, isActive);
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-user-btn').forEach(button => {
|
||||
// Add event listeners for collapse buttons
|
||||
document.querySelectorAll('.collapse-row').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.getAttribute('data-user-id');
|
||||
const username = this.getAttribute('data-username');
|
||||
console.log('Delete button clicked:', userId, username);
|
||||
deleteUser(userId, username);
|
||||
const editRow = document.getElementById(`edit-row-${userId}`);
|
||||
const userRow = document.querySelector(`.clickable-row[data-user-id="${userId}"]`);
|
||||
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
|
||||
window.editUser = editUser;
|
||||
window.deleteUser = deleteUser;
|
||||
|
||||
// Setup click handlers for user rows (also call this when tabs change)
|
||||
function setupUserRowClickHandlers() {
|
||||
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
|
||||
checkForUserOperationSuccess();
|
||||
@@ -972,6 +1065,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Maintenance tab content found:', !!maintenanceContent);
|
||||
console.log('Scheduler tab content found:', !!schedulerContent);
|
||||
});
|
||||
|
||||
// Frequency change handler for schedule preview
|
||||
document.getElementById('quickFrequency').addEventListener('change', function() {
|
||||
const frequency = this.value;
|
||||
const timeInput = document.getElementById('quickTime');
|
||||
const previewText = document.getElementById('schedulePreview');
|
||||
@@ -1081,6 +1177,6 @@ function checkForUserOperationSuccess() {
|
||||
}, 2000); // Wait 2 seconds to let user read the success message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,5 +2,3 @@
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# 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_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():
|
||||
hashed_password = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
||||
admin_user = User(
|
||||
@@ -51,6 +51,24 @@ def create_default_admin():
|
||||
log_action(f"Default admin user '{admin_username}' created")
|
||||
else:
|
||||
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__':
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user