add jobs dashboard

This commit is contained in:
Nick Sweeting 2024-11-17 20:09:55 -08:00
parent fb82fdae16
commit 36d24cd8d7
No known key found for this signature in database
3 changed files with 181 additions and 16 deletions

View file

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Dashboard</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
width: 100%;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
background-color: #f9f9f9;
}
.card h2 {
margin-top: 0;
border-bottom: 2px solid #ddd;
padding-bottom: 10px;
font-family: monospace;
}
.scroll-area {
height: 800px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
background-color: #fff;
}
.job-item {
border: 1px solid #eee;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
}
.job-item:last-child {
margin-bottom: 0;
}
.badge {
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.badge-started {
background-color: #4CAF50;
color: white;
}
.badge-queued {
background-color: #2196F3;
color: white;
}
.badge-failed {
background-color: #f44336;
color: white;
}
.date {
font-size: 16px;
color: #666;
float: right;
}
</style>
</head>
<body>
<h1>Job Dashboard <small><a href="?refresh=true">♻️ {{now}}</a></small></h1>
<div id="dashboard" class="dashboard"></div>
<script>
function formatDate(dateString) {
// return new Date(dateString).toLocaleString();
return new Date(dateString).toISOString().split('T').at(-1).replace('Z', '');
}
function createJobElement(job) {
const jobElement = document.createElement('div');
jobElement.className = 'job-item';
jobElement.innerHTML = `
<p><a href="/api/v1/core/any/${job.abid}?api_key={{api_token|default:'NONE PROVIDED BY VIEW'}}"><code>${job.abid}</code></a></p>
<p>
<span class="badge badge-${job.status}">${job.status}</span>
<span class="date">♻️ ${formatDate(job.retry_at)}</span>
</p>
<p style="font-size: 12px; color: #666;">${job.description}</p>
`;
return jobElement;
}
function updateDashboard(data) {
const dashboard = document.getElementById('dashboard');
dashboard.innerHTML = '';
data.forEach(actor => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<h2>${actor.model}</h2>
<h3>Queue</h3>
<div class="scroll-area" id="queue-${actor.model}"></div>
<h3>Past Tasks</h3>
<div class="scroll-area" id="past-${actor.model}"></div>
`;
dashboard.appendChild(card);
const queueContainer = document.getElementById(`queue-${actor.model}`);
actor.queue.forEach(job => {
queueContainer.appendChild(createJobElement(job));
});
const pastContainer = document.getElementById(`past-${actor.model}`);
actor.past.forEach(job => {
pastContainer.appendChild(createJobElement(job));
});
});
}
function fetchData() {
fetch('/api/v1/jobs/actors', {
headers: {
'Authorization': `Bearer {{api_token|default:'NONE PROVIDED BY VIEW'}}`
}
})
.then(response => response.json())
.then(data => updateDashboard(data))
.catch(error => console.error('Error fetching data:', error));
}
fetchData();
setInterval(fetchData, 1000);
</script>
</body>
</html>

View file

@ -1,3 +1,20 @@
from django.shortcuts import render
# Create your views here.
from django.views.generic import TemplateView
from django.contrib.auth.mixins import UserPassesTestMixin
from django.utils import timezone
from api.auth import get_or_create_api_token
class JobsDashboardView(UserPassesTestMixin, TemplateView):
template_name = "jobs_dashboard.html"
def test_func(self):
return self.request.user and self.request.user.is_superuser
def get_context_data(self, **kwargs):
api_token = get_or_create_api_token(self.request.user)
context = super().get_context_data(**kwargs)
context['api_token'] = api_token.token if api_token else 'UNABLE TO GENERATE API TOKEN'
context['now'] = timezone.now().strftime("%H:%M:%S")
return context