runner_q 0.6.4

Durable activity queue and worker system
Documentation
# RunnerQ Console UI

This directory contains the observability UI for RunnerQ.

## Single-Page HTML Version

The `runnerq-console.html` is a self-contained, single-page HTML file that provides a complete observability dashboard for RunnerQ. It's designed to be embedded in your Rust application, similar to how Swagger UI works.

### Features

- **Real-time Updates**: Server-Sent Events (SSE) for instant activity updates
- **Live Statistics**: Queue stats with processing, pending, scheduled, and dead-letter counts
- **Priority Distribution**: Real-time breakdown of activities by priority level
- **Activity Management**: Browse pending, processing, scheduled, completed, and dead-letter activities
- **Activity Results**: View execution results and outputs for completed activities
- **Event Timeline**: Detailed activity lifecycle events with multiple view modes
- **Search & Filter**: Filter activities by type, status, and ID
- **Modern UI**: Temporal-inspired design with dark theme and responsive layout
- **Zero Build Step**: No npm, webpack, or build process required
- **Self-Contained**: All HTML, CSS, and JavaScript in a single file
- **7-Day History**: Completed activities queryable for 7 days

### Usage

Simply use the provided Rust helper functions to serve the UI:

```rust
use runner_q::{runnerq_ui, storage::PostgresBackend, WorkerEngine};
use axum::{serve, Router};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let backend = PostgresBackend::new("postgres://localhost/mydb", "my_app").await?;
    let engine = WorkerEngine::builder()
        .backend(Arc::new(backend))
        .queue_name("my_app")
        .build()
        .await?;
    let inspector = engine.inspector();

    let app = Router::new()
        .nest("/console", runnerq_ui(inspector));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8081").await?;
    println!("✨ RunnerQ Console: http://localhost:8081/console");
    serve(listener, app).await?;
    Ok(())
}
```

That's it! The UI will be available at `http://localhost:8081/console` with the API automatically mounted at `http://localhost:8081/console/api/observability`.

### API-Only Mode

If you want to serve just the API (e.g., for a custom UI):

```rust
use runner_q::{observability_api, QueueInspector};

let app = Router::new()
    .nest("/api/observability", observability_api(inspector));
```

### API Endpoints

The UI expects these endpoints to be available:

- `GET /api/observability/stats` - Queue statistics (including priority distribution)
- `GET /api/observability/stream` - Server-Sent Events stream for real-time updates
- `GET /api/observability/activities/pending` - List pending activities
- `GET /api/observability/activities/processing` - List processing activities
- `GET /api/observability/activities/scheduled` - List scheduled activities
- `GET /api/observability/activities/completed` - List completed activities (7-day history)
- `GET /api/observability/activities/dead_letter` - List dead-letter activities
- `GET /api/observability/activities/{id}` - Get activity details
- `GET /api/observability/activities/{id}/events` - Get activity events
- `GET /api/observability/activities/{id}/result` - Get activity execution result
- `GET /api/observability/dead-letter` - Get dead-letter records

### Customization

The UI **automatically detects** the correct API path based on where it's served from:
- Served at `/console` → API at `/console/api/observability`
- Served at `/ui` → API at `/ui/api/observability`
- Served at `/` → API at `/api/observability`

To override the auto-detection and use a custom API base URL, set `window.RUNNERQ_API_BASE` before the page loads:

```html
<script>
    window.RUNNERQ_API_BASE = '/custom/api/path';
</script>
```

### Real-time Updates

The UI uses Server-Sent Events (SSE) for instant real-time updates with zero configuration.

#### How it Works

1. When you call `runnerq_ui(inspector)` or `engine.inspector()`, event streaming is automatically enabled
2. The UI connects via SSE to `/api/observability/stream`
3. Every activity lifecycle event triggers instant UI updates
4. Connection automatically reconnects if interrupted
5. Works in modern browsers only (no polling fallback)

#### Event Types

The UI receives real-time events for:
- `Enqueued` - Activity added to queue
- `Started` - Worker begins processing
- `Completed` - Activity finished successfully
- `Failed` - Activity failed (will retry)
- `DeadLetter` - Activity moved to dead-letter queue
- `Heartbeat` - Worker is still processing

#### Usage

Get an inspector from the engine (built with any backend that implements `InspectionStorage`, e.g. Postgres or Redis via `runner_q_redis`):

```rust
let backend = PostgresBackend::new("postgres://localhost/mydb", "my_app").await?;
let engine = WorkerEngine::builder()
    .backend(Arc::new(backend))
    .queue_name("my_app")
    .build()
    .await?;
let inspector = engine.inspector();
let app = Router::new().nest("/console", runnerq_ui(inspector));
```

## Running the Example

```bash
# With PostgreSQL: set DATABASE_URL and run the console example
DATABASE_URL=postgres://localhost/mydb cargo run --example console_ui

# With Redis: set REDIS_URL and run the Redis example
REDIS_URL=redis://127.0.0.1:6379 cargo run --example redis_console_ui

# Open http://localhost:8081/console (or :8082 for redis_console_ui)
```

## Development

The console is a single HTML file (`runnerq-console.html`) with embedded CSS and JavaScript. To modify:

1. Edit `ui/runnerq-console.html` directly
2. Test by running `cargo run --example console_ui`
3. No build step required - changes are immediately visible on page refresh

The UI is designed to work in modern browsers and uses:
- Vanilla JavaScript (no framework)
- CSS Grid and Flexbox for layout
- Server-Sent Events for real-time updates
- Fetch API for HTTP requests