Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,13 @@ APOLLO_SEED_ON_STARTUP=false
# Due-check worker loop (seconds)
CHECK_INTERVAL_SECONDS=60

EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
DEFAULT_FROM_EMAIL=apollo@localhost
EMAIL_HOST=
EMAIL_PORT=587
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_USE_TLS=true

SESSION_COOKIE_SECURE=false
CSRF_COOKIE_SECURE=false
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,6 @@ jobs:
run: docker build -t apollo:ci .

- name: Validate compose file
run: docker compose config -q
run: |
cp .env.example .env
docker compose config -q
97 changes: 37 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,74 @@ Django + React API Health Monitor (monolith).

## Stack

- **Backend:** Django 6 + DRF + httpx + Token auth + WhiteNoise + Gunicorn (Poetry)
- **Frontend:** React + Vite + Redux Toolkit (pnpm)
- **Data:** SQLite (local default) or **PostgreSQL** via `DATABASE_URL`
- **Backend:** Django 6 + DRF + httpx + Token auth + WhiteNoise + Gunicorn
- **Frontend:** React + Vite + Redux Toolkit + React Router (pnpm)
- **Data:** SQLite (local) or PostgreSQL (`DATABASE_URL`)
- **Ops:** Docker Compose (`db` + `web` + `worker`), GitHub Actions CI
- **Features:** due checks, webhook alerts, uptime dashboard, background check worker
- **Product:** due checks, webhooks + email alerts, incidents, tags, public status page, uptime dashboard

## Quick start (local / SQLite)
## Quick start (local)

```bash
cp .env.example .env
poetry install
poetry run python manage.py migrate
poetry run python manage.py seed

pnpm --dir frontend install

poetry run python manage.py runserver
pnpm --dir frontend dev
```

Open http://localhost:5173 — login **`apollo` / `apollo`**.
- App: http://localhost:5173 — login **`apollo` / `apollo`**
- Public status: http://localhost:5173/status

## Docker (Postgres + web + worker)
## Docker

```bash
cp .env.example .env
# set a real DJANGO_SECRET_KEY for deploys
docker compose up --build
```

Services:

| Service | Role |
|---------|------|
| `db` | PostgreSQL 16 |
| `web` | Gunicorn API + SPA |
| `worker` | Loop: `check_endpoints --due` every `CHECK_INTERVAL_SECONDS` |
- App: http://localhost:8000
- Status: http://localhost:8000/status
- Worker runs `check_endpoints --due` on an interval

App: http://localhost:8000
Login: `apollo` / `apollo` (seeded by web when `APOLLO_SEED_ON_STARTUP=true`)

```bash
docker compose logs -f worker
docker compose down
```
## Features

## Environment
| Area | Details |
|------|---------|
| Auth | Token login/logout/me; API protected except health + public status |
| Endpoints | CRUD, tags, public flag, interval, webhook + email |
| Checks | Manual, due, history; worker loop in Compose |
| Alerts | Transition-only webhook/email; stored `AlertEvent` |
| Incidents | Auto-open on failure, auto-resolve on recovery |
| Status page | Public `/status` + `GET /api/status/public/` |
| Dashboard | Uptime, latency, due, open incidents |

See [`.env.example`](.env.example).
## Key API routes

| Variable | Purpose |
|----------|---------|
| `DATABASE_URL` | Postgres URL (empty = SQLite) |
| `DJANGO_SQLITE_PATH` | SQLite path when `DATABASE_URL` is empty |
| `CHECK_INTERVAL_SECONDS` | Worker loop interval (default 60) |
| `APOLLO_SEED_ON_STARTUP` | Seed demo user/endpoints on web boot |
| `DJANGO_SECRET_KEY` / `DJANGO_DEBUG` / `DJANGO_ALLOWED_HOSTS` | Django core |
| Method | Path | Auth |
|--------|------|------|
| GET | `/api/health/` | public |
| GET | `/api/status/public/` | public |
| POST | `/api/auth/login/` | public |
| GET | `/api/dashboard/` | token |
| CRUD | `/api/endpoints/` | token |
| GET | `/api/incidents/?status=open` | token |
| GET/POST | `/api/tags/` | token |
| GET | `/api/alerts/` | token |

## Auth
## Env highlights

Token auth (`Authorization: Token <key>`). Public: `/api/health/`, `/api/auth/login/`.
See `.env.example` for full list (`DATABASE_URL`, `CHECK_INTERVAL_SECONDS`, `EMAIL_*` / mailer settings, demo user, etc.).

## Scheduling

**Docker:** the `worker` service runs due checks automatically.

**Host cron (SQLite/Poetry):**

```bash
* * * * * cd /path/to/Apollo && poetry run python manage.py check_endpoints --due
```

**One-shot / debug:**

```bash
poetry run python manage.py run_check_worker --once
poetry run python manage.py run_check_worker --interval 30
```

## CI

`.github/workflows/ci.yml`:

1. Pytest against Postgres service
2. pnpm build
3. `docker build` + `docker compose config`

## Useful commands
## Commands

```bash
poetry run pytest
poetry run python manage.py check_endpoints --due
poetry run python manage.py run_check_worker --once
pnpm --dir frontend build
docker compose up --build
```
16 changes: 16 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ def env_list(name: str, default: str = "") -> list[str]:
APOLLO_SEED_ON_STARTUP = env_bool("APOLLO_SEED_ON_STARTUP", False)
CHECK_INTERVAL_SECONDS = int(env("CHECK_INTERVAL_SECONDS", "60") or "60")

_email_backend = (
env("EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend")
or "django.core.mail.backends.console.EmailBackend"
)
DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", "apollo@localhost") or "apollo@localhost"
MAILERS = {
"default": {
"BACKEND": _email_backend,
"HOST": env("EMAIL_HOST", "") or "",
"PORT": int(env("EMAIL_PORT", "587") or "587"),
"USERNAME": env("EMAIL_HOST_USER", "") or "",
"PASSWORD": env("EMAIL_HOST_PASSWORD", "") or "",
"USE_TLS": env_bool("EMAIL_USE_TLS", True),
}
}

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = env_bool("SESSION_COOKIE_SECURE", not DEBUG)
CSRF_COOKIE_SECURE = env_bool("CSRF_COOKIE_SECURE", not DEBUG)
6 changes: 4 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ services:
ports:
- "${PORT:-8000}:8000"
env_file:
- .env
- path: .env
required: false
environment:
DJANGO_DEBUG: "false"
DJANGO_ALLOWED_HOSTS: "*"
Expand All @@ -36,7 +37,8 @@ services:
build: .
command: ["/worker.sh"]
env_file:
- .env
- path: .env
required: false
environment:
DJANGO_DEBUG: "false"
DJANGO_ALLOWED_HOSTS: "*"
Expand Down
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"@reduxjs/toolkit": "^2.12.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-redux": "^9.3.0"
"react-redux": "^9.3.0",
"react-router-dom": "^7.18.4"
},
"devDependencies": {
"@types/node": "^24.13.6",
Expand Down
45 changes: 45 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading