No description
  • Python 61.9%
  • HTML 37.7%
  • Dockerfile 0.2%
  • Shell 0.2%
Find a file
Alex Au 2018d43ddf feat: add fan_unit to metrics table, polling, and API responses
Include fan_unit column in database schema with migration script.
Update IPMI poller to extract unit information from hardware.
Modify API responses to include unit for dashboard and charting.
Allows system to distinguish between RPM and percentage readings.
2026-04-24 13:01:02 +08:00
.clinerules
api
app
auth
config
models
polling
scripts
templates
tests
.gitignore
app.py
config.py
database.py
docker-compose.yml
Dockerfile
nginx.conf
pytest.ini
README.md
requirements.txt
run.py
stop-server.sh

SME Monitoring System

Agentless, dual-protocol (SNMP v2c + IPMI RMCP+) real-time monitoring for SME PCs and servers, delivered as a Docker Compose stack.

Features

  • Real-time monitoring via Socket.IO push notifications
  • Multi-protocol support:
    • Ping (ICMP) for availability and latency
    • SNMP v2c for Windows/Linux/Firewall/Switch metrics
    • IPMI RMCP+ for hardware sensors (fan, power, temperature)
  • Device types: Windows, Linux, Firewall, Switch, BMC
  • Vendor-specific OIDs for Firewalls (FortiGate, Cisco, Huawei) and Servers (HPE, Dell, Lenovo)
  • Session-based authentication with Flask-Login and bcrypt password hashing
  • Network scanning for device discovery
  • Dark theme dashboard using AdminLTE 3.2
  • Thread-safe SQLite database with automatic metric retention cleanup
  • Async polling engine with concurrent device polling

Architecture

┌─────────────────────────────────────────────────────┐
│              Docker Host (VPS)                       │
│  ┌──────────────┐        ┌──────────────────────┐  │
│  │    Nginx     │◄──────►│    Flask Dashboard   │  │
│  │  (Port 80)   │        │    (Port 5000)       │  │
│  └──────────────┘        └──────────┬───────────┘  │
│                                     │              │
│                              ┌──────▼───────┐      │
│                              │ Polling Engine│     │
│                              │  Ping+SNMP+IPMI│    │
│                              └──────┬───────┘      │
│                                     │              │
│                              ┌──────▼───────┐      │
│                              │  SQLite DB    │     │
│                              └───────────────┘     │
└─────────────────────────────────────────────────────┘

Technology Stack

Layer Choice Notes
Runtime Python 3.12.3 python:3.12.3-slim Docker image
Web Framework Flask + Flask-SocketIO Socket.IO transport for real-time push
Async Runtime asyncio Used inside polling modules
SNMP Net-SNMP tools snmpget, snmpwalk via subprocess
IPMI pyghmi RMCP+ SDR sensor reads
Database SQLite via sqlite3 stdlib Thread-safe via threading.Lock()
Frontend AdminLTE 3.2 (dark theme) CDN-loaded; no Node.js build step
Reverse Proxy Nginx (stable-alpine) HTTP proxy + WebSocket support
Container Docker Compose v2 Services: dashboard, nginx
Config config.yaml + env vars YAML parsed by PyYAML

Quick Start

Local Development

For local development and review (without Docker):

# 1. Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate  # Linux/Mac
# .venv\Scripts\activate   # Windows

# 2. Install dependencies
pip install -r requirements.txt

# 3. Run the development server
python run.py

# Or with custom options
python run.py --port 8080          # Custom port
python run.py --host 0.0.0.0       # Accessible from network
python run.py --no-polling         # Disable background polling (faster for review)
python run.py --help               # Show all options

The server will start at http://127.0.0.1:5000 with:

  • Username: admin
  • Password: admin

Note: Make sure you're using the virtual environment (source .venv/bin/activate) before running the server.

Production Deployment with Docker

1. Prerequisites

  • Docker Engine 20.10+
  • Docker Compose v2
  • WireGuard VPN (for remote device access)

2. Clone and Setup

# Clone the repository
git clone <repository-url>
cd sme-monitoring

# Create required directories
mkdir -p data config logs

# Set up environment variables (optional)
cp .env.example .env  # if exists
# Or set directly in docker-compose.yml

3. Start the System

# Build and start containers
docker compose up -d

# View logs
docker compose logs -f

4. Access the Dashboard

Open your browser and navigate to: http://your-server-ip:8080

Default credentials:

  • Username: admin
  • Password: admin

⚠️ Change the default password immediately!

Configuration

Environment Variables

Variable Default Description
SECRET_KEY change-me-in-production Flask secret key for session encryption
DASHBOARD_USERNAME admin Default admin username
DASHBOARD_PASSWORD admin Default admin password (plaintext, hashed on startup)
POLL_INTERVAL 10 Polling interval in seconds
POLL_TIMEOUT 5 SNMP/IPMI timeout in seconds
POLL_RETRIES 2 Number of retry attempts for SNMP/IPMI
PING_TIMEOUT 2 Ping timeout in seconds
PING_PACKETS 1 Number of ping packets to send
DB_PATH /app/data/monitoring.db SQLite database path
CONFIG_YAML_PATH /app/config/config.yaml Device configuration YAML path
LOG_LEVEL INFO Logging level (DEBUG/INFO/WARNING/ERROR)
SESSION_DURATION_HOURS 12 Session timeout in hours
CLEANUP_DAYS 30 Metric retention period in days

Adding Devices

Devices can be added via the web UI or API:

Via Web UI

  1. Log in to the dashboard
  2. Navigate to Devices page
  3. Click Add Device
  4. Fill in device details:
    • Name: Display name
    • IP: IP address (via WireGuard tunnel or network)
    • Type: Windows/Linux/Firewall/Switch/BMC
    • Vendor: (for Firewalls/Switches)
    • SNMP Settings: Community string (default: public)
    • IPMI Settings: (optional) BMC credentials

Via API

curl -X POST http://localhost:8080/api/devices \
  -H "Content-Type: application/json" \
  -u admin:admin \
  -d '{
    "name": "My Server",
    "ip": "10.0.0.10",
    "device_type": "linux",
    "snmp_enabled": true,
    "snmp_community": "public",
    "ipmi_enabled": false
  }'

Network Scanning

  1. Click Scan Network on the Devices page
  2. Enter network range (e.g., 10.0.0.0/24)
  3. Enter SNMP community string
  4. Click Start Scan
  5. Add discovered devices with one click

Note: Network scans are limited to /16 prefix to prevent accidental large scans.

Supported Metrics

Ping (All Devices)

Metric Description
Online/Offline Device reachability status
Latency (RTT) Round-trip time in milliseconds

SNMP Metrics

Category Windows Linux Firewall Switch
CPU Usage hrProcessorLoad UCD-SNMP ⚠️ Vendor-specific ⚠️ Vendor-specific
Memory Usage hrMemorySize memTotalReal ⚠️ Vendor-specific ⚠️ Vendor-specific
Hostname sysName sysName sysName sysName
Uptime hrSystemUptime sysUpTime sysUpTime sysUpTime
MAC Addresses ifPhysAddress ifPhysAddress ifPhysAddress ifPhysAddress

Vendor-Specific OIDs

Vendor CPU OID Memory OID
FortiGate fgSysCpuUsage fgSysMemUsage
Cisco ciscoMemoryPool memPoolUsed
Huawei Vendor-specific Vendor-specific

Server Hardware OIDs

Vendor Fan OID Power OID
HPE probesAndSensors probesAndSensors
Dell oem Dell Mgmt oem Dell Mgmt
Lenovo lmFanSensors lmFanSensors

IPMI Metrics (when enabled)

Metric Description
CPU Temperature Processor temperature in °C
Board Temperature System/board temperature in °C
Fan Speed (RPM) Fan rotation speed
Power Consumption Power draw in Watts

Project Structure

sme-monitoring/
├── app.py                  # Main entry point (production)
├── run.py                  # Development server launcher
├── app/
│   └── __init__.py        # Flask app factory, Socket.IO setup
├── api/
│   ├── __init__.py
│   ├── devices.py         # Device CRUD API + web routes
│   ├── metrics.py         # Metrics API
│   └── dashboard.py       # Dashboard data API
├── auth/
│   └── __init__.py        # Authentication, Flask-Login setup
├── config/
│   └── config.yaml        # Device configuration (optional)
├── config.py              # Configuration loader (env vars + YAML)
├── database.py            # SQLite database layer
├── models/
│   ├── __init__.py
│   ├── device.py          # Device dataclass
│   └── user.py            # User dataclass
├── polling/
│   ├── __init__.py
│   ├── ping_poller.py     # ICMP ping implementation
│   ├── snmp_poller.py     # SNMP v2c polling
│   ├── ipmi_poller.py     # IPMI RMCP+ polling
│   └── scheduler.py       # Polling cycle coordinator
├── templates/
│   ├── index.html         # Login page
│   ├── dashboard.html     # Main dashboard
│   ├── devices.html       # Device management
│   └── user.html          # User settings
├── data/                  # SQLite database (runtime)
├── logs/                  # Log files (runtime)
├── certs/                 # TLS certificates (optional)
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── requirements.txt
└── README.md

API Endpoints

All API endpoints require authentication (Basic Auth or session).

Authentication

Method Endpoint Description
GET / Login page
POST /api/auth/login Authenticate
POST /api/auth/logout Logout
POST /api/auth/change-password Change password

Dashboard

Method Endpoint Description
GET /dashboard Dashboard page
GET /api/devices-status Get all devices with latest metrics

Devices

Method Endpoint Description
GET /devices Device management page
GET /api/devices List all devices
POST /api/devices Add device
GET /api/devices/<id> Get specific device
PUT /api/devices/<id> Update device
DELETE /api/devices/<id> Delete device
GET /api/devices/scan?range=<CIDR> Network scan

Metrics

