Skip to content

Commit 0dd360b

Browse files
authored
Merge pull request #1 from solutionforest/lam0819-patch-1
Add A Simple Dashboard
2 parents 95af02b + 5fc8ecf commit 0dd360b

File tree

3 files changed

+884
-17
lines changed

3 files changed

+884
-17
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ A lightweight Python-based agent that provides real-time monitoring of Docker co
1212
- **Fast & Lightweight**: Designed for quick responses, even with many containers.
1313
- **Easy Deployment**: Runs as a Docker container with minimal configuration.
1414

15+
## Screenshot
16+
17+
![image](https://github.com/user-attachments/assets/b7a1f2dc-6f58-4edc-bb29-ba92002c26a5)
18+
![image](https://github.com/user-attachments/assets/3960da14-9f7a-4b45-8e46-90d84ddcbb21)
19+
20+
1521
---
1622

1723
## Quick Start
@@ -136,6 +142,43 @@ curl http://localhost:8080/status | jq
136142

137143
---
138144

145+
## Web Dashboard
146+
147+
A modern, interactive dashboard UI is included! Just open:
148+
149+
http://localhost:8080/dashboard.html
150+
151+
in your browser after starting the agent. No extra setup is required.
152+
153+
### Dashboard Features
154+
- **System Overview**: Uptime, CPU idle %, core count, memory, swap, and disk usage cards.
155+
- **Charts**: Real-time graphs for system load, CPU modes, memory, disk IO, network, and swap.
156+
- **Container Selector**: Switch between running containers to view their stats.
157+
- **Per-Container Stats**: Status, image, uptime, restart count, ports, and resource usage (CPU %, memory MB) with historical charts.
158+
- **Responsive Design**: Works on desktop and mobile.
159+
160+
---
161+
162+
## API
163+
164+
- **/status**: Returns a JSON object with host and container stats. See example above for structure.
165+
- **/dashboard.html**: Serves the dashboard UI.
166+
167+
---
168+
169+
## Running Without Docker (Advanced)
170+
171+
You can run the agent directly with Python 3.8+ (Linux recommended):
172+
173+
```bash
174+
pip install flask psutil docker humanize
175+
python agent.py
176+
```
177+
178+
Then visit http://localhost:8080/dashboard.html in your browser.
179+
180+
---
181+
139182
## Configuration
140183

141184
- **Port**: Default is `8080` (see `agent.py`).

agent.py

Lines changed: 109 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,119 @@
22
import docker
33
import humanize
44
import concurrent.futures
5-
from flask import Flask, jsonify
5+
from flask import Flask, jsonify, send_from_directory
66
from datetime import datetime, timezone
7+
import os
78

89
app = Flask(__name__)
910

1011
def get_host_resources():
12+
# Uptime
13+
boot_time = psutil.boot_time()
14+
uptime_seconds = int(datetime.now(timezone.utc).timestamp() - boot_time)
15+
# Load average
16+
load1, load5, load15 = psutil.getloadavg()
17+
# CPU
18+
cpu_times = psutil.cpu_times_percent(interval=0)
19+
cpu_count = psutil.cpu_count()
20+
cpu_percent = psutil.cpu_percent(interval=0)
21+
# Memory
1122
mem = psutil.virtual_memory()
23+
swap = psutil.swap_memory()
24+
# Disk
1225
disk = psutil.disk_usage('/')
13-
cpu_percent = psutil.cpu_percent(interval=0) # non-blocking, instantaneous
26+
# IO
27+
disk_io = psutil.disk_io_counters()
28+
net_io = psutil.net_io_counters(pernic=True)
29+
# Processes
30+
procs = list(psutil.process_iter(['status']))
31+
running = sum(1 for p in procs if p.info['status'] == psutil.STATUS_RUNNING)
32+
blocked = sum(1 for p in procs if p.info['status'] == psutil.STATUS_DISK_SLEEP)
33+
# Interrupts (if available)
34+
interrupts = None
35+
try:
36+
with open("/proc/stat") as f:
37+
for line in f:
38+
if line.startswith("intr"):
39+
interrupts = int(line.split()[1])
40+
except Exception:
41+
interrupts = None
42+
1443
return {
15-
'cpu_percent': cpu_percent,
16-
'memory': {
17-
'total': mem.total,
18-
'used': mem.used,
19-
'percent': mem.percent,
20-
'total_human': humanize.naturalsize(mem.total),
21-
'used_human': humanize.naturalsize(mem.used)
44+
"uptime_seconds": uptime_seconds,
45+
"uptime_human": humanize.precisedelta(uptime_seconds),
46+
"boot_time": datetime.fromtimestamp(boot_time, tz=timezone.utc).isoformat(),
47+
"cpu": {
48+
"percent": cpu_percent,
49+
"idle_percent": cpu_times.idle,
50+
"count": cpu_count,
51+
"times": cpu_times._asdict()
52+
},
53+
"memory": {
54+
"total": mem.total,
55+
"available": mem.available,
56+
"used": mem.used,
57+
"free": mem.free,
58+
"percent": mem.percent,
59+
"buffers": getattr(mem, "buffers", 0),
60+
"cached": getattr(mem, "cached", 0),
61+
"total_human": humanize.naturalsize(mem.total),
62+
"used_human": humanize.naturalsize(mem.used),
63+
"available_human": humanize.naturalsize(mem.available),
64+
"buffers_human": humanize.naturalsize(getattr(mem, "buffers", 0)),
65+
"cached_human": humanize.naturalsize(getattr(mem, "cached", 0)),
66+
},
67+
"swap": {
68+
"total": swap.total,
69+
"used": swap.used,
70+
"free": swap.free,
71+
"percent": swap.percent,
72+
"sin": swap.sin,
73+
"sout": swap.sout,
74+
"total_human": humanize.naturalsize(swap.total),
75+
"used_human": humanize.naturalsize(swap.used),
76+
"free_human": humanize.naturalsize(swap.free),
77+
},
78+
"disk": {
79+
"total": disk.total,
80+
"used": disk.used,
81+
"free": disk.free,
82+
"percent": disk.percent,
83+
"total_human": humanize.naturalsize(disk.total),
84+
"used_human": humanize.naturalsize(disk.used),
85+
"free_human": humanize.naturalsize(disk.free)
2286
},
23-
'disk': {
24-
'total': disk.total,
25-
'used': disk.used,
26-
'percent': disk.percent,
27-
'total_human': humanize.naturalsize(disk.total),
28-
'used_human': humanize.naturalsize(disk.used)
29-
}
87+
"disk_io": {
88+
"read_bytes": disk_io.read_bytes,
89+
"write_bytes": disk_io.write_bytes,
90+
"read_count": disk_io.read_count,
91+
"write_count": disk_io.write_count,
92+
"read_time": getattr(disk_io, 'read_time', None),
93+
"write_time": getattr(disk_io, 'write_time', None)
94+
},
95+
"net_io": {
96+
dev: {
97+
"bytes_sent": val.bytes_sent,
98+
"bytes_recv": val.bytes_recv,
99+
"packets_sent": val.packets_sent,
100+
"packets_recv": val.packets_recv,
101+
"errin": val.errin,
102+
"errout": val.errout,
103+
"dropin": val.dropin,
104+
"dropout": val.dropout
105+
}
106+
for dev, val in net_io.items()
107+
},
108+
"loadavg": {
109+
"1min": load1,
110+
"5min": load5,
111+
"15min": load15
112+
},
113+
"processes": {
114+
"running": running,
115+
"blocked_io": blocked
116+
},
117+
"interrupts": interrupts
30118
}
31119

32120
def calculate_cpu_percent(stat):
@@ -134,5 +222,9 @@ def status():
134222
'docker_services': get_docker_services()
135223
})
136224

225+
@app.route('/dashboard.html')
226+
def dashboard():
227+
return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'dashboard.html')
228+
137229
if __name__ == "__main__":
138-
app.run(host='0.0.0.0', port=8080)
230+
app.run(host='0.0.0.0', port=8080)

0 commit comments

Comments
 (0)