simple-queue-web 0.1.0

Web UI for inspecting and managing simple-queue persistent job queues backed by PostgreSQL
<div class="bg-gray-900 rounded-lg border border-gray-800 overflow-hidden">
    {% if t.jobs.is_empty() %}
    <div class="px-4 py-12 text-center text-gray-500">No jobs found</div>
    {% else %}
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead>
                <tr class="text-left text-gray-400 text-xs uppercase tracking-wider">
                    <th class="px-4 py-3">ID</th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_status }}" class="hover:text-white">Status</a></th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_attempt }}" class="hover:text-white">Atmpt</a></th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_reprocess_count }}" class="hover:text-white">Reproc</a></th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_created_at }}" class="hover:text-white">Created At</a></th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_run_at }}" class="hover:text-white">Run At</a></th>
                    <th class="px-4 py-3"><a href="{{ t.sort_link_updated_at }}" class="hover:text-white">Updated At</a></th>
                    <th class="px-4 py-3">Actions</th>
                </tr>
            </thead>
            <tbody>
                {% for job in t.jobs %}
                <tr class="border-t border-gray-800 hover:bg-gray-800/50">
                    <td class="px-4 py-2">
                        <a href="/jobs/{{ job.id }}?source={{ job.source }}"
                           class="text-blue-400 hover:text-blue-300 font-mono text-xs">
                            {{ job.short_id }}
                        </a>
                    </td>
                    <td class="px-4 py-2">
                        <span class="text-xs px-2 py-0.5 rounded
                            {% if job.status == "pending" %}bg-yellow-900/50 text-yellow-300
                            {% else if job.status == "running" %}bg-blue-900/50 text-blue-300
                            {% else if job.status == "completed" %}bg-green-900/50 text-green-300
                            {% else if job.status == "failed" %}bg-red-900/50 text-red-300
                            {% else if job.status == "cancelled" %}bg-gray-800 text-gray-500
                            {% else %}bg-gray-800 text-gray-400{% endif %}">
                            {{ job.status }}
                        </span>
                    </td>
                    <td class="px-4 py-2 text-gray-300 font-mono text-xs">{{ job.attempt }}/{{ job.max_attempts }}</td>
                    <td class="px-4 py-2 text-gray-400 font-mono text-xs">{{ job.reprocess_count }}</td>
                    <td class="px-4 py-2 text-gray-400 text-xs whitespace-nowrap">{% match job.created_at_fmt %}{% when Some(t) %}{{ t }}{% else %}-{% endmatch %}</td>
                    <td class="px-4 py-2 text-xs"><div class="leading-none">{{ job.run_at_date }}</div><div class="text-[10px] text-gray-500 leading-none mt-0.5">{{ job.run_at_time }}</div></td>
                    <td class="px-4 py-2 text-xs"><div class="leading-none">{{ job.updated_at_date }}</div><div class="text-[10px] text-gray-500 leading-none mt-0.5">{{ job.updated_at_time }}</div></td>
                    <td class="px-4 py-2">
                        <div class="flex items-center gap-1">
                            <a href="/jobs/{{ job.id }}?source={{ job.source }}"
                               class="text-xs text-blue-400 hover:text-blue-300 px-2 py-1">Inspect</a>
                            {% if t.is_queue_source %}
                            <form method="post" action="/jobs/{{ job.id }}/restart?queue={{ t.selected_queue }}&page={{ t.page }}&source=queue"
                                  class="inline" onsubmit="return confirm('Restart this job?')">
                                <button type="submit" class="text-xs text-yellow-400 hover:text-yellow-300 px-2 py-1">Restart</button>
                            </form>
                            <form method="post" action="/jobs/{{ job.id }}/cancel?queue={{ t.selected_queue }}&page={{ t.page }}&source=queue"
                                  class="inline" onsubmit="return confirm('Cancel this job?')">
                                <button type="submit" class="text-xs text-red-400 hover:text-red-300 px-2 py-1">Cancel</button>
                            </form>
                            {% endif %}
                            {% if t.is_dlq_source || t.is_archive_source %}
                            <form method="post" action="/jobs/{{ job.id }}/requeue?queue={{ t.selected_queue }}&page={{ t.page }}&source={{ t.selected_source }}"
                                  class="inline" onsubmit="return confirm('Move this job to queue as pending?')">
                                <button type="submit" class="text-xs text-green-400 hover:text-green-300 px-2 py-1">Requeue</button>
                            </form>
                            {% endif %}
                        </div>
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
    <div class="px-4 py-3 border-t border-gray-800 flex items-center justify-between text-sm">
        <div class="text-gray-400">
            {{ t.show_from }}-{{ t.show_to }} of {{ t.total }}
        </div>
        <div class="flex items-center gap-2">
            {% if t.page > 1 %}
            <a href="/queues/browse?queue={{ t.selected_queue }}&source={{ t.selected_source }}&sort_by={{ t.sort_by }}&sort_dir={{ t.sort_dir }}&page={{ t.page - 1 }}"
               class="px-3 py-1 bg-gray-800 hover:bg-gray-700 text-gray-300 rounded text-xs transition-colors">
                Prev
            </a>
            {% endif %}
            <span class="text-gray-500 text-xs">Page {{ t.page }} of {{ t.total_pages }}</span>
            {% if t.page < t.total_pages %}
            <a href="/queues/browse?queue={{ t.selected_queue }}&source={{ t.selected_source }}&sort_by={{ t.sort_by }}&sort_dir={{ t.sort_dir }}&page={{ t.page + 1 }}"
               class="px-3 py-1 bg-gray-800 hover:bg-gray-700 text-gray-300 rounded text-xs transition-colors">
                Next
            </a>
            {% endif %}
        </div>
    </div>
    {% endif %}
</div>