Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Daniel Cuevas

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
79 changes: 75 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

A small durable background queue built with Go and SQLite.

The project shows leases, retries, idempotency, crash recovery, priority dispatch, priority aging, a dead-letter queue, Prometheus metrics, and an append-only event log.
The project shows leases, retries, idempotency, crash recovery, priority dispatch, priority aging, a dead-letter queue, and an append-only event log.

It also exposes Prometheus metrics and a read-only web dashboard.

## Value

Expand All @@ -21,6 +23,7 @@ The queue separates durable state from worker execution.
- `internal/fault` injects deterministic errors, panics, delays, and stalls.
- `internal/cli` renders commands, snapshots, history, and the demo.
- `internal/metrics` renders queue state in the Prometheus text format.
- `internal/dashboard` serves a read-only web interface and JSON endpoints.
- `internal/fixture` provides repeatable sample workloads.

A job starts as `pending`.
Expand Down Expand Up @@ -102,6 +105,14 @@ Use `-aging 0` to disable aging.

Use `-metrics-addr` to serve Prometheus metrics beside the worker.

Serve the inspection dashboard with this command.

```text
jobqueue web -db queue.db
```

Open `http://localhost:8080` in a browser.

### `inspect`

```text
Expand Down Expand Up @@ -154,6 +165,38 @@ Scrape the endpoint with a Prometheus server.

Use `-once` to print one snapshot and exit.

### `web`

```text
jobqueue web [-addr <addr>] [-refresh <duration>] [-db <path>]
```

The command serves a read-only inspection dashboard.

The dashboard renders state counts, jobs, and recent events in one page.

The page refreshes in place, so it needs no reload.

Use `-addr` to change the listen address.

The default address is `:8080`.

Use `-refresh` to set the auto-refresh interval.

The default interval is two seconds.

The page links to the Prometheus endpoint at `/metrics`.

The command never writes to the queue.

JSON endpoints expose the same state to scripts.

`GET /api/overview` returns the full snapshot.

`GET /api/jobs` returns every job.

`GET /api/jobs/<id>` returns one job and its event timeline.

### `demo`

```text
Expand Down Expand Up @@ -260,6 +303,22 @@ Use the `metrics` command for one snapshot or a live endpoint.

Use `work -metrics-addr` to serve the same endpoint beside a worker.

### Web dashboard

The `web` command serves a read-only dashboard in a browser.

The page shows state counts, jobs, recent events, and per-kind totals.

The page refreshes in place every two seconds.

Each refresh reads the SQLite store and renders a fresh snapshot.

The dashboard is read-only and never modifies the queue.

JSON endpoints back the page and support scripts.

The `/metrics` endpoint works beside the dashboard.

### Scheduling

A scheduled job stores its earliest lease time in `run_at`.
Expand Down Expand Up @@ -376,7 +435,7 @@ A high-priority stream can delay lower-priority jobs until aging lifts them.

The worker is one process and does not coordinate across hosts.

The project does not provide a web interface.
The dashboard serves the queue read-only, so it does not run jobs.

## Roadmap

Expand All @@ -386,12 +445,24 @@ The project does not provide a web interface.
- [x] Priority aging to prevent starvation.
- [x] Dead-letter queue with requeue of permanently failed jobs.
- [x] Prometheus metrics for queue inspection.
- [ ] Web UI for queue inspection.
- [x] Web dashboard for queue inspection.
- [ ] Horizontal scaling with a shared SQLite file.

### Release notes

This release adds Prometheus metrics.
This release adds a read-only web dashboard.

The new `web` command serves one HTML page and JSON endpoints.

The page shows state counts, jobs, recent events, and per-kind totals.

It refreshes in place, so it needs no reload.

Scripts can read the same state from `/api/overview`.

The dashboard never writes to the queue.

The previous release added Prometheus metrics.

The new `metrics` command serves the exposition format over HTTP.

Expand Down
16 changes: 16 additions & 0 deletions cmd/web/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"log"
"os"

"github.com/local-first-job-queue/internal/cli"
)

func main() {
log.SetFlags(0)
if err := cli.Web(os.Args[1:]); err != nil {
log.Fatal(err)
os.Exit(1)
}
}
1 change: 1 addition & 0 deletions internal/cli/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func Demo(args []string) error {
fmt.Printf("\ninspect again with: jobqueue inspect -db %q\n", path)
fmt.Printf("inspect a job with: jobqueue history <id> -db %q\n", path)
fmt.Printf("requeue a dead letter with: jobqueue requeue <id> -db %q\n", path)
fmt.Printf("open the dashboard with: jobqueue web -db %q\n", path)
return nil
}

Expand Down
35 changes: 35 additions & 0 deletions internal/cli/web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cli

import (
"flag"
"fmt"
"log"
"net/http"

"github.com/local-first-job-queue/internal/dashboard"
"github.com/local-first-job-queue/internal/queue"
)

// Web serves the read-only inspection dashboard. The dashboard renders the
// queue state as one HTML page and exposes the same state as JSON endpoints.
// It never writes to the queue, so it is safe to leave running beside a worker.
func Web(args []string) error {
fs := flag.NewFlagSet("web", flag.ExitOnError)
addr := fs.String("addr", ":8080", "listen address for the dashboard")
dbPath := fs.String("db", "queue.db", "database path")
refresh := fs.Duration("refresh", dashboard.DefaultRefreshInterval, "client auto-refresh interval")
fs.Parse(args)

store, err := queue.NewSQLiteStore(*dbPath)
if err != nil {
return fmt.Errorf("open store: %w", err)
}
defer store.Close()

log.Printf("dashboard listening on %s (db=%s)", *addr, *dbPath)
log.Printf("open http://localhost%s", *addr)
return http.ListenAndServe(*addr, dashboard.Handler(store,
dashboard.WithDBPath(*dbPath),
dashboard.WithRefreshInterval(*refresh),
))
}
Loading
Loading