Skip to content

Repository files navigation

CI CodeQL License: MIT

Sensor Dashboard

A multi-service demo that simulates industrial-plant sensor readings — generated, stored, streamed live, and charted.

A Python (Flask) backend generates sensor data via a standalone worker, stores it in PostgreSQL, and exposes it through a versioned REST API; an Angular frontend shows a live table and embeds Grafana for charting.

🌐 Live demo: https://sensor-app-frontend.onrender.com — the Live Table tab streams new readings in real time, and the Charts tab embeds the Grafana dashboard. It runs on Render's free tier, so the first request after a while may cold-start for ~30–60s.

Features

  • Live table — Angular SPA (standalone components) with explicit loading, empty, and error states; new readings arrive over Server-Sent Events
  • Charts — the embedded Grafana "Sensor Readings" dashboard with a metric selector (temperature / humidity / vibration) and a moving-average trend, provisioned as code.
  • Versioned REST API/api/v1/sensors with a bounded ?limit=, ISO-8601 UTC timestamps, and RFC 9457 JSON errors; plus /health and /ready.
  • Sensor Simulator — the simulator runs as its own worker process, not a thread inside the web app.
  • Deployable — a free-tier Render blueprint runs the whole stack (see Deploy).

Quick start

Prerequisites:

Clone the repository and start the full stack:

git clone https://github.com/braboj/demo-sensor-app
cd demo-sensor-app
docker compose up

This starts the frontend, backend, a PostgreSQL database, a one-shot migration step, the data-generator worker, and Grafana:

Grafana ships with a PostgreSQL datasource and the "Sensor Readings" dashboard provisioned as code (deploy/grafana/provisioning/). The admin login is set via the GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD environment variables (see docker-compose.yml) — set your own before exposing Grafana beyond local use.

Usage

Fetch the most recent readings (the last 100 by default):

curl http://localhost:5000/api/v1/sensors
[
  {
    "id": 1940,
    "timestamp": "2026-07-01T06:27:14.147279+00:00",
    "temperature": 4.4,
    "humidity": 93.14,
    "vibration": 0
  },
  {
    "id": 1939,
    "timestamp": "2026-07-01T06:27:04.139090+00:00",
    "temperature": 11.32,
    "humidity": 92.68,
    "vibration": 1
  }
]

Vibration is a coded value: 0 = none, 1 = detected.

Bound the result with ?limit= (an integer 1–100; a non-integer or out-of-range value returns 400, never a silent fallback):

curl "http://localhost:5000/api/v1/sensors?limit=1"

Subscribe to the live stream (Server-Sent Events) — new readings arrive as they are recorded (see ADR-0005):

curl -N http://localhost:5000/api/v1/sensors/stream

Operational endpoints: GET /health (liveness — 200 if the process is up) and GET /ready (readiness — 200 if the database is reachable, else 503).

Project structure

Path Purpose
backend/ Flask REST API + data-generator worker
backend/src/sensor_api/ App factory, config, blueprints (sensors, health), worker
backend/migrations/ Alembic migrations
backend/tests/ pytest suite
frontend/ Angular SPA (standalone components)
frontend/src/app/home/ Live readings table + SSE
frontend/src/app/charts/ Embedded Grafana dashboard
frontend/src/environments/ API / Grafana URLs (dev + prod)
deploy/grafana/ Grafana image + datasource/dashboard provisioned as code
docs/ ONBOARDING, PLAYBOOK, DEPLOY, decisions (ADRs), history
docs/arc42/ Architecture documentation (arc42)
docker-compose.yml Full local stack (backend, frontend, db, grafana, worker)
render.yaml Render free-tier blueprint

Development

docker compose up (above) runs the whole stack. To work on a service on its own — PostgreSQL is the only external dependency (docker compose up db starts one):

Backend (Python 3.12, from backend/):

pip install -r requirements.txt
pytest && mypy src --strict          # tests + strict type check
flask --app sensor_api run           # dev server (needs a reachable PostgreSQL)
python -m sensor_api.worker          # run the data generator

Frontend (Node 22, from frontend/):

npm ci
npm start                            # ng serve on :4200
npm test && npx eslint .             # unit tests + lint

The backend reads its configuration from environment variables — copy the committed backend/.env.example to backend/.env and adjust. See Onboarding for full setup and Playbook for day-to-day commands.

Configuration

Services are configured through environment variables (documented in backend/.env.example and docker-compose.yml); the most important:

Variable Service Default Description
DATABASE_URL backend, grafana PostgreSQL connection string.
SECRET_KEY backend Flask secret key (sensitive — set your own).
CORS_ORIGINS backend Allowed frontend origin (never *).
APP_CONFIG backend production Config profile (development / production).
SAMPLE_INTERVAL_SECONDS backend, worker 10 Generator sample interval.
LOG_LEVEL backend, worker INFO Log level for the structured JSON logs (never DEBUG in prod).
RUN_INPROCESS_GENERATOR backend false Run the generator in-process (free-tier only).
WEB_CONCURRENCY backend 1 gunicorn worker count.
API_URL frontend (build) same-origin /api/v1/sensors Absolute backend URL baked into the bundle.
GRAFANA_URL frontend (build) empty Grafana dashboard URL for the Charts view.
GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD grafana Local admin login (sensitive).

See Deploy for the full deployment variable set.

Deploy

The repo ships a render.yaml blueprint that runs the whole stack on Render's free tier — managed Postgres, the Dockerized backend, the static frontend, and Grafana. The live demo above runs from this blueprint. See Deploy for the walkthrough and the free-tier caveats.

Next steps

License

Licensed under the MIT License.

About

Multi-service demo simulating industrial plant sensor readings: Flask REST API, Angular SPA, PostgreSQL, and Grafana — orchestrated with Docker Compose.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages