Skip to content
Parth-VasavePublic

About

Pulse is self-hosted API uptime monitoring with incident detection. Distributed Celery workers check your endpoints on a schedule. An incident opens after N failures and resolves after M straight successes, and alerts go out by webhook or Discord. Built with FastAPI, Next.js, PostgreSQL and Redis.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Pulse: API monitoring & incident detection

A small-scale take on UptimeRobot / Datadog synthetic monitoring. Register an API, and distributed workers check it on a schedule, store every result, open an incident after N consecutive failures, resolve it after M consecutive successes, and notify you by email, webhook or Discord.

Dashboard: health headline, last-hour response time per monitor, and the monitor list
Monitor details Incident timeline
Monitor page with uptime, percentiles and charts Resolved incident with root cause and timeline
Landing page Public status page
Landing page with quick-start commands and an outage replay Public status page

Screenshots follow your GitHub theme; the app itself follows your system's light or dark setting.

Features

  • Monitors: URL, method (GET/POST/PUT/PATCH/DELETE), headers, JSON/text body, expected status, timeout, interval (30 s–24 h), response-time threshold, failure/recovery thresholds, optional immediate retries on network errors
  • Pluggable response assertions (body_contains, body_not_contains, json_field)
  • Error classification: timeout, DNS, connect, invalid response, unexpected status, HTTP error, assertion failed, slow, blocked target
  • Incident engine: pure state machine, DB-enforced deduplication, full timeline; pausing or re-pointing a monitor closes its open incident and restarts its state (no false recovery alert)
  • Notifications with retry/backoff: email (SMTP), generic webhook, Discord
  • Dashboard: a one-line health headline ("1 monitor is down" / all up), 24 h uptime and active incidents, a last-hour response-time lane per monitor with failures marked in red, and a sortable monitor list (UP / DOWN / PAUSED, recent-check strip, 24 h uptime, response, last check); refreshes every 10 s
  • Monitor page: 1 h/24 h/7 d/30 d charts for response time, time-weighted availability and errors by cause, each with a "Show as table" view; avg/P50/P95/P99; 24 h/7 d/30 d uptime and downtime; every recent check. Monitors carry a short catalog number (PLS-004)
  • Incidents list and incident page: root cause, consecutive failures/passes, and a timeline of every state change, check and notification
  • Landing page at / with the quick-start commands and an animated replay of an outage being caught; the app lives at /dashboard
  • Light and dark themes, following the system setting
  • Account settings: change email/password (re-authenticated; a password change signs out other sessions) and delete account; settings organised into Account / Notifications / Status page / API keys
  • Public status page, API keys (hashed, shown once, revocable)
  • Security: Argon2, HttpOnly cookies, tenant isolation, SSRF protection with DNS-rebinding defence, secrets (monitor headers, webhook URLs) encrypted at rest with key rotation, rate limiting, security headers, request limits
  • Observability: structured JSON logs, Prometheus metrics, /health, /ready; Pulse monitors itself: scheduler-tick and queue-depth metrics, ready-made Prometheus alert rules, and an optional dead-man's-switch ping

Architecture

See docs/ARCHITECTURE.md: service layout, scheduling, incident state machine, failure handling, and the design rationale (why Redis/Celery/Postgres, how incidents are deduplicated, how SSRF/tenancy/retries work).

Next.js ─► FastAPI ─► PostgreSQL        Celery Beat ─► Redis queue ─► Celery workers ─► your APIs
                                                                         └─► incident engine ─► notifications

The API never runs checks; Beat only enqueues; workers do the work.

Tech stack

Next.js 16 · React 19 · TypeScript · Tailwind 4 · Recharts │ Python 3.12 · FastAPI · Pydantic · SQLAlchemy 2 · Alembic │ PostgreSQL 16 · Redis 7 · Celery │ Docker Compose · GitHub Actions │ pytest · Vitest + Testing Library · Playwright

Quick start (Docker)

git clone https://github.com/Parth-Vasave/pulse.git && cd pulse
cp .env.example .env
docker compose up --build        # or: make dev
URL What
http://localhost:3000 Landing page; Log in with the seeded demo@example.com / demo-password-123 (or register) to reach the dashboard at /dashboard
http://localhost:8000/docs API docs
http://localhost:8025 Mailpit: catches alert emails
http://localhost:9000 Demo target API with a fail/restore switch

Startup order is handled by Compose: Postgres + Redis (healthchecks) → migrate (Alembic + seed) → API, worker, scheduler → frontend.

Walk-through (the demo)

  1. Open http://localhost:3000, click Log in and sign in as the demo user. Charts are pre-populated from seeded history.
  2. Add monitor → URL http://demo-service:9000/switch, check every 30 s, open incident after 2, resolve after 1.
  3. Add an email channel in Settings (use Send test and watch Mailpit).
  4. Open http://localhost:9000 → Make it fail. Within ~1 minute the monitor turns DOWN, an incident opens, and an email arrives.
  5. Restore. After the next passing check the incident resolves automatically; open it for the timeline.

Other demo endpoints: /healthy (200), /error (500), /slow (delayed), /flaky (random).

Local development without Docker

Requires Python 3.12+, Node 22+, PostgreSQL, Redis.

createdb monitor && createdb monitor_test
cd backend && python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
export DATABASE_URL=postgresql+psycopg://$USER@localhost/monitor PYTHONPATH=.:..
.venv/bin/alembic upgrade head
.venv/bin/uvicorn app.main:app --reload                                        # API :8000
.venv/bin/celery -A worker.celery_app worker -P threads -Q checks,notifications # worker
.venv/bin/celery -A worker.celery_app beat                                     # scheduler
cd ../frontend && npm ci && npm run dev                                        # UI :3000 (proxies /api → :8000)

To monitor something on your own machine, set SSRF_ALLOWED_HOSTS=localhost (dev only; see Security).

Configuration

All via environment (see .env.example):

Variable Purpose
DATABASE_URL, REDIS_URL connections
SECRET_KEY JWT signing key, ≥32 chars. Generate your own
ENCRYPTION_KEY Fernet key(s) encrypting monitor headers and webhook URLs in Postgres. Required outside development; see Security
HEARTBEAT_URL, HEARTBEAT_INTERVAL_SECONDS optional dead-man's-switch ping, so an external service alerts you if Pulse stops scheduling
ACCESS_TOKEN_MINUTES, COOKIE_SECURE session lifetime; set COOKIE_SECURE=true over HTTPS
CORS_ORIGINS, INTERNAL_API_URL allowed origins; where Next.js proxies /api
SSRF_ALLOWED_HOSTS dev only hostnames exempt from private-IP blocking
SMTP_* email delivery
RATE_LIMIT_PER_MINUTE, AUTH_RATE_LIMIT_PER_MINUTE, TRUST_PROXY_HEADERS rate limiting
CHECK_RETENTION_DAYS result retention (default 35)
SEED_DEMO_DATA seed on first start

Database migrations

make migrate                         # docker
cd backend && alembic upgrade head   # local
alembic revision --autogenerate -m "describe change"

CI applies, rolls back and re-applies all migrations.

Testing

make test            # backend + frontend unit/integration
make e2e             # Playwright against the running stack
make lint            # ruff, mypy, eslint, tsc
  • Backend (302 tests): unit (state machine, SSRF, assertions, retry, cache fail-open and single-flight), time-weighted metrics on exact timelines (DST, gaps, pauses), API (auth, validation, CRUD, tenant isolation, rate limiting, headers, size limits, status page, API keys), integration against real Postgres/Redis and a real local HTTP target (checker, incident lifecycle, 4-thread race → one incident, scheduler incl. broker outage, scheduler self-monitoring and the heartbeat ping, encryption at rest incl. key rotation and the data migration, notification retry/failure, Redis-outage recovery), and an API-level end-to-end lifecycle test.
  • Frontend (46 tests): formatting, form parsing and notices, session expiry (401 handling, safe redirects, stale-data notice), account settings, and the status badge, heartbeat, banner, timeline, dialog, button, toggle, segmented control, copy button and toast components.
  • Browser E2E (2 specs): an account flow (change password signs out a second browser, change email, delete account) and the lifecycle flow: register → create monitor → fail demo API → incident → restore → resolved, driven through the UI.

Backend tests need Postgres database monitor_test and Redis (TEST_DATABASE_URL, REDIS_URL override the defaults).

Security

Full detail in docs/SECURITY.md. Highlights: Argon2id; HttpOnly SameSite cookie or hashed API keys; every query scoped to the owner (foreign IDs → 404); SSRF: blocked schemes, localhost, private/loopback/link-local/metadata/CGNAT ranges, IPv6-mapped forms, validated at creation and at connect time against the exact IP used (DNS-rebinding safe), redirects never followed; rate limiting, CORS allow-list, size limits, security headers, log redaction.

API

See docs/API.md or /docs on the running API.

Project layout

backend/   FastAPI app (api, core, models, schemas, services, repositories), Alembic, tests
worker/    Celery app, check/notify tasks, HTTP checker, scheduler tasks
frontend/  Next.js app, components, hooks, unit + e2e tests
demo-service/  target API with a fail switch
infrastructure/  Dockerfiles, Prometheus config and alert rules
docs/      ARCHITECTURE, SECURITY, API, DEPLOYMENT, screenshots

Deployment

Tagging vX.Y.Z publishes images to GHCR and a GitHub release; see docs/DEPLOYMENT.md and docker-compose.prod.yml.

Dependency pinning

Python dependencies are pinned with hashes: edit backend/requirements.in (and requirements-dev.in), run make lock, and commit the generated requirements.txt / requirements-dev.txt. Docker and CI install only those exact, hash-verified versions, so a build next month is the same build as today. Node dependencies are pinned by frontend/package-lock.json (npm ci). Dependabot opens weekly update PRs, and CI runs pip-audit, npm audit and a Trivy image scan (HIGH/CRITICAL) and enforces a backend coverage floor of 88%. Infrastructure images use fixed tags (postgres:16, redis:7, mailpit:v1.31.4, prometheus:v3.15.0).

License

MIT (c) 2026 Parth Vasave

Known limitations & future work

Verified on an Apple-silicon (ARM64) machine: docker compose up from empty volumes reaches all-healthy in about 25 s (images cached) and seeds the demo data; the full alert loop runs unattended (failure → incident → alert email in Mailpit → recovery → resolved → recovery email); both Playwright specs pass against the Docker stack; and stopping Redis or Postgres for 30–40 s leaves every container running, serves clean 503s, and resumes checking by itself. The GitHub Actions workflow runs on every push and pull request (lint, types, tests, migrations, audits, image scans, browser E2E). No email verification, password reset or MFA yet. Single region.

Future: multi-region probes · Kubernetes · OpenTelemetry tracing · Grafana dashboards · maintenance windows · multi-step synthetic workflows · browser checks · teams/RBAC/SSO · alert routing and escalation policies · anomaly detection · AI incident summaries · JWT revocation, encrypting monitor request bodies at rest.

About

Pulse is self-hosted API uptime monitoring with incident detection. Distributed Celery workers check your endpoints on a schedule. An incident opens after N failures and resolves after M straight successes, and alerts go out by webhook or Discord. Built with FastAPI, Next.js, PostgreSQL and Redis.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages