{"id":51611390,"url":"https://github.com/braboj/demo-sensor-app","last_synced_at":"2026-07-12T08:03:55.807Z","repository":{"id":274966027,"uuid":"921196029","full_name":"braboj/demo-sensor-app","owner":"braboj","description":"Multi-service demo simulating industrial plant sensor readings: Flask REST API, Angular SPA, PostgreSQL, and Grafana — orchestrated with Docker Compose.","archived":false,"fork":false,"pushed_at":"2026-07-04T10:22:17.000Z","size":886,"stargazers_count":2,"open_issues_count":20,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-04T10:24:39.906Z","etag":null,"topics":["angular","dashboard","demo","docker","docker-compose","flask","grafana","gunicorn","iot","postgresql","python","rest-api","sensor-data","sqlalchemy","typescript"],"latest_commit_sha":null,"homepage":"https://sensor-app-frontend.onrender.com/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/braboj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-23T14:21:21.000Z","updated_at":"2026-07-04T10:06:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"c885ecef-11c0-4180-8e63-086493f53bd5","html_url":"https://github.com/braboj/demo-sensor-app","commit_stats":null,"previous_names":["braboj/sensor-data-app","braboj/demo-sensor-app"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/braboj/demo-sensor-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braboj%2Fdemo-sensor-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braboj%2Fdemo-sensor-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braboj%2Fdemo-sensor-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braboj%2Fdemo-sensor-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/braboj","download_url":"https://codeload.github.com/braboj/demo-sensor-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braboj%2Fdemo-sensor-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35385773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-12T02:00:06.386Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","dashboard","demo","docker","docker-compose","flask","grafana","gunicorn","iot","postgresql","python","rest-api","sensor-data","sqlalchemy","typescript"],"created_at":"2026-07-12T08:03:54.983Z","updated_at":"2026-07-12T08:03:55.793Z","avatar_url":"https://github.com/braboj.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/braboj/demo-sensor-app/actions/workflows/ci.yml/badge.svg)](https://github.com/braboj/demo-sensor-app/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/braboj/demo-sensor-app/actions/workflows/codeql.yml/badge.svg)](https://github.com/braboj/demo-sensor-app/actions/workflows/codeql.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n# Sensor Dashboard\n\n*A multi-service demo that simulates industrial-plant sensor readings —\ngenerated, stored, streamed live, and charted.*\n\nA Python (Flask) backend generates sensor data via a standalone worker, stores\nit in PostgreSQL, and exposes it through a versioned REST API; an Angular\nfrontend shows a live table and embeds Grafana for charting.\n\n**🌐 Live demo:** \u003chttps://sensor-app-frontend.onrender.com\u003e — the **Live Table**\ntab streams new readings in real time, and the **Charts** tab embeds the Grafana\ndashboard. It runs on Render's free tier, so the first request after a while may\ncold-start for ~30–60s.\n\n## Features\n\n- **Live table** — Angular SPA (standalone components) with explicit loading,\n  empty, and error states; new readings arrive over **Server-Sent Events**\n- **Charts** — the embedded Grafana \"Sensor Readings\" dashboard with a metric\n  selector (temperature / humidity / vibration) and a moving-average trend,\n  provisioned as code.\n- **Versioned REST API** — `/api/v1/sensors` with a bounded `?limit=`, ISO-8601\n  UTC timestamps, and RFC 9457 JSON errors; plus `/health` and `/ready`.\n- **Sensor Simulator** — the simulator runs as its own worker process, not a\n  thread inside the web app.\n- **Deployable** — a free-tier [Render blueprint](render.yaml) runs the whole\n  stack (see [Deploy](docs/DEPLOY.md)).\n\n## Quick start\n\nPrerequisites:\n\n- [Docker](https://docs.docker.com/engine/install/)\n- [git](https://git-scm.com/downloads)\n\nClone the repository and start the full stack:\n\n```bash\ngit clone https://github.com/braboj/demo-sensor-app\ncd demo-sensor-app\ndocker compose up\n```\n\nThis starts the frontend, backend, a PostgreSQL database, a one-shot migration\nstep, the data-generator worker, and Grafana:\n\n- [Frontend (Angular) / localhost:4200](http://localhost:4200)\n- [Backend (Flask) / localhost:5000](http://localhost:5000)\n- [Grafana / localhost:3000](http://localhost:3000)\n\nGrafana ships with a PostgreSQL datasource and the \"Sensor Readings\" dashboard\nprovisioned as code (`deploy/grafana/provisioning/`). The admin login is set via\nthe `GRAFANA_ADMIN_USER` / `GRAFANA_ADMIN_PASSWORD` environment variables (see\n`docker-compose.yml`) — set your own before exposing Grafana beyond local use.\n\n## Usage\n\nFetch the most recent readings (the last 100 by default):\n\n```bash\ncurl http://localhost:5000/api/v1/sensors\n```\n\n```json\n[\n  {\n    \"id\": 1940,\n    \"timestamp\": \"2026-07-01T06:27:14.147279+00:00\",\n    \"temperature\": 4.4,\n    \"humidity\": 93.14,\n    \"vibration\": 0\n  },\n  {\n    \"id\": 1939,\n    \"timestamp\": \"2026-07-01T06:27:04.139090+00:00\",\n    \"temperature\": 11.32,\n    \"humidity\": 92.68,\n    \"vibration\": 1\n  }\n]\n```\n\nVibration is a coded value: `0` = none, `1` = detected.\n\nBound the result with `?limit=` (an integer 1–100; a non-integer or\nout-of-range value returns `400`, never a silent fallback):\n\n```bash\ncurl \"http://localhost:5000/api/v1/sensors?limit=1\"\n```\n\nSubscribe to the live stream (Server-Sent Events) — new readings arrive as they\nare recorded (see [ADR-0005](docs/decisions/0005-realtime-delivery-sse.md)):\n\n```bash\ncurl -N http://localhost:5000/api/v1/sensors/stream\n```\n\nOperational endpoints: `GET /health` (liveness — 200 if the process is up) and\n`GET /ready` (readiness — 200 if the database is reachable, else 503).\n\n## Project structure\n\n| Path | Purpose |\n|------|---------|\n| `backend/` | Flask REST API + data-generator worker |\n| `backend/src/sensor_api/` | App factory, config, blueprints (sensors, health), worker |\n| `backend/migrations/` | Alembic migrations |\n| `backend/tests/` | pytest suite |\n| `frontend/` | Angular SPA (standalone components) |\n| `frontend/src/app/home/` | Live readings table + SSE |\n| `frontend/src/app/charts/` | Embedded Grafana dashboard |\n| `frontend/src/environments/` | API / Grafana URLs (dev + prod) |\n| `deploy/grafana/` | Grafana image + datasource/dashboard provisioned as code |\n| `docs/` | ONBOARDING, PLAYBOOK, DEPLOY, decisions (ADRs), history |\n| `docs/arc42/` | Architecture documentation (arc42) |\n| `docker-compose.yml` | Full local stack (backend, frontend, db, grafana, worker) |\n| `render.yaml` | Render free-tier blueprint |\n\n## Development\n\n`docker compose up` (above) runs the whole stack. To work on a service on its\nown — PostgreSQL is the only external dependency (`docker compose up db` starts\none):\n\nBackend (Python 3.12, from `backend/`):\n\n```bash\npip install -r requirements.txt\npytest \u0026\u0026 mypy src --strict          # tests + strict type check\nflask --app sensor_api run           # dev server (needs a reachable PostgreSQL)\npython -m sensor_api.worker          # run the data generator\n```\n\nFrontend (Node 22, from `frontend/`):\n\n```bash\nnpm ci\nnpm start                            # ng serve on :4200\nnpm test \u0026\u0026 npx eslint .             # unit tests + lint\n```\n\nThe backend reads its configuration from environment variables — copy the\ncommitted `backend/.env.example` to `backend/.env` and adjust. See\n[Onboarding](docs/ONBOARDING.md) for full setup and [Playbook](docs/PLAYBOOK.md)\nfor day-to-day commands.\n\n## Configuration\n\nServices are configured through environment variables (documented in\n`backend/.env.example` and [`docker-compose.yml`](docker-compose.yml)); the most\nimportant:\n\n| Variable | Service | Default | Description |\n|----------|---------|---------|-------------|\n| `DATABASE_URL` | backend, grafana | — | PostgreSQL connection string. |\n| `SECRET_KEY` | backend | — | Flask secret key (**sensitive** — set your own). |\n| `CORS_ORIGINS` | backend | — | Allowed frontend origin (never `*`). |\n| `APP_CONFIG` | backend | `production` | Config profile (`development` / `production`). |\n| `SAMPLE_INTERVAL_SECONDS` | backend, worker | `10` | Generator sample interval. |\n| `LOG_LEVEL` | backend, worker | `INFO` | Log level for the structured JSON logs (never `DEBUG` in prod). |\n| `RUN_INPROCESS_GENERATOR` | backend | `false` | Run the generator in-process (free-tier only). |\n| `WEB_CONCURRENCY` | backend | `1` | gunicorn worker count. |\n| `API_URL` | frontend (build) | same-origin `/api/v1/sensors` | Absolute backend URL baked into the bundle. |\n| `GRAFANA_URL` | frontend (build) | empty | Grafana dashboard URL for the Charts view. |\n| `GRAFANA_ADMIN_USER` / `GRAFANA_ADMIN_PASSWORD` | grafana | — | Local admin login (**sensitive**). |\n\nSee [Deploy](docs/DEPLOY.md) for the full deployment variable set.\n\n## Deploy\n\nThe repo ships a [`render.yaml`](render.yaml) blueprint that runs the whole\nstack on Render's free tier — managed Postgres, the Dockerized backend, the\nstatic frontend, and Grafana. The live demo above runs from this blueprint. See\n[Deploy](docs/DEPLOY.md) for the walkthrough and the free-tier caveats.\n\n## Next steps\n\n- To understand the architecture, see the [Architecture docs (arc42)](docs/arc42/README.md)\n- To set up a dev environment, see [Onboarding](docs/ONBOARDING.md)\n- For day-to-day commands, see the [Playbook](docs/PLAYBOOK.md)\n- To deploy on Render (free tier), see [Deploy](docs/DEPLOY.md)\n- To learn more about the project, see the [Assignment](docs/history/ASSIGNMENT.md)\n- To read about the solution, see the [Solution](docs/history/SOLUTION.md)\n- To contribute, see [Contributing](CONTRIBUTING.md)\n- To leave feedback, visit [Discussions](https://github.com/braboj/demo-sensor-app/discussions)\n\n## License\n\nLicensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbraboj%2Fdemo-sensor-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbraboj%2Fdemo-sensor-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbraboj%2Fdemo-sensor-app/lists"}