diff --git a/README.md b/README.md index e215bc4..d95cd21 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,200 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# OverSnitch -## Getting Started +A self-hosted dashboard for monitoring Overseerr/Jellyseerr users — who's requesting, how much storage they're consuming, how often they actually watch what they request, and whether anything needs your attention. -First, run the development server: +Built with Next.js 16, TypeScript, and Tailwind CSS. + +--- + +## Features + +- **Leaderboard** — per-user request count, total storage, average GB per request, and optional Tautulli watch stats (plays, watch hours), each ranked against the full userbase +- **Alerting** — automatic alerts for stalled downloads, neglected requesters, and abusive patterns, with open/close state, notes, and auto-resolve when conditions clear +- **SWR caching** — stats are cached server-side for 5 minutes and seeded from localStorage on the client, so the dashboard is instant on return visits + +--- + +## Setup + +### 1. Clone and install ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +git clone https://gitea.thewrightserver.net/josh/OverSnitch.git +cd OverSnitch +npm install ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### 2. Configure environment -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Create `.env.local` in the project root: -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +```env +# Required +SEERR_URL=http://overseerr:5055 +SEERR_API=your_overseerr_api_key -## Learn More +RADARR_URL=http://radarr:7878 +RADARR_API=your_radarr_api_key -To learn more about Next.js, take a look at the following resources: +SONARR_URL=http://sonarr:8989 +SONARR_API=your_sonarr_api_key -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +# Optional — enables watch time stats and ghost/watch-rate alerts +TAUTULLI_URL=http://tautulli:8181 +TAUTULLI_API=your_tautulli_api_key -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +# Optional — if your services use self-signed certs +# NODE_TLS_REJECT_UNAUTHORIZED=0 +``` -## Deploy on Vercel +### 3. Run -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +```bash +npm run dev # development +npm run build && npm start # production +``` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +--- + +## Alerts + +Alerts are generated on every stats refresh and persisted in `data/alerts.json` (gitignored). They have two states — **Open** and **Closed** — and can be manually closed with a per-category cooldown, or auto-resolved when the underlying condition clears. + +### Content alerts + +These are keyed per piece of media, not per user. If multiple users requested the same item, they're grouped into a single alert. + +--- + +#### Not Downloaded + +> A movie or TV show was approved but no file exists in Radarr/Sonarr. + +| Parameter | Default | Description | +|---|---|---| +| `UNFULFILLED_MIN_AGE_DAYS` | `3` | Minimum days since approval before alerting. Prevents noise on brand-new requests. | + +- Skipped entirely if Radarr reports `isAvailable: false` (unreleased) or Sonarr reports `status: "upcoming"`. +- **Auto-resolves** when the file appears (i.e. `sizeOnDisk > 0`). +- Manual close cooldown: **3 days**. + +--- + +#### Incomplete Download + +> An ended TV series has been partially downloaded (less than 90% of episodes on disk). + +| Parameter | Default | Description | +|---|---|---| +| `UNFULFILLED_MIN_AGE_DAYS` | `3` | Same minimum age as above. | +| Completion threshold | `90%` | Series below this are flagged. Hardcoded. | + +- Only fires for series with `status: "ended"` in Sonarr. Continuing shows are excluded because missing episodes may simply not have aired yet. +- Percentage is calculated as `episodeFileCount / totalEpisodeCount`, not Sonarr's `percentOfEpisodes` (which counts against monitored episodes only and would be inaccurate here). +- **Auto-resolves** when completion reaches 90%+. +- Manual close cooldown: **3 days**. + +--- + +#### Pending Approval + +> A request has been sitting unapproved for too long. + +| Parameter | Default | Description | +|---|---|---| +| `PENDING_MIN_AGE_DAYS` | `7` | Minimum days a request must be pending before alerting. | + +- One alert per request item, not per user. +- Skipped if the content is unreleased (same availability checks as above). +- **Auto-resolves** when the request is approved or declined. +- Manual close cooldown: **3 days**. + +--- + +### User behavior alerts + +These fire once per user. Ghost Requester takes priority over Low Watch Rate — a user will only ever have one behavior alert open at a time. Both require the user to be "established" (at least one request older than `USER_MIN_AGE_DAYS`) to avoid flagging new users. + +> Requires Tautulli to be configured (except Frequent Declines). + +| Parameter | Default | Description | +|---|---|---| +| `USER_MIN_AGE_DAYS` | `14` | Days since oldest request before a user is eligible for behavior alerts. | + +--- + +#### Ghost Requester + +> A user has made several requests but has never watched anything on Plex. + +| Parameter | Default | Description | +|---|---|---| +| `MIN_REQUESTS_GHOST` | `5` | Minimum total requests before flagging. | + +- Manual close cooldown: **14 days**. + +--- + +#### Low Watch Rate + +> A user watches a small fraction of what they request. + +| Parameter | Default | Description | +|---|---|---| +| `MIN_REQUESTS_WATCHRATE` | `10` | Minimum requests before the ratio is considered meaningful. | +| `LOW_WATCH_RATE` | `0.2` | Ratio of `plays / requests` below which an alert fires (default: under 20%). | + +- Manual close cooldown: **14 days**. + +--- + +#### Frequent Declines + +> A user has had multiple requests declined in a rolling window. + +| Parameter | Default | Description | +|---|---|---| +| `MIN_DECLINES` | `3` | Minimum declined requests in the lookback window. | +| `DECLINE_LOOKBACK_DAYS` | `60` | Rolling window in days. | + +- Does not require Tautulli. +- Manual close cooldown: **14 days**. + +--- + +### System alerts + +#### No Tautulli Watch Data + +> Tautulli is configured but no plays matched any Overseerr user. + +This usually means emails don't align between the two services. Check that users have the same email address in both Overseerr and Tautulli (or that display names match as a fallback). + +- Manual close cooldown: **1 day**. + +--- + +## Alert lifecycle + +``` +Condition detected + │ + ▼ + [OPEN] ◄──────────────────────────────────────┐ + │ │ + ┌────┴───────────────┐ Cooldown │ + │ │ expires │ + ▼ ▼ │ +Condition Manually │ +clears closed │ + │ │ │ + ▼ ▼ │ +[AUTO-RESOLVED] [CLOSED] ──── Condition ────────┘ + no cooldown cooldown returns after + reopens suppresses cooldown + immediately re-open +``` + +- **Auto-resolved** alerts have no cooldown and will reopen immediately if the condition returns. +- **Manually closed** alerts suppress re-opening for a category-specific number of days (see cooldowns above). +- Reopening a manually closed alert via the UI clears the cooldown immediately.