A web dashboard for monitoring multiple hosts in parallel — real-time CPU/memory/latency polling, live status classification, and historical trends, all served through a Flask backend with a vanilla-JS frontend.
This is the web counterpart to Parallel System Monitor v1.0.0 (the CustomTkinter desktop app). The entire polling engine — host models, the agent client, the parser, and the parallel dispatcher — is reused unchanged from v1.0.0. Only the interface layer is new: a Flask + Jinja + vanilla JS web app in place of the desktop GUI.
Row expand-on-click — per-host poll timestamps, independent per row

Offline host detail — real error message, not a placeholder

Live search — in-memory filter, no new poll triggered

Recent system events — real status transitions from live polls

- Real-time host polling via a proven parallel dispatcher (polls all hosts concurrently, not one at a time)
- Browser-triggered polling — a "Poll now" button and an optional 5-second auto-refresh, both updating the page in place with no full reload
- Live search and sort on the host table (client-side, doesn't trigger a new poll)
- Per-host expand-on-click detail (start/finish timestamps, error messages for unreachable hosts)
- Sparkline trend lines on the summary cards, driven by real stored history
- 24-hour health score chart in the footer, computed from real poll history
- Light/dark mode, with your preference saved across visits
A few decisions worth explaining, since they weren't the obvious defaults:
Browser-triggered polling, not a background server poller. The dispatcher only runs when the browser asks it to — via the "Poll now" button or the auto-refresh timer — rather than a server-side background thread polling continuously regardless of who's watching. This keeps the app simple (no background thread lifecycle to manage) and matches how the desktop app's own "Poll now" button worked, just moved to the browser side of an HTTP request.
One shared function is the single source of truth for what the dashboard shows. web/dashboard_state.py's build_dashboard_state() takes a list of poll results and returns everything the page needs to render — status counts, health score, per-host display data, sparkline points, timeline scaling. Both the initial page load (GET /) and every live poll (POST /api/poll) call this same function, so there's exactly one place that logic lives. Early on, this logic was duplicated between the two routes, which caused a real bug — fixed by consolidating into this shared function.
In-place DOM updates, not page reloads. Early versions of "Poll now" and auto-refresh worked by reloading the whole page after every poll. That's simple but visibly janky on a 5-second timer, so the JS was rewritten to update just the cards, table, timeline, and events panel directly from the JSON response — the page itself never reloads after the first load.
git clone https://github.com/Aayush29052006/Parallel-System-Monitor-V2.git
cd Parallel-System-Monitor-V2
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtStart a few mock agents with a varied load spread (so you see all 4 status colors), leaving one port unstarted for a genuine offline host:
python mock-agents/agent_server.py --port 9001 --fake-load 20 & # healthy
python mock-agents/agent_server.py --port 9002 --fake-load 35 & # healthy
python mock-agents/agent_server.py --port 9003 --fake-load 55 & # warning
python mock-agents/agent_server.py --port 9004 --fake-load 68 & # warning
python mock-agents/agent_server.py --port 9005 --fake-load 92 & # critical
# port 9006 intentionally left unstarted — shows as offlineStart the app:
flask --app web.app:create_app runOpen http://127.0.0.1:5000 and click Poll now.
To stop everything: Ctrl+C in the Flask terminal, then kill the background mock agents in one command:
pkill -f agent_server.pygit clone https://github.com/Aayush29052006/Parallel-System-Monitor-V2.git
cd Parallel-System-Monitor-V2
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txtStart the mock agents (same varied spread, one port left off):
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9001 --fake-load 20"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9002 --fake-load 35"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9003 --fake-load 55"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9004 --fake-load 68"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9005 --fake-load 92"Start the app:
flask --app web.app:create_app runOpen http://127.0.0.1:5000 and click Poll now.
To stop everything: Ctrl+C in the Flask terminal, then kill all the background mock agent processes in one command:
Get-Process python -ErrorAction SilentlyContinue | Stop-Process -ForceThis kills every Python process currently running on your machine, not just the mock agents — safe here since they're the only Python processes typically running during local testing, but worth knowing before you run it if you have other Python work open.
Windows note: a virtual environment is tied to the absolute path it was created at. If you move or rename the project folder after creating
.venv, activating it will fail withFatal error in launcher: Unable to create process.... Delete.venvand recreate it (python -m venv .venv) at the new location rather than trying to reuse the old one.
python -m pytest67 tests covering the core polling engine (reused from v1.0.0), storage layer, and the web routes/state logic — all passing on Linux and Windows.
Python, Flask, Jinja2, vanilla JavaScript (no frontend framework), SQLite, psutil (mock agents), pytest.
This repo reuses the entire psm core package — models, agent client, parser, dispatcher, and storage — unchanged from Parallel System Monitor v1.0.0, the original CustomTkinter desktop app. Only the interface layer differs.
MIT — see LICENSE.


