Skip to main content

SAQ Board

A dashboard to monitor SAQ queues, manage jobs, and control cron schedules.

Inspired by bull-board. It extends SAQ's built-in dashboard and its API is a superset of SAQ's, so it drops in wherever you use saq.web today.

Overview

Queue Cron jobs
Failed jobs Cron jobs

Features

  • Queues: overview with live counts, per-queue tabs for active, queued, scheduled, completed, failed and aborted jobs, plus workers.
  • Jobs: rows that expand in place to a timeline and data, result, error and options views, with progress bars, and retry, abort, run now (promote), duplicate and remove actions. Each job also has its own page.
  • Bulk actions: retry all, clean all, promote all, abort all.
  • Pause and resume a queue.
  • Add jobs from the dashboard, picking from the functions your workers register.
  • Cron jobs, Sidekiq-cron style: defined in code, then enabled, disabled, enqueued now or deleted from the dashboard, with next run, last enqueued and history.
  • Light and dark theme, adjustable refresh, relative or absolute times, local or UTC, read-only mode, Redis stats.
  • Preact + htm with no build step, like SAQ's own dashboard. Fonts and scripts ship with the package, so it works offline.

Install

pip install "saq-board[starlette]"   # Starlette / FastAPI
pip install "saq-board[web]"         # aiohttp and the saq-board command

Redis queues only for now.

1. Add the plugin to your workers

SAQ doesn't keep finished jobs, can't pause a queue and schedules cron jobs in code only. The plugin adds all three. Wrap your worker settings:

from saq import Queue
from saq_board import CronJob, with_board

queue = Queue.from_url("redis://localhost")

settings = with_board({
    "queue": queue,
    "functions": [send_email],
    "cron_jobs": [
        CronJob(cleanup, cron="0 * * * *", description="Hourly cleanup"),
    ],
})

Run workers exactly as before: saq app.worker.settings.

with_board(settings, history_limit=1000, cron_poll=1.0):

  • history_limit: finished jobs kept per status (complete, failed, aborted). The oldest are dropped first.
  • cron_poll: seconds between cron checks.

2. Run the dashboard

Starlette / FastAPI, a drop-in for saq.web.starlette.saq_web:

from starlette.routing import Mount
from saq_board import saq_board

routes = [Mount("/monitor", saq_board("/monitor", queues=[queue]))]
# FastAPI: app.mount("/monitor", saq_board("/monitor", queues=[queue]))

aiohttp, a drop-in for saq.web.aiohttp.create_app:

from saq_board import create_app

app.add_subapp("/monitor", create_app([queue], root_path="/monitor"))

On its own:

saq-board --url redis://localhost:6379                # queues registered by the plugin
saq-board --url redis://localhost:6379 -q default -q emails
saq-board --settings app.worker.settings --port 8080

All of them take read_only=True (--read-only), which hides every action and rejects them server-side.

Authentication

Same as SAQ's dashboard. The aiohttp app and the saq-board command turn on basic auth when AUTH_PASSWORD is set (user AUTH_USER, default admin); this needs aiohttp_basicauth, which the web extra installs. With Starlette or FastAPI, protect the mount with your own middleware or dependencies.

Cron jobs

Cron jobs work like Sidekiq-cron:

  • They are defined in code (cron_jobs in the worker settings) and synced to Redis when a worker starts.
  • The dashboard enables, disables, enqueues now or deletes them. Enabled or disabled survives restarts; a deleted job comes back the next time a worker that defines it starts.
  • Every worker process checks the schedule, and a claim in Redis makes sure each run is enqueued once.
  • A run that is more than 60 seconds late (for example, no worker was up) is skipped, not caught up.
  • unique=True (the default, as in SAQ) skips a run while the previous one is still queued or running.
  • Times follow the worker's cron_tz (UTC by default).

saq_board.CronJob is SAQ's CronJob plus name, description and enabled (the initial state). Plain saq.CronJob works too and is named after its function. Give jobs that share a function different names.

API

Everything SAQ's dashboard serves, plus:

Method Path
GET /api/queues Queues with counts, totals, paused flag and workers
GET /api/queues/{queue} One queue, as in SAQ, plus the registered functions
GET /api/queues/{queue}/jobs?status=&offset=&limit= Jobs by status
POST /api/queues/{queue}/jobs Add a job: {"function", "kwargs", "options"}
GET /api/queues/{queue}/jobs/{job} One job
POST /api/queues/{queue}/jobs/{job}/{action} retry, abort, promote, remove
POST /api/queues/{queue}/{action} pause, resume, promote-all, and with {"status"}: clean, retry-all, abort-all
GET /api/redis Redis stats
GET /api/cron Cron jobs
GET /api/cron/{queue}/{name} One cron job with its history
POST /api/cron/{queue}/{name}/{action} enqueue, enable, disable, delete
POST /api/cron/{action}-all The same for every cron job

How it works

The plugin wraps the worker's queue: finish also records the job in a capped history, and dequeue waits while the queue is paused (a pause takes effect within a second). It also moves cron_jobs to its own scheduler, so SAQ's scheduler doesn't run them too, and tags the worker's metadata so the dashboard can tell whether workers run the plugin.

Board data lives under each queue's SAQ namespace, saq:<queue>:board:*, so it goes away with the queue.

Mutation safety

Retry and abort each run as one Redis script that first checks the job is still what the dashboard showed, so a double click, two open tabs or a busy queue can't corrupt it.

  • Retry requeues only a finished job, and only the run you are looking at. If the key now belongs to a newer or running job, or another retry got there first, it returns 409.
  • Abort finishes a queued or scheduled job at once. A running job stays aborting until its worker acknowledges cancellation. Repeated abort requests are idempotent; retry and enqueue with the same key remain blocked during cancellation cleanup.
  • Pending aborts appear in the active tab and persist in board:aborting, outside stock SAQ's sweep list. The incomplete entry still prevents duplicate enqueue. If a worker disappears, the request stays pending and visible for operator investigation. A second abort does not prove worker death or force completion; recovery must verify that the old execution has stopped before marking it terminal.
  • While a worker is picking a job up, before it marks it active, abort returns 409. Refresh and try again.

Development

python -m venv .venv && .venv/bin/pip install -e ".[dev]" uvicorn
redis-server --daemonize yes
.venv/bin/pytest            # uses redis://localhost:6379/15, override with SAQ_BOARD_TEST_REDIS
.venv/bin/python examples/demo.py   # http://localhost:8000/monitor

Credits

Built by rkwap.

SAQ Board stands on the shoulders of:

License

MIT, like SAQ and bull-board.

Metadata

Release files for saq-board 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for saq-board 0.1.3
File Size Uploaded
saq_board-0.1.3.tar.gz 113.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for saq-board 0.1.3
File Interpreter ABI Platform
saq_board-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 235.3 kB

Release files / saq_board-0.1.3.tar.gz

Download URL saq_board-0.1.3.tar.gz
Size 113.8 kB
Tags Source
SHA-256 checksum
How to use checksums
475d4fcf59488fe192f7695b63c1c2fcb2aef2850b214e7459d70d174667204b
BLAKE2b-256 checksum
How to use checksums
2d89de3e453569a5520f658a2939625422a99aee045fac3698c63251591edee5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / saq_board-0.1.3-py3-none-any.whl

Download URL saq_board-0.1.3-py3-none-any.whl
Size 121.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a55d2197ff36a3b403c0d8cd12edfb9dfc75d2164084a3a3027291e158376d2d
BLAKE2b-256 checksum
How to use checksums
2bb243187600b21ebd9c33da67fb1ab3b7ebf1f1234f902769991ec575aa072f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.4

2 release files

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page