Python Agent → Flask Server → Live Dashboard
A self-hosted, real-time machine monitoring system built from scratch.
NetPulse collects live CPU, memory, disk, and process data from any number of machines and displays them on a web dashboard — similar in concept to Datadog or Grafana, but built entirely from scratch in Python and vanilla JavaScript.
[Machine A: agent.py] ──┐
[Machine B: agent.py] ──┼──► app.py (POST /heartbeat) ──► live_servers{}
[Machine C: agent.py] ──┘ Flask (GET /servers) ──► dashboard.html
| Component | File | Technology | Role |
|---|---|---|---|
| Agent | agent.py |
Python + psutil | Collects metrics, POSTs to server every 5s |
| Server | app.py |
Python + Flask | Receives data, serves API and dashboard |
| Alert logic | alert.py |
Python | Evaluates thresholds, returns OK / WARNING / CRITICAL |
| Frontend | templates/ + static/ |
HTML + CSS + JS | Polls /servers, renders live charts and tables |
netpulse/
├── agent.py # Runs on every machine you want to monitor
├── alert.py # Alert threshold logic (called by app.py)
├── app.py # Flask server — API + serves the dashboard
├── config.py # All configuration in one place
├── requirements.txt # Python dependencies
├── README.md
├── static/
│ ├── charts.js # Fetch, charts, and table logic
│ └── style.css # Styles
└── templates/
└── dashboard.html # Main UI
All settings live in config.py. Both agent.py and app.py import from it.
# config.py
CPU_THRESHOLD = 80 # % — triggers CRITICAL alert
MEMORY_THRESHOLD = 85 # % — triggers WARNING alert
DISK_THRESHOLD = 90 # % — reserved for future disk alerting
REFRESH_INTERVAL = 5 # seconds between dashboard pollsNote:
SERVER_URLis no longer inconfig.py. The agent automatically detects its own LAN IP and builds the URL at runtime. See the Agent section below.
Install all Python dependencies:
pip install -r requirements.txtrequirements.txt:
flask
flask-cors
psutil
requests
The dashboard has no dependencies — Chart.js loads from CDN automatically.
Run this once, on the machine that acts as your central server:
python app.py
⚠️ Always usepython app.py, notflask run. Theflask runcommand ignores thehost="0.0.0.0"setting in the code and defaults to127.0.0.1, which makes the server unreachable from other machines on the network.
Flask binds to 0.0.0.0:5000 — meaning it listens on all network interfaces, including your LAN IP. Open http://localhost:5000 in a browser to view the dashboard.
On every machine you want to monitor, run:
python agent.pyOn startup, the agent prints its detected IP and the server URL it will POST to:
[NetPulse Agent] Starting — DESKTOP-ABC (192.168.0.107) → http://192.168.0.107:5000/heartbeat
Machine cards appear on the dashboard automatically within one poll cycle.
Open a browser and go to:
http://<server-ip>:5000
agent.py automatically detects its own LAN IP using a UDP socket trick — no data is sent, it just forces the OS to reveal which network interface it uses for outbound traffic:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0] # actual LAN IP, not 127.0.0.1It then builds SERVER_URL from that IP:
SERVER_URL = f"http://{IP}:5000/heartbeat"This works automatically when the agent and server run on the same machine. If you run the agent on a different machine, change the line to point at the server's LAN IP:
# agent.py — for remote machines
SERVER_URL = "http://192.168.0.107:5000/heartbeat"| Feature | Description |
|---|---|
| Summary bar | Total / Online / Offline / Critical counts, always visible at top |
| Machine cards | One card per agent — live CPU, Memory, Disk bars with colour thresholds |
| Status badge | ONLINE / OFFLINE — machine marked offline after 15s with no heartbeat |
| Alert indicator | OK (green) · WARNING >75% memory (yellow) · CRITICAL >85% CPU or offline (red) |
| Detail panel | Click any card — opens CPU and Memory line graphs (last 20 readings) |
| Process table | Top 15 processes by CPU inside the detail panel, with inline bar chart |
| Overview table | All machines in one scrollable table at the bottom — also clickable |
Evaluated server-side in alert.py on every GET /servers request. Thresholds are read from config.py.
| Level | Condition | Colour |
|---|---|---|
CRITICAL |
CPU > CPU_THRESHOLD or machine is offline |
🔴 Red |
WARNING |
Memory > MEMORY_THRESHOLD |
🟡 Yellow |
OK |
All metrics within thresholds and machine online | 🟢 Green |
Called by agent.py every REFRESH_INTERVAL seconds. Registers or updates a machine.
Request body:
{
"name": "DESKTOP-ABC",
"ip": "192.168.0.107",
"cpu": 42.5,
"memory": 67.2,
"disk": 55.0,
"processes": [{ "name": "python.exe", "cpu": 12.3 }]
}Response: { "message": "received" } — HTTP 200
Called by the dashboard every REFRESH_INTERVAL seconds. Returns all known machines.
Response:
[
{
"name": "DESKTOP-ABC",
"ip": "192.168.0.107",
"cpu": 42.5,
"memory": 67.2,
"disk": 55.0,
"status": "online",
"alert": "OK",
"processes": [...]
}
]Called once by the dashboard on load to read the refresh interval.
Response: { "refresh_interval": 5 }
Agent says "Server not reachable"
- Make sure you started the server with
python app.py, notflask run - Confirm the server is running by opening
http://<server-ip>:5000in a browser - Check your firewall — port
5000must be open for inbound TCP
Machine shows Offline immediately
- Offline timeout is 15 seconds; the agent sends every 5s so this should not trigger normally
- Check the agent terminal — it logs a timestamped line for every successful send
Process table shows "No process data"
- On Linux, some processes require the agent to run with
sudoto read CPU data
CPU always shows 0%
- psutil always returns 0 on the very first
cpu_percent()call — the agent primes it on startup with a 1-second sleep to fix this
Dashboard not loading charts.js or style.css
- Make sure
static/andtemplates/are in the same directory asapp.py - Do not pass
static_folderortemplate_folderarguments toFlask()— the default names are picked up automatically
- Persistent storage — Add SQLite or InfluxDB to keep history and plot graphs over hours or days
- Email / Slack alerts — Fire a notification from
alert.pywhen a machine hits CRITICAL - Authentication — Add Flask-Login so only you can view the dashboard
- Docker — Package the server in a container for one-command deployment
- Windows disk path — Change
'/'inagent.pyto'C:\\'for Windows support - Public access — Deploy
app.pyto a VPS and point all agents at its public IP