<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Queue Manager{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {}
}
}
</script>
<script type="module">
import { Datastar } from 'https://cdn.jsdelivr.net/npm/@starfederation/datastar/+esm';
</script>
</head>
<body class="h-full bg-gray-950 text-gray-100 antialiased">
<nav class="bg-gray-900 border-b border-gray-800 px-6 py-3 sticky top-0 z-50">
<div class="max-w-7xl mx-auto flex items-center justify-between">
<div class="flex items-center gap-6">
<a href="/dashboard" class="text-lg font-bold text-white tracking-tight">Queue Manager</a>
<a href="/dashboard" class="text-sm text-gray-400 hover:text-white transition-colors">Dashboard</a>
<a href="/queues/browse" class="text-sm text-gray-400 hover:text-white transition-colors">Queues</a>
</div>
<div class="flex items-center gap-4">
<form id="job-search" class="flex items-center gap-1" onsubmit="document.getElementById('job-search-input').blur()">
<input type="text" id="job-search-input" placeholder="Job ID"
class="bg-gray-800 border border-gray-700 text-white text-xs rounded px-2 py-1 w-36 focus:outline-none focus:ring-1 focus:ring-blue-500 placeholder-gray-500">
</form>
<label class="flex items-center gap-2 text-sm text-gray-400">
Poll:
<select id="poll-interval"
class="bg-gray-800 border border-gray-700 text-white text-sm rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500">
<option value="200">0.2s</option>
<option value="500">0.5s</option>
<option value="1000">1s</option>
<option value="2000" selected>2s</option>
<option value="5000">5s</option>
<option value="10000">10s</option>
<option value="30000">30s</option>
<option value="0">Off</option>
</select>
</label>
<span id="connection-status" class="text-xs text-gray-600">--</span>
</div>
</div>
</nav>
<main class="max-w-7xl mx-auto px-4 sm:px-6 py-6">
{% block content %}{% endblock %}
</main>
<script>
let pollMs = 2000;
let pollTimer = null;
function processResponse(text) {
const blocks = text.trim().split('\n\n');
for (const block of blocks) {
const lines = block.split('\n');
const directive = lines[0].trim();
if (!directive.startsWith('selector ')) continue;
const selector = directive.slice(9).trim();
const html = lines.slice(1).join('\n');
const el = document.querySelector(selector);
if (el) {
el.innerHTML = html.trim();
}
}
}
function getPollEndpoint() {
const path = window.location.pathname;
const params = new URLSearchParams(window.location.search);
if (path === '/dashboard') return '/api/dashboard/poll';
if (path === '/queues/browse') {
let ep = '/api/queues/poll?';
const keep = ['queue', 'status', 'source', 'page', 'sort_by', 'sort_dir'];
const filtered = new URLSearchParams();
for (const k of keep) {
if (params.has(k)) filtered.set(k, params.get(k));
}
return '/api/queues/poll?' + filtered.toString();
}
return null;
}
function poll() {
const endpoint = getPollEndpoint();
if (!endpoint) return;
const status = document.getElementById('connection-status');
fetch(endpoint)
.then(r => {
if (r.ok) {
status.textContent = 'Live';
status.className = 'text-xs text-green-500';
return r.text();
}
throw new Error(r.status);
})
.then(text => processResponse(text))
.catch(err => {
status.textContent = 'Error';
status.className = 'text-xs text-red-500';
console.error('Poll error:', err);
});
}
function startPolling() {
stopPolling();
if (pollMs > 0) {
poll();
pollTimer = setInterval(poll, pollMs);
} else {
const status = document.getElementById('connection-status');
status.textContent = 'Paused';
status.className = 'text-xs text-yellow-500';
}
}
function stopPolling() {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
}
document.getElementById('poll-interval').addEventListener('change', function() {
pollMs = parseInt(this.value);
startPolling();
});
document.getElementById('job-search').addEventListener('submit', function(e) {
e.preventDefault();
var id = document.getElementById('job-search-input').value.trim();
if (id) window.location.href = '/jobs/' + id + '?source=auto';
});
startPolling();
</script>
</body>
</html>