298 lines
9.0 KiB
HTML
298 lines
9.0 KiB
HTML
{% extends "admin/base_site.html" %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}{{ title }} - {{ site_title|default:_('Django site admin') }}{% endblock %}
|
|
|
|
{% block extrastyle %}
|
|
{{ block.super }}
|
|
<style>
|
|
.sync-stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.stat-card.success { border-left: 4px solid #28a745; }
|
|
.stat-card.danger { border-left: 4px solid #dc3545; }
|
|
.stat-card.warning { border-left: 4px solid #ffc107; }
|
|
.stat-card.info { border-left: 4px solid #17a2b8; }
|
|
|
|
.recent-tasks {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.recent-tasks h3 {
|
|
margin: 0;
|
|
padding: 1rem;
|
|
background: #f8f9fa;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.task-item {
|
|
padding: 1rem;
|
|
border-bottom: 1px solid #eee;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.task-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.task-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.task-name {
|
|
font-weight: bold;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.task-meta {
|
|
font-size: 0.85rem;
|
|
color: #666;
|
|
}
|
|
|
|
.task-status {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 12px;
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.task-status.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.task-status.failed {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.task-status.pending {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
}
|
|
|
|
.refresh-btn {
|
|
background: #007cba;
|
|
color: white;
|
|
border: none;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.refresh-btn:hover {
|
|
background: #005a87;
|
|
}
|
|
|
|
.auto-refresh {
|
|
margin-left: 1rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.chart-container {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div id="content-main">
|
|
<div class="module">
|
|
<h1>{{ title }}</h1>
|
|
|
|
<!-- Auto-refresh controls -->
|
|
<div style="margin-bottom: 1rem;">
|
|
<button class="refresh-btn" onclick="refreshStats()">
|
|
<i class="fas fa-sync-alt"></i> Refresh Now
|
|
</button>
|
|
<div class="auto-refresh">
|
|
<input type="checkbox" id="autoRefresh" checked>
|
|
<label for="autoRefresh">Auto-refresh every 30 seconds</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Grid -->
|
|
<div class="sync-stats-grid">
|
|
<div class="stat-card info">
|
|
<div class="stat-number">{{ total_tasks }}</div>
|
|
<div class="stat-label">Total Sync Tasks</div>
|
|
</div>
|
|
|
|
<div class="stat-card success">
|
|
<div class="stat-number">{{ successful_tasks }}</div>
|
|
<div class="stat-label">Successful Tasks</div>
|
|
</div>
|
|
|
|
<div class="stat-card danger">
|
|
<div class="stat-number">{{ failed_tasks }}</div>
|
|
<div class="stat-label">Failed Tasks</div>
|
|
</div>
|
|
|
|
<div class="stat-card warning">
|
|
<div class="stat-number">{{ pending_tasks }}</div>
|
|
<div class="stat-label">Pending Tasks</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Rate Charts -->
|
|
<div class="chart-container">
|
|
<h3>Success Rates</h3>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
|
<div>
|
|
<h4>Overall Success Rate</h4>
|
|
<div style="font-size: 2rem; font-weight: bold; color: {% if success_rate > 80 %}#28a745{% elif success_rate > 60 %}#ffc107{% else %}#dc3545{% endif %};">
|
|
{{ success_rate|floatformat:1 }}%
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h4>Last 24 Hours</h4>
|
|
<div style="font-size: 2rem; font-weight: bold; color: {% if last_24h_success_rate > 80 %}#28a745{% elif last_24h_success_rate > 60 %}#ffc107{% else %}#dc3545{% endif %};">
|
|
{{ last_24h_success_rate|floatformat:1 }}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Tasks -->
|
|
<div class="recent-tasks">
|
|
<h3>Recent Sync Tasks</h3>
|
|
{% for task in recent_tasks %}
|
|
<div class="task-item">
|
|
<div class="task-info">
|
|
<div class="task-name">{{ task.name }}</div>
|
|
<div class="task-meta">
|
|
{% if task.started %}
|
|
Started: {{ task.started|date:"Y-m-d H:i:s" }}
|
|
{% endif %}
|
|
{% if task.stopped %}
|
|
• Duration: {{ task.time_taken|floatformat:2 }}s
|
|
{% endif %}
|
|
{% if task.group %}
|
|
• Group: {{ task.group }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="task-status {% if task.success %}success{% elif task.stopped %}failed{% else %}pending{% endif %}">
|
|
{% if task.success %}Success{% elif task.stopped %}Failed{% else %}Pending{% endif %}
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="task-item">
|
|
<div class="task-info">
|
|
<div class="task-name">No sync tasks found</div>
|
|
<div class="task-meta">Sync tasks will appear here once they are executed.</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div style="margin-top: 2rem;">
|
|
<h3>Quick Actions</h3>
|
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
<a href="{% url 'admin:django_q_task_changelist' %}" class="button">
|
|
View All Tasks
|
|
</a>
|
|
<a href="{% url 'admin:django_q_schedule_changelist' %}" class="button">
|
|
Manage Schedules
|
|
</a>
|
|
<a href="/recruitment/sources/" class="button">
|
|
Configure Sources
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let refreshInterval;
|
|
|
|
function refreshStats() {
|
|
fetch('/admin/api/sync-stats/')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Update statistics
|
|
document.querySelector('.stat-card.info .stat-number').textContent = data.total_tasks;
|
|
document.querySelector('.stat-card.success .stat-number').textContent = data.successful_24h;
|
|
document.querySelector('.stat-card.danger .stat-number').textContent = data.failed_24h;
|
|
document.querySelector('.stat-card.warning .stat-number').textContent = data.pending_tasks;
|
|
|
|
// Show refresh indicator
|
|
const btn = document.querySelector('.refresh-btn');
|
|
const originalText = btn.innerHTML;
|
|
btn.innerHTML = '<i class="fas fa-check"></i> Refreshed!';
|
|
btn.style.background = '#28a745';
|
|
|
|
setTimeout(() => {
|
|
btn.innerHTML = originalText;
|
|
btn.style.background = '#007cba';
|
|
}, 2000);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error refreshing stats:', error);
|
|
});
|
|
}
|
|
|
|
function toggleAutoRefresh() {
|
|
const checkbox = document.getElementById('autoRefresh');
|
|
|
|
if (checkbox.checked) {
|
|
// Start auto-refresh
|
|
refreshInterval = setInterval(refreshStats, 30000); // 30 seconds
|
|
} else {
|
|
// Stop auto-refresh
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize auto-refresh
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.getElementById('autoRefresh').addEventListener('change', toggleAutoRefresh);
|
|
|
|
// Start auto-refresh if checked
|
|
if (document.getElementById('autoRefresh').checked) {
|
|
refreshInterval = setInterval(refreshStats, 30000);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|