1087 lines
50 KiB
HTML
1087 lines
50 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Admin Panel - SKE Digital Signage{% 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>
|
|
<p class="text-muted">System administration and user management</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-primary">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">Total Users</h5>
|
|
<h2>{{ users|length }}</h2>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-people" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-success">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">Active Users</h5>
|
|
<h2>{{ users|selectattr('is_active_user')|list|length }}</h2>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-person-check" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-warning">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">Admin Users</h5>
|
|
<h2>{{ users|selectattr('role', 'equalto', 'admin')|list|length }}</h2>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-shield-check" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-info">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">Regular Users</h5>
|
|
<h2>{{ users|selectattr('role', 'equalto', 'user')|list|length }}</h2>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-person" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation Tabs -->
|
|
<ul class="nav nav-tabs mb-4" id="adminTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="users-tab" data-bs-toggle="tab" data-bs-target="#users" type="button" role="tab">
|
|
<i class="bi bi-people"></i> User Management
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="maintenance-tab" data-bs-toggle="tab" data-bs-target="#maintenance" type="button" role="tab">
|
|
<i class="bi bi-tools"></i> System Maintenance
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="scheduler-tab" data-bs-toggle="tab" data-bs-target="#scheduler" type="button" role="tab">
|
|
<i class="bi bi-clock"></i> Task Scheduler
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content" id="adminTabContent">
|
|
<!-- User Management Tab -->
|
|
<div class="tab-pane fade show active" id="users" role="tabpanel">
|
|
<div class="row">
|
|
<!-- User Management -->
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5><i class="bi bi-people"></i> User Management</h5>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
|
<i class="bi bi-plus"></i> Add User
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if users %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Role</th>
|
|
<th>Status</th>
|
|
<th>Created</th>
|
|
<th>Last Login</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for user in users %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ user.username }}</strong>
|
|
{% if user.username == current_user.username %}
|
|
<span class="badge bg-secondary">You</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if user.role == 'admin' %}
|
|
<span class="badge bg-danger">Admin</span>
|
|
{% else %}
|
|
<span class="badge bg-primary">User</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if user.is_active_user %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
{% endif %}
|
|
</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>
|
|
<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>
|
|
</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 %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4">
|
|
<i class="bi bi-people text-muted" style="font-size: 3rem;"></i>
|
|
<h5 class="text-muted mt-2">No Users Found</h5>
|
|
<p class="text-muted">Create your first user to get started.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Info -->
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-info-circle"></i> System Assets</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<h6>Logo Asset</h6>
|
|
{% if logo_exists %}
|
|
<span class="badge bg-success">Available</span>
|
|
<div class="mt-2">
|
|
<img src="{{ url_for('static', filename='assets/logo.png') }}" alt="Logo" class="img-thumbnail" style="max-height: 100px;">
|
|
</div>
|
|
{% else %}
|
|
<span class="badge bg-warning">Missing</span>
|
|
<p class="text-muted small mt-1">Upload logo.png to static/assets/</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6>Login Picture</h6>
|
|
{% if login_picture_exists %}
|
|
<span class="badge bg-success">Available</span>
|
|
<div class="mt-2">
|
|
<img src="{{ url_for('static', filename='assets/login_picture.png') }}" alt="Login Picture" class="img-thumbnail" style="max-height: 100px;">
|
|
</div>
|
|
{% else %}
|
|
<span class="badge bg-warning">Missing</span>
|
|
<p class="text-muted small mt-1">Upload login_picture.png to static/assets/</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-tools"></i> Quick Actions</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{{ url_for('dashboard.index') }}" class="btn btn-outline-primary">
|
|
<i class="bi bi-speedometer2"></i> Dashboard
|
|
</a>
|
|
<a href="{{ url_for('player.index') }}" class="btn btn-outline-info">
|
|
<i class="bi bi-display"></i> Manage Players
|
|
</a>
|
|
<a href="{{ url_for('content.upload') }}" class="btn btn-outline-success">
|
|
<i class="bi bi-upload"></i> Upload Content
|
|
</a>
|
|
<button type="button" class="btn btn-outline-warning" onclick="clearLogs()">
|
|
<i class="bi bi-trash"></i> Clear Logs
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Maintenance Tab -->
|
|
<div class="tab-pane fade" id="maintenance" role="tabpanel">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-trash"></i> File Management</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-4">
|
|
<h6><i class="bi bi-trash"></i> Clean Unused Files</h6>
|
|
<p class="text-muted small">Remove media files from uploads folder that are not referenced by any players. This helps free up storage space by removing orphaned files.</p>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-warning" onclick="cleanUnusedFiles()">
|
|
<i class="bi bi-play-circle"></i> Run Now
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="scheduleCleanupTask()">
|
|
<i class="bi bi-clock"></i> Schedule
|
|
</button>
|
|
</div>
|
|
<div id="cleanupStatus" class="mt-2"></div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<h6><i class="bi bi-journal-x"></i> Clear Server Logs</h6>
|
|
<p class="text-muted small">Remove all server log entries from the database to free up space.</p>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-outline-warning" onclick="clearLogs()">
|
|
<i class="bi bi-play-circle"></i> Clear Now
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="scheduleLogClearTask()">
|
|
<i class="bi bi-clock"></i> Schedule
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6><i class="bi bi-database"></i> Database Maintenance</h6>
|
|
<p class="text-muted small">Optimize database performance and clean up orphaned records.</p>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-outline-info" onclick="optimizeDatabase()">
|
|
<i class="bi bi-play-circle"></i> Optimize Now
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="scheduleDatabaseTask()">
|
|
<i class="bi bi-clock"></i> Schedule
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-info-circle"></i> Maintenance Status</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="maintenanceStatus">
|
|
<div class="alert alert-info">
|
|
<i class="bi bi-info-circle"></i> Ready to perform maintenance tasks.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-upload"></i> Asset Upload</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="{{ url_for('admin.upload_assets') }}" enctype="multipart/form-data">
|
|
<div class="mb-3">
|
|
<label for="logo" class="form-label">Logo (PNG format)</label>
|
|
<input type="file" class="form-control" id="logo" name="logo" accept=".png">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="login_picture" class="form-label">Login Picture (PNG format)</label>
|
|
<input type="file" class="form-control" id="login_picture" name="login_picture" accept=".png">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-upload"></i> Upload Assets
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Task Scheduler Tab -->
|
|
<div class="tab-pane fade" id="scheduler" role="tabpanel">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5><i class="bi bi-clock"></i> Scheduled Tasks</h5>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createTaskModal">
|
|
<i class="bi bi-plus"></i> Add Task
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="scheduledTasks">
|
|
<!-- Tasks will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="bi bi-gear"></i> Quick Setup</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<h6>Daily File Cleanup</h6>
|
|
<p class="text-muted small">Automatically clean unused files daily at 1:00 AM</p>
|
|
<form method="POST" action="{{ url_for('admin.create_scheduled_task') }}">
|
|
<input type="hidden" name="task_type" value="cleanup_files">
|
|
<input type="hidden" name="schedule" value="0 1 * * *">
|
|
<input type="hidden" name="enabled" value="true">
|
|
<button type="submit" class="btn btn-success btn-sm w-100">
|
|
<i class="bi bi-clock"></i> Enable Daily Cleanup
|
|
</button>
|
|
</form>
|
|
|
|
<hr>
|
|
|
|
<h6>Custom Schedule</h6>
|
|
<form method="POST" action="{{ url_for('admin.create_scheduled_task') }}">
|
|
<div class="mb-2">
|
|
<label class="form-label">Task Type</label>
|
|
<select class="form-select form-select-sm" name="task_type" required>
|
|
<option value="cleanup_files">Clean Unused Files</option>
|
|
<option value="clear_logs">Clear Server Logs</option>
|
|
<option value="optimize_db">Optimize Database</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label">Time</label>
|
|
<input type="time" class="form-control form-control-sm" name="time" value="01:00" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Frequency</label>
|
|
<select class="form-select form-select-sm" name="frequency" required>
|
|
<option value="daily">Daily</option>
|
|
<option value="weekly">Weekly</option>
|
|
<option value="monthly">Monthly</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-sm w-100">
|
|
<i class="bi bi-plus"></i> Create Task
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Scheduled Task Modal -->
|
|
<div class="modal fade" id="createTaskModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create Scheduled Task</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('admin.create_scheduled_task') }}">
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="taskName" class="form-label">Task Name *</label>
|
|
<input type="text" class="form-control" id="taskName" name="name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="taskType" class="form-label">Task Type *</label>
|
|
<select class="form-select" id="taskType" name="task_type" required>
|
|
<option value="cleanup_files">Clean Unused Files</option>
|
|
<option value="clear_logs">Clear Server Logs</option>
|
|
<option value="optimize_db">Optimize Database</option>
|
|
<option value="backup_db">Backup Database</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="cronExpression" class="form-label">Cron Expression *</label>
|
|
<input type="text" class="form-control" id="cronExpression" name="schedule"
|
|
placeholder="0 1 * * * (daily at 1:00 AM)" required>
|
|
<div class="form-text">
|
|
Examples: <br>
|
|
• <code>0 1 * * *</code> - Daily at 1:00 AM<br>
|
|
• <code>0 0 * * 0</code> - Weekly on Sunday at midnight<br>
|
|
• <code>0 2 1 * *</code> - Monthly on 1st at 2:00 AM
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="taskEnabled" name="enabled" checked>
|
|
<label class="form-check-label" for="taskEnabled">
|
|
Enable Task
|
|
</label>
|
|
</div>
|
|
</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">Create Task</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create User Modal -->
|
|
<div class="modal fade" id="createUserModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create New User</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('admin.create_user') }}">
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="username" class="form-label">Username *</label>
|
|
<input type="text" class="form-control" id="username" name="username" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="password" class="form-label">Password *</label>
|
|
<input type="password" class="form-control" id="password" name="password" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="role" class="form-label">Role</label>
|
|
<select class="form-select" id="role" name="role">
|
|
<option value="user">User</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
</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">Create User</button>
|
|
</div>
|
|
</form>
|
|
</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 -->
|
|
<div class="modal fade" id="quickScheduleModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="quickScheduleTitle">Schedule Maintenance Task</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('admin.create_scheduled_task') }}" id="quickScheduleForm">
|
|
<input type="hidden" id="quickTaskType" name="task_type">
|
|
<input type="hidden" name="enabled" value="true">
|
|
<div class="modal-body">
|
|
<div class="alert alert-info">
|
|
<i class="bi bi-info-circle"></i> <span id="quickScheduleDescription"></span>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="quickTaskName" class="form-label">Task Name</label>
|
|
<input type="text" class="form-control" id="quickTaskName" name="name" required>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label for="quickTime" class="form-label">Time</label>
|
|
<input type="time" class="form-control" id="quickTime" name="time" value="01:00" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="quickFrequency" class="form-label">Frequency</label>
|
|
<select class="form-select" id="quickFrequency" name="frequency" required>
|
|
<option value="daily">Daily</option>
|
|
<option value="weekly">Weekly (Sunday)</option>
|
|
<option value="monthly">Monthly (1st day)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<small class="text-muted">
|
|
<strong>Preview:</strong> <span id="schedulePreview">Daily at 01:00</span>
|
|
</small>
|
|
</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">
|
|
<i class="bi bi-clock"></i> Schedule Task
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</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', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showMaintenanceStatus('success', 'Server logs cleared successfully');
|
|
} else {
|
|
showMaintenanceStatus('danger', 'Error clearing logs: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showMaintenanceStatus('danger', 'Error clearing logs');
|
|
});
|
|
}
|
|
}
|
|
|
|
function cleanUnusedFiles() {
|
|
if (confirm('Are you sure you want to clean unused files? This will remove files from uploads folder that are not referenced in the database.')) {
|
|
showCleanupStatus('info', 'Scanning for unused files...');
|
|
showMaintenanceStatus('info', 'Cleaning unused files...');
|
|
|
|
fetch('/admin/clean_unused_files', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const message = `Successfully cleaned ${data.deleted_count} unused files`;
|
|
showCleanupStatus('success', message);
|
|
showMaintenanceStatus('success', message);
|
|
} else {
|
|
const errorMsg = 'Error cleaning files: ' + data.message;
|
|
showCleanupStatus('danger', errorMsg);
|
|
showMaintenanceStatus('danger', errorMsg);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
const errorMsg = 'Error cleaning files';
|
|
showCleanupStatus('danger', errorMsg);
|
|
showMaintenanceStatus('danger', errorMsg);
|
|
});
|
|
}
|
|
}
|
|
|
|
function showCleanupStatus(type, message) {
|
|
const statusDiv = document.getElementById('cleanupStatus');
|
|
if (statusDiv) {
|
|
const alertClass = type === 'success' ? 'alert-success' :
|
|
type === 'danger' ? 'alert-danger' :
|
|
type === 'warning' ? 'alert-warning' : 'alert-info';
|
|
|
|
const icon = type === 'success' ? 'check-circle' :
|
|
type === 'danger' ? 'exclamation-triangle' :
|
|
type === 'warning' ? 'exclamation-triangle' : 'info-circle';
|
|
|
|
statusDiv.innerHTML = `
|
|
<div class="alert ${alertClass} alert-sm">
|
|
<i class="bi bi-${icon}"></i> ${message}
|
|
</div>
|
|
`;
|
|
|
|
// Auto-hide success messages after 5 seconds
|
|
if (type === 'success') {
|
|
setTimeout(() => {
|
|
statusDiv.innerHTML = '';
|
|
}, 5000);
|
|
}
|
|
}
|
|
}
|
|
|
|
function optimizeDatabase() {
|
|
if (confirm('Are you sure you want to optimize the database? This may take a few moments.')) {
|
|
showMaintenanceStatus('info', 'Optimizing database...');
|
|
|
|
fetch('/admin/optimize_database', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showMaintenanceStatus('success', 'Database optimized successfully');
|
|
} else {
|
|
showMaintenanceStatus('danger', 'Error optimizing database: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showMaintenanceStatus('danger', 'Error optimizing database');
|
|
});
|
|
}
|
|
}
|
|
|
|
function showMaintenanceStatus(type, message) {
|
|
const statusDiv = document.getElementById('maintenanceStatus');
|
|
const alertClass = type === 'success' ? 'alert-success' :
|
|
type === 'danger' ? 'alert-danger' :
|
|
type === 'warning' ? 'alert-warning' : 'alert-info';
|
|
|
|
const icon = type === 'success' ? 'check-circle' :
|
|
type === 'danger' ? 'exclamation-triangle' :
|
|
type === 'warning' ? 'exclamation-triangle' : 'info-circle';
|
|
|
|
statusDiv.innerHTML = `
|
|
<div class="alert ${alertClass}">
|
|
<i class="bi bi-${icon}"></i> ${message}
|
|
</div>
|
|
`;
|
|
|
|
// Auto-hide success messages after 5 seconds
|
|
if (type === 'success') {
|
|
setTimeout(() => {
|
|
statusDiv.innerHTML = `
|
|
<div class="alert alert-info">
|
|
<i class="bi bi-info-circle"></i> Ready to perform maintenance tasks.
|
|
</div>
|
|
`;
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
function loadScheduledTasks() {
|
|
fetch('/admin/scheduled_tasks')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const tasksDiv = document.getElementById('scheduledTasks');
|
|
|
|
if (data.tasks && data.tasks.length > 0) {
|
|
let tasksHtml = '<div class="list-group">';
|
|
|
|
data.tasks.forEach(task => {
|
|
const statusBadge = task.enabled ?
|
|
'<span class="badge bg-success">Enabled</span>' :
|
|
'<span class="badge bg-secondary">Disabled</span>';
|
|
|
|
tasksHtml += `
|
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-1">${task.name}</h6>
|
|
<p class="mb-1 text-muted">${task.task_type} - ${task.schedule}</p>
|
|
<small class="text-muted">Next run: ${task.next_run || 'Not scheduled'}</small>
|
|
</div>
|
|
<div>
|
|
${statusBadge}
|
|
<button class="btn btn-sm btn-outline-warning ms-2" onclick="toggleTask(${task.id})">
|
|
<i class="bi bi-${task.enabled ? 'pause' : 'play'}"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger ms-1" onclick="deleteTask(${task.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
tasksHtml += '</div>';
|
|
tasksDiv.innerHTML = tasksHtml;
|
|
} else {
|
|
tasksDiv.innerHTML = `
|
|
<div class="text-center py-4">
|
|
<i class="bi bi-clock text-muted" style="font-size: 3rem;"></i>
|
|
<h5 class="text-muted mt-2">No Scheduled Tasks</h5>
|
|
<p class="text-muted">Create your first scheduled task to automate maintenance.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading tasks:', error);
|
|
document.getElementById('scheduledTasks').innerHTML = `
|
|
<div class="alert alert-danger">
|
|
<i class="bi bi-exclamation-triangle"></i> Error loading scheduled tasks.
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function toggleTask(taskId) {
|
|
fetch(`/admin/toggle_task/${taskId}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
loadScheduledTasks(); // Reload tasks
|
|
} else {
|
|
alert('Error toggling task: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error toggling task');
|
|
});
|
|
}
|
|
|
|
function deleteTask(taskId) {
|
|
if (confirm('Are you sure you want to delete this scheduled task?')) {
|
|
fetch(`/admin/delete_task/${taskId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
loadScheduledTasks(); // Reload tasks
|
|
} else {
|
|
alert('Error deleting task: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error deleting task');
|
|
});
|
|
}
|
|
}
|
|
|
|
// Consolidated DOMContentLoaded event
|
|
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() {
|
|
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);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.delete-user-btn').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);
|
|
});
|
|
});
|
|
|
|
// Make functions globally accessible
|
|
window.editUser = editUser;
|
|
window.deleteUser = deleteUser;
|
|
|
|
// Check for successful user operations and handle refresh
|
|
checkForUserOperationSuccess();
|
|
|
|
// Load scheduled tasks immediately on page load
|
|
loadScheduledTasks();
|
|
|
|
// Load scheduled tasks when scheduler tab is shown
|
|
const schedulerTab = document.getElementById('scheduler-tab');
|
|
if (schedulerTab) {
|
|
schedulerTab.addEventListener('shown.bs.tab', function() {
|
|
console.log('Loading scheduled tasks...');
|
|
loadScheduledTasks();
|
|
});
|
|
}
|
|
|
|
// Add event listeners for the schedule preview
|
|
const timeInput = document.getElementById('quickTime');
|
|
const frequencySelect = document.getElementById('quickFrequency');
|
|
|
|
if (timeInput && frequencySelect) {
|
|
timeInput.addEventListener('change', updateSchedulePreview);
|
|
frequencySelect.addEventListener('change', updateSchedulePreview);
|
|
}
|
|
|
|
// Debug: Check if tab content is visible
|
|
const maintenanceContent = document.getElementById('maintenance');
|
|
const schedulerContent = document.getElementById('scheduler');
|
|
|
|
console.log('Maintenance tab content found:', !!maintenanceContent);
|
|
console.log('Scheduler tab content found:', !!schedulerContent);
|
|
});
|
|
const frequency = this.value;
|
|
const timeInput = document.getElementById('quickTime');
|
|
const previewText = document.getElementById('schedulePreview');
|
|
|
|
let defaultTime = '01:00';
|
|
let description = '';
|
|
|
|
switch (frequency) {
|
|
case 'daily':
|
|
description = 'Runs daily at the specified time.';
|
|
break;
|
|
case 'weekly':
|
|
description = 'Runs weekly on Sunday at the specified time.';
|
|
defaultTime = '01:00';
|
|
break;
|
|
case 'monthly':
|
|
description = 'Runs monthly on the 1st day at the specified time.';
|
|
defaultTime = '01:00';
|
|
break;
|
|
}
|
|
|
|
timeInput.value = defaultTime;
|
|
previewText.textContent = description;
|
|
});
|
|
|
|
// Open quick schedule modal
|
|
function openQuickScheduleModal(taskType) {
|
|
const title = taskType === 'cleanup_files' ? 'Quick Schedule: Daily File Cleanup' :
|
|
taskType === 'clear_logs' ? 'Quick Schedule: Weekly Log Clearance' :
|
|
'Quick Schedule: Monthly Database Optimization';
|
|
|
|
const description = taskType === 'cleanup_files' ? 'This task will clean unused files daily at 1:00 AM.' :
|
|
taskType === 'clear_logs' ? 'This task will clear server logs weekly on Sunday at 1:00 AM.' :
|
|
'This task will optimize the database monthly on the 1st day at 1:00 AM.';
|
|
|
|
document.getElementById('quickScheduleTitle').textContent = title;
|
|
document.getElementById('quickScheduleDescription').innerHTML = `<i class="bi bi-info-circle"></i> ${description}`;
|
|
document.getElementById('quickTaskType').value = taskType;
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('quickScheduleModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function scheduleCleanupTask() {
|
|
showQuickScheduleModal('cleanup_files', 'Clean Unused Files', 'Automatically remove unused media files from uploads folder');
|
|
}
|
|
|
|
function scheduleLogClearTask() {
|
|
showQuickScheduleModal('clear_logs', 'Clear Server Logs', 'Automatically clear server log entries to free up database space');
|
|
}
|
|
|
|
function scheduleDatabaseTask() {
|
|
showQuickScheduleModal('optimize_db', 'Optimize Database', 'Automatically optimize database performance and clean up orphaned records');
|
|
}
|
|
|
|
function showQuickScheduleModal(taskType, taskName, description) {
|
|
document.getElementById('quickTaskType').value = taskType;
|
|
document.getElementById('quickTaskName').value = taskName;
|
|
document.getElementById('quickScheduleTitle').textContent = `Schedule ${taskName}`;
|
|
document.getElementById('quickScheduleDescription').textContent = description;
|
|
|
|
// Update preview
|
|
updateSchedulePreview();
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('quickScheduleModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function updateSchedulePreview() {
|
|
const time = document.getElementById('quickTime').value;
|
|
const frequency = document.getElementById('quickFrequency').value;
|
|
const preview = document.getElementById('schedulePreview');
|
|
|
|
let frequencyText = frequency.charAt(0).toUpperCase() + frequency.slice(1);
|
|
if (frequency === 'weekly') {
|
|
frequencyText = 'Weekly (Sunday)';
|
|
} else if (frequency === 'monthly') {
|
|
frequencyText = 'Monthly (1st day)';
|
|
}
|
|
|
|
preview.textContent = `${frequencyText} at ${time}`;
|
|
}
|
|
|
|
// Check for flash messages indicating user operations and refresh if needed
|
|
function checkForUserOperationSuccess() {
|
|
// Check if there are any flash messages indicating successful user operations
|
|
const flashMessages = document.querySelectorAll('.alert');
|
|
let shouldRefresh = false;
|
|
|
|
flashMessages.forEach(function(alert) {
|
|
const text = alert.textContent.toLowerCase();
|
|
if (text.includes('created successfully') ||
|
|
text.includes('updated successfully') ||
|
|
text.includes('deleted successfully')) {
|
|
shouldRefresh = true;
|
|
}
|
|
});
|
|
|
|
// If we just completed a user operation, we might need to refresh to show updated data
|
|
if (shouldRefresh && window.performance && window.performance.navigation.type === 1) {
|
|
// This is a refresh after a form submission, ensure we show the latest data
|
|
setTimeout(function() {
|
|
if (document.querySelector('.nav-link.active').getAttribute('data-bs-target') === '#users') {
|
|
// We're on the users tab, reload to show updated user list
|
|
window.location.reload();
|
|
}
|
|
}, 2000); // Wait 2 seconds to let user read the success message
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|