Excitel Dashboard is a self-hosted broadband usage dashboard for Excitel accounts. It combines a React frontend, an Express API, PostgreSQL, and a scheduled worker that imports usage history from the Excitel self-care API.
This is an independent, unofficial project. It is designed for local or private use and stores account usage history, including session metadata and IP addresses, in your local database.
The screenshots below show the desktop layout with the fully populated July 2026 period selected. They are sanitized crops: account names, usernames, IP addresses, and other connection identifiers are intentionally kept out of frame.
- Usage summaries and trends by billing month
- Daily usage and session-duration charts
- Session search and termination-cause breakdowns
- IP address insights
- Scheduled synchronization from the Excitel API
- PostgreSQL-backed history with rotating local backups
- Docker Compose deployment behind Nginx
The per-session (device split) breakdown of usage data is only possible if you set your router to restart every 24 hours.
When the router restarts, the ISP — Excitel in this case — automatically terminates the current session and a new one starts. Each stored session therefore covers roughly a 24-hour window, which is what makes the daily and per-session figures in the dashboard meaningful.
Without a scheduled restart, a single session can stay open for days or weeks, and the dashboard will show one long session instead of a usable day-by-day split. Most routers expose this as a scheduled reboot or auto-restart setting in their admin interface. Set it for a time when the connection is idle, such as early morning.
- Language: TypeScript with strict checking across every package
- Frontend: React 19, Vite 7, Tailwind CSS 4, Recharts
- Backend: Express, Helmet, PostgreSQL client, executed directly by Bun
- Worker: Bun, node-cron, PostgreSQL client, executed directly by Bun
- Infrastructure: Docker Compose, Nginx, PostgreSQL 16
src/ React UI, hooks, utilities, and API client
shared/ Shared TypeScript API and upstream contracts
backend/src/ Express routes, database access, and sync service
worker/src/ Scheduled sync and database backup worker
scripts/theme-init.ts TypeScript source for the generated pre-paint theme script
database/init.sql PostgreSQL schema bootstrap
docker/ Frontend, backend, worker, and Nginx configuration
docker-compose.yml Local full-stack orchestration
For the recommended workflow:
- Docker Desktop or a compatible Docker Engine with Compose
For running individual services directly:
- Bun 1.x
- PostgreSQL 16, or the PostgreSQL container started from Compose
Docker is the easiest way to run the complete stack.
-
Create a local environment file:
cp .env.example .env
-
Edit
.envand set a local PostgreSQL password plus either your Excitel username/password or a currentSELFCARE_COOKIE. -
Build and start the stack:
docker compose up -d --build
-
Open the dashboard at http://localhost:3080.
The worker syncs on startup when RUN_ON_STARTUP=true and then follows SYNC_CRON_SCHEDULE. It also creates database backups according to BACKUP_CRON_SCHEDULE.
The Compose ports are bound to loopback by default, so the database and API are not exposed to your local network:
- Frontend: http://localhost:3080
- Backend health check: http://localhost:3000/health
- PostgreSQL:
127.0.0.1:5432
Useful commands:
docker compose ps
docker compose logs --tail=50
docker compose logs -f worker
docker compose downdocker compose down -v removes the local PostgreSQL and backup volumes. Use it only when you intentionally want to delete local data and recreate the schema from database/init.sql.
.env.example is safe to commit. .env is ignored by Git and must stay local.
| Variable | Purpose |
|---|---|
POSTGRES_DB |
Database name; defaults to excitel |
POSTGRES_USER |
Database user; defaults to excitel |
POSTGRES_PASSWORD |
Required local database password |
EXCITEL_USERNAME |
Excitel login username; not needed when using SELFCARE_COOKIE |
EXCITEL_PASSWORD |
Excitel login password; not needed when using SELFCARE_COOKIE |
SELFCARE_COOKIE |
Optional current self-care cookie; treat it like a password |
CORS_ORIGIN |
Comma-separated origins allowed for direct frontend development |
API_AUTH_REQUIRED |
Set to true to require Basic Auth on every /api route |
API_AUTH_USERNAME |
Server-only Basic Auth username; never use a VITE_* variable |
API_AUTH_PASSWORD |
Server-only Basic Auth password; use a long random value |
API_RATE_LIMIT_MAX / API_RATE_LIMIT_WINDOW_MS |
Read API request limit |
SYNC_RATE_LIMIT_MAX / SYNC_RATE_LIMIT_WINDOW_MS |
Manual sync request limit |
MAX_UPSTREAM_RESPONSE_BYTES |
Maximum upstream JSON response size |
MAX_SESSIONS_PER_SYNC |
Maximum sessions accepted from one upstream response |
MAX_MONTHS_PER_RESPONSE |
Maximum months accepted from one upstream response |
SYNC_CRON_SCHEDULE |
Worker sync schedule; default is every two hours |
BACKUP_CRON_SCHEDULE |
Backup schedule; default is 2:00 AM UTC daily |
BACKUP_RETENTION_DAYS |
Number of days to retain backups; default is 30 |
RUN_ON_STARTUP |
Set to false to skip the initial sync |
Docker builds the frontend with VITE_API_BASE_URL=/api. Values prefixed with VITE_ are public build-time configuration and must never contain credentials.
The Docker workflow above is the canonical local setup. Direct service commands are useful when developing a single package.
bun install
bun run devThe Vite development proxy intentionally targets the live Excitel API by default; see vite.config.ts. To point the browser at a locally running backend instead, start the backend and run:
VITE_API_BASE_URL=http://localhost:3000/api bun run devThe backend allows the default Vite origins (localhost:5173 and 127.0.0.1:5173). Set CORS_ORIGIN if you use a different development origin.
Start PostgreSQL first:
docker compose up -d postgresThen run the backend with the root environment file. Replace the password placeholder with the value in your local .env:
cd backend
bun install
DATABASE_URL='postgres://excitel:your_secure_password_here@127.0.0.1:5432/excitel' bun --env-file=../.env run devThe backend executes backend/src/index.ts directly and listens on port 3000 by default.
The worker needs the same credentials and DATABASE_URL as the backend:
cd worker
bun install
DATABASE_URL='postgres://excitel:your_secure_password_here@127.0.0.1:5432/excitel' bun --env-file=../.env run startThe worker executes worker/src/index.ts directly. Backend and worker implementations remain separate; only their shared HTTP and upstream contracts live in shared/contracts.ts.
The API is available through the frontend at /api or directly at http://localhost:3000/api.
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Database-backed health check |
GET |
/api/months |
List imported billing months |
GET |
/api/sessions/:monthId |
Return sessions for M-YYYY, MM-YYYY, or all |
GET |
/api/sync-status |
Return sync metadata |
POST |
/api/sync/:monthId |
Trigger a sync for one month |
Example read-only request:
curl http://localhost:3000/api/months
curl http://localhost:3000/api/sessions/allThe local Compose default keeps API authentication disabled because the stack is bound to loopback. Before allowing access from another machine, set API_AUTH_REQUIRED=true plus a server-only API_AUTH_USERNAME and API_AUTH_PASSWORD; browsers will use the standard Basic Auth prompt. Do not expose port 3000 or the sync endpoint directly to the internet without TLS, authentication, authorization, rate limiting, and an appropriate deployment boundary. CORS is not an authentication control.
- PostgreSQL data is stored in the
postgres_dataDocker volume. - Worker backups are stored in the
backupsDocker volume and are rotated by age. - The schema is applied by PostgreSQL only when the data volume is first created.
- After changing
database/init.sql, either apply the DDL manually or recreate the local database withdocker compose down -v.
Run these from the repository root:
bun install
bun run typecheck:all
bun run lint
bun test
bun run buildThe frontend/root tooling, backend, and worker each have their own package.json and bun.lock; run bun install independently in the repository root, backend/, and worker/. There is no package workspace that hoists dependencies. bun run typecheck checks browser code and root tooling, while bun run typecheck:all also checks the backend and worker.
scripts/theme-init.ts is bundled by Bun into the ignored public/theme-init.js before both bun run dev and bun run build. The generated file remains a synchronous classic script so the saved light/dark/system preference is applied before the first paint without inline JavaScript.
The Docker Compose build is the final integration path: backend and worker use repository-root build contexts so their validation stages can type-check the shared contracts before the production images retain only runtime dependencies.
- Never commit
.env, real Excitel credentials,SELFCARE_COOKIE, database URLs containing passwords, database dumps, logs, or screenshots containing account data or IP addresses. - Treat all
VITE_*values as public because they are embedded into the browser bundle. - The Excitel service URLs in the source are integration endpoints, not secrets.
- Keep the local API and database bound to loopback unless you have deliberately secured the deployment.
- Before publishing, review both the current files and Git history. If a real credential was ever committed, rotate it and remove it from history before making the repository public.
This project is released under the MIT License.
Before creating or pushing to a public GitHub repository:
- Confirm
.envis not tracked:git status --shortandgit ls-files .env. - Review tracked files for credentials, dumps, logs, captures, and personal data.
- Review Git history for credentials, not just the current checkout.
- Confirm the included MIT License reflects your preferred copyright attribution.
- Run the quality checks above and verify the Docker stack locally.
This repository does not publish anything automatically. Creating or pushing a GitHub repository is a separate, explicit step.