Method Endpoint Description
GET /api/metrics/<id> Get device metrics (limit=50)
GET /api/metrics/<id>/latest Get latest device metrics

System

Method Endpoint Description
GET /health Health check endpoint

Socket.IO Events

Event Direction Description
connect Client→Server Client connection established
disconnect Client→Server Client disconnection
metrics_update Server→Client Push notification when metrics updated

Database Schema

Tables

users

Column Type Description
id INTEGER Primary key, autoincrement
username TEXT Unique username
password_hash TEXT bcrypt hashed password
created_at TIMESTAMP Creation timestamp
updated_at TIMESTAMP Last update timestamp

devices

Column Type Description
id INTEGER Primary key, autoincrement
name TEXT Display name
ip TEXT IP address
device_type TEXT Type (windows, linux, firewall, switch, bmc)
vendor TEXT Vendor name (for firewall/switch)
snmp_enabled BOOLEAN Enable SNMP polling
snmp_community TEXT SNMP community string
snmp_version TEXT SNMP version (default: 2c)
ipmi_enabled BOOLEAN Enable IPMI polling
ipmi_username TEXT IPMI username
ipmi_password TEXT IPMI password
status TEXT Status (online, offline, unknown)
last_seen TIMESTAMP Last seen timestamp
created_at TIMESTAMP Creation timestamp

metrics

Column Type Description
id INTEGER Primary key, autoincrement
device_id INTEGER Foreign key to devices
timestamp TIMESTAMP Measurement timestamp
ping_ok BOOLEAN Ping success status
latency_ms REAL Ping latency in ms
device_name TEXT Hostname from SNMP
sys_up_time TEXT System uptime string
cpu_model TEXT CPU model
mac_addresses TEXT JSON string of MAC addresses
cpu_percent REAL CPU usage percentage
mem_total_kb REAL Total memory in KB
mem_percent REAL Memory usage percentage
fan_speed_rpm REAL Fan speed RPM
power_watts REAL Power consumption in Watts
fdb_mac_port_map TEXT JSON string of MAC-to-port mapping

Docker Configuration

Dockerfile

The Dockerfile creates a production-ready container:

  • Base image: python:3.12.3-slim
  • Non-root user (appuser, UID 1000)
  • Health check every 30 seconds
  • Installs curl and iputils-ping for network utilities
  • Exposes port 5000

Docker Compose Services

dashboard

  • Flask application service
  • Environment variables for configuration
  • Volume mounts for data persistence
  • Health check for service monitoring

nginx

  • Reverse proxy on port 80
  • WebSocket proxy for Socket.IO
  • Depends on healthy dashboard service

Nginx Configuration

  • Proxies all requests to Flask on dashboard:5000
  • Special handling for /socket.io/ with WebSocket upgrade
  • Disabled buffering for real-time communication
  • Long timeouts (86400s) for persistent connections

Troubleshooting

Devices showing as offline

  1. Verify network connectivity: ping <device-ip>
  2. Check device IP is reachable
  3. Verify SNMP is enabled on device
  4. Check firewall allows UDP port 161

SNMP polling errors

  1. Verify community string is correct
  2. Check SNMP service is running on target
  3. Review logs: docker compose logs dashboard
  4. Test manually: snmpget -v2c -c public <device-ip> 1.3.6.1.2.1.1.5.0

IPMI polling errors

  1. Verify BMC is reachable on port 623
  2. Check IPMI credentials are correct
  3. Review logs: docker compose logs dashboard
  4. Test manually: ipmi-raw -H <bmc-ip> -U <user> -P <pass> 0x06 0x20

Socket.IO connection issues

  1. Check nginx configuration
  2. Verify WebSocket proxy settings
  3. Check browser console for errors
  4. Ensure nginx is running and proxying correctly

Database locked errors

  1. Check if multiple instances are running
  2. Verify file permissions on data directory
  3. Increase DB_TIMEOUT in environment if needed

Security Notes

  • Change default admin credentials immediately after deployment
  • Use strong passwords (minimum 8 characters, recommend 12+)
  • Deploy behind TLS/SSL in production (add SSL termination to nginx)
  • SNMP community strings are stored in the database (TODO: encryption)
  • IPMI credentials are stored in the database (TODO: encryption)
  • Restrict network access to the dashboard port
  • Regularly update the system and dependencies
  • Monitor logs for suspicious activity

Security TODOs

  • Encrypt credentials at rest in the database
  • Add TLS/SSL termination in nginx
  • Implement rate limiting on API endpoints
  • Add audit logging for administrative actions
  • Support for multi-factor authentication

Development

Running Tests

# Run pytest
pytest tests/

# Run with coverage
pytest --cov=. tests/

Code Style

The project follows PEP 8 standards. Use ruff for linting:

ruff check .
ruff format .

Adding a New Device Type

  1. Add the device type to models/device.py DeviceType enum
  2. Add OID mappings in config.py DEVICE_TYPE_OIDS
  3. Update polling/snmp_poller.py with type-specific polling logic
  4. Update dashboard template